/*
* 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.test;
import co.cask.cdap.api.dataset.lib.cube.AggregationFunction;
import co.cask.cdap.api.dataset.lib.cube.TimeValue;
import co.cask.cdap.api.metrics.MetricDataQuery;
import co.cask.cdap.api.metrics.MetricStore;
import co.cask.cdap.api.metrics.MetricTimeSeries;
import co.cask.cdap.api.metrics.RuntimeMetrics;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.common.metrics.MetricsTags;
import co.cask.cdap.proto.Id;
import co.cask.cdap.proto.ProgramType;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;
import com.google.common.base.Throwables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;
/**
* Used by tests for querying metrics system.
*/
public class MetricsManager {
private MetricStore metricStore;
public MetricsManager(MetricStore metricStore) {
this.metricStore = metricStore;
}
/**
* query the metric store and return the Collection<MetricTimeSeries>
* @param query
* @return Collection<MetricTimeSeries>
* @throws Exception
*/
public Collection<MetricTimeSeries> query(MetricDataQuery query) throws Exception {
return metricStore.query(query);
}
/**
* returns flowlet related metrics
* @param namespace
* @param appName
* @param flowName
* @param flowletName
* @return {@link co.cask.cdap.api.metrics.RuntimeMetrics}
*/
public RuntimeMetrics getFlowletMetrics(String namespace, String appName,
String flowName, String flowletName) {
Id.Program id = Id.Program.from(namespace, appName, ProgramType.FLOW, flowName);
return getMetrics(MetricsTags.flowlet(id, flowletName),
Constants.Metrics.Name.Flow.FLOWLET_INPUT,
Constants.Metrics.Name.Flow.FLOWLET_PROCESSED,
Constants.Metrics.Name.Flow.FLOWLET_EXCEPTIONS);
}
/**
* returns service related metrics
* @param namespace
* @param applicationId
* @param serviceId
* @return {@link co.cask.cdap.api.metrics.RuntimeMetrics}
*/
public RuntimeMetrics getServiceMetrics(String namespace, String applicationId, String serviceId) {
Id.Program id = Id.Program.from(namespace, applicationId, ProgramType.SERVICE, serviceId);
return getMetrics(MetricsTags.service(id),
Constants.Metrics.Name.Service.SERVICE_INPUT,
Constants.Metrics.Name.Service.SERVICE_PROCESSED,
Constants.Metrics.Name.Service.SERVICE_EXCEPTIONS);
}
/**
* returns service handler related metrics
* @param namespace
* @param applicationId
* @param serviceId
* @param handlerId
* @return {@link co.cask.cdap.api.metrics.RuntimeMetrics}
*/
public RuntimeMetrics getServiceHandlerMetrics(String namespace, String applicationId, String serviceId,
String handlerId) {
Id.Program id = Id.Program.from(namespace, applicationId, ProgramType.SERVICE, serviceId);
return getMetrics(MetricsTags.serviceHandler(id, handlerId),
Constants.Metrics.Name.Service.SERVICE_INPUT,
Constants.Metrics.Name.Service.SERVICE_PROCESSED,
Constants.Metrics.Name.Service.SERVICE_EXCEPTIONS);
}
/**
* get metrics total count value for a given context and metric.
* @param tags that identify a context
* @param metricName
* @return the total metric
*/
public long getTotalMetric(Map<String, String> tags, String metricName) {
MetricDataQuery query = getTotalCounterQuery(tags, metricName);
return getSingleValueFromTotals(query);
}
/**
* waitFor a metric value count for the metric identified by metricName and context.
* @param tags - context identified by tags map
* @param metricName
* @param count - expected metric total count value
* @param timeout
* @param timeoutUnit
* @throws TimeoutException
* @throws InterruptedException
*/
public void waitForTotalMetricCount(Map<String, String> tags, String metricName, long count, long timeout,
TimeUnit timeoutUnit) throws TimeoutException, InterruptedException {
long value = getTotalMetric(tags, metricName);
// Min sleep time is 10ms, max sleep time is 1 seconds
long sleepMillis = Math.max(10, Math.min(timeoutUnit.toMillis(timeout) / 10, TimeUnit.SECONDS.toMillis(1)));
Stopwatch stopwatch = new Stopwatch().start();
while (value < count && stopwatch.elapsedTime(timeoutUnit) < timeout) {
TimeUnit.MILLISECONDS.sleep(sleepMillis);
value = getTotalMetric(tags, metricName);
}
if (value < count) {
throw new TimeoutException("Time limit reached: Expected '" + count + "' but got '" + value + "'");
}
}
/**
* deletes all metrics
* @throws Exception
*/
public void resetAll() throws Exception {
metricStore.deleteAll();
}
private RuntimeMetrics getMetrics(final Map<String, String> context,
final String inputName,
final String processedName,
@Nullable final String exceptionName) {
return new RuntimeMetrics() {
@Override
public long getInput() {
return getTotalMetric(context, inputName);
}
@Override
public long getProcessed() {
return getTotalMetric(context, processedName);
}
@Override
public long getException() {
Preconditions.checkArgument(exceptionName != null, "exception count not supported");
return getTotalMetric(context, exceptionName);
}
@Override
public void waitForinput(long count, long timeout, TimeUnit timeoutUnit)
throws TimeoutException, InterruptedException {
waitForTotalMetricCount(context, inputName, count, timeout, timeoutUnit);
}
@Override
public void waitForProcessed(long count, long timeout, TimeUnit timeoutUnit)
throws TimeoutException, InterruptedException {
waitForTotalMetricCount(context, processedName, count, timeout, timeoutUnit);
}
@Override
public void waitForException(long count, long timeout, TimeUnit timeoutUnit)
throws TimeoutException, InterruptedException {
waitForTotalMetricCount(context, exceptionName, count, timeout, timeoutUnit);
}
@Override
public void waitFor(String name, long count,
long timeout, TimeUnit timeoutUnit) throws TimeoutException, InterruptedException {
waitForTotalMetricCount(context, name, count, timeout, timeoutUnit);
}
@Override
public String toString() {
return String.format("%s; input=%d, processed=%d, exception=%d",
Joiner.on(",").withKeyValueSeparator(":").join(context),
getInput(), getProcessed(), getException());
}
};
}
private MetricDataQuery getTotalCounterQuery(Map<String, String> context, String metricName) {
return new MetricDataQuery(0, 0, Integer.MAX_VALUE, metricName, AggregationFunction.SUM,
context, new ArrayList<String>());
}
private long getSingleValueFromTotals(MetricDataQuery query) {
try {
Collection<MetricTimeSeries> result = metricStore.query(query);
if (result.isEmpty()) {
return 0;
}
// since it is totals query and not groupBy specified, we know there's one time series
List<TimeValue> timeValues = result.iterator().next().getTimeValues();
if (timeValues.isEmpty()) {
return 0;
}
// since it is totals, we know there's one value only
return timeValues.get(0).getValue();
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
}