package org.stagemonitor.tracing.metrics;
import com.codahale.metrics.Timer;
import org.stagemonitor.core.metrics.metrics2.Metric2Registry;
import org.stagemonitor.core.metrics.metrics2.MetricName;
import org.stagemonitor.core.util.TimeUtils;
import org.stagemonitor.tracing.SpanContextInformation;
import org.stagemonitor.tracing.TracingPlugin;
import org.stagemonitor.tracing.wrapper.ClientServerAwareSpanEventListener;
import org.stagemonitor.tracing.wrapper.SpanEventListener;
import org.stagemonitor.tracing.wrapper.SpanEventListenerFactory;
import org.stagemonitor.tracing.wrapper.SpanWrapper;
import org.stagemonitor.util.StringUtils;
import java.util.concurrent.TimeUnit;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.stagemonitor.core.metrics.metrics2.MetricName.name;
public class ServerRequestMetricsSpanEventListener extends ClientServerAwareSpanEventListener {
private static final MetricName.MetricNameTemplate responseTimeCpuTemplate = name("response_time_cpu")
.tag("request_name", "").layer("All")
.templateFor("request_name");
private static final MetricName.MetricNameTemplate errorRateTemplate = name("error_rate_server")
.tag("request_name", "")
.layer("All")
.templateFor("request_name");
private static MetricName.MetricNameTemplate timerMetricNameTemplate = name("response_time_server")
.tag("request_name", "")
.layer("All")
.templateFor("request_name");
private static final MetricName.MetricNameTemplate externalRequestRateTemplate = name("external_requests_rate")
.templateFor("request_name", "type");
private static final MetricName.MetricNameTemplate responseTimeExternalRequestLayerTemplate = name("response_time_server")
.templateFor("request_name", "layer");
private final Metric2Registry metricRegistry;
private final TracingPlugin tracingPlugin;
private long startCpu;
private boolean error;
public ServerRequestMetricsSpanEventListener(Metric2Registry metricRegistry, TracingPlugin tracingPlugin) {
this.metricRegistry = metricRegistry;
this.tracingPlugin = tracingPlugin;
}
@Override
public void onStart(SpanWrapper spanWrapper) {
startCpu = TimeUtils.getCpuTime();
}
@Override
public boolean onSetTag(String key, boolean value) {
if (Tags.ERROR.getKey().equals(key)) {
error = value;
}
return value;
}
@Override
public void onFinish(SpanWrapper spanWrapper, String operationName, long durationNanos) {
final SpanContextInformation contextInformation = SpanContextInformation.forSpan(spanWrapper);
if (isServer && StringUtils.isNotEmpty(operationName)) {
final long cpuTime = trackCpuTime(spanWrapper.getDelegate());
trackMetrics(spanWrapper, operationName, durationNanos, cpuTime);
trackExternalRequestMetricsOfParent(spanWrapper.getDelegate(), operationName, contextInformation);
}
}
private long trackCpuTime(Span span) {
final long cpuTime = TimeUtils.getCpuTime() - startCpu;
span.setTag("duration_cpu", NANOSECONDS.toMicros(cpuTime));
span.setTag("duration_cpu_ms", NANOSECONDS.toMillis(cpuTime));
return cpuTime;
}
private void trackMetrics(Span span, String operationName, long durationNanos, long cpuTime) {
final Timer timer = metricRegistry.timer(getTimerMetricName(operationName));
timer.update(durationNanos, NANOSECONDS);
final SpanContextInformation spanContext = SpanContextInformation.forSpan(span);
spanContext.setTimerForThisRequest(timer);
metricRegistry.timer(getTimerMetricName("All")).update(durationNanos, NANOSECONDS);
if (tracingPlugin.isCollectCpuTime()) {
metricRegistry.timer(responseTimeCpuTemplate.build(operationName)).update(cpuTime, MICROSECONDS);
metricRegistry.timer(responseTimeCpuTemplate.build("All")).update(cpuTime, MICROSECONDS);
}
if (error) {
metricRegistry.meter(getErrorMetricName(operationName)).mark();
metricRegistry.meter(getErrorMetricName("All")).mark();
}
}
/*
* tracks the external requests grouped by the parent request name
*/
private void trackExternalRequestMetricsOfParent(Span span, String operationName, SpanContextInformation spanContext) {
for (SpanContextInformation.ExternalRequestStats externalRequestStats : spanContext.getExternalRequestStats()) {
long durationNanos = externalRequestStats.getExecutionTimeNanos();
final String requestType = externalRequestStats.getRequestType();
span.setTag("external_requests." + requestType + ".duration_ms", TimeUnit.NANOSECONDS.toMillis(durationNanos));
span.setTag("external_requests." + requestType + ".count", externalRequestStats.getExecutionCount());
if (durationNanos > 0) {
if (tracingPlugin.isCollectDbTimePerRequest()) {
metricRegistry.timer(responseTimeExternalRequestLayerTemplate
.build(operationName, requestType))
.update(durationNanos, NANOSECONDS);
}
metricRegistry.timer(responseTimeExternalRequestLayerTemplate
.build("All", requestType))
.update(durationNanos, NANOSECONDS);
}
metricRegistry.meter(externalRequestRateTemplate
.build(operationName, requestType))
.mark(externalRequestStats.getExecutionCount());
}
}
public static MetricName getErrorMetricName(String requestName) {
return errorRateTemplate.build(requestName);
}
public static MetricName getTimerMetricName(String requestName) {
return timerMetricNameTemplate.build(requestName);
}
public static class Factory implements SpanEventListenerFactory {
private final Metric2Registry metricRegistry;
private final TracingPlugin tracingPlugin;
public Factory(Metric2Registry metricRegistry, TracingPlugin tracingPlugin) {
this.metricRegistry = metricRegistry;
this.tracingPlugin = tracingPlugin;
}
@Override
public SpanEventListener create() {
return new ServerRequestMetricsSpanEventListener(metricRegistry, tracingPlugin);
}
}
}