/* * Copyright 2018, OpenCensus Authors * * 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 io.opencensus.implcore.metrics; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import io.opencensus.common.Clock; import io.opencensus.common.ToDoubleFunction; import io.opencensus.implcore.internal.Utils; import io.opencensus.metrics.DerivedDoubleGauge; import io.opencensus.metrics.LabelKey; import io.opencensus.metrics.LabelValue; import io.opencensus.metrics.export.Metric; import io.opencensus.metrics.export.MetricDescriptor; import io.opencensus.metrics.export.MetricDescriptor.Type; import io.opencensus.metrics.export.Point; import io.opencensus.metrics.export.TimeSeries; import io.opencensus.metrics.export.Value; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /*>>> import org.checkerframework.checker.nullness.qual.Nullable; */ /** Implementation of {@link DerivedDoubleGauge}. */ public final class DerivedDoubleGaugeImpl extends DerivedDoubleGauge implements Meter { private final MetricDescriptor metricDescriptor; private final int labelKeysSize; @SuppressWarnings("rawtypes") private volatile Map, PointWithFunction> registeredPoints = Collections., PointWithFunction>emptyMap(); DerivedDoubleGaugeImpl(String name, String description, String unit, List labelKeys) { labelKeysSize = labelKeys.size(); this.metricDescriptor = MetricDescriptor.create(name, description, unit, Type.GAUGE_DOUBLE, labelKeys); } @Override @SuppressWarnings("rawtypes") public synchronized void createTimeSeries( List labelValues, /*@Nullable*/ T obj, ToDoubleFunction function) { Utils.checkListElementNotNull( checkNotNull(labelValues, "labelValues"), "labelValue element should not be null."); checkArgument(labelKeysSize == labelValues.size(), "Incorrect number of labels."); checkNotNull(function, "function"); List labelValuesCopy = Collections.unmodifiableList(new ArrayList(labelValues)); PointWithFunction existingPoint = registeredPoints.get(labelValuesCopy); if (existingPoint != null) { throw new IllegalArgumentException( "A different time series with the same labels already exists."); } PointWithFunction newPoint = new PointWithFunction(labelValuesCopy, obj, function); // Updating the map of time series happens under a lock to avoid multiple add operations // to happen in the same time. Map, PointWithFunction> registeredPointsCopy = new LinkedHashMap, PointWithFunction>(registeredPoints); registeredPointsCopy.put(labelValuesCopy, newPoint); registeredPoints = Collections.unmodifiableMap(registeredPointsCopy); } @Override @SuppressWarnings("rawtypes") public synchronized void removeTimeSeries(List labelValues) { checkNotNull(labelValues, "labelValues"); Map, PointWithFunction> registeredPointsCopy = new LinkedHashMap, PointWithFunction>(registeredPoints); if (registeredPointsCopy.remove(labelValues) == null) { // The element not present, no need to update the current map of time series. return; } registeredPoints = Collections.unmodifiableMap(registeredPointsCopy); } @Override @SuppressWarnings("rawtypes") public synchronized void clear() { registeredPoints = Collections., PointWithFunction>emptyMap(); } /*@Nullable*/ @Override @SuppressWarnings("rawtypes") public Metric getMetric(Clock clock) { Map, PointWithFunction> currentRegisteredPoints = registeredPoints; if (currentRegisteredPoints.isEmpty()) { return null; } if (currentRegisteredPoints.size() == 1) { PointWithFunction point = currentRegisteredPoints.values().iterator().next(); return Metric.createWithOneTimeSeries(metricDescriptor, point.getTimeSeries(clock)); } List timeSeriesList = new ArrayList(currentRegisteredPoints.size()); for (Map.Entry, PointWithFunction> entry : currentRegisteredPoints.entrySet()) { timeSeriesList.add(entry.getValue().getTimeSeries(clock)); } return Metric.create(metricDescriptor, timeSeriesList); } /** Implementation of {@link PointWithFunction} with an object and a callback function. */ public static final class PointWithFunction { private final List labelValues; @javax.annotation.Nullable private final WeakReference ref; private final ToDoubleFunction function; PointWithFunction( List labelValues, /*@Nullable*/ T obj, ToDoubleFunction function) { this.labelValues = labelValues; ref = obj != null ? new WeakReference(obj) : null; this.function = function; } private TimeSeries getTimeSeries(Clock clock) { final T obj = ref != null ? ref.get() : null; double value = function.applyAsDouble(obj); // TODO(mayurkale): OPTIMIZATION: Avoid re-evaluate the labelValues all the time (issue#1490). return TimeSeries.createWithOnePoint( labelValues, Point.create(Value.doubleValue(value), clock.now()), null); } } }