Home | History | Annotate | Download | only in metrics
      1 /*
      2  * Copyright 2018, OpenCensus Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package io.opencensus.implcore.metrics;
     18 
     19 import static com.google.common.base.Preconditions.checkArgument;
     20 import static com.google.common.base.Preconditions.checkNotNull;
     21 
     22 import com.google.common.annotations.VisibleForTesting;
     23 import com.google.common.util.concurrent.AtomicDouble;
     24 import io.opencensus.common.Clock;
     25 import io.opencensus.implcore.internal.Utils;
     26 import io.opencensus.metrics.DoubleGauge;
     27 import io.opencensus.metrics.LabelKey;
     28 import io.opencensus.metrics.LabelValue;
     29 import io.opencensus.metrics.export.Metric;
     30 import io.opencensus.metrics.export.MetricDescriptor;
     31 import io.opencensus.metrics.export.MetricDescriptor.Type;
     32 import io.opencensus.metrics.export.Point;
     33 import io.opencensus.metrics.export.TimeSeries;
     34 import io.opencensus.metrics.export.Value;
     35 import java.util.ArrayList;
     36 import java.util.Collections;
     37 import java.util.LinkedHashMap;
     38 import java.util.List;
     39 import java.util.Map;
     40 import javax.annotation.Nullable;
     41 
     42 /** Implementation of {@link DoubleGauge}. */
     43 public final class DoubleGaugeImpl extends DoubleGauge implements Meter {
     44   @VisibleForTesting static final LabelValue UNSET_VALUE = LabelValue.create(null);
     45 
     46   private final MetricDescriptor metricDescriptor;
     47   private volatile Map<List<LabelValue>, PointImpl> registeredPoints =
     48       Collections.<List<LabelValue>, PointImpl>emptyMap();
     49   private final int labelKeysSize;
     50   private final List<LabelValue> defaultLabelValues;
     51 
     52   DoubleGaugeImpl(String name, String description, String unit, List<LabelKey> labelKeys) {
     53     labelKeysSize = labelKeys.size();
     54     this.metricDescriptor =
     55         MetricDescriptor.create(name, description, unit, Type.GAUGE_DOUBLE, labelKeys);
     56 
     57     // initialize defaultLabelValues
     58     defaultLabelValues = new ArrayList<LabelValue>(labelKeysSize);
     59     for (int i = 0; i < labelKeysSize; i++) {
     60       defaultLabelValues.add(UNSET_VALUE);
     61     }
     62   }
     63 
     64   @Override
     65   public DoublePoint getOrCreateTimeSeries(List<LabelValue> labelValues) {
     66     // lock free point retrieval, if it is present
     67     PointImpl existingPoint = registeredPoints.get(labelValues);
     68     if (existingPoint != null) {
     69       return existingPoint;
     70     }
     71 
     72     List<LabelValue> labelValuesCopy =
     73         Collections.unmodifiableList(
     74             new ArrayList<LabelValue>(checkNotNull(labelValues, "labelValues")));
     75     return registerTimeSeries(labelValuesCopy);
     76   }
     77 
     78   @Override
     79   public DoublePoint getDefaultTimeSeries() {
     80     // lock free default point retrieval, if it is present
     81     PointImpl existingPoint = registeredPoints.get(defaultLabelValues);
     82     if (existingPoint != null) {
     83       return existingPoint;
     84     }
     85     return registerTimeSeries(Collections.unmodifiableList(defaultLabelValues));
     86   }
     87 
     88   @Override
     89   public synchronized void removeTimeSeries(List<LabelValue> labelValues) {
     90     checkNotNull(labelValues, "labelValues");
     91 
     92     Map<List<LabelValue>, PointImpl> registeredPointsCopy =
     93         new LinkedHashMap<List<LabelValue>, PointImpl>(registeredPoints);
     94     if (registeredPointsCopy.remove(labelValues) == null) {
     95       // The element not present, no need to update the current map of points.
     96       return;
     97     }
     98     registeredPoints = Collections.unmodifiableMap(registeredPointsCopy);
     99   }
    100 
    101   @Override
    102   public synchronized void clear() {
    103     registeredPoints = Collections.<List<LabelValue>, PointImpl>emptyMap();
    104   }
    105 
    106   private synchronized DoublePoint registerTimeSeries(List<LabelValue> labelValues) {
    107     PointImpl existingPoint = registeredPoints.get(labelValues);
    108     if (existingPoint != null) {
    109       // Return a Point that are already registered. This can happen if a multiple threads
    110       // concurrently try to register the same {@code TimeSeries}.
    111       return existingPoint;
    112     }
    113 
    114     checkArgument(labelKeysSize == labelValues.size(), "Incorrect number of labels.");
    115     Utils.checkListElementNotNull(labelValues, "labelValue element should not be null.");
    116 
    117     PointImpl newPoint = new PointImpl(labelValues);
    118     // Updating the map of points happens under a lock to avoid multiple add operations
    119     // to happen in the same time.
    120     Map<List<LabelValue>, PointImpl> registeredPointsCopy =
    121         new LinkedHashMap<List<LabelValue>, PointImpl>(registeredPoints);
    122     registeredPointsCopy.put(labelValues, newPoint);
    123     registeredPoints = Collections.unmodifiableMap(registeredPointsCopy);
    124 
    125     return newPoint;
    126   }
    127 
    128   @Nullable
    129   @Override
    130   public Metric getMetric(Clock clock) {
    131     Map<List<LabelValue>, PointImpl> currentRegisteredPoints = registeredPoints;
    132     if (currentRegisteredPoints.isEmpty()) {
    133       return null;
    134     }
    135 
    136     if (currentRegisteredPoints.size() == 1) {
    137       PointImpl point = currentRegisteredPoints.values().iterator().next();
    138       return Metric.createWithOneTimeSeries(metricDescriptor, point.getTimeSeries(clock));
    139     }
    140 
    141     List<TimeSeries> timeSeriesList = new ArrayList<TimeSeries>(currentRegisteredPoints.size());
    142     for (Map.Entry<List<LabelValue>, PointImpl> entry : currentRegisteredPoints.entrySet()) {
    143       timeSeriesList.add(entry.getValue().getTimeSeries(clock));
    144     }
    145     return Metric.create(metricDescriptor, timeSeriesList);
    146   }
    147 
    148   /** Implementation of {@link DoubleGauge.DoublePoint}. */
    149   public static final class PointImpl extends DoublePoint {
    150 
    151     // TODO(mayurkale): Consider to use DoubleAdder here, once we upgrade to Java8.
    152     private final AtomicDouble value = new AtomicDouble(0);
    153     private final List<LabelValue> labelValues;
    154 
    155     PointImpl(List<LabelValue> labelValues) {
    156       this.labelValues = labelValues;
    157     }
    158 
    159     @Override
    160     public void add(double amt) {
    161       value.addAndGet(amt);
    162     }
    163 
    164     @Override
    165     public void set(double val) {
    166       value.set(val);
    167     }
    168 
    169     private TimeSeries getTimeSeries(Clock clock) {
    170       return TimeSeries.createWithOnePoint(
    171           labelValues, Point.create(Value.doubleValue(value.get()), clock.now()), null);
    172     }
    173   }
    174 }
    175