package com.rackspacecloud.blueflood.io;
import com.rackspacecloud.blueflood.types.Locator;
import org.elasticsearch.common.lang3.StringUtils;
import org.junit.Test;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
public class MetricIndexDataTest {
//regex used by the current analyzer(in index_settings.json) to create metric indexes
private static final Pattern patternToCreateMetricIndices = Pattern.compile("([^.]+)");
private static final int METRIC_VALUE = 1;
@Test
public void testMetricNamesWithNextLevelSingleIndex() {
MetricIndexData mi = new MetricIndexData(3);
mi.add("a.b.c.d", METRIC_VALUE);
assertEquals("metric names with next level", 1, mi.getMetricNamesWithNextLevel().size());
assertTrue("metric names", mi.getMetricNamesWithNextLevel().contains("a.b.c"));
assertEquals("Complete metric names", 0, mi.getCompleteMetricNamesAtBaseLevel().size());
}
@Test
public void testBaseLevelCompleteMetricNameSingleIndex() {
MetricIndexData mi = new MetricIndexData(3);
mi.add("a.b.c", METRIC_VALUE);
assertEquals("metric names with next level", 0, mi.getMetricNamesWithNextLevel().size());
assertEquals("base level metric names", 1, mi.getCompleteMetricNamesAtBaseLevel().size());
}
@Test
public void testNoCompleteMetricName() {
ArrayList<String> metricNames = new ArrayList<String>() {{
add("foo.bar.baz.qux");
}};
String query = "foo.bar.*";
Map<String, Long> metricIndexes = buildMetricIndexesSimilarToES(metricNames, query);
//for base at foo
MetricIndexData mi = new MetricIndexData(query.split(Locator.METRIC_TOKEN_SEPARATOR_REGEX).length);
for (Map.Entry<String, Long> entry: metricIndexes.entrySet()) {
mi.add(entry.getKey(), entry.getValue());
}
assertEquals("metric names with next level count", 1, mi.getMetricNamesWithNextLevel().size());
assertEquals("metric names with next level", true, mi.getMetricNamesWithNextLevel().contains("foo.bar.baz"));
assertEquals("base level complete metric names count", 0, mi.getCompleteMetricNamesAtBaseLevel().size());
}
@Test
public void testBaseLevelCompleteMetricName() {
ArrayList<String> metricNames = new ArrayList<String>() {{
add("foo.bar.baz.qux.xxx");
add("foo.bar.baz");
}};
String query = "foo.bar.*";
Map<String, Long> metricIndexes = buildMetricIndexesSimilarToES(metricNames, query);
//for base at foo.bar
MetricIndexData mi = new MetricIndexData(query.split(Locator.METRIC_TOKEN_SEPARATOR_REGEX).length);
for (Map.Entry<String, Long> entry: metricIndexes.entrySet()) {
mi.add(entry.getKey(), entry.getValue());
}
assertEquals("base level complete metric names count", 1, mi.getCompleteMetricNamesAtBaseLevel().size());
}
@Test
public void testSingleLevelQuery() {
ArrayList<String> metricNames = new ArrayList<String>() {{
add("foo.bar.baz");
add("foo.bar");
}};
String query = "*";
Map<String, Long> metricIndexes = buildMetricIndexesSimilarToES(metricNames, query);
//for base at foo
MetricIndexData mi = new MetricIndexData(query.split(Locator.METRIC_TOKEN_SEPARATOR_REGEX).length);
for (Map.Entry<String, Long> entry: metricIndexes.entrySet()) {
mi.add(entry.getKey(), entry.getValue());
}
assertEquals("metric names with next level count", 1, mi.getMetricNamesWithNextLevel().size());
assertEquals("metric names with next level", true, mi.getMetricNamesWithNextLevel().contains("foo"));
assertEquals("base level complete metric names count", 0, mi.getCompleteMetricNamesAtBaseLevel().size());
}
@Test
public void testTwoLevelQuery() {
ArrayList<String> metricNames = new ArrayList<String>() {{
add("foo.bar.baz");
add("foo.bar");
}};
String query = "foo.*";
Map<String, Long> metricIndexes = buildMetricIndexesSimilarToES(metricNames, query);
//for base at foo
MetricIndexData mi = new MetricIndexData(query.split(Locator.METRIC_TOKEN_SEPARATOR_REGEX).length);
for (Map.Entry<String, Long> entry: metricIndexes.entrySet()) {
mi.add(entry.getKey(), entry.getValue());
}
assertEquals("metric names with next level count", 1, mi.getMetricNamesWithNextLevel().size());
assertEquals("metric names with next level", true, mi.getMetricNamesWithNextLevel().contains("foo.bar"));
assertEquals("base level complete metric names count", 1, mi.getCompleteMetricNamesAtBaseLevel().size());
}
@Test
public void testMetricIndexesBuilderSingleMetricName() {
ArrayList<String> metricNames = new ArrayList<String>() {{
add("foo.bar.baz");
}};
Map<String, Long> metricIndexMap = buildMetricIndexesSimilarToES(metricNames, "foo");
Set<String> expectedIndexes = new HashSet<String>() {{
add("foo.bar|1");
}};
verifyMetricIndexes(metricIndexMap, expectedIndexes);
}
@Test
public void testMetricIndexesBuilderSingleMetricNameSecondTokenQuery() {
ArrayList<String> metricNames = new ArrayList<String>() {{
add("foo.bar.baz.qux");
}};
Map<String, Long> metricIndexMap = buildMetricIndexesSimilarToES(metricNames, "foo.*");
Set<String> expectedIndexes = new HashSet<String>() {{
add("foo.bar|1");
add("foo.bar.baz|1");
}};
verifyMetricIndexes(metricIndexMap, expectedIndexes);
}
@Test (expected = IllegalArgumentException.class)
public void testMetricIndexesBuilderSingleMetricName1() {
ArrayList<String> metricNames = new ArrayList<String>() {{
add("foo");
}};
buildMetricIndexesSimilarToES(metricNames, "");
}
@Test
public void testMetricIndexesBuilderLongMetrics() {
ArrayList<String> metricNames = new ArrayList<String>() {{
add("foo.bar.baz.qux.x.y");
}};
Map<String, Long> metricIndexMap = buildMetricIndexesSimilarToES(metricNames, "foo.bar");
Set<String> expectedIndexes = new HashSet<String>() {{
add("foo.bar|1");
add("foo.bar.baz|1");
}};
verifyMetricIndexes(metricIndexMap, expectedIndexes);
}
@Test
public void testMetricIndexesBuilderLongMetricsWildcard() {
ArrayList<String> metricNames = new ArrayList<String>() {{
add("foo.bar.baz.qux.x.y");
}};
Map<String, Long> metricIndexMap = buildMetricIndexesSimilarToES(metricNames, "foo.bar.*");
Set<String> expectedIndexes = new HashSet<String>() {{
add("foo.bar.baz|1");
add("foo.bar.baz.qux|1");
}};
verifyMetricIndexes(metricIndexMap, expectedIndexes);
}
@Test
public void testMetricIndexesBuilderMultipleMetrics() {
ArrayList<String> metricNames = new ArrayList<String>() {{
add("foo.bar.baz.qux.x");
add("foo.bar.baz.qux");
add("foo.bar.baz");
}};
Map<String, Long> metricIndexMap = buildMetricIndexesSimilarToES(metricNames, "foo.bar.*");
Set<String> expectedIndexes = new HashSet<String>() {{
add("foo.bar.baz|3");
add("foo.bar.baz.qux|2");
}};
verifyMetricIndexes(metricIndexMap, expectedIndexes);
}
@Test
public void testSingleLevelQueryMultipleMetrics() {
ArrayList<String> metricNames = new ArrayList<String>() {{
add("foo.bar.baz.x.y.z");
add("foo.bar");
}};
Map<String, Long> metricIndexMap = buildMetricIndexesSimilarToES(metricNames, "foo");
Set<String> expectedIndexes = new HashSet<String>() {{
add("foo.bar|2");
}};
verifyMetricIndexes(metricIndexMap, expectedIndexes);
}
@Test
public void testSingleLevelWildcardQueryMultipleMetrics() {
ArrayList<String> metricNames = new ArrayList<String>() {{
add("foo.bar.baz.x.y.z");
add("moo.bar");
}};
Map<String, Long> metricIndexMap = buildMetricIndexesSimilarToES(metricNames, "*");
Set<String> expectedIndexes = new HashSet<String>() {{
add("moo.bar|1");
add("foo.bar|1");
}};
verifyMetricIndexes(metricIndexMap, expectedIndexes);
}
private void verifyMetricIndexes(Map<String, Long> metricIndexMap, Set<String> expectedIndexes) {
Set<String> outputIndexes = new HashSet<String>();
for (Map.Entry<String, Long> entry: metricIndexMap.entrySet()) {
outputIndexes.add(entry.getKey() + "|" + entry.getValue());
}
assertTrue("All expected indexes should be created", outputIndexes.containsAll(expectedIndexes));
assertTrue("Output indexes should not exceed expected indexes", expectedIndexes.containsAll(outputIndexes));
}
/**
*
* This is test method to generate response similar to the ES aggregate search call, where given a
* metric name regex we get their metric indexes and their aggregate counts.
*
* Ex: For a metric foo.bar.baz.qux, it generates a map
* {foo, 1}
* {bar, 1}
* {baz, 1}
* {foo.bar, 1}
* {foo.bar.baz, 1}
*
* For multiple metric names, it generates the corresponding indexes and their document counts.
*
* This methods uses the tokenizer and filter similar to the one used during ES setup(index_settings.json).
*
* @param metricNames
* @return
*/
private Map<String, Long> buildMetricIndexesSimilarToES(List<String> metricNames, String query) {
Map<String, Long> metricIndexMap = new HashMap<String, Long>();
for (String metricName: metricNames) {
int totalTokens = metricName.split(Locator.METRIC_TOKEN_SEPARATOR_REGEX).length;
//imitating path hierarchy tokenizer(prefix-test-tokenizer) in analyzer(prefix-test-analyzer) we use.
// For metric, foo.bar.baz path hierarchy tokenizer creates foo, foo.bar, foo.bar.baz
Set<String> metricIndexes = new HashSet<String>();
metricIndexes.add(metricName);
for (int i = 1; i < totalTokens; i++) {
String path = metricName.substring(0, StringUtils.ordinalIndexOf(metricName, ".", i));
metricIndexes.add(path);
}
//imitating filter(dotted) in the analyzer we use. if path is foo.bar.baz, creates tokens foo, bar, baz
Set<String> tokens = new HashSet<String>();
for (String path: metricIndexes) {
Matcher matcher = patternToCreateMetricIndices.matcher(path);
while (matcher.find()) {
tokens.add(matcher.group(1));
}
}
metricIndexes.addAll(tokens);
for (String metricIndex: metricIndexes) {
Long count = metricIndexMap.containsKey(metricIndex) ? metricIndexMap.get(metricIndex) : 0L;
metricIndexMap.put(metricIndex, count + 1);
}
}
ElasticIO elasticIO = new ElasticIO();
Pattern patternToGet2Levels = Pattern.compile(elasticIO.regexToGrabCurrentAndNextLevel(query));
Map<String, Long> outputMap = new HashMap<String, Long>();
for (Map.Entry<String, Long> entry: metricIndexMap.entrySet()) {
Matcher matcher = patternToGet2Levels.matcher(entry.getKey());
if (matcher.matches()) {
outputMap.put(entry.getKey(), entry.getValue());
}
}
return outputMap;
}
}