package com.linkedin.thirdeye.hadoop.config;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.linkedin.thirdeye.hadoop.config.MetricType;
import com.linkedin.thirdeye.hadoop.config.TopKDimensionToMetricsSpec;
import com.linkedin.thirdeye.hadoop.config.TopkWhitelistSpec;
public class ThirdEyeConfigTest {
private Properties props;
private ThirdEyeConfig thirdeyeConfig;
private ThirdEyeConfig config;
@BeforeClass
public void setup() {
props = new Properties();
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TABLE_NAME.toString(), "collection");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_DIMENSION_NAMES.toString(), "d1,d2,d3");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_METRIC_NAMES.toString(), "m1,m2,m3");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_METRIC_TYPES.toString(), "LONG,FLOAT,INT");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TIMECOLUMN_NAME.toString(), "t1");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TIMECOLUMN_TYPE.toString(), "DAYS");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TIMECOLUMN_SIZE.toString(), "10");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_SPLIT_THRESHOLD.toString(), "1000");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_SPLIT_ORDER.toString(), "d1,d2,d3");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TOPK_THRESHOLD_METRIC_NAMES.toString(), "m1,m3");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TOPK_METRIC_THRESHOLD_VALUES.toString(), "0.02,0.1");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TOPK_DIMENSION_NAMES.toString(), "d2,d3");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TOPK_METRICS.toString() + ".d2", "m1,m2");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TOPK_KVALUES.toString() + ".d2", "20,30");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TOPK_METRICS.toString() + ".d3", "m1");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TOPK_KVALUES.toString() + ".d3", "50");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_WHITELIST_DIMENSION_NAMES.toString(), "d1,d2");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_WHITELIST_DIMENSION.toString() + ".d1", "x,y");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_WHITELIST_DIMENSION.toString() + ".d2", "a");
thirdeyeConfig = ThirdEyeConfig.fromProperties(props);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testTableNameConfig() throws IllegalArgumentException {
Assert.assertEquals("collection", thirdeyeConfig.getCollection(), "Collection name not correctly set");
try {
props.remove(ThirdEyeConfigProperties.THIRDEYE_TABLE_NAME.toString());
config = ThirdEyeConfig.fromProperties(props);
} finally {
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TABLE_NAME.toString(), "collection");
}
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testDimensionsConfig() throws IllegalArgumentException {
Assert.assertEquals(3, thirdeyeConfig.getDimensionNames().size(), "Incorrect number of dimensions");
Assert.assertEquals(new String[]{"d1", "d2", "d3"}, thirdeyeConfig.getDimensionNames().toArray(), "Incorrect dimensions");
try {
props.remove(ThirdEyeConfigProperties.THIRDEYE_DIMENSION_NAMES.toString());
config = ThirdEyeConfig.fromProperties(props);
} finally {
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_DIMENSION_NAMES.toString(), "d1,d2,d3");
}
}
@Test
public void testMetricsConfig() throws IllegalArgumentException {
boolean failed = false;
Assert.assertEquals(3, thirdeyeConfig.getMetricNames().size(), "Incorrect number of metrics");
Assert.assertEquals(3, thirdeyeConfig.getMetrics().size(), "Incorrect number of metric specs");
Assert.assertEquals(new String[]{"m1", "m2", "m3"}, thirdeyeConfig.getMetricNames().toArray(), "Incorrect metrics");
MetricType[] actualMetricTypes = new MetricType[3];
for (int i = 0; i < 3; i++) {
actualMetricTypes[i] = thirdeyeConfig.getMetrics().get(i).getType();
}
Assert.assertEquals(actualMetricTypes, new MetricType[]{MetricType.LONG, MetricType.FLOAT, MetricType.INT}, "Incorrect metric specs");
try {
props.remove(ThirdEyeConfigProperties.THIRDEYE_METRIC_NAMES.toString());
config = ThirdEyeConfig.fromProperties(props);
} catch (IllegalArgumentException e) {
failed = true;
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_METRIC_NAMES.toString(), "m1,m2,m3");
}
Assert.assertTrue(failed, "Expected exception due to missing metric names property");
failed = false;
try {
props.remove(ThirdEyeConfigProperties.THIRDEYE_METRIC_TYPES.toString());
config = ThirdEyeConfig.fromProperties(props);
} catch (IllegalArgumentException e) {
failed = true;
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_METRIC_TYPES.toString(), "LONG,FLOAT,INT");
}
Assert.assertTrue(failed, "Expected exception due to missing metric types property");
failed = false;
try {
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_METRIC_NAMES.toString(), "m1,m2");
config = ThirdEyeConfig.fromProperties(props);
} catch (IllegalStateException e) {
failed = true;
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_METRIC_NAMES.toString(), "m1,m2,m3");
}
Assert.assertTrue(failed, "Expecetd exception due to inequal number of metric names and types in properties");
}
@Test
public void testTimeConfig() throws IllegalArgumentException {
boolean failed = false;
Assert.assertEquals(thirdeyeConfig.getTime().getColumnName(), "t1", "Incorrect time column name");
Assert.assertNull(thirdeyeConfig.getInputTime(), "Incorrect input time column name");
Assert.assertEquals(thirdeyeConfig.getTime().getTimeGranularity().getSize(), 10, "Incorrect time size");
Assert.assertEquals(thirdeyeConfig.getTime().getTimeGranularity().getUnit(), TimeUnit.DAYS, "Incorrect time unit");
try {
props.remove(ThirdEyeConfigProperties.THIRDEYE_TIMECOLUMN_NAME.toString());
config = ThirdEyeConfig.fromProperties(props);
} catch (IllegalArgumentException e) {
failed = true;
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TIMECOLUMN_NAME.toString(), "t1");
}
Assert.assertTrue(failed, "Expected exception due to missing time column property");
props.remove(ThirdEyeConfigProperties.THIRDEYE_TIMECOLUMN_SIZE.toString());
props.remove(ThirdEyeConfigProperties.THIRDEYE_TIMECOLUMN_TYPE.toString());
config = ThirdEyeConfig.fromProperties(props);
Assert.assertEquals(config.getTime().getTimeGranularity().getSize(), 1, "Incorrect default time size");
Assert.assertEquals(config.getTime().getTimeGranularity().getUnit(), TimeUnit.HOURS, "Incorrect default time unit");
}
@Test
public void testSplitConfig() throws Exception {
Assert.assertEquals(thirdeyeConfig.getSplit().getThreshold(), 1000, "Incorrect split threshold");
Assert.assertEquals(thirdeyeConfig.getSplit().getOrder().toArray(), new String[]{"d1", "d2", "d3"}, "Incorrect split order");
props.remove(ThirdEyeConfigProperties.THIRDEYE_SPLIT_THRESHOLD.toString());
config = ThirdEyeConfig.fromProperties(props);
Assert.assertEquals(config.getSplit(), null, "Default split should be null");
}
@Test
public void testTopKWhitelistConfig() throws IllegalArgumentException {
boolean failed = false;
TopkWhitelistSpec topKWhitelistSpec = thirdeyeConfig.getTopKWhitelist();
// thresholds
Map<String, Double> threshold = topKWhitelistSpec.getThreshold();
Assert.assertEquals(threshold.size(), 2, "Incorrect metric thresholds size");
Assert.assertEquals(threshold.get("m1") == 0.02 && threshold.get("m3") == 0.1, true, "Incorrect metric thresholds config");
try {
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TOPK_METRIC_THRESHOLD_VALUES.toString(), "0.1");
config = ThirdEyeConfig.fromProperties(props);
} catch (IllegalStateException e) {
failed = true;
}
Assert.assertTrue(failed, "Expected exception due to unequal number of metrics and threshold");
props.remove(ThirdEyeConfigProperties.THIRDEYE_TOPK_METRIC_THRESHOLD_VALUES.toString());
props.remove(ThirdEyeConfigProperties.THIRDEYE_TOPK_THRESHOLD_METRIC_NAMES.toString());
config = ThirdEyeConfig.fromProperties(props);
Assert.assertEquals(config.getTopKWhitelist().getThreshold(), null, "Default threshold config should be null");
// whitelist
Map<String, String> whitelist = topKWhitelistSpec.getWhitelist();
Assert.assertEquals(whitelist.size(), 2, "Incorrect size of whitelist dimensions");
Assert.assertEquals(whitelist.get("d1"), "x,y", "Incorrect whitelist config");
Assert.assertEquals(whitelist.get("d2"), "a", "Incorrect whitelist config");
props.remove(ThirdEyeConfigProperties.THIRDEYE_WHITELIST_DIMENSION_NAMES.toString());
config = ThirdEyeConfig.fromProperties(props);
Assert.assertEquals(config.getTopKWhitelist().getWhitelist(), null, "Default whitelist config should be null");
// topk
List<TopKDimensionToMetricsSpec> topk = topKWhitelistSpec.getTopKDimensionToMetricsSpec();
Assert.assertEquals(topk.size(), 2, "Incorrect topk dimensions config size");
TopKDimensionToMetricsSpec topkSpec = topk.get(0);
Assert.assertEquals(topkSpec.getDimensionName().equals("d2")
&& topkSpec.getTopk().size() == 2
&& topkSpec.getTopk().get("m1") == 20
&& topkSpec.getTopk().get("m2") == 30, true, "Incorrect topk config");
topkSpec = topk.get(1);
Assert.assertEquals(topkSpec.getDimensionName().equals("d3")
&& topkSpec.getTopk().size() == 1
&& topkSpec.getTopk().get("m1") == 50, true, "Incorrect topk config");
failed = false;
try {
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TOPK_METRICS.toString() + ".d3", "m1");
props.setProperty(ThirdEyeConfigProperties.THIRDEYE_TOPK_KVALUES.toString() + ".d3", "50,50");
config = ThirdEyeConfig.fromProperties(props);
} catch (IllegalStateException e) {
failed = true;
}
Assert.assertTrue(failed, "Expecetd exception due to inequal number of metrics and kvalues for dimension");
props.remove(ThirdEyeConfigProperties.THIRDEYE_TOPK_DIMENSION_NAMES.toString());
config = ThirdEyeConfig.fromProperties(props);
Assert.assertEquals(config.getTopKWhitelist(), null, "Default topk should be null");
}
}