aboutsummaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java/com/yahoo/metrics/simple/MetricReceiver.java
blob: 021f3a170d8157edad248b521582c08190df9bb9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.metrics.simple;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import com.yahoo.api.annotations.Beta;
import com.google.common.collect.ImmutableMap;
import com.yahoo.concurrent.ThreadLocalDirectory;

/**
 * The reception point for measurements. This is the class users should inject
 * in constructors for declaring instances of {@link Counter} and {@link Gauge}
 * for the actual measurement of metrics.
 *
 * @author Steinar Knutsen
 */
@Beta
public class MetricReceiver {

    public static final MetricReceiver nullImplementation = new NullReceiver();
    private final ThreadLocalDirectory<Bucket, Sample> metricsCollection;

    // A reference to the current snapshot. The *reference* is shared with MetricsAggregator and updated from there :-/
    private final AtomicReference<Bucket> currentSnapshot;

    // metricSettings is volatile for reading, the lock is for updates
    private final Object histogramDefinitionsLock = new Object();
    private volatile Map<String, MetricSettings> metricSettings;

    public MetricReceiver(ThreadLocalDirectory<Bucket, Sample> metricsCollection, AtomicReference<Bucket> currentSnapshot) {
        this.metricsCollection = metricsCollection;
        this.currentSnapshot = currentSnapshot;
        metricSettings = new ImmutableMap.Builder<String, MetricSettings>().build();
    }

    /**
     * Update a metric. This API is not intended for clients for the
     * simplemetrics API, declare a Counter or a Gauge using
     * {@link #declareCounter(String)}, {@link #declareCounter(String, Point)},
     * {@link #declareGauge(String)}, or {@link #declareGauge(String, Point)}
     * instead.
     *
     * @param sample a single simple containing all meta data necessary to update a metric
     */
    public void update(Sample sample) {
        // pass around the receiver instead of histogram settings to avoid reading any volatile if unnecessary
        sample.setReceiver(this);
        metricsCollection.update(sample);
    }

    /**
     * Declare a counter metric without setting any default position.
     *
     * @param name the name of the metric
     * @return a thread-safe counter
     */
    public Counter declareCounter(String name) {
        return declareCounter(name, null);
    }

    /**
     * Declare a counter metric, with default dimension values as given. Create
     * the point argument by using a builder from {@link #pointBuilder()}.
     *
     * @param name the name of the metric
     * @param boundDimensions dimensions which have a fixed value in the life cycle of the metric object or null
     * @return a thread-safe counter with given default values
     */
    public Counter declareCounter(String name, Point boundDimensions) {
        return new Counter(name, boundDimensions, this);
    }

    /**
     * Declare a gauge metric with any default position.
     *
     * @param name the name of the metric
     * @return a thread-safe gauge instance
     */
    public Gauge declareGauge(String name) {
        return declareGauge(name, null);
    }

    /**
     * Declare a gauge metric, with default dimension values as given. Create
     * the point argument by using a builder from {@link #pointBuilder()}.
     *
     * @param name the name of the metric
     * @param boundDimensions dimensions which have a fixed value in the life cycle of the metric object or null
     * @return a thread-safe gauge metric
     */
    public Gauge declareGauge(String name, Point boundDimensions) {
        return declareGauge(name, Optional.ofNullable(boundDimensions), null);
    }

    /**
     * Declare a gauge metric, with default dimension values as given. Create
     * the point argument by using a builder from {@link #pointBuilder()}.
     * MetricSettings instances are built using
     * {@link MetricSettings.Builder}.
     *
     * @param name the name of the metric
     * @param boundDimensions an optional of dimensions which have a fixed value in the life cycle of the metric object
     * @param customSettings any optional settings
     * @return a thread-safe gauge metric
     */
    public Gauge declareGauge(String name, Optional<Point> boundDimensions, MetricSettings customSettings) {
        if (customSettings != null) {
            addMetricDefinition(name, customSettings);
        }
        Point defaultDimensions = null;
        if (boundDimensions.isPresent()) {
            defaultDimensions = boundDimensions.get();
        }
        return new Gauge(name, defaultDimensions, this);
    }

    /**
     * Create a PointBuilder instance with no default settings. PointBuilder
     * instances are not thread-safe.
     *
     * @return an "empty" point builder instance
     */
    public PointBuilder pointBuilder() {
        return new PointBuilder();
    }

    /**
     * Fetch the latest metric values, aggregated over all threads for the
     * configured sample history (by default five minutes). The values will be
     * less than 1 second old, and this method has only a memory barrier as side
     * effect.
     *
     * @return the latest five minutes of metrics
     */
    public Bucket getSnapshot() {
        return currentSnapshot.get();
    }

    /**
     * Add how to build a histogram for a given metric.
     *
     * @param metricName the metric where samples should be put in a histogram
     * @param definition settings for a histogram
     */
    void addMetricDefinition(String metricName, MetricSettings definition) {
        synchronized (histogramDefinitionsLock) {
            // read the volatile _after_ acquiring the lock
            Map<String, MetricSettings> oldMetricDefinitions = metricSettings;
            Map<String, MetricSettings> builderMap = new HashMap<>(oldMetricDefinitions.size() + 1);
            builderMap.putAll(oldMetricDefinitions);
            builderMap.put(metricName, definition);
            metricSettings = ImmutableMap.copyOf(builderMap);
        }
    }

    /**
     * Get how to build a histogram for a given metric, or null if no histogram should be created.
     *
     * @param metricName the name of an arbitrary metric
     * @return the corresponding histogram definition or null
     */
    MetricSettings getMetricDefinition(String metricName) {
        return metricSettings.get(metricName);
    }

    private static final class NullCounter extends Counter {

        NullCounter() {
            super(null, null, null);
        }

        @Override
        public void add() {
        }

        @Override
        public void add(long n) {
        }

        @Override
        public void add(Point p) {
        }

        @Override
        public void add(long n, Point p) {
        }

        @Override
        public PointBuilder builder() {
            return super.builder();
        }
    }

    private static final class NullGauge extends Gauge {
        NullGauge() {
            super(null, null, null);
        }

        @Override
        public void sample(double x) {
        }

        @Override
        public void sample(double x, Point p) {
        }

        @Override
        public PointBuilder builder() {
            return super.builder();
        }

    }

    public static final class MockReceiver extends MetricReceiver {

        private final ThreadLocalDirectory<Bucket, Sample> collection;

        private MockReceiver(ThreadLocalDirectory<Bucket, Sample> collection) {
            super(collection, null);
            this.collection = collection;
        }

        public MockReceiver() {
            this(new ThreadLocalDirectory<>(new MetricUpdater()));
        }

        /** Gathers all data since last snapshot */
        public Bucket getSnapshot() {
            Bucket merged = new Bucket();
            for (Bucket b : collection.fetch()) {
                merged.merge(b, true);
            }
            return merged;
        }

        /** Utility method for testing */
        public Point point(String dim, String val) {
            return pointBuilder().set(dim, val).build();
        }

    }

    private static final class NullReceiver extends MetricReceiver {

        NullReceiver() {
            super(null, null);
        }

        @Override
        public void update(Sample s) {
        }

        @Override
        public Counter declareCounter(String name) {
            return new NullCounter();
        }

        @Override
        public Counter declareCounter(String name, Point boundDimensions) {
            return new NullCounter();
        }

        @Override
        public Gauge declareGauge(String name) {
            return new NullGauge();
        }

        @Override
        public Gauge declareGauge(String name, Point boundDimensions) {
            return new NullGauge();
        }

        @Override
        public Gauge declareGauge(String name, Optional<Point> boundDimensions, MetricSettings customSettings) {
            return null;
        }

        @Override
        public PointBuilder pointBuilder() {
            return null;
        }

        @Override
        public Bucket getSnapshot() {
            return null;
        }

        @Override
        void addMetricDefinition(String metricName, MetricSettings definition) {
        }

        @Override
        MetricSettings getMetricDefinition(String metricName) {
            return null;
        }
    }

}