dd-trace 4.33.0 → 4.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/index.d.ts +15 -0
  2. package/package.json +3 -2
  3. package/packages/datadog-instrumentations/src/fetch.js +6 -45
  4. package/packages/datadog-instrumentations/src/helpers/fetch.js +17 -0
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -1
  6. package/packages/datadog-instrumentations/src/jest.js +161 -14
  7. package/packages/datadog-instrumentations/src/kafkajs.js +4 -7
  8. package/packages/datadog-instrumentations/src/mongoose.js +2 -1
  9. package/packages/datadog-instrumentations/src/oracledb.js +1 -1
  10. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +6 -1
  11. package/packages/datadog-instrumentations/src/selenium.js +69 -0
  12. package/packages/datadog-plugin-cucumber/src/index.js +2 -2
  13. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +2 -2
  14. package/packages/datadog-plugin-cypress/src/support.js +19 -3
  15. package/packages/datadog-plugin-fetch/src/index.js +17 -11
  16. package/packages/datadog-plugin-jest/src/index.js +7 -2
  17. package/packages/datadog-plugin-mocha/src/index.js +4 -5
  18. package/packages/datadog-plugin-openai/src/services.js +2 -1
  19. package/packages/datadog-plugin-playwright/src/index.js +2 -2
  20. package/packages/datadog-plugin-selenium/src/index.js +71 -0
  21. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +70 -0
  23. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +14 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +12 -0
  25. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-rule-type.js +6 -0
  26. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +5 -50
  27. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +742 -0
  28. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +539 -66
  29. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +6 -2
  30. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  31. package/packages/dd-trace/src/appsec/reporter.js +11 -10
  32. package/packages/dd-trace/src/appsec/telemetry.js +36 -7
  33. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  34. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +9 -2
  35. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
  36. package/packages/dd-trace/src/config.js +97 -10
  37. package/packages/dd-trace/src/dogstatsd.js +13 -11
  38. package/packages/dd-trace/src/index.js +5 -1
  39. package/packages/dd-trace/src/noop/dogstatsd.js +11 -0
  40. package/packages/dd-trace/src/noop/proxy.js +3 -0
  41. package/packages/dd-trace/src/opentracing/propagation/text_map.js +10 -4
  42. package/packages/dd-trace/src/opentracing/span.js +2 -0
  43. package/packages/dd-trace/src/plugins/index.js +2 -0
  44. package/packages/dd-trace/src/plugins/util/git.js +33 -11
  45. package/packages/dd-trace/src/plugins/util/test.js +34 -3
  46. package/packages/dd-trace/src/profiling/config.js +8 -4
  47. package/packages/dd-trace/src/profiling/exporters/agent.js +5 -3
  48. package/packages/dd-trace/src/profiling/profiler.js +4 -0
  49. package/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +33 -0
  50. package/packages/dd-trace/src/profiling/ssi-telemetry.js +167 -0
  51. package/packages/dd-trace/src/proxy.js +7 -1
  52. package/packages/dd-trace/src/tagger.js +13 -3
  53. package/packages/dd-trace/src/telemetry/index.js +5 -4
  54. package/packages/dd-trace/src/telemetry/metrics.js +2 -2
@@ -28,7 +28,7 @@ const {
28
28
  const { filterSensitiveInfoFromRepository } = require('./url')
29
29
  const { storage } = require('../../../../datadog-core')
30
30
 
31
- const GIT_REV_LIST_MAX_BUFFER = 8 * 1024 * 1024 // 8MB
31
+ const GIT_REV_LIST_MAX_BUFFER = 12 * 1024 * 1024 // 12MB
32
32
 
33
33
  function sanitizedExec (
34
34
  cmd,
@@ -53,11 +53,15 @@ function sanitizedExec (
53
53
  distributionMetric(durationMetric.name, durationMetric.tags, Date.now() - startTime)
54
54
  }
55
55
  return result
56
- } catch (e) {
56
+ } catch (err) {
57
57
  if (errorMetric) {
58
- incrementCountMetric(errorMetric.name, { ...errorMetric.tags, exitCode: e.status })
58
+ incrementCountMetric(errorMetric.name, {
59
+ ...errorMetric.tags,
60
+ errorType: err.code,
61
+ exitCode: err.status || err.errno
62
+ })
59
63
  }
60
- log.error(e)
64
+ log.error(err)
61
65
  return ''
62
66
  } finally {
63
67
  storage.enterWith(store)
@@ -129,7 +133,10 @@ function unshallowRepository () {
129
133
  } catch (err) {
130
134
  // If the local HEAD is a commit that has not been pushed to the remote, the above command will fail.
131
135
  log.error(err)
132
- incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'unshallow', exitCode: err.status })
136
+ incrementCountMetric(
137
+ TELEMETRY_GIT_COMMAND_ERRORS,
138
+ { command: 'unshallow', errorType: err.code, exitCode: err.status || err.errno }
139
+ )
133
140
  const upstreamRemote = sanitizedExec('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'])
134
141
  try {
135
142
  cp.execFileSync('git', [
@@ -139,7 +146,10 @@ function unshallowRepository () {
139
146
  } catch (err) {
140
147
  // If the CI is working on a detached HEAD or branch tracking hasn’t been set up, the above command will fail.
141
148
  log.error(err)
142
- incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'unshallow', exitCode: err.status })
149
+ incrementCountMetric(
150
+ TELEMETRY_GIT_COMMAND_ERRORS,
151
+ { command: 'unshallow', errorType: err.code, exitCode: err.status || err.errno }
152
+ )
143
153
  // We use sanitizedExec here because if this last option fails, we'll give up.
144
154
  sanitizedExec(
145
155
  'git',
@@ -175,13 +185,16 @@ function getLatestCommits () {
175
185
  return result
176
186
  } catch (err) {
177
187
  log.error(`Get latest commits failed: ${err.message}`)
178
- incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'get_local_commits', errorType: err.status })
188
+ incrementCountMetric(
189
+ TELEMETRY_GIT_COMMAND_ERRORS,
190
+ { command: 'get_local_commits', errorType: err.status }
191
+ )
179
192
  return []
180
193
  }
181
194
  }
182
195
 
183
196
  function getCommitsRevList (commitsToExclude, commitsToInclude) {
184
- let result = []
197
+ let result = null
185
198
 
186
199
  const commitsToExcludeString = commitsToExclude.map(commit => `^${commit}`)
187
200
 
@@ -205,7 +218,10 @@ function getCommitsRevList (commitsToExclude, commitsToInclude) {
205
218
  .filter(commit => commit)
206
219
  } catch (err) {
207
220
  log.error(`Get commits to upload failed: ${err.message}`)
208
- incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'get_objects', errorType: err.status })
221
+ incrementCountMetric(
222
+ TELEMETRY_GIT_COMMAND_ERRORS,
223
+ { command: 'get_objects', errorType: err.code, exitCode: err.status || err.errno } // err.status might be null
224
+ )
209
225
  }
210
226
  distributionMetric(TELEMETRY_GIT_COMMAND_MS, { command: 'get_objects' }, Date.now() - startTime)
211
227
  return result
@@ -245,7 +261,10 @@ function generatePackFilesForCommits (commitsToUpload) {
245
261
  result = execGitPackObjects(temporaryPath)
246
262
  } catch (err) {
247
263
  log.error(err)
248
- incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'pack_objects', errorType: err.status })
264
+ incrementCountMetric(
265
+ TELEMETRY_GIT_COMMAND_ERRORS,
266
+ { command: 'pack_objects', exitCode: err.status || err.errno, errorType: err.code }
267
+ )
249
268
  /**
250
269
  * The generation of pack files in the temporary folder (from `os.tmpdir()`)
251
270
  * sometimes fails in certain CI setups with the error message
@@ -262,7 +281,10 @@ function generatePackFilesForCommits (commitsToUpload) {
262
281
  result = execGitPackObjects(cwdPath)
263
282
  } catch (err) {
264
283
  log.error(err)
265
- incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'pack_objects', errorType: err.status })
284
+ incrementCountMetric(
285
+ TELEMETRY_GIT_COMMAND_ERRORS,
286
+ { command: 'pack_objects', exitCode: err.status || err.errno, errorType: err.code }
287
+ )
266
288
  }
267
289
  }
268
290
  distributionMetric(TELEMETRY_GIT_COMMAND_MS, { command: 'pack_objects' }, Date.now() - startTime)
@@ -53,7 +53,8 @@ const TEST_CONFIGURATION_BROWSER_NAME = 'test.configuration.browser_name'
53
53
  // Early flake detection
54
54
  const TEST_IS_NEW = 'test.is_new'
55
55
  const TEST_IS_RETRY = 'test.is_retry'
56
- const TEST_EARLY_FLAKE_IS_ENABLED = 'test.early_flake.is_enabled'
56
+ const TEST_EARLY_FLAKE_ENABLED = 'test.early_flake.enabled'
57
+ const TEST_EARLY_FLAKE_ABORT_REASON = 'test.early_flake.abort_reason'
57
58
 
58
59
  const CI_APP_ORIGIN = 'ciapp-test'
59
60
 
@@ -71,6 +72,12 @@ const ITR_CORRELATION_ID = 'itr_correlation_id'
71
72
 
72
73
  const TEST_CODE_COVERAGE_LINES_PCT = 'test.code_coverage.lines_pct'
73
74
 
75
+ // selenium tags
76
+ const TEST_BROWSER_DRIVER = 'test.browser.driver'
77
+ const TEST_BROWSER_DRIVER_VERSION = 'test.browser.driver_version'
78
+ const TEST_BROWSER_NAME = 'test.browser.name'
79
+ const TEST_BROWSER_VERSION = 'test.browser.version'
80
+
74
81
  // jest worker variables
75
82
  const JEST_WORKER_TRACE_PAYLOAD_CODE = 60
76
83
  const JEST_WORKER_COVERAGE_PAYLOAD_CODE = 61
@@ -102,7 +109,8 @@ module.exports = {
102
109
  TEST_CONFIGURATION_BROWSER_NAME,
103
110
  TEST_IS_NEW,
104
111
  TEST_IS_RETRY,
105
- TEST_EARLY_FLAKE_IS_ENABLED,
112
+ TEST_EARLY_FLAKE_ENABLED,
113
+ TEST_EARLY_FLAKE_ABORT_REASON,
106
114
  getTestEnvironmentMetadata,
107
115
  getTestParametersString,
108
116
  finishAllTraceSpans,
@@ -141,7 +149,12 @@ module.exports = {
141
149
  EFD_STRING,
142
150
  EFD_TEST_NAME_REGEX,
143
151
  removeEfdStringFromTestName,
144
- addEfdStringToTestName
152
+ addEfdStringToTestName,
153
+ getIsFaultyEarlyFlakeDetection,
154
+ TEST_BROWSER_DRIVER,
155
+ TEST_BROWSER_DRIVER_VERSION,
156
+ TEST_BROWSER_NAME,
157
+ TEST_BROWSER_VERSION
145
158
  }
146
159
 
147
160
  // Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
@@ -571,3 +584,21 @@ function addEfdStringToTestName (testName, numAttempt) {
571
584
  function removeEfdStringFromTestName (testName) {
572
585
  return testName.replace(EFD_TEST_NAME_REGEX, '')
573
586
  }
587
+
588
+ function getIsFaultyEarlyFlakeDetection (projectSuites, testsBySuiteName, faultyThresholdPercentage) {
589
+ let newSuites = 0
590
+ for (const suite of projectSuites) {
591
+ if (!testsBySuiteName[suite]) {
592
+ newSuites++
593
+ }
594
+ }
595
+ const newSuitesPercentage = (newSuites / projectSuites.length) * 100
596
+
597
+ // The faulty threshold represents a percentage, but we also want to consider
598
+ // smaller projects, where big variations in the % are more likely.
599
+ // This is why we also check the absolute number of new suites.
600
+ return (
601
+ newSuites > faultyThresholdPercentage &&
602
+ newSuitesPercentage > faultyThresholdPercentage
603
+ )
604
+ }
@@ -21,6 +21,7 @@ class Config {
21
21
  DD_AGENT_HOST,
22
22
  DD_ENV,
23
23
  DD_PROFILING_CODEHOTSPOTS_ENABLED,
24
+ DD_PROFILING_CPU_ENABLED,
24
25
  DD_PROFILING_DEBUG_SOURCE_MAPS,
25
26
  DD_PROFILING_ENABLED,
26
27
  DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
@@ -165,7 +166,7 @@ class Config {
165
166
 
166
167
  this.timelineEnabled = isTrue(coalesce(options.timelineEnabled,
167
168
  DD_PROFILING_TIMELINE_ENABLED,
168
- DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED, false))
169
+ DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED, samplingContextsAvailable))
169
170
  logExperimentalVarDeprecation('TIMELINE_ENABLED')
170
171
  checkOptionWithSamplingContextAllowed(this.timelineEnabled, 'Timeline view')
171
172
 
@@ -176,7 +177,9 @@ class Config {
176
177
  checkOptionWithSamplingContextAllowed(this.codeHotspotsEnabled, 'Code hotspots')
177
178
 
178
179
  this.cpuProfilingEnabled = isTrue(coalesce(options.cpuProfilingEnabled,
179
- DD_PROFILING_EXPERIMENTAL_CPU_ENABLED, false))
180
+ DD_PROFILING_CPU_ENABLED,
181
+ DD_PROFILING_EXPERIMENTAL_CPU_ENABLED, samplingContextsAvailable))
182
+ logExperimentalVarDeprecation('CPU_ENABLED')
180
183
  checkOptionWithSamplingContextAllowed(this.cpuProfilingEnabled, 'CPU profiling')
181
184
 
182
185
  this.profilers = ensureProfilers(profilers, this)
@@ -288,8 +291,9 @@ function ensureProfilers (profilers, options) {
288
291
  }
289
292
  }
290
293
 
291
- // Events profiler is a profiler for timeline events
292
- if (options.timelineEnabled) {
294
+ // Events profiler is a profiler that produces timeline events. It is only
295
+ // added if timeline is enabled and there's a wall profiler.
296
+ if (options.timelineEnabled && profilers.some(p => p instanceof WallProfiler)) {
293
297
  profilers.push(new EventsProfiler(options))
294
298
  }
295
299
 
@@ -10,6 +10,7 @@ const FormData = require('../../exporters/common/form-data')
10
10
  const { storage } = require('../../../../datadog-core')
11
11
  const version = require('../../../../../package.json').version
12
12
  const os = require('os')
13
+ const { urlToHttpOptions } = require('url')
13
14
  const perf = require('perf_hooks').performance
14
15
 
15
16
  const containerId = docker.id()
@@ -177,9 +178,10 @@ class AgentExporter {
177
178
  if (this._url.protocol === 'unix:') {
178
179
  options.socketPath = this._url.pathname
179
180
  } else {
180
- options.protocol = this._url.protocol
181
- options.hostname = this._url.hostname
182
- options.port = this._url.port
181
+ const httpOptions = urlToHttpOptions(this._url)
182
+ options.protocol = httpOptions.protocol
183
+ options.hostname = httpOptions.hostname
184
+ options.port = httpOptions.port
183
185
  }
184
186
 
185
187
  this._logger.debug(() => {
@@ -4,6 +4,9 @@ const { EventEmitter } = require('events')
4
4
  const { Config } = require('./config')
5
5
  const { snapshotKinds } = require('./constants')
6
6
  const { threadNamePrefix } = require('./profilers/shared')
7
+ const dc = require('dc-polyfill')
8
+
9
+ const profileSubmittedChannel = dc.channel('datadog:profiling:profile-submitted')
7
10
 
8
11
  function maybeSourceMap (sourceMap, SourceMapper, debug) {
9
12
  if (!sourceMap) return
@@ -161,6 +164,7 @@ class Profiler extends EventEmitter {
161
164
  this._capture(this._timeoutInterval, endDate)
162
165
  }
163
166
  await this._submit(encodedProfiles, startDate, endDate, snapshotKind)
167
+ profileSubmittedChannel.publish()
164
168
  this._logger.debug('Submitted profiles')
165
169
  } catch (err) {
166
170
  this._logger.error(err)
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ const dc = require('dc-polyfill')
4
+ const coalesce = require('koalas')
5
+ const profileSubmittedChannel = dc.channel('datadog:profiling:mock-profile-submitted')
6
+ const { DD_PROFILING_UPLOAD_PERIOD } = process.env
7
+
8
+ let timerId
9
+
10
+ module.exports = {
11
+ start: config => {
12
+ // Copied from packages/dd-trace/src/profiler.js
13
+ const flushInterval = coalesce(config.interval, Number(DD_PROFILING_UPLOAD_PERIOD) * 1000, 65 * 1000)
14
+
15
+ function scheduleProfileSubmit () {
16
+ timerId = setTimeout(emitProfileSubmit, flushInterval)
17
+ }
18
+
19
+ function emitProfileSubmit () {
20
+ profileSubmittedChannel.publish()
21
+ scheduleProfileSubmit()
22
+ }
23
+
24
+ scheduleProfileSubmit()
25
+ },
26
+
27
+ stop: () => {
28
+ if (timerId !== undefined) {
29
+ clearTimeout(timerId)
30
+ timerId = undefined
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,167 @@
1
+ 'use strict'
2
+
3
+ const telemetryMetrics = require('../telemetry/metrics')
4
+ const profilersNamespace = telemetryMetrics.manager.namespace('profilers')
5
+ const performance = require('perf_hooks').performance
6
+ const dc = require('dc-polyfill')
7
+ const { isTrue, isFalse } = require('../util')
8
+
9
+ // If the process lived for less than 30 seconds, it's considered short-lived
10
+ const DEFAULT_SHORT_LIVED_THRESHOLD = 30000
11
+
12
+ const EnablementChoice = {
13
+ MANUALLY_ENABLED: Symbol('SSITelemetry.EnablementChoice.MANUALLY_ENABLED'),
14
+ SSI_ENABLED: Symbol('SSITelemetry.EnablementChoice.SSI_ENABLED'),
15
+ SSI_NOT_ENABLED: Symbol('SSITelemetry.EnablementChoice.SSI_NOT_ENABLED'),
16
+ DISABLED: Symbol('SSITelemetry.EnablementChoice.MANUALLY_DISABLED')
17
+ }
18
+ Object.freeze(EnablementChoice)
19
+
20
+ function getEnablementChoiceFromEnv () {
21
+ const { DD_PROFILING_ENABLED, DD_INJECTION_ENABLED } = process.env
22
+ if (DD_INJECTION_ENABLED === undefined || isFalse(DD_PROFILING_ENABLED)) {
23
+ return EnablementChoice.DISABLED
24
+ } else if (DD_INJECTION_ENABLED.split(',').includes('profiling')) {
25
+ return EnablementChoice.SSI_ENABLED
26
+ } else if (isTrue(DD_PROFILING_ENABLED)) {
27
+ return EnablementChoice.MANUALLY_ENABLED
28
+ } else {
29
+ return EnablementChoice.SSI_NOT_ENABLED
30
+ }
31
+ }
32
+
33
+ function enablementChoiceToTagValue (enablementChoice) {
34
+ switch (enablementChoice) {
35
+ case EnablementChoice.MANUALLY_ENABLED:
36
+ return 'manually_enabled'
37
+ case EnablementChoice.SSI_ENABLED:
38
+ return 'ssi_enabled'
39
+ case EnablementChoice.SSI_NOT_ENABLED:
40
+ return 'not_enabled'
41
+ case EnablementChoice.MANUALLY_DISABLED:
42
+ // Can't emit this one as a tag
43
+ throw new Error('Invalid enablement choice')
44
+ }
45
+ }
46
+
47
+ /**
48
+ * This class emits telemetry metrics about the profiler behavior under SSI. It will only emit metrics
49
+ * when the application closes, and will emit the following metrics:
50
+ * - `number_of_profiles`: The number of profiles that were submitted
51
+ * - `number_of_runtime_id`: The number of runtime IDs in the app (always 1 for Node.js)
52
+ * It will also add tags describing the state of heuristics triggers, the enablement choice, and whether
53
+ * actual profiles were sent (as opposed to mock profiles). There is a mock profiler that is activated
54
+ * when the profiler is not enabled, and it will emit mock profile submission events at the same cadence
55
+ * the profiler would, providing insight into how many profiles would've been emitted if SSI enabled
56
+ * profiling. Note that telemetry is per tracer instance, and each worker thread will have its own instance.
57
+ */
58
+ class SSITelemetry {
59
+ constructor ({
60
+ enablementChoice = getEnablementChoiceFromEnv(),
61
+ shortLivedThreshold = DEFAULT_SHORT_LIVED_THRESHOLD
62
+ } = {}) {
63
+ if (!Object.values(EnablementChoice).includes(enablementChoice)) {
64
+ throw new Error('Invalid enablement choice')
65
+ }
66
+ if (typeof shortLivedThreshold !== 'number' || shortLivedThreshold <= 0) {
67
+ throw new Error('Short-lived threshold must be a positive number')
68
+ }
69
+ this.enablementChoice = enablementChoice
70
+ this.shortLivedThreshold = shortLivedThreshold
71
+
72
+ this.hasSentProfiles = false
73
+ this.noSpan = true
74
+ }
75
+
76
+ enabled () {
77
+ return this.enablementChoice !== EnablementChoice.DISABLED
78
+ }
79
+
80
+ start () {
81
+ if (this.enabled()) {
82
+ // Used to determine short-livedness of the process. We could use the process start time as the
83
+ // reference point, but the tracer initialization point is more relevant, as we couldn't be
84
+ // collecting profiles earlier anyway. The difference is not particularly significant if the
85
+ // tracer is initialized early in the process lifetime.
86
+ this.startTime = performance.now()
87
+
88
+ this._onSpanCreated = this._onSpanCreated.bind(this)
89
+ this._onProfileSubmitted = this._onProfileSubmitted.bind(this)
90
+ this._onMockProfileSubmitted = this._onMockProfileSubmitted.bind(this)
91
+ this._onAppClosing = this._onAppClosing.bind(this)
92
+
93
+ dc.subscribe('dd-trace:span:start', this._onSpanCreated)
94
+ dc.subscribe('datadog:profiling:profile-submitted', this._onProfileSubmitted)
95
+ dc.subscribe('datadog:profiling:mock-profile-submitted', this._onMockProfileSubmitted)
96
+ dc.subscribe('datadog:telemetry:app-closing', this._onAppClosing)
97
+ }
98
+ }
99
+
100
+ _onSpanCreated () {
101
+ this.noSpan = false
102
+ dc.unsubscribe('dd-trace:span:start', this._onSpanCreated)
103
+ }
104
+
105
+ _onProfileSubmitted () {
106
+ this.hasSentProfiles = true
107
+ this._incProfileCount()
108
+ }
109
+
110
+ _onMockProfileSubmitted () {
111
+ this._incProfileCount()
112
+ }
113
+
114
+ _incProfileCount () {
115
+ this._ensureProfileMetrics()
116
+ this._profileCount.inc()
117
+ }
118
+
119
+ _ensureProfileMetrics () {
120
+ const decision = []
121
+ if (this.noSpan) {
122
+ decision.push('no_span')
123
+ }
124
+ if (performance.now() - this.startTime < this.shortLivedThreshold) {
125
+ decision.push('short_lived')
126
+ }
127
+ if (decision.length === 0) {
128
+ decision.push('triggered')
129
+ }
130
+
131
+ const tags = [
132
+ 'installation:ssi',
133
+ `enablement_choice:${enablementChoiceToTagValue(this.enablementChoice)}`,
134
+ `has_sent_profiles:${this.hasSentProfiles}`,
135
+ `heuristic_hypothetical_decision:${decision.join('_')}`
136
+ ]
137
+
138
+ this._profileCount = profilersNamespace.count('ssi_heuristic.number_of_profiles', tags)
139
+ this._runtimeIdCount = profilersNamespace.count('ssi_heuristic.number_of_runtime_id', tags)
140
+
141
+ if (!this._emittedRuntimeId && decision[0] === 'triggered') {
142
+ // Tags won't change anymore, so we can emit the runtime ID metric now
143
+ this._emittedRuntimeId = true
144
+ this._runtimeIdCount.inc()
145
+ }
146
+ }
147
+
148
+ _onAppClosing () {
149
+ this._ensureProfileMetrics()
150
+ // Last ditch effort to emit a runtime ID count metric
151
+ if (!this._emittedRuntimeId) {
152
+ this._emittedRuntimeId = true
153
+ this._runtimeIdCount.inc()
154
+ }
155
+ // So we have the metrics in the final state
156
+ this._profileCount.inc(0)
157
+
158
+ dc.unsubscribe('datadog:profiling:profile-submitted', this._onProfileSubmitted)
159
+ dc.unsubscribe('datadog:profiling:mock-profile-submitted', this._onMockProfileSubmitted)
160
+ dc.unsubscribe('datadog:telemetry:app-closing', this._onAppClosing)
161
+ if (this.noSpan) {
162
+ dc.unsubscribe('dd-trace:span:start', this._onSpanCreated)
163
+ }
164
+ }
165
+ }
166
+
167
+ module.exports = { SSITelemetry, EnablementChoice }
@@ -11,7 +11,9 @@ const PluginManager = require('./plugin_manager')
11
11
  const remoteConfig = require('./appsec/remote_config')
12
12
  const AppsecSdk = require('./appsec/sdk')
13
13
  const dogstatsd = require('./dogstatsd')
14
+ const NoopDogStatsDClient = require('./noop/dogstatsd')
14
15
  const spanleak = require('./spanleak')
16
+ const { SSITelemetry } = require('./profiling/ssi-telemetry')
15
17
 
16
18
  class Tracer extends NoopProxy {
17
19
  constructor () {
@@ -20,7 +22,7 @@ class Tracer extends NoopProxy {
20
22
  this._initialized = false
21
23
  this._nomenclature = nomenclature
22
24
  this._pluginManager = new PluginManager(this)
23
- this.dogstatsd = new dogstatsd.NoopDogStatsDClient()
25
+ this.dogstatsd = new NoopDogStatsDClient()
24
26
  this._tracingInitialized = false
25
27
  }
26
28
 
@@ -72,6 +74,8 @@ class Tracer extends NoopProxy {
72
74
  require('./serverless').maybeStartServerlessMiniAgent(config)
73
75
  }
74
76
 
77
+ const ssiTelemetry = new SSITelemetry()
78
+ ssiTelemetry.start()
75
79
  if (config.profiling.enabled) {
76
80
  // do not stop tracer initialization if the profiler fails to be imported
77
81
  try {
@@ -80,6 +84,8 @@ class Tracer extends NoopProxy {
80
84
  } catch (e) {
81
85
  log.error(e)
82
86
  }
87
+ } else if (ssiTelemetry.enabled()) {
88
+ require('./profiling/ssi-telemetry-mock-profiler').start(config)
83
89
  }
84
90
  if (!this._profilerStarted) {
85
91
  this._profilerStarted = Promise.resolve(false)
@@ -2,7 +2,13 @@
2
2
 
3
3
  const log = require('./log')
4
4
 
5
- function add (carrier, keyValuePairs) {
5
+ const otelTagMap = {
6
+ 'deployment.environment': 'env',
7
+ 'service.name': 'service',
8
+ 'service.version': 'version'
9
+ }
10
+
11
+ function add (carrier, keyValuePairs, parseOtelTags = false) {
6
12
  if (!carrier || !keyValuePairs) return
7
13
 
8
14
  if (Array.isArray(keyValuePairs)) {
@@ -13,12 +19,16 @@ function add (carrier, keyValuePairs) {
13
19
  if (typeof keyValuePairs === 'string') {
14
20
  const segments = keyValuePairs.split(',')
15
21
  for (const segment of segments) {
16
- const separatorIndex = segment.indexOf(':')
22
+ const separatorIndex = parseOtelTags ? segment.indexOf('=') : segment.indexOf(':')
17
23
  if (separatorIndex === -1) continue
18
24
 
19
- const key = segment.slice(0, separatorIndex)
25
+ let key = segment.slice(0, separatorIndex)
20
26
  const value = segment.slice(separatorIndex + 1)
21
27
 
28
+ if (parseOtelTags && key in otelTagMap) {
29
+ key = otelTagMap[key]
30
+ }
31
+
22
32
  carrier[key.trim()] = value.trim()
23
33
  }
24
34
  } else {
@@ -10,6 +10,7 @@ const logs = require('./logs')
10
10
 
11
11
  const telemetryStartChannel = dc.channel('datadog:telemetry:start')
12
12
  const telemetryStopChannel = dc.channel('datadog:telemetry:stop')
13
+ const telemetryAppClosingChannel = dc.channel('datadog:telemetry:app-closing')
13
14
 
14
15
  let config
15
16
  let pluginManager
@@ -129,12 +130,12 @@ function appClosing () {
129
130
  if (!config?.telemetry?.enabled) {
130
131
  return
131
132
  }
133
+ // Give chance to listeners to update metrics before shutting down.
134
+ telemetryAppClosingChannel.publish()
132
135
  const { reqType, payload } = createPayload('app-closing')
133
136
  sendData(config, application, host, reqType, payload)
134
- // we flush before shutting down. Only in CI Visibility
135
- if (config.isCiVisibility) {
136
- metricsManager.send(config, application, host)
137
- }
137
+ // We flush before shutting down.
138
+ metricsManager.send(config, application, host)
138
139
  }
139
140
 
140
141
  function onBeforeExit () {
@@ -75,8 +75,8 @@ class CountMetric extends Metric {
75
75
  return this.track(value)
76
76
  }
77
77
 
78
- dec (value = -1) {
79
- return this.track(value)
78
+ dec (value = 1) {
79
+ return this.track(-value)
80
80
  }
81
81
 
82
82
  track (value = 1) {