Skip to content

Commit d603d22

Browse files
authored
Custom metrics aggregation (#5347)
1 parent 743f896 commit d603d22

File tree

4 files changed

+175
-115
lines changed

4 files changed

+175
-115
lines changed

packages/dd-trace/src/dogstatsd.js

+137-28
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const dgram = require('dgram')
66
const isIP = require('net').isIP
77
const log = require('./log')
88
const { URL, format } = require('url')
9+
const Histogram = require('./histogram')
910

1011
const MAX_BUFFER_SIZE = 1024 // limit from the agent
1112

@@ -193,6 +194,117 @@ class DogStatsDClient {
193194
}
194195
}
195196

197+
// TODO: Handle arrays of tags and tags translation.
198+
class MetricsAggregationClient {
199+
constructor (client) {
200+
this._client = client
201+
202+
this.reset()
203+
}
204+
205+
flush () {
206+
this._captureCounters()
207+
this._captureGauges()
208+
this._captureHistograms()
209+
210+
this._client.flush()
211+
}
212+
213+
reset () {
214+
this._counters = {}
215+
this._gauges = {}
216+
this._histograms = {}
217+
}
218+
219+
distribution (name, value, tag) {
220+
this._client.distribution(name, value, tag && [tag])
221+
}
222+
223+
boolean (name, value, tag) {
224+
this.gauge(name, value ? 1 : 0, tag)
225+
}
226+
227+
histogram (name, value, tag) {
228+
this._histograms[name] = this._histograms[name] || new Map()
229+
230+
if (!this._histograms[name].has(tag)) {
231+
this._histograms[name].set(tag, new Histogram())
232+
}
233+
234+
this._histograms[name].get(tag).record(value)
235+
}
236+
237+
count (name, count, tag, monotonic = false) {
238+
if (typeof tag === 'boolean') {
239+
monotonic = tag
240+
tag = undefined
241+
}
242+
243+
const map = monotonic ? this._counters : this._gauges
244+
245+
map[name] = map[name] || new Map()
246+
247+
const value = map[name].get(tag) || 0
248+
249+
map[name].set(tag, value + count)
250+
}
251+
252+
gauge (name, value, tag) {
253+
this._gauges[name] = this._gauges[name] || new Map()
254+
this._gauges[name].set(tag, value)
255+
}
256+
257+
increment (name, count = 1, tag, monotonic) {
258+
this.count(name, count, tag, monotonic)
259+
}
260+
261+
decrement (name, count = 1, tag) {
262+
this.count(name, -count, tag)
263+
}
264+
265+
_captureGauges () {
266+
Object.keys(this._gauges).forEach(name => {
267+
this._gauges[name].forEach((value, tag) => {
268+
this._client.gauge(name, value, tag && [tag])
269+
})
270+
})
271+
}
272+
273+
_captureCounters () {
274+
Object.keys(this._counters).forEach(name => {
275+
this._counters[name].forEach((value, tag) => {
276+
this._client.increment(name, value, tag && [tag])
277+
})
278+
})
279+
280+
this._counters = {}
281+
}
282+
283+
_captureHistograms () {
284+
Object.keys(this._histograms).forEach(name => {
285+
this._histograms[name].forEach((stats, tag) => {
286+
const tags = tag && [tag]
287+
288+
// Stats can contain garbage data when a value was never recorded.
289+
if (stats.count === 0) {
290+
stats = { max: 0, min: 0, sum: 0, avg: 0, median: 0, p95: 0, count: 0 }
291+
}
292+
293+
this._client.gauge(`${name}.min`, stats.min, tags)
294+
this._client.gauge(`${name}.max`, stats.max, tags)
295+
this._client.increment(`${name}.sum`, stats.sum, tags)
296+
this._client.increment(`${name}.total`, stats.sum, tags)
297+
this._client.gauge(`${name}.avg`, stats.avg, tags)
298+
this._client.increment(`${name}.count`, stats.count, tags)
299+
this._client.gauge(`${name}.median`, stats.median, tags)
300+
this._client.gauge(`${name}.95percentile`, stats.p95, tags)
301+
302+
stats.reset()
303+
})
304+
})
305+
}
306+
}
307+
196308
/**
197309
* This is a simplified user-facing proxy to the underlying DogStatsDClient instance
198310
*
@@ -201,7 +313,7 @@ class DogStatsDClient {
201313
class CustomMetrics {
202314
constructor (config) {
203315
const clientConfig = DogStatsDClient.generateClientConfig(config)
204-
this.dogstatsd = new DogStatsDClient(clientConfig)
316+
this._client = new MetricsAggregationClient(new DogStatsDClient(clientConfig))
205317

206318
const flush = this.flush.bind(this)
207319

@@ -212,47 +324,43 @@ class CustomMetrics {
212324
}
213325

214326
increment (stat, value = 1, tags) {
215-
return this.dogstatsd.increment(
216-
stat,
217-
value,
218-
CustomMetrics.tagTranslator(tags)
219-
)
327+
for (const tag of this._normalizeTags(tags)) {
328+
this._client.increment(stat, value, tag)
329+
}
220330
}
221331

222332
decrement (stat, value = 1, tags) {
223-
return this.dogstatsd.decrement(
224-
stat,
225-
value,
226-
CustomMetrics.tagTranslator(tags)
227-
)
333+
for (const tag of this._normalizeTags(tags)) {
334+
this._client.decrement(stat, value, tag)
335+
}
228336
}
229337

230338
gauge (stat, value, tags) {
231-
return this.dogstatsd.gauge(
232-
stat,
233-
value,
234-
CustomMetrics.tagTranslator(tags)
235-
)
339+
for (const tag of this._normalizeTags(tags)) {
340+
this._client.gauge(stat, value, tag)
341+
}
236342
}
237343

238344
distribution (stat, value, tags) {
239-
return this.dogstatsd.distribution(
240-
stat,
241-
value,
242-
CustomMetrics.tagTranslator(tags)
243-
)
345+
for (const tag of this._normalizeTags(tags)) {
346+
this._client.distribution(stat, value, tag)
347+
}
244348
}
245349

246350
histogram (stat, value, tags) {
247-
return this.dogstatsd.histogram(
248-
stat,
249-
value,
250-
CustomMetrics.tagTranslator(tags)
251-
)
351+
for (const tag of this._normalizeTags(tags)) {
352+
this._client.histogram(stat, value, tag)
353+
}
252354
}
253355

254356
flush () {
255-
return this.dogstatsd.flush()
357+
return this._client.flush()
358+
}
359+
360+
_normalizeTags (tags) {
361+
tags = CustomMetrics.tagTranslator(tags)
362+
363+
return tags.length === 0 ? [undefined] : tags
256364
}
257365

258366
/**
@@ -274,5 +382,6 @@ class CustomMetrics {
274382

275383
module.exports = {
276384
DogStatsDClient,
277-
CustomMetrics
385+
CustomMetrics,
386+
MetricsAggregationClient
278387
}

packages/dd-trace/src/runtime_metrics/runtime_metrics.js

+15-80
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
const v8 = require('v8')
66
const os = require('os')
7-
const { DogStatsDClient } = require('../dogstatsd')
7+
const { DogStatsDClient, MetricsAggregationClient } = require('../dogstatsd')
88
const log = require('../log')
99
const Histogram = require('../histogram')
1010
const { performance, PerformanceObserver } = require('perf_hooks')
@@ -25,9 +25,6 @@ let interval
2525
let client
2626
let time
2727
let cpuUsage
28-
let gauges
29-
let counters
30-
let histograms
3128
let elu
3229

3330
reset()
@@ -49,7 +46,7 @@ const runtimeMetrics = module.exports = {
4946
nativeMetrics = null
5047
}
5148

52-
client = new DogStatsDClient(clientConfig)
49+
client = new MetricsAggregationClient(new DogStatsDClient(clientConfig))
5350

5451
time = process.hrtime()
5552

@@ -98,50 +95,27 @@ const runtimeMetrics = module.exports = {
9895
},
9996

10097
boolean (name, value, tag) {
101-
this.gauge(name, value ? 1 : 0, tag)
98+
client && client.boolean(name, value, tag)
10299
},
103100

104101
histogram (name, value, tag) {
105-
if (!client) return
106-
107-
histograms[name] = histograms[name] || new Map()
108-
109-
if (!histograms[name].has(tag)) {
110-
histograms[name].set(tag, new Histogram())
111-
}
112-
113-
histograms[name].get(tag).record(value)
102+
client && client.histogram(name, value, tag)
114103
},
115104

116105
count (name, count, tag, monotonic = false) {
117-
if (!client) return
118-
if (typeof tag === 'boolean') {
119-
monotonic = tag
120-
tag = undefined
121-
}
122-
123-
const map = monotonic ? counters : gauges
124-
125-
map[name] = map[name] || new Map()
126-
127-
const value = map[name].get(tag) || 0
128-
129-
map[name].set(tag, value + count)
106+
client && client.count(name, count, tag, monotonic)
130107
},
131108

132109
gauge (name, value, tag) {
133-
if (!client) return
134-
135-
gauges[name] = gauges[name] || new Map()
136-
gauges[name].set(tag, value)
110+
client && client.gauge(name, value, tag)
137111
},
138112

139113
increment (name, tag, monotonic) {
140-
this.count(name, 1, tag, monotonic)
114+
client && client.increment(name, 1, tag, monotonic)
141115
},
142116

143117
decrement (name, tag) {
144-
this.count(name, -1, tag)
118+
client && client.decrement(name, 1, tag)
145119
}
146120
}
147121

@@ -150,9 +124,6 @@ function reset () {
150124
client = null
151125
time = null
152126
cpuUsage = null
153-
gauges = {}
154-
counters = {}
155-
histograms = {}
156127
nativeMetrics = null
157128
gcObserver && gcObserver.disconnect()
158129
gcObserver = null
@@ -246,33 +217,6 @@ function captureGCMetrics () {
246217
gcProfiler.start()
247218
}
248219

249-
function captureGauges () {
250-
Object.keys(gauges).forEach(name => {
251-
gauges[name].forEach((value, tag) => {
252-
client.gauge(name, value, tag && [tag])
253-
})
254-
})
255-
}
256-
257-
function captureCounters () {
258-
Object.keys(counters).forEach(name => {
259-
counters[name].forEach((value, tag) => {
260-
client.increment(name, value, tag && [tag])
261-
})
262-
})
263-
264-
counters = {}
265-
}
266-
267-
function captureHistograms () {
268-
Object.keys(histograms).forEach(name => {
269-
histograms[name].forEach((stats, tag) => {
270-
histogram(name, stats, tag && [tag])
271-
stats.reset()
272-
})
273-
})
274-
}
275-
276220
/**
277221
* Gathers and reports Event Loop Utilization (ELU) since last run
278222
*
@@ -295,9 +239,6 @@ function captureCommonMetrics () {
295239
captureMemoryUsage()
296240
captureProcess()
297241
captureHeapStats()
298-
captureGauges()
299-
captureCounters()
300-
captureHistograms()
301242
captureELU()
302243
captureGCMetrics()
303244
}
@@ -339,21 +280,15 @@ function captureNativeMetrics () {
339280
}
340281

341282
function histogram (name, stats, tags) {
342-
tags = [].concat(tags)
283+
tags = tags ? [].concat(tags) : []
343284

344-
// Stats can contain garbage data when a value was never recorded.
345-
if (stats.count === 0) {
346-
stats = { max: 0, min: 0, sum: 0, avg: 0, median: 0, p95: 0, count: 0 }
285+
if (tags.length > 0) {
286+
for (const tag of tags) {
287+
client.histogram(name, stats, tag)
288+
}
289+
} else {
290+
client.histogram(name, stats)
347291
}
348-
349-
client.gauge(`${name}.min`, stats.min, tags)
350-
client.gauge(`${name}.max`, stats.max, tags)
351-
client.increment(`${name}.sum`, stats.sum, tags)
352-
client.increment(`${name}.total`, stats.sum, tags)
353-
client.gauge(`${name}.avg`, stats.avg, tags)
354-
client.increment(`${name}.count`, stats.count, tags)
355-
client.gauge(`${name}.median`, stats.median, tags)
356-
client.gauge(`${name}.95percentile`, stats.p95, tags)
357292
}
358293

359294
function startGCObserver () {

packages/dd-trace/test/custom-metrics.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe('Custom Metrics', () => {
5353
if (stdout) console.log(stdout)
5454
if (stderr) console.error(stderr)
5555

56-
expect(metricsData.split('#')[0]).to.equal('page.views.data:1|c|')
56+
expect(metricsData.split('#')[0]).to.equal('page.views.data:1|g|')
5757

5858
done()
5959
})

0 commit comments

Comments
 (0)