com.spotify.apollo.metrics.semantic.SemanticServiceMetrics Maven / Gradle / Ivy
/*
* -\-\-
* Spotify Apollo Metrics Module
* --
* Copyright (C) 2013 - 2016 Spotify AB
* --
* 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 com.spotify.apollo.metrics.semantic;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.RatioGauge;
import com.codahale.metrics.RatioGauge.Ratio;
import com.codahale.metrics.Timer;
import com.spotify.apollo.Response;
import com.spotify.apollo.metrics.RequestMetrics;
import com.spotify.apollo.metrics.ServiceMetrics;
import com.spotify.metrics.core.MetricId;
import com.spotify.metrics.core.SemanticMetricRegistry;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import okio.ByteString;
import static com.spotify.apollo.metrics.semantic.What.DROPPED_REQUEST_RATE;
import static com.spotify.apollo.metrics.semantic.What.ENDPOINT_REQUEST_DURATION;
import static com.spotify.apollo.metrics.semantic.What.ENDPOINT_REQUEST_RATE;
import static com.spotify.apollo.metrics.semantic.What.ERROR_RATIO;
import static com.spotify.apollo.metrics.semantic.What.REQUEST_FANOUT_FACTOR;
import static com.spotify.apollo.metrics.semantic.What.REQUEST_PAYLOAD_SIZE;
import static com.spotify.apollo.metrics.semantic.What.RESPONSE_PAYLOAD_SIZE;
import static java.util.Objects.requireNonNull;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
class SemanticServiceMetrics implements ServiceMetrics {
private final SemanticMetricRegistry metricRegistry;
private final MetricId metricId;
private final Predicate enabledMetrics;
private final Set precreateCodes;
private final LoadingCache metersCache;
SemanticServiceMetrics(SemanticMetricRegistry metricRegistry,
MetricId id,
Set precreateCodes,
Predicate enabledMetrics) {
this.metricRegistry = requireNonNull(metricRegistry);
// Already tagged with 'application' and 'service'
this.metricId = requireNonNull(id);
this.enabledMetrics = requireNonNull(enabledMetrics);
this.precreateCodes = ImmutableSet.copyOf(precreateCodes);
metersCache = CacheBuilder.newBuilder()
.build(new CacheLoader() {
@Override
public CachedMeters load(String endpoint) throws Exception {
return metersForEndpoint(endpoint);
}
});
}
@Override
public RequestMetrics metricsForEndpointCall(String endpoint) {
CachedMeters meters = metersCache.getUnchecked(endpoint);
return new SemanticRequestMetrics(
meters.requestRateCounter,
meters.fanoutHistogram,
meters.responseSizeHistogram,
meters.requestSizeHistogram,
meters.requestDurationTimer.map(Timer::time),
meters.droppedRequests,
meters.sentReplies,
meters.sentErrors);
}
private CachedMeters metersForEndpoint(String endpoint) {
MetricId id = metricId.tagged("endpoint", endpoint);
// precreate meters for defined status codes to ensure that they start out at value 0 on restart
for (Integer code : precreateCodes) {
requestRateMeter(id, code);
}
Meter sentReplies = new Meter();
Meter sentErrors = new Meter();
if (enabledMetrics.test(ERROR_RATIO)) {
registerRatioGauge(id, "1m", () -> Ratio.of(sentErrors.getOneMinuteRate(),
sentReplies.getOneMinuteRate()),
metricRegistry);
registerRatioGauge(id, "5m", () -> Ratio.of(sentErrors.getFiveMinuteRate(),
sentReplies.getFiveMinuteRate()),
metricRegistry);
registerRatioGauge(id, "15m", () -> Ratio.of(sentErrors.getFifteenMinuteRate(),
sentReplies.getFifteenMinuteRate()),
metricRegistry);
}
return new CachedMeters(
requestRateCounter(id),
fanoutHistogram(id),
responseSizeHistogram(id),
requestSizeHistogram(id),
requestDurationTimer(id),
droppedRequests(id),
sentReplies,
sentErrors);
}
private Optional droppedRequests(MetricId id) {
return enabledMetrics.test(DROPPED_REQUEST_RATE) ?
Optional.of(metricRegistry.meter(
id.tagged(
"what", DROPPED_REQUEST_RATE.tag(),
"unit", "request"
))) :
Optional.empty();
}
private Optional requestDurationTimer(MetricId id) {
return enabledMetrics.test(ENDPOINT_REQUEST_DURATION) ?
Optional.of(metricRegistry
.timer(id.tagged("what", ENDPOINT_REQUEST_DURATION.tag()))) :
Optional.empty();
}
private Optional requestSizeHistogram(MetricId id) {
return enabledMetrics.test(REQUEST_PAYLOAD_SIZE) ?
Optional.of(metricRegistry.histogram(
id.tagged(
"what", REQUEST_PAYLOAD_SIZE.tag(),
"unit", "B"
))) :
Optional.empty();
}
private Optional responseSizeHistogram(MetricId id) {
return enabledMetrics.test(RESPONSE_PAYLOAD_SIZE) ?
Optional.of(metricRegistry.histogram(
id.tagged(
"what", RESPONSE_PAYLOAD_SIZE.tag(),
"unit", "B"
))) :
Optional.empty();
}
private Optional fanoutHistogram(MetricId id) {
return enabledMetrics.test(REQUEST_FANOUT_FACTOR) ?
Optional.of(metricRegistry.histogram(
id.tagged(
"what", REQUEST_FANOUT_FACTOR.tag(),
"unit", "request/request"))) :
Optional.empty();
}
private Optional>> requestRateCounter(MetricId id) {
return enabledMetrics.test(ENDPOINT_REQUEST_RATE) ?
Optional.of(response -> requestRateMeter(id, response.status().code()).mark()) :
Optional.empty();
}
private Meter requestRateMeter(MetricId id, int code) {
return metricRegistry
.meter(id.tagged(
"what", ENDPOINT_REQUEST_RATE.tag(),
"unit", "request",
"status-code", String.valueOf(code)));
}
private void registerRatioGauge(MetricId metricId,
String stat,
Supplier ratioSupplier,
SemanticMetricRegistry metricRegistry) {
metricRegistry.register(
metricId.tagged("what", ERROR_RATIO.tag(), "stat", stat),
new RatioGauge() {
@Override
protected Ratio getRatio() {
return ratioSupplier.get();
}
});
}
private static class CachedMeters {
private final Optional>> requestRateCounter;
private final Optional fanoutHistogram;
private final Optional responseSizeHistogram;
private final Optional requestSizeHistogram;
private final Optional requestDurationTimer;
private final Optional droppedRequests;
private final Meter sentReplies;
private final Meter sentErrors;
private CachedMeters(Optional>> requestRateCounter,
Optional fanoutHistogram,
Optional responseSizeHistogram,
Optional requestSizeHistogram,
Optional requestDurationTimer,
Optional droppedRequests,
Meter sentReplies, Meter sentErrors) {
this.requestRateCounter = requestRateCounter;
this.fanoutHistogram = fanoutHistogram;
this.requestSizeHistogram = requestSizeHistogram;
this.responseSizeHistogram = responseSizeHistogram;
this.requestDurationTimer = requestDurationTimer;
this.droppedRequests = droppedRequests;
this.sentReplies = sentReplies;
this.sentErrors = sentErrors;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy