/*
* Copyright © 2015 Cask Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package co.cask.cdap.gateway.handlers.metrics;
import co.cask.cdap.api.metrics.MetricDeleteQuery;
import co.cask.cdap.api.metrics.MetricType;
import co.cask.cdap.api.metrics.MetricValues;
import co.cask.cdap.api.metrics.Metrics;
import co.cask.cdap.api.metrics.MetricsContext;
import co.cask.cdap.app.metrics.MapReduceMetrics;
import co.cask.cdap.app.metrics.ProgramUserMetrics;
import co.cask.cdap.proto.MetricQueryResult;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Search available contexts and metrics tests
*/
public class MetricsHandlerTestRun extends MetricsSuiteTestBase {
private static final Gson GSON = new Gson();
private static final List<String> FLOW_TAGS_HUMAN = ImmutableList.of("namespace", "app", "flow", "flowlet");
private static long emitTs;
@Before
public void setup() throws Exception {
setupMetrics();
}
private static void setupMetrics() throws Exception {
// Adding metrics for app "WordCount1" in namespace "myspace", "WCount1" in "yourspace"
MetricsContext collector =
collectionService.getContext(getFlowletContext("myspace", "WordCount1", "WordCounter", "run1", "splitter"));
collector.increment("reads", 1);
collector.increment("writes", 1);
collector = collectionService.getContext(getFlowletContext("yourspace", "WCount1", "WordCounter",
"run1", "splitter"));
collector.increment("reads", 1);
collector = collectionService.getContext(getFlowletContext("yourspace", "WCount1", "WCounter",
"run1", "splitter"));
emitTs = System.currentTimeMillis();
// we want to emit in two different seconds
// todo : figure out why we need this
TimeUnit.SECONDS.sleep(1);
collector.increment("reads", 1);
TimeUnit.MILLISECONDS.sleep(2000);
collector.increment("reads", 2);
collector = collectionService.getContext(getFlowletContext("yourspace", "WCount1", "WCounter",
"run1", "counter"));
collector.increment("reads", 1);
collector = collectionService.getContext(getMapReduceTaskContext("yourspace", "WCount1", "ClassicWordCount",
MapReduceMetrics.TaskType.Mapper,
"run1", "task1"));
collector.increment("reads", 1);
collector = collectionService.getContext(
getMapReduceTaskContext("yourspace", "WCount1", "ClassicWordCount",
MapReduceMetrics.TaskType.Reducer, "run1", "task2"));
collector.increment("reads", 1);
collector = collectionService.getContext(getFlowletContext("myspace", "WordCount1", "WordCounter",
"run1", "splitter"));
collector.increment("reads", 1);
collector.increment("writes", 1);
collector = collectionService.getContext(getFlowletContext("myspace", "WordCount1", "WordCounter",
"run1", "collector"));
collector.increment("aa", 1);
collector.increment("zz", 1);
collector.increment("ab", 1);
collector = collectionService.getContext(getWorkerContext("yourspace", "WCount1", "WorkerWordCount",
"run1", "task1"));
collector.increment("workerreads", 5);
collector.increment("workerwrites", 6);
collector = collectionService.getContext(getWorkerContext("yourspace", "WCount1", "WorkerWordCount",
"run2", "task1"));
collector.increment("workerreads", 5);
collector.increment("workerwrites", 6);
// also: user metrics
Metrics userMetrics = new ProgramUserMetrics(
collectionService.getContext(getFlowletContext("myspace", "WordCount1", "WordCounter",
"run1", "splitter")));
userMetrics.count("reads", 1);
userMetrics.count("writes", 2);
collector = collectionService.getContext(new HashMap<String, String>());
collector.increment("resources.total.storage", 10);
// need a better way to do this
TimeUnit.SECONDS.sleep(2);
}
private List<Map<String, String>> getSearchResultExpected(String... searchExpectations) {
// iterate search results, add them as name-value map to the result list and return the list
List<Map<String, String>> result = Lists.newArrayList();
for (int i = 0; i < searchExpectations.length; i += 2) {
result.add(ImmutableMap.of("name", searchExpectations[i], "value", searchExpectations[i + 1]));
}
return result;
}
@Test
public void testSearchWithTags() throws Exception {
// empty context
verifySearchResultWithTags("/v3/metrics/search?target=tag", getSearchResultExpected("namespace", "myspace",
"namespace", "yourspace",
"namespace", "system"));
// WordCount is in myspace, WCount in yourspace
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:myspace",
getSearchResultExpected("app", "WordCount1"));
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:yourspace",
getSearchResultExpected("app", "WCount1"));
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:yourspace",
getSearchResultExpected("app", "WCount1"));
// WordCount should be found in myspace, not in yourspace
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:myspace&tag=app:WordCount1",
getSearchResultExpected("flow", "WordCounter"));
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:yourspace&tag=app:WordCount1",
getSearchResultExpected());
// WCount should be found in yourspace, not in myspace
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:yourspace&tag=app:WCount1",
getSearchResultExpected("flow", "WCounter",
"flow", "WordCounter",
"mapreduce", "ClassicWordCount",
"worker", "WorkerWordCount"
));
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:myspace&tag=app:WCount1",
getSearchResultExpected());
// verify other metrics for WCount app
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:yourspace&tag=app:WCount1" +
"&tag=mapreduce:ClassicWordCount",
getSearchResultExpected("run", "run1"));
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:yourspace&tag=app:WCount1" +
"&tag=mapreduce:ClassicWordCount&tag=run:run1",
getSearchResultExpected("tasktype", "m", "tasktype", "r"));
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:yourspace&tag=app:WCount1" +
"&tag=mapreduce:ClassicWordCount&tag=run:run1&tag=tasktype:m",
getSearchResultExpected("instance", "task1"));
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:yourspace&tag=app:WCount1" +
"&tag=mapreduce:ClassicWordCount&tag=run:run1&tag=tasktype:m&tag=instance:task1",
getSearchResultExpected());
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:yourspace&tag=app:WCount1" +
"&tag=worker:WorkerWordCount",
getSearchResultExpected("run", "run1",
"run", "run2"));
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:yourspace&tag=app:WCount1" +
"&tag=worker:WorkerWordCount&tag=run:run1",
getSearchResultExpected("instance", "task1"));
// verify "*"
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:*",
getSearchResultExpected("app", "WordCount1",
"app", "WCount1",
"component", "metrics.processor"));
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:*&tag=app:*",
getSearchResultExpected("flow", "WCounter",
"flow", "WordCounter",
"mapreduce", "ClassicWordCount",
"worker", "WorkerWordCount"));
verifySearchResultWithTags("/v3/metrics/search?target=tag&tag=namespace:*&tag=app:*&tag=flow:*",
getSearchResultExpected("run", "run1"));
}
@Test
public void testAggregateQueryBatch() throws Exception {
QueryRequestFormat query1 = new QueryRequestFormat(getTagsMap("namespace", "yourspace", "app", "WCount1",
"flow", "WCounter", "flowlet", "splitter"),
ImmutableList.of("system.reads"), ImmutableList.<String>of(),
ImmutableMap.<String, String>of());
// empty time range should default to aggregate=true
QueryRequestFormat query2 = new QueryRequestFormat(getTagsMap("namespace", "yourspace", "app", "WCount1",
"flow", "WCounter", "flowlet", "counter"),
ImmutableList.of("system.reads"),
ImmutableList.<String>of(),
ImmutableMap.of("aggregate", "true"));
QueryRequestFormat query3 = new QueryRequestFormat(getTagsMap("namespace", "yourspace", "app", "WCount1",
"flow", "WCounter", "flowlet", "*"),
ImmutableList.of("system.reads"),
ImmutableList.<String>of(),
ImmutableMap.of("aggregate", "true"));
QueryRequestFormat query4 = new QueryRequestFormat(ImmutableMap.of("namespace", "myspace", "app", "WordCount1",
"flow", "WordCounter", "flowlet", "splitter"),
ImmutableList.of("system.reads", "system.writes"),
ImmutableList.<String>of(),
ImmutableMap.of("aggregate", "true"));
// test batching of multiple queries
String resolution = Integer.MAX_VALUE + "s";
ImmutableMap<String, QueryResult> expected =
ImmutableMap.of("testQuery1",
new QueryResult(ImmutableList.of(new TimeSeriesSummary(
ImmutableMap.<String, String>of(), "system.reads", 1, 3)), resolution),
"testQuery2",
new QueryResult(ImmutableList.of(new TimeSeriesSummary(
ImmutableMap.<String, String>of(), "system.reads", 1, 1)), resolution));
batchTest(ImmutableMap.of("testQuery1", query1, "testQuery2", query2), expected);
// test batching of multiple queries, with one query having multiple metrics to query
expected = ImmutableMap.of("testQuery3",
new QueryResult(ImmutableList.of(
new TimeSeriesSummary(ImmutableMap.<String, String>of(), "system.reads", 1, 4))
, resolution),
"testQuery4",
new QueryResult(ImmutableList.of(
new TimeSeriesSummary(ImmutableMap.<String, String>of(), "system.reads", 1, 2),
new TimeSeriesSummary(ImmutableMap.<String, String>of(),
"system.writes", 1, 2)), resolution)
);
batchTest(ImmutableMap.of("testQuery3", query3, "testQuery4", query4), expected);
}
@Test
public void testInvalidRequest() throws Exception {
// test invalid request - query without any query Params and body content
HttpResponse response = doPost("/v3/metrics/query", null);
Assert.assertEquals(400, response.getStatusLine().getStatusCode());
// batch query with empty metrics list
QueryRequestFormat invalidQuery = new QueryRequestFormat(
ImmutableMap.of("namespace", "myspace", "app", "WordCount1", "flow", "WordCounter", "flowlet", "splitter"),
ImmutableList.<String>of(), ImmutableList.<String>of(), ImmutableMap.of("aggregate", "true"));
response = doPost("/v3/metrics/query", GSON.toJson(ImmutableMap.of("invalid", invalidQuery)));
Assert.assertEquals(400, response.getStatusLine().getStatusCode());
// test invalid request - query without any metric Params
response = doPost("/v3/metrics/query?context=namespace.default", null);
Assert.assertEquals(400, response.getStatusLine().getStatusCode());
response = doPost("/v3/metrics/query?tag=namespace.default", null);
Assert.assertEquals(400, response.getStatusLine().getStatusCode());
}
private Map<String, String> getTagsMap(String... entries) {
Map<String, String> tagsMap = Maps.newHashMap();
for (int i = 0; i < entries.length; i += 2) {
tagsMap.put(entries[i], entries[i + 1]);
}
return tagsMap;
}
/**
* Helper class to construct json for MetricQueryRequest for batch queries
*/
private class QueryRequestFormat {
Map<String, String> tags;
List<String> metrics;
List<String> groupBy;
Map<String, String> timeRange;
QueryRequestFormat(Map<String, String> tags, List<String> metrics, List<String> groupBy,
Map<String, String> timeRange) {
this.tags = tags;
this.metrics = metrics;
this.groupBy = groupBy;
this.timeRange = timeRange;
}
}
private class QueryResult {
private ImmutableList<TimeSeriesSummary> expectedList;
private String expectedResolution;
public QueryResult(ImmutableList<TimeSeriesSummary> list, String resolution) {
expectedList = list;
expectedResolution = resolution;
}
public String getExpectedResolution() {
return expectedResolution;
}
public ImmutableList<TimeSeriesSummary> getExpectedList() {
return expectedList;
}
}
@Test
public void testTimeRangeQueryBatch() throws Exception {
// note: times are in seconds, hence "divide by 1000";
long start = (emitTs - 60 * 1000) / 1000;
long end = (emitTs + 60 * 1000) / 1000;
int count = 120;
QueryRequestFormat query1 = new QueryRequestFormat(getTagsMap("namespace", "yourspace", "app", "WCount1",
"flow", "WCounter", "flowlet", "splitter"),
ImmutableList.of("system.reads"), ImmutableList.<String>of(),
ImmutableMap.of("start", String.valueOf(start),
"end", String.valueOf(end)));
QueryRequestFormat query2 = new QueryRequestFormat(getTagsMap("namespace", "yourspace", "app", "WCount1",
"flow", "WCounter"), ImmutableList.of("system.reads"),
ImmutableList.of("flowlet"),
ImmutableMap.of("start", String.valueOf(start),
"count", String.valueOf(count)));
ImmutableMap<String, QueryResult> expected =
ImmutableMap.of("timeRangeQuery1",
new QueryResult(ImmutableList.of(new TimeSeriesSummary(ImmutableMap.<String,
String>of(), "system.reads", 2, 3)), "1s"),
"timeRangeQuery2",
new QueryResult(ImmutableList.of(
new TimeSeriesSummary(ImmutableMap.of("flowlet", "counter"), "system.reads", 1, 1),
new TimeSeriesSummary(ImmutableMap.of("flowlet", "splitter"), "system.reads", 2, 3)), "1s"));
Map<String, QueryRequestFormat> batchQueries = ImmutableMap.of("timeRangeQuery1", query1,
"timeRangeQuery2", query2);
batchTest(batchQueries, expected);
}
@Test
public void testMultipleMetricsSingleContext() throws Exception {
verifyAggregateQueryResult(
"/v3/metrics/query?tag=namespace:myspace&tag=app:WordCount1&tag=flow:WordCounter&tag=flowlet:splitter" +
"&metric=system.reads&metric=system.writes&aggregate=true", ImmutableList.of(2L, 2L));
long start = (emitTs - 60 * 1000) / 1000;
long end = (emitTs + 300 * 1000) / 1000;
verifyRangeQueryResult(
"/v3/metrics/query?tag=namespace:myspace&tag=app:WordCount1&tag=flow:WordCounter&tag=flowlet:collector" +
"&metric=system.aa&metric=system.ab&metric=system.zz&start=" + start + "&end="
+ end, ImmutableList.of(1L, 1L, 1L), ImmutableList.of(1L, 1L, 1L));
}
@Test
public void testQueryMetricsWithTags() throws Exception {
//aggregate result, in the right namespace
verifyAggregateQueryResult(
"/v3/metrics/query?" + getTags("yourspace", "WCount1", "WCounter", "splitter") +
"&metric=system.reads&aggregate=true", 3);
verifyAggregateQueryResult(
"/v3/metrics/query?" + getTags("yourspace", "WCount1", "WCounter", "counter") +
"&metric=system.reads&aggregate=true", 1);
verifyAggregateQueryResult(
"/v3/metrics/query?" + getTags("yourspace", "WCount1", "WCounter", "*") +
"&metric=system.reads&aggregate=true", 4);
verifyAggregateQueryResult(
"/v3/metrics/query?" + getTags("yourspace", "WCount1", "WCounter") +
"&metric=system.reads&aggregate=true", 4);
// aggregate result, in the wrong namespace
verifyEmptyQueryResult(
"/v3/metrics/query?" + getTags("myspace", "WCount1", "WCounter", "splitter") +
"&metric=system.reads&aggregate=true");
// time range
// now-60s, now+60s
verifyRangeQueryResult(
"/v3/metrics/query?" + getTags("yourspace", "WCount1", "WCounter", "splitter") +
"&metric=system.reads&start=now%2D60s&end=now%2B60s", 2, 3);
// note: times are in seconds, hence "divide by 1000";
long start = (emitTs - 60 * 1000) / 1000;
long end = (emitTs + 60 * 1000) / 1000;
verifyRangeQueryResult(
"/v3/metrics/query?" + getTags("yourspace", "WCount1", "WCounter", "splitter") +
"&metric=system.reads&start=" + start + "&end="
+ end, 2, 3);
// range query, in the wrong namespace
verifyEmptyQueryResult(
"/v3/metrics/query?" + getTags("myspace", "WCount1", "WCounter", "splitter") +
"&metric=system.reads&start=" + start + "&end="
+ end);
List<TimeSeriesResult> groupByResult =
ImmutableList.of(new TimeSeriesResult(ImmutableMap.of("flowlet", "counter"), 1),
new TimeSeriesResult(ImmutableMap.of("flowlet", "splitter"), 3));
verifyGroupByResult(
"/v3/metrics/query?" + getTags("yourspace", "WCount1", "WCounter") +
"&metric=system.reads&groupBy=flowlet&start=" + start + "&end="
+ end, groupByResult);
groupByResult =
ImmutableList.of(new TimeSeriesResult(ImmutableMap.of("namespace", "myspace", "flowlet", "splitter"), 2),
new TimeSeriesResult(ImmutableMap.of("namespace", "yourspace", "flowlet", "counter"), 1),
new TimeSeriesResult(ImmutableMap.of("namespace", "yourspace", "flowlet", "splitter"), 4));
verifyGroupByResult(
"/v3/metrics/query?metric=system.reads" +
"&groupBy=namespace&groupBy=flowlet&start=" + start + "&end=" + end, groupByResult);
}
@Test
public void testInterpolate() throws Exception {
long start = System.currentTimeMillis() / 1000;
long end = start + 3;
Map<String, String> sliceBy = getFlowletContext("interspace", "WordCount1", "WordCounter", "run1", "splitter");
MetricValues value =
new MetricValues(sliceBy, "reads", start, 100, MetricType.COUNTER);
metricStore.add(value);
value =
new MetricValues(sliceBy, "reads", end, 400, MetricType.COUNTER);
metricStore.add(value);
verifyRangeQueryResult(
"/v3/metrics/query?" + getTags("interspace", "WordCount1", "WordCounter", "splitter") +
"&metric=system.reads&interpolate=step&start=" + start + "&end="
+ end, 4, 700);
verifyRangeQueryResult(
"/v3/metrics/query?" + getTags("interspace", "WordCount1", "WordCounter", "splitter") +
"&metric=system.reads&interpolate=linear&start=" + start + "&end="
+ end, 4, 1000);
// delete the added metrics for testing interpolator
MetricDeleteQuery deleteQuery = new MetricDeleteQuery(start, end, sliceBy);
metricStore.delete(deleteQuery);
}
@Test
public void testResolutionInResponse() throws Exception {
long start = 1;
String url = "/v3/metrics/query?" + getTags("resolutions", "WordCount1", "WordCounter", "splitter") +
"&metric=system.reads&resolution=auto&start=" + (start - 1) + "&end="
+ (start + 36000);
MetricQueryResult queryResult = post(url, MetricQueryResult.class);
Assert.assertEquals("3600s", queryResult.getResolution());
url = "/v3/metrics/query?" + getTags("resolutions", "WordCount1", "WordCounter", "splitter") +
"&metric=system.reads&resolution=1m&start=" + (start - 1) + "&end="
+ (start + 36000);
queryResult = post(url, MetricQueryResult.class);
Assert.assertEquals("60s", queryResult.getResolution());
// Have an aggregate query and ensure that its resolution is INT_MAX
url = "/v3/metrics/query?" + getTags("WordCount1", "WordCounter", "splitter") +
"&metric=system.reads";
queryResult = post(url, MetricQueryResult.class);
Assert.assertEquals(Integer.MAX_VALUE + "s", queryResult.getResolution());
}
@Test
public void testAutoResolutions() throws Exception {
long start = 1;
Map<String, String> sliceBy = getFlowletContext("resolutions", "WordCount1", "WordCounter", "run1", "splitter");
// 1 second
metricStore.add(new MetricValues(sliceBy, "reads", start, 1, MetricType.COUNTER));
// 30 second
metricStore.add(new MetricValues(sliceBy, "reads", start + 30, 1, MetricType.COUNTER));
// 1 minute
metricStore.add(new MetricValues(sliceBy, "reads", start + 60, 1, MetricType.COUNTER));
// 10 minutes
metricStore.add(new MetricValues(sliceBy, "reads", start + 600, 1, MetricType.COUNTER));
// 1 hour
metricStore.add(new MetricValues(sliceBy, "reads", start + 3600, 1, MetricType.COUNTER));
// 10 hour
metricStore.add(new MetricValues(sliceBy, "reads", start + 36000, 1, MetricType.COUNTER));
// seconds
verifyRangeQueryResult(
"/v3/metrics/query?" + getTags("resolutions", "WordCount1", "WordCounter", "splitter") +
"&metric=system.reads&resolution=auto&start=" + start + "&end="
+ (start + 600), 4, 4);
// minutes
verifyRangeQueryResult(
"/v3/metrics/query?" + getTags("resolutions", "WordCount1", "WordCounter", "splitter") +
"&metric=system.reads&resolution=auto&start=" + (start - 1) + "&end="
+ (start + 600), 3, 4);
// minutes
verifyRangeQueryResult(
"/v3/metrics/query?" + getTags("resolutions", "WordCount1", "WordCounter", "splitter") +
"&metric=system.reads&resolution=auto&start=" + (start - 1) + "&end="
+ (start + 3600), 4, 5);
// hours
verifyRangeQueryResult(
"/v3/metrics/query?" + getTags("resolutions", "WordCount1", "WordCounter", "splitter") +
"&metric=system.reads&resolution=auto&start=" + (start - 1) + "&end="
+ (start + 36000), 3, 6);
// delete the added metrics for testing auto resolutions
MetricDeleteQuery deleteQuery = new MetricDeleteQuery(start, (start + 36000), sliceBy);
metricStore.delete(deleteQuery);
}
private void verifyGroupByResult(String url, List<TimeSeriesResult> groupByResult) throws Exception {
MetricQueryResult result = post(url, MetricQueryResult.class);
Assert.assertEquals(groupByResult.size(), result.getSeries().length);
for (MetricQueryResult.TimeSeries timeSeries : result.getSeries()) {
boolean timeSeriesMatchFound = false;
for (TimeSeriesResult expectedTs : groupByResult) {
if (expectedTs.getTagValues().equals(ImmutableMap.copyOf(timeSeries.getGrouping()))) {
assertTimeValues(expectedTs, timeSeries);
timeSeriesMatchFound = true;
}
}
Assert.assertTrue(timeSeriesMatchFound);
}
}
private void assertTimeValues(TimeSeriesResult expected, MetricQueryResult.TimeSeries actual) {
long expectedValue = expected.getTimeSeriesSum();
for (MetricQueryResult.TimeValue timeValue : actual.getData()) {
expectedValue -= timeValue.getValue();
}
Assert.assertEquals(0, expectedValue);
}
private void verifyEmptyQueryResult(String url) throws Exception {
MetricQueryResult queryResult = post(url, MetricQueryResult.class);
Assert.assertEquals(0, queryResult.getSeries().length);
}
private String getTags(String... tags) {
String result = "";
for (int i = 0; i < tags.length; i++) {
result += "&tag=" + FLOW_TAGS_HUMAN.get(i) + ":" + tags[i];
}
return result;
}
@Test
public void testSearchMetricsWithTags() throws Exception {
// metrics in myspace
verifySearchMetricResult("/v3/metrics/search?target=metric&tag=namespace:myspace&tag=app:WordCount1" +
"&tag=flow:WordCounter&tag=dataset:*&tag=run:run1&tag=flowlet:splitter",
ImmutableList.of("system.reads", "system.writes", "user.reads", "user.writes"));
verifySearchMetricResult("/v3/metrics/search?target=metric&tag=namespace:myspace&tag=app:WordCount1" +
"&tag=flow:WordCounter&tag=dataset:*&tag=run:run1&tag=flowlet:collector",
ImmutableList.of("system.aa", "system.ab", "system.zz"));
verifySearchMetricResult("/v3/metrics/search?target=metric&tag=namespace:myspace&tag=app:WordCount1" +
"&tag=flow:WordCounter&tag=dataset:*&tag=run:run1",
ImmutableList.of("system.aa", "system.ab", "system.reads",
"system.writes", "system.zz", "user.reads", "user.writes"));
// wrong namespace
verifySearchMetricResult("/v3/metrics/search?target=metric&tag=namespace:yourspace&tag=app:WordCount1" +
"&tag=flow:WordCounter&tag=dataset:*&tag=run:run1&tag=flowlet:splitter",
ImmutableList.<String>of());
// metrics in yourspace
verifySearchMetricResult("/v3/metrics/search?target=metric&tag=namespace:yourspace&tag=app:WCount1" +
"&tag=flow:WCounter&tag=dataset:*&tag=run:run1&tag=flowlet:splitter",
ImmutableList.of("system.reads"));
verifySearchMetricResult("/v3/metrics/search?target=metric&tag=namespace:yourspace",
ImmutableList.of("system.reads", "system.workerreads", "system.workerwrites"));
// wrong namespace
verifySearchMetricResult("/v3/metrics/search?target=metric&tag=namespace:myspace&tag=app:WCount1" +
"&tag=flow:WCounter&tag=dataset:*&tag=run:run1&tag=flowlet:splitter",
ImmutableList.<String>of());
// verify "*"
verifySearchMetricResult("/v3/metrics/search?target=metric&tag=namespace:myspace&tag=app:WordCount1" +
"&tag=flow:WordCounter&tag=dataset:*&tag=run:run1&tag=flowlet:*",
ImmutableList.of("system.aa", "system.ab", "system.reads",
"system.writes", "system.zz", "user.reads", "user.writes"));
verifySearchMetricResult("/v3/metrics/search?target=metric&tag=namespace:myspace&tag=app:WordCount1" +
"&tag=flow:*&tag=dataset:*&tag=run:run1",
ImmutableList.of("system.aa", "system.ab", "system.reads",
"system.writes", "system.zz", "user.reads", "user.writes"));
}
/**
* Used to test time range queries when requests are batched
*/
class TimeSeriesSummary {
Map<String, String> grouping;
String metricName;
long numPoints;
long totalSum;
public TimeSeriesSummary(Map<String, String> grouping, String metricName, long numPoints, long totalSum) {
this.grouping = grouping;
this.metricName = metricName;
this.numPoints = numPoints;
this.totalSum = totalSum;
}
public long getTotalSum() {
return totalSum;
}
public long getNumPoints() {
return numPoints;
}
public String getMetricName() {
return metricName;
}
public Map<String, String> getGrouping() {
return grouping;
}
}
private void batchTest(Map<String, QueryRequestFormat> jsonBatch,
ImmutableMap<String, QueryResult> expected) throws Exception {
String url = "/v3/metrics/query";
Map<String, MetricQueryResult> results =
post(url, GSON.toJson(jsonBatch), new TypeToken<Map<String, MetricQueryResult>>() { }.getType());
// check we have all the keys
Assert.assertEquals(expected.keySet(), results.keySet());
for (Map.Entry<String, MetricQueryResult> entry : results.entrySet()) {
ImmutableList<TimeSeriesSummary> expectedTimeSeriesSummary = expected.get(entry.getKey()).getExpectedList();
MetricQueryResult actualQueryResult = entry.getValue();
compareQueryResults(expectedTimeSeriesSummary, actualQueryResult);
Assert.assertEquals(expected.get(entry.getKey()).getExpectedResolution(), actualQueryResult.getResolution());
}
}
private void compareQueryResults(ImmutableList<TimeSeriesSummary> expected, MetricQueryResult actual) {
MetricQueryResult.TimeSeries[] actualTimeSeries = actual.getSeries();
for (MetricQueryResult.TimeSeries actualTimeSery : actualTimeSeries) {
TimeSeriesSummary expectedTimeSeriesSummary = findExpectedQueryResult(expected, actualTimeSery);
Assert.assertNotNull(expectedTimeSeriesSummary);
MetricQueryResult.TimeValue[] values = actualTimeSery.getData();
long numPoints = 0;
long totalSum = 0;
for (MetricQueryResult.TimeValue tv : values) {
numPoints++;
totalSum += tv.getValue();
}
Assert.assertEquals(expectedTimeSeriesSummary.getNumPoints(), numPoints);
Assert.assertEquals(expectedTimeSeriesSummary.getTotalSum(), totalSum);
}
}
private TimeSeriesSummary findExpectedQueryResult(ImmutableList<TimeSeriesSummary> expected,
MetricQueryResult.TimeSeries actualTimeSeries) {
for (TimeSeriesSummary result : expected) {
if (result.getGrouping().equals(actualTimeSeries.getGrouping()) &&
result.getMetricName().equals(actualTimeSeries.getMetricName())) {
return result;
}
}
return null;
}
private void verifyAggregateQueryResult(String url, List<Long> expectedValue) throws Exception {
MetricQueryResult queryResult = post(url, MetricQueryResult.class);
for (int i = 0; i < queryResult.getSeries().length; i++) {
Assert.assertEquals(expectedValue.get(i), (Long) queryResult.getSeries()[i].getData()[0].getValue());
}
}
private void verifyRangeQueryResult(String url, List<Long> expectedPoints, List<Long> expectedSum) throws Exception {
MetricQueryResult queryResult = post(url, MetricQueryResult.class);
for (int i = 0; i < queryResult.getSeries().length; i++) {
verifyTimeSeries(queryResult.getSeries()[i], expectedPoints.get(i), expectedSum.get(i));
}
}
@Test
public void testResultLimit() throws Exception {
long start = 1;
Map<String, String> sliceBy = getFlowletContext("resolutions", "WordCount1", "WordCounter", "run1", "splitter");
// 1 second
metricStore.add(new MetricValues(sliceBy, "reads", start, 1, MetricType.COUNTER));
// 30 second
metricStore.add(new MetricValues(sliceBy, "reads", start + 30, 1, MetricType.COUNTER));
// 1 minute
metricStore.add(new MetricValues(sliceBy, "reads", start + 60, 1, MetricType.COUNTER));
// 10 minutes
metricStore.add(new MetricValues(sliceBy, "reads", start + 600, 1, MetricType.COUNTER));
// 1 hour
metricStore.add(new MetricValues(sliceBy, "reads", start + 3600, 1, MetricType.COUNTER));
// count is one record
verifyRangeQueryResult(
"/v3/metrics/query?" + getTags("resolutions", "WordCount1", "WordCounter", "splitter") +
"&metric=system.reads&resolution=auto&count=1&start=" + start + "&end="
+ (start + 600), 1, 1);
// count is greater than data points in time-range
verifyRangeQueryResult(
"/v3/metrics/query?" + getTags("resolutions", "WordCount1", "WordCounter", "splitter") +
"&metric=system.reads&resolution=auto&count=6&start=" + start + "&end="
+ (start + 600), 4, 4);
// count is less than data points in time-range
verifyRangeQueryResult(
"/v3/metrics/query?" + getTags("resolutions", "WordCount1", "WordCounter", "splitter") +
"&metric=system.reads&resolution=auto&count=2&start=" + (start - 1) + "&end="
+ (start + 3600), 2, 3);
}
private void verifyAggregateQueryResult(String url, long expectedValue) throws Exception {
// todo : can refactor this to test only the new tag name queries once we deprecate queryParam using context.
MetricQueryResult queryResult = post(url, MetricQueryResult.class);
Assert.assertEquals(expectedValue, queryResult.getSeries()[0].getData()[0].getValue());
}
private void verifyRangeQueryResult(String url, long nonZeroPointsCount, long expectedSum) throws Exception {
MetricQueryResult queryResult = post(url, MetricQueryResult.class);
verifyTimeSeries(queryResult.getSeries()[0], nonZeroPointsCount, expectedSum);
}
private void verifyTimeSeries(MetricQueryResult.TimeSeries timeSeries, long nonZeroPointsCount, long expectedSum) {
MetricQueryResult.TimeValue[] data = timeSeries.getData();
for (MetricQueryResult.TimeValue point : data) {
if (point.getValue() != 0) {
nonZeroPointsCount--;
expectedSum -= point.getValue();
}
}
Assert.assertEquals(0, nonZeroPointsCount);
Assert.assertEquals(0, expectedSum);
}
private <T> T post(String url, Type type) throws Exception {
return post(url, null, type);
}
private <T> T post(String url, String body, Type type) throws Exception {
HttpResponse response = doPost(url, body);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
return GSON.fromJson(EntityUtils.toString(response.getEntity(), Charsets.UTF_8), type);
}
private void verifySearchMetricResult(String url, List<String> expectedValues) throws Exception {
HttpResponse response = doPost(url, null);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
String result = EntityUtils.toString(response.getEntity());
List<String> reply = GSON.fromJson(result, new TypeToken<List<String>>() { }.getType());
reply = removeMetricsSystemMetrics(reply);
Assert.assertEquals(expectedValues.size(), reply.size());
for (int i = 0; i < expectedValues.size(); i++) {
Assert.assertEquals(expectedValues.get(i), reply.get(i));
}
}
private List<String> removeMetricsSystemMetrics(List<String> reply) {
List<String> result = Lists.newArrayList();
for (String metric : reply) {
if (!metric.startsWith("system.metrics") && !metric.startsWith("user.metrics")) {
result.add(metric);
}
}
return result;
}
private void verifySearchResultWithTags(String url, List<Map<String, String>> expectedValues) throws Exception {
HttpResponse response = doPost(url, null);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
String result = EntityUtils.toString(response.getEntity(), Charsets.UTF_8);
List<Map<String, String>> reply = GSON.fromJson(result, new TypeToken<List<Map<String, String>>>() { }.getType());
Assert.assertTrue(reply.containsAll(expectedValues) && expectedValues.containsAll(reply));
}
class TimeSeriesResult {
public Map<String, String> getTagValues() {
return tagValues;
}
public long getTimeSeriesSum() {
return timeSeriesSum;
}
Map<String, String> tagValues;
long timeSeriesSum;
TimeSeriesResult(Map<String, String> tagValues, long timeSeriesSum) {
this.tagValues = tagValues;
this.timeSeriesSum = timeSeriesSum;
}
}
}