UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

215 lines (183 loc) 4.6 kB
'use strict' const os = require('os') const { version } = require('./pkg') const pkg = require('../../../package.json') const { LogCollapsingLowestDenseDDSketch } = require('@datadog/sketches-js') const { ORIGIN_KEY, TOP_LEVEL_KEY } = require('./constants') const { MEASURED, HTTP_STATUS_CODE } = require('../../../ext/tags') const { SpanStatsExporter } = require('./exporters/span-stats') const { DEFAULT_SPAN_NAME, DEFAULT_SERVICE_NAME } = require('./encode/tags-processors') class SpanAggStats { constructor (aggKey) { this.aggKey = aggKey this.hits = 0 this.topLevelHits = 0 this.errors = 0 this.duration = 0 this.okDistribution = new LogCollapsingLowestDenseDDSketch() this.errorDistribution = new LogCollapsingLowestDenseDDSketch() } record (span) { const durationNs = span.duration this.hits++ this.duration += durationNs if (span.metrics[TOP_LEVEL_KEY]) { this.topLevelHits++ } if (span.error) { this.errors++ this.errorDistribution.accept(durationNs) } else { this.okDistribution.accept(durationNs) } } toJSON () { const { name, service, resource, type, statusCode, synthetics } = this.aggKey return { Name: name, Service: service, Resource: resource, Type: type, HTTPStatusCode: statusCode, Synthetics: synthetics, Hits: this.hits, TopLevelHits: this.topLevelHits, Errors: this.errors, Duration: this.duration, OkSummary: this.okDistribution.toProto(), // TODO: custom proto encoding ErrorSummary: this.errorDistribution.toProto() // TODO: custom proto encoding } } } class SpanAggKey { constructor (span) { this.name = span.name || DEFAULT_SPAN_NAME this.service = span.service || DEFAULT_SERVICE_NAME this.resource = span.resource || '' this.type = span.type || '' this.statusCode = span.meta[HTTP_STATUS_CODE] || 0 this.synthetics = span.meta[ORIGIN_KEY] === 'synthetics' } toString () { return [ this.name, this.service, this.resource, this.type, this.statusCode, this.synthetics ].join(',') } } class SpanBuckets extends Map { forSpan (span) { const aggKey = new SpanAggKey(span) const key = aggKey.toString() if (!this.has(key)) { this.set(key, new SpanAggStats(aggKey)) } return this.get(key) } } class TimeBuckets extends Map { forTime (time) { if (!this.has(time)) { this.set(time, new SpanBuckets()) } return this.get(time) } } class SpanStatsProcessor { constructor ({ stats: { enabled = false, interval = 10 }, hostname, port, url, env, tags, version } = {}) { this.exporter = new SpanStatsExporter({ hostname, port, tags, url }) this.interval = interval this.bucketSizeNs = interval * 1e9 this.buckets = new TimeBuckets() this.hostname = os.hostname() this.enabled = enabled this.env = env this.tags = tags || {} this.sequence = 0 this.version = version if (this.enabled) { this.timer = setInterval(this.onInterval.bind(this), interval * 1e3) this.timer.unref() } } onInterval () { const serialized = this._serializeBuckets() if (!serialized) return this.exporter.export({ Hostname: this.hostname, Env: this.env, Version: this.version || version, Stats: serialized, Lang: 'javascript', TracerVersion: pkg.version, RuntimeID: this.tags['runtime-id'], Sequence: ++this.sequence }) } onSpanFinished (span) { if (!this.enabled) return if (!span.metrics[TOP_LEVEL_KEY] && !span.metrics[MEASURED]) return const spanEndNs = span.startTime + span.duration const bucketTime = spanEndNs - (spanEndNs % this.bucketSizeNs) this.buckets.forTime(bucketTime) .forSpan(span) .record(span) } _serializeBuckets () { const { bucketSizeNs } = this const serializedBuckets = [] for (const [timeNs, bucket] of this.buckets.entries()) { const bucketAggStats = [] for (const stats of bucket.values()) { bucketAggStats.push(stats.toJSON()) } serializedBuckets.push({ Start: timeNs, Duration: bucketSizeNs, Stats: bucketAggStats }) } this.buckets.clear() return serializedBuckets } } module.exports = { SpanAggStats, SpanAggKey, SpanBuckets, TimeBuckets, SpanStatsProcessor }