package com.rackspacecloud.blueflood.io;
import java.util.*;
import static com.rackspacecloud.blueflood.types.Locator.METRIC_TOKEN_SEPARATOR_REGEX;
/**
* When we ingest these below metrics, ES generates the following indexes
*
* metric indexes
* ----------- -------
* foo.bar.baz.aux -> [foo, foo.bar, foo.bar.baz, foo.bar.baz.aux, bar, baz, aux]
* foo.bar.baz -> [foo, foo.bar, foo.bar.baz, bar, baz]
*
* If we request ES to return us upto the next level of the metric indexes for foo.bar.*,
* we get this below response from ES where key is the metric index and doc_count is
* the number of documents(think metric names) it points to.
*
* "buckets" : [ {
* "key" : "foo.bar.baz",
* "doc_count" : 2
* }, {
* "key" : "foo.bar.baz.aux",
* "doc_count" : 1
* } ]
*
* If we feed each metric index to this class by calling the add(metricIndex, docCount) method,
* this class analyzes the data and provides methods to retrieve different aspects of this data
* with respect to baseLevel(if set to 3 with above data, uses foo.bar.baz as reference).
*
*/
public class MetricIndexData {
private static class MetricIndexDocCount {
private long actualDocCount;
private long childrenTotalDocCount;
public MetricIndexDocCount(long actualDocCount, long childrenTotalDocCount) {
this.actualDocCount = actualDocCount;
this.childrenTotalDocCount = childrenTotalDocCount;
}
public void setActualDocCount(long actualDocCount) {
this.actualDocCount = actualDocCount;
}
public void addChildrenTotalDocCount(long childrenDocCount) {
this.childrenTotalDocCount += childrenDocCount;
}
}
private final int baseLevel;
//for metric names which have next level. (compared to baseLevel)
private final Set<String> metricNamesWithNextLevelSet = new LinkedHashSet<String>();
//contains all metric indexes which are of the same length as baseLevel. This is to determine if we
//have any complete metric names at base level itself.
final Map<String, MetricIndexDocCount> metricNameBaseLevelMap = new HashMap<>();
public MetricIndexData(int baseLevel) {
this.baseLevel = baseLevel;
}
/**
* For a given metricIndex and docCount, classifies the data with respect to baseLevel
* and stores it accordingly.
*
* @param metricIndex
* @param docCount is document(metric name) count
*/
public void add(String metricIndex, long docCount) {
final String[] tokens = metricIndex.split(METRIC_TOKEN_SEPARATOR_REGEX);
/**
*
* For the ES response shown in class description, for a baseLevel of 2,
* the data is classified as follows
*
* metricNamesWithNextLevelSet -> {foo.bar.baz} (Metric Names which are at base + 1 level and also have a subsequent level.)
*
* For count map, data is in the form {metricIndex, (actualDocCount, childrenTotalDocCount)}
*
* metricNameBaseLevelMap -> {foo.bar.baz -> (2, 1)} (all indexes which are of same length as baseLevel)
*
*/
switch (tokens.length - baseLevel) {
case 1:
if (baseLevel > 0) {
metricNamesWithNextLevelSet.add(metricIndex.substring(0, metricIndex.lastIndexOf(".")));
} else {
metricNamesWithNextLevelSet.add(metricIndex.substring(0, metricIndex.indexOf(".")));
}
//For foo.bar.baz, baseLevel=3 we update children doc count of foo.bar.baz
addChildrenDocCount(metricNameBaseLevelMap,
metricIndex.substring(0, metricIndex.lastIndexOf(".")),
docCount);
break;
case 0:
setActualDocCount(metricNameBaseLevelMap, metricIndex, docCount);
break;
default:
break;
}
}
/**
* Metric Names which have next level compared to baseLevel.
*
* Ex: For metrics foo.bar.baz.qux, foo.bar.baz
* if baseLevel = 3, returns foo.bar.baz as there it has next level token 'qux'
*
* @return
*/
public Set<String> getMetricNamesWithNextLevel() {
return Collections.unmodifiableSet(metricNamesWithNextLevelSet);
}
/**
* Returns complete metric names which are of same length as baseLevel
*
* Ex: For metrics foo.bar.baz.qux, foo.bar.baz
* if baseLevel = 3, returns foo.bar.baz
*
* @return
*/
public Set<String> getCompleteMetricNamesAtBaseLevel() {
return getCompleteMetricNames(metricNameBaseLevelMap);
}
/**
* Compares actualDocCount and total docCount of its immediate children of an index
* to determine if the metric index is a complete metric name or not.
*
* For the ES response shown in class description, for a baseLevel of 2,
* foo.bar.baz has actualDocCount of 2, but total doc count of all its children,
* which in this case is only foo.bar.baz, is 1. So foo.bar.baz must be metric by itself
*
* @param metricIndexMap
* @return
*/
private Set<String> getCompleteMetricNames(Map<String, MetricIndexDocCount> metricIndexMap) {
Set<String> completeMetricNames = new HashSet<String>();
for (Map.Entry<String, MetricIndexDocCount> entry : metricIndexMap.entrySet()) {
MetricIndexDocCount metricIndexDocCount = entry.getValue();
if (metricIndexDocCount != null) {
//if total doc count is greater than its children docs, its a complete metric name
if (metricIndexDocCount.actualDocCount > 0 &&
metricIndexDocCount.actualDocCount > metricIndexDocCount.childrenTotalDocCount) {
completeMetricNames.add(entry.getKey());
}
}
}
return Collections.unmodifiableSet(completeMetricNames);
}
private void setActualDocCount(Map<String, MetricIndexDocCount> metricIndexMap, String key, final long count) {
MetricIndexDocCount metricIndexDocCount = metricIndexMap.containsKey(key) ? metricIndexMap.get(key) : new MetricIndexDocCount(0, 0);
metricIndexDocCount.setActualDocCount(count);
metricIndexMap.put(key, metricIndexDocCount);
}
private void addChildrenDocCount(Map<String, MetricIndexDocCount> metricIndexMap, String key, final long childrenDocCount) {
MetricIndexDocCount metricIndexDocCount = metricIndexMap.containsKey(key) ? metricIndexMap.get(key) : new MetricIndexDocCount(0, 0);
metricIndexDocCount.addChildrenTotalDocCount(childrenDocCount);
metricIndexMap.put(key, metricIndexDocCount);
}
}