UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

215 lines (169 loc) 6.04 kB
'use strict' const coalesce = require('koalas') const RateLimiter = require('./rate_limiter') const Sampler = require('./sampler') const ext = require('../../../ext') const { setSamplingRules } = require('./startup-log') const { SAMPLING_MECHANISM_DEFAULT, SAMPLING_MECHANISM_AGENT, SAMPLING_MECHANISM_RULE, SAMPLING_MECHANISM_MANUAL, SAMPLING_RULE_DECISION, SAMPLING_LIMIT_DECISION, SAMPLING_AGENT_DECISION, UPSTREAM_SERVICES_KEY } = require('./constants') const SERVICE_NAME = ext.tags.SERVICE_NAME const SAMPLING_PRIORITY = ext.tags.SAMPLING_PRIORITY const MANUAL_KEEP = ext.tags.MANUAL_KEEP const MANUAL_DROP = ext.tags.MANUAL_DROP const USER_REJECT = ext.priority.USER_REJECT const AUTO_REJECT = ext.priority.AUTO_REJECT const AUTO_KEEP = ext.priority.AUTO_KEEP const USER_KEEP = ext.priority.USER_KEEP const DEFAULT_KEY = 'service:,env:' const defaultSampler = new Sampler(AUTO_KEEP) const serviceNames = new Map() class PrioritySampler { constructor (env, { sampleRate, rateLimit = 100, rules = [] } = {}) { this._env = env this._rules = this._normalizeRules(rules, sampleRate) this._limiter = new RateLimiter(rateLimit) setSamplingRules(this._rules) this.update({}) } isSampled (span) { const priority = this._getPriorityFromAuto(span) return priority === USER_KEEP || priority === AUTO_KEEP } sample (span, auto = true) { if (!span) return const context = this._getContext(span) const root = context._trace.started[0] if (context._sampling.priority !== undefined) return if (!root) return // noop span const tag = this._getPriorityFromTags(context._tags) if (this.validate(tag)) { context._sampling.priority = tag context._sampling.mechanism = SAMPLING_MECHANISM_MANUAL } else if (auto) { context._sampling.priority = this._getPriorityFromAuto(root) } else { return } this._addUpstreamService(root) } update (rates) { const samplers = {} for (const key in rates) { const rate = rates[key] const sampler = new Sampler(rate) samplers[key] = sampler } samplers[DEFAULT_KEY] = samplers[DEFAULT_KEY] || defaultSampler this._samplers = samplers } validate (samplingPriority) { switch (samplingPriority) { case USER_REJECT: case USER_KEEP: case AUTO_REJECT: case AUTO_KEEP: return true default: return false } } _getContext (span) { return typeof span.context === 'function' ? span.context() : span } _getPriorityFromAuto (span) { const context = this._getContext(span) const rule = this._findRule(context) return rule ? this._getPriorityByRule(context, rule) : this._getPriorityByAgent(context) } _getPriorityFromTags (tags) { if (tags.hasOwnProperty(MANUAL_KEEP) && tags[MANUAL_KEEP] !== false) { return USER_KEEP } else if (tags.hasOwnProperty(MANUAL_DROP) && tags[MANUAL_DROP] !== false) { return USER_REJECT } else { const priority = parseInt(tags[SAMPLING_PRIORITY], 10) if (priority === 1 || priority === 2) { return USER_KEEP } else if (priority === 0 || priority === -1) { return USER_REJECT } } } _getPriorityByRule (context, rule) { context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate context._sampling.mechanism = SAMPLING_MECHANISM_RULE return rule.sampler.isSampled(context) && this._isSampledByRateLimit(context) ? USER_KEEP : USER_REJECT } _isSampledByRateLimit (context) { const allowed = this._limiter.isAllowed() context._trace[SAMPLING_LIMIT_DECISION] = this._limiter.effectiveRate() return allowed } _getPriorityByAgent (context) { const key = `service:${context._tags[SERVICE_NAME]},env:${this._env}` const sampler = this._samplers[key] || this._samplers[DEFAULT_KEY] context._trace[SAMPLING_AGENT_DECISION] = sampler.rate() if (sampler === defaultSampler) { context._sampling.mechanism = SAMPLING_MECHANISM_DEFAULT } else { context._sampling.mechanism = SAMPLING_MECHANISM_AGENT } return sampler.isSampled(context) ? AUTO_KEEP : AUTO_REJECT } _addUpstreamService (span) { const context = span.context() const trace = context._trace const service = this._toBase64(context._tags['service.name']) const priority = context._sampling.priority const mechanism = context._sampling.mechanism const rate = Math.ceil(coalesce( context._trace[SAMPLING_RULE_DECISION], context._trace[SAMPLING_AGENT_DECISION] ) * 10000) / 10000 const group = `${service}|${priority}|${mechanism}|${rate}` const groups = trace.tags[UPSTREAM_SERVICES_KEY] ? `${trace.tags[UPSTREAM_SERVICES_KEY]};${group}` : group trace.tags[UPSTREAM_SERVICES_KEY] = groups } _toBase64 (serviceName) { let encoded = serviceNames.get(serviceName) if (!encoded) { encoded = Buffer.from(serviceName).toString('base64') serviceNames.set(serviceName, encoded) } return encoded } _normalizeRules (rules, sampleRate) { return rules .concat({ sampleRate }) .map(rule => ({ ...rule, sampleRate: parseFloat(rule.sampleRate) })) .filter(rule => !isNaN(rule.sampleRate)) .map(rule => ({ ...rule, sampler: new Sampler(rule.sampleRate) })) } _findRule (context) { for (let i = 0, l = this._rules.length; i < l; i++) { if (this._matchRule(context, this._rules[i])) return this._rules[i] } } _matchRule (context, rule) { const name = context._name const service = context._tags['service.name'] if (rule.name instanceof RegExp && !rule.name.test(name)) return false if (typeof rule.name === 'string' && rule.name !== name) return false if (rule.service instanceof RegExp && !rule.service.test(service)) return false if (typeof rule.service === 'string' && rule.service !== service) return false return true } } module.exports = PrioritySampler