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