UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

226 lines (205 loc) 8.7 kB
'use strict' const satisfies = require('../../../../vendor/dist/semifies') const getGitMetadata = require('../git_metadata') const log = require('../log') const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags') const { getIsAzureFunction } = require('../serverless') const { getAzureTagsFromMetadata, getAzureAppMetadata, getAzureFunctionMetadata } = require('../azure_metadata') const { getEnvironmentVariable, getValueFromEnvSources } = require('../config/helper') const { isACFActive } = require('../../../datadog-core/src/storage') const { AgentExporter } = require('./exporters/agent') const { FileExporter } = require('./exporters/file') const WallProfiler = require('./profilers/wall') const SpaceProfiler = require('./profilers/space') const EventsProfiler = require('./profilers/events') const { ensureOOMExportStrategies } = require('./oom') const { tagger } = require('./tagger') /** @typedef {import('../config/config-base')} TracerConfig */ /** @typedef {AgentExporter | FileExporter} ProfilingExporter */ /** @typedef {WallProfiler | SpaceProfiler | EventsProfiler} ProfilingProfiler */ /** @param {TracerConfig} config */ function getProfilingTags (config) { const functionName = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') const tags = { ...config.tags, ...tagger.parse({ host: config.reportHostname ? require('os').hostname() : undefined, functionname: functionName, }), ...getAzureTagsFromMetadata(getIsAzureFunction() ? getAzureFunctionMetadata() : getAzureAppMetadata()), } const { commitSHA, repositoryUrl } = getGitMetadata(config) if (repositoryUrl && commitSHA) { tags[GIT_REPOSITORY_URL] = repositoryUrl tags[GIT_COMMIT_SHA] = commitSHA } return tags } /** @param {TracerConfig} config */ function getAsyncContextFrameEnabled (config) { const enabled = config.DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED if (enabled && !isACFActive) { // The default value already tracks runtime support, so an unset config landing // here is expected; only an explicit opt-in the runtime can't honor is worth a warning. if (getValueFromEnvSources('DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED', true)) { let reason if (satisfies(process.versions.node, '>=24.0.0')) { reason = 'with --no-async-context-frame' } else if (satisfies(process.versions.node, '>=22.9.0')) { reason = 'without --experimental-async-context-frame' } else { reason = 'but it requires at least Node.js 22.9.0' } log.warn('DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED was set %s, it will have no effect.', reason) } return false } return enabled } /** * @param {TracerConfig} config * @param {{ * asyncContextFrameEnabled: boolean, * flushInterval: number, * tags: Record<string, string>, * exporters: ProfilingExporter[], * }} runtime */ function createProfilers (config, { asyncContextFrameEnabled, flushInterval, tags, exporters }) { const profilers = [] for (const name of selectProfilerTypes(config)) { switch (name) { case 'cpu': case 'wall': profilers.push(new WallProfiler(config, { asyncContextFrameEnabled, flushInterval })) break case 'space': profilers.push(new SpaceProfiler(config, { tags, exporters })) break default: log.error('Unknown profiler "%s"', name) } } // The events profiler produces timeline events. It is only added if timeline // is enabled and there's a wall profiler. if (config.DD_PROFILING_TIMELINE_ENABLED && profilers.some(profiler => profiler instanceof WallProfiler)) { profilers.push(new EventsProfiler(config, { flushInterval })) } return profilers } /** @param {TracerConfig} config */ function selectProfilerTypes ({ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS, }) { // First consider "legacy" DD_PROFILING_PROFILERS env variable, defaulting to space + wall // Use a Set to avoid duplicates // NOTE: space profiler is very deliberately in the first position. This way // when profilers are stopped sequentially one after the other to create // snapshots the space profile won't include memory taken by profiles created // before it in the sequence. That memory is ultimately transient and will be // released when all profiles are subsequently encoded. const profilers = new Set(DD_PROFILING_PROFILERS) let spaceExplicitlyEnabled = false // Add/remove space depending on the value of DD_PROFILING_HEAP_ENABLED if (DD_PROFILING_HEAP_ENABLED !== undefined) { if (DD_PROFILING_HEAP_ENABLED) { if (!profilers.has('space')) { profilers.add('space') spaceExplicitlyEnabled = true } } else { profilers.delete('space') } } // Add/remove wall depending on the value of DD_PROFILING_WALLTIME_ENABLED if (DD_PROFILING_WALLTIME_ENABLED !== undefined) { if (DD_PROFILING_WALLTIME_ENABLED) { profilers.add('wall') } else { profilers.delete('wall') profilers.delete('cpu') // remove alias too } } const profilersArray = [...profilers] // If space was added through DD_PROFILING_HEAP_ENABLED, ensure it is in the // first position. Basically, the only way for it not to be in the first // position is if it was explicitly specified in a different position in // DD_PROFILING_PROFILERS. if (spaceExplicitlyEnabled) { const spaceIdx = profilersArray.indexOf('space') if (spaceIdx > 0) { profilersArray.splice(spaceIdx, 1) profilersArray.unshift('space') } } return profilersArray } /** * @param {string} name * @param {TracerConfig} config */ function getExporter (name, config) { switch (name) { case 'agent': return new AgentExporter(config) case 'file': return new FileExporter(config) default: log.error('Unknown exporter "%s"', name) } } /** * Assembles everything the profiler needs from the tracer config: the runtime objects (tags, * exporters, profilers) the {@link import('./profiler').Profiler#start} consumes and the system * info report sent with each profile. The leaves read the canonical DD_PROFILING_* fields straight * off the config; only the genuinely runtime values (tags, exporters, the resolved async context * frame flag, the flush interval) are derived here. * * @param {TracerConfig} config */ function buildProfilingRuntime (config) { const tags = getProfilingTags(config) const exporters = [] for (const name of config.DD_PROFILING_EXPORTERS) { const exporter = getExporter(name, config) // getExporter logs and returns undefined for an unknown exporter name; drop it so a misconfigured // DD_PROFILING_EXPORTERS entry can't crash the export path later. if (exporter !== undefined) { exporters.push(exporter) } } const asyncContextFrameEnabled = getAsyncContextFrameEnabled(config) const flushInterval = config.DD_PROFILING_UPLOAD_PERIOD * 1000 const profilers = createProfilers(config, { asyncContextFrameEnabled, flushInterval, tags, exporters }) const uploadCompression = config.DD_PROFILING_DEBUG_UPLOAD_COMPRESSION const oomMonitoringEnabled = config.DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED const systemInfoReport = { allocationProfilingEnabled: config.DD_PROFILING_ALLOCATION_ENABLED, asyncContextFrameEnabled, codeHotspotsEnabled: config.DD_PROFILING_CODEHOTSPOTS_ENABLED, cpuProfilingEnabled: config.DD_PROFILING_CPU_ENABLED, debugSourceMaps: config.DD_PROFILING_DEBUG_SOURCE_MAPS, endpointCollectionEnabled: config.DD_PROFILING_ENDPOINT_COLLECTION_ENABLED, heapSamplingInterval: config.DD_PROFILING_HEAP_SAMPLING_INTERVAL, oomMonitoring: { enabled: oomMonitoringEnabled, heapLimitExtensionSize: config.DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE, maxHeapExtensionCount: config.DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT, exportStrategies: oomMonitoringEnabled ? ensureOOMExportStrategies(config.DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES) : [], }, profilerTypes: profilers.map(profiler => profiler.type), sourceMap: config.DD_PROFILING_SOURCE_MAP, timelineEnabled: config.DD_PROFILING_TIMELINE_ENABLED, timelineSamplingEnabled: config.DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, uploadCompression: { ...uploadCompression }, v8ProfilerBugWorkaroundEnabled: config.DD_PROFILING_V8_PROFILER_BUG_WORKAROUND, } return { tags, exporters, flushInterval, profilers, uploadCompression, systemInfoReport } } module.exports = { buildProfilingRuntime, }