dd-trace 5.90.0 → 5.92.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 (42) hide show
  1. package/index.d.ts +7 -0
  2. package/package.json +8 -7
  3. package/packages/datadog-instrumentations/src/child_process.js +14 -8
  4. package/packages/datadog-instrumentations/src/cucumber.js +18 -3
  5. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  6. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  7. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  8. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +30 -0
  9. package/packages/datadog-instrumentations/src/jest.js +9 -2
  10. package/packages/datadog-instrumentations/src/langgraph.js +7 -0
  11. package/packages/datadog-instrumentations/src/mocha/main.js +32 -9
  12. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -1
  13. package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
  14. package/packages/datadog-instrumentations/src/vitest.js +53 -24
  15. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +1 -1
  16. package/packages/datadog-plugin-cypress/src/support.js +5 -7
  17. package/packages/datadog-plugin-graphql/src/execute.js +2 -2
  18. package/packages/datadog-plugin-graphql/src/resolve.js +22 -35
  19. package/packages/datadog-plugin-http/src/client.js +1 -1
  20. package/packages/datadog-plugin-langgraph/src/index.js +24 -0
  21. package/packages/datadog-plugin-langgraph/src/stream.js +41 -0
  22. package/packages/dd-trace/src/config/defaults.js +3 -0
  23. package/packages/dd-trace/src/config/index.js +14 -1
  24. package/packages/dd-trace/src/config/supported-configurations.json +7 -0
  25. package/packages/dd-trace/src/constants.js +1 -0
  26. package/packages/dd-trace/src/crashtracking/crashtracker.js +1 -1
  27. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -0
  28. package/packages/dd-trace/src/dogstatsd.js +1 -0
  29. package/packages/dd-trace/src/encode/agentless-json.js +4 -1
  30. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +11 -0
  31. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +2 -0
  32. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +114 -0
  33. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -2
  34. package/packages/dd-trace/src/plugins/index.js +1 -0
  35. package/packages/dd-trace/src/plugins/util/test.js +7 -10
  36. package/packages/dd-trace/src/priority_sampler.js +20 -2
  37. package/packages/dd-trace/src/process-tags/index.js +41 -34
  38. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -1
  39. package/packages/dd-trace/src/proxy.js +4 -0
  40. package/packages/dd-trace/src/telemetry/send-data.js +5 -0
  41. package/packages/dd-trace/src/telemetry/session-propagation.js +78 -0
  42. package/packages/dd-trace/src/telemetry/telemetry.js +3 -0
@@ -10,13 +10,13 @@ class GraphQLResolvePlugin extends TracingPlugin {
10
10
  static operation = 'resolve'
11
11
 
12
12
  start (fieldCtx) {
13
- const { info, rootCtx, args } = fieldCtx
13
+ const { info, rootCtx, args, path: pathAsArray, pathString } = fieldCtx
14
14
 
15
- const path = getPath(info, this.config)
15
+ const path = getPath(this.config, pathAsArray)
16
16
 
17
17
  // we need to get the parent span to the field if it exists for correct span parenting
18
18
  // of nested fields
19
- const parentField = getParentField(rootCtx, pathToArray(info && info.path))
19
+ const parentField = getParentField(rootCtx, pathString)
20
20
  const childOf = parentField?.ctx?.currentStore?.span
21
21
 
22
22
  fieldCtx.parent = parentField
@@ -76,9 +76,9 @@ class GraphQLResolvePlugin extends TracingPlugin {
76
76
  super(...args)
77
77
 
78
78
  this.addTraceSub('updateField', (ctx) => {
79
- const { field, info, error } = ctx
79
+ const { field, error, path: pathAsArray } = ctx
80
80
 
81
- const path = getPath(info, this.config)
81
+ const path = getPath(this.config, pathAsArray)
82
82
 
83
83
  if (!shouldInstrument(this.config, path)) return
84
84
 
@@ -118,28 +118,14 @@ function shouldInstrument (config, path) {
118
118
  return config.depth < 0 || config.depth >= depth
119
119
  }
120
120
 
121
- function getPath (info, config) {
122
- const responsePathAsArray = config.collapse
123
- ? withCollapse(pathToArray)
124
- : pathToArray
125
- return responsePathAsArray(info && info.path)
121
+ function getPath (config, pathAsArray) {
122
+ return config.collapse
123
+ ? withCollapse(pathAsArray)
124
+ : pathAsArray
126
125
  }
127
126
 
128
- function pathToArray (path) {
129
- const flattened = []
130
- let curr = path
131
- while (curr) {
132
- flattened.push(curr.key)
133
- curr = curr.prev
134
- }
135
- return flattened.reverse()
136
- }
137
-
138
- function withCollapse (responsePathAsArray) {
139
- return function () {
140
- return responsePathAsArray.apply(this, arguments)
141
- .map(segment => typeof segment === 'number' ? '*' : segment)
142
- }
127
+ function withCollapse (pathAsArray) {
128
+ return pathAsArray.map(segment => typeof segment === 'number' ? '*' : segment)
143
129
  }
144
130
 
145
131
  function getResolverInfo (info, args) {
@@ -173,19 +159,20 @@ function getResolverInfo (info, args) {
173
159
  return resolverInfo
174
160
  }
175
161
 
176
- function getParentField (parentCtx, path) {
177
- for (let i = path.length - 1; i > 0; i--) {
178
- const field = getField(parentCtx, path.slice(0, i))
179
- if (field) {
180
- return field
181
- }
162
+ function getParentField (parentCtx, pathToString) {
163
+ let current = pathToString
164
+
165
+ while (current) {
166
+ const lastJoin = current.lastIndexOf('.')
167
+ if (lastJoin === -1) break
168
+
169
+ current = current.slice(0, lastJoin)
170
+ const field = parentCtx.fields[current]
171
+
172
+ if (field) return field
182
173
  }
183
174
 
184
175
  return null
185
176
  }
186
177
 
187
- function getField (parentCtx, path) {
188
- return parentCtx.fields[path.join('.')]
189
- }
190
-
191
178
  module.exports = GraphQLResolvePlugin
@@ -211,7 +211,7 @@ function getHeaders (config) {
211
211
  if (typeof header === 'string') {
212
212
  const separatorIndex = header.indexOf(':')
213
213
  result.push(separatorIndex === -1
214
- ? [header, undefined]
214
+ ? [header.toLowerCase(), undefined]
215
215
  : [
216
216
  header.slice(0, separatorIndex).toLowerCase(),
217
217
  header.slice(separatorIndex + 1),
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ const CompositePlugin = require('../../dd-trace/src/plugins/composite')
4
+ const langgraphLLMObsPlugins = require('../../dd-trace/src/llmobs/plugins/langgraph')
5
+ const streamPlugin = require('./stream')
6
+
7
+ const plugins = {}
8
+
9
+ // CRITICAL: LLMObs plugins MUST come first
10
+ for (const Plugin of langgraphLLMObsPlugins) {
11
+ plugins[Plugin.id] = Plugin
12
+ }
13
+
14
+ // Tracing plugins second
15
+ for (const Plugin of streamPlugin) {
16
+ plugins[Plugin.id] = Plugin
17
+ }
18
+
19
+ class LanggraphPlugin extends CompositePlugin {
20
+ static id = 'langgraph'
21
+ static plugins = plugins
22
+ }
23
+
24
+ module.exports = LanggraphPlugin
@@ -0,0 +1,41 @@
1
+ 'use strict'
2
+
3
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
+ const { spanHasError } = require('../../dd-trace/src/llmobs/util')
5
+
6
+ // We are only tracing Pregel.stream because Pregel.invoke calls stream internally resulting in
7
+ // a graph with spans that look redundant.
8
+ class PregelStreamPlugin extends TracingPlugin {
9
+ static id = 'langgraph_pregel_stream'
10
+ static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream'
11
+
12
+ bindStart (ctx) {
13
+ this.startSpan('LangGraph', {
14
+ service: this.config.service,
15
+ kind: 'internal',
16
+ component: 'langgraph',
17
+ }, ctx)
18
+ return ctx.currentStore
19
+ }
20
+ }
21
+ class NextStreamPlugin extends TracingPlugin {
22
+ static id = 'langgraph_stream_next'
23
+ static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream_next'
24
+
25
+ bindStart (ctx) {
26
+ return ctx.currentStore
27
+ }
28
+
29
+ asyncEnd (ctx) {
30
+ const span = ctx.currentStore?.span
31
+ if (!span) return
32
+ if (ctx.result.done === true || spanHasError(span)) {
33
+ span.finish()
34
+ }
35
+ }
36
+ }
37
+
38
+ module.exports = [
39
+ PregelStreamPlugin,
40
+ NextStreamPlugin,
41
+ ]
@@ -2,6 +2,7 @@
2
2
 
3
3
  const pkg = require('../pkg')
4
4
  const { isFalse, isTrue } = require('../util')
5
+ const { DD_MAJOR } = require('../../../../version')
5
6
  const { getEnvironmentVariable: getEnv } = require('./helper')
6
7
 
7
8
  const {
@@ -106,6 +107,7 @@ const defaultsWithoutSupportedConfigurationEntry = {
106
107
  isGCPFunction: false,
107
108
  instrumentationSource: 'manual',
108
109
  isServiceUserProvided: false,
110
+ isServiceNameInferred: true,
109
111
  lookup: undefined,
110
112
  plugins: true,
111
113
  }
@@ -116,6 +118,7 @@ const defaultsWithoutSupportedConfigurationEntry = {
116
118
  // TODO: These entries should be removed. They are off by default
117
119
  // because they rely on other configs.
118
120
  const defaultsWithConditionalRuntimeBehavior = {
121
+ startupLogs: DD_MAJOR >= 6,
119
122
  isGitUploadEnabled: false,
120
123
  isImpactedTestsEnabled: false,
121
124
  isIntelligentTestRunnerEnabled: false,
@@ -49,6 +49,8 @@ const VALID_PROPAGATION_BEHAVIOR_EXTRACT = new Set(['continue', 'restart', 'igno
49
49
  const VALID_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error'])
50
50
  const DEFAULT_OTLP_PORT = 4318
51
51
  const RUNTIME_ID = uuid()
52
+ // eslint-disable-next-line eslint-rules/eslint-process-env -- internal propagation, not user config
53
+ const ROOT_SESSION_ID = process.env.DD_ROOT_JS_SESSION_ID || RUNTIME_ID
52
54
  const NAMING_VERSIONS = new Set(['v0', 'v1'])
53
55
  const DEFAULT_NAMING_VERSION = 'v0'
54
56
 
@@ -145,6 +147,8 @@ class Config {
145
147
  'runtime-id': RUNTIME_ID,
146
148
  })
147
149
 
150
+ this.rootSessionId = ROOT_SESSION_ID
151
+
148
152
  if (this.isCiVisibility) {
149
153
  tagger.add(this.tags, {
150
154
  [ORIGIN_KEY]: 'ciapp-test',
@@ -722,8 +726,10 @@ class Config {
722
726
  // Priority:
723
727
  // DD_SERVICE > tags.service > OTEL_SERVICE_NAME > NX_TASK_TARGET_PROJECT (if DD_ENABLE_NX_SERVICE_NAME) > default
724
728
  let serviceName = DD_SERVICE || tags.service || OTEL_SERVICE_NAME
729
+ let isServiceNameInferred
725
730
  if (!serviceName && NX_TASK_TARGET_PROJECT) {
726
731
  if (isTrue(DD_ENABLE_NX_SERVICE_NAME)) {
732
+ isServiceNameInferred = true
727
733
  serviceName = NX_TASK_TARGET_PROJECT
728
734
  } else if (DD_MAJOR < 6) {
729
735
  // Warn about v6 behavior change for Nx projects
@@ -734,6 +740,7 @@ class Config {
734
740
  }
735
741
  }
736
742
  setString(target, 'service', serviceName)
743
+ if (serviceName) setBoolean(target, 'isServiceNameInferred', isServiceNameInferred ?? false)
737
744
  if (DD_SERVICE_MAPPING) {
738
745
  target.serviceMapping = Object.fromEntries(
739
746
  DD_SERVICE_MAPPING.split(',').map(x => x.trim().split(':'))
@@ -1004,7 +1011,11 @@ class Config {
1004
1011
  setUnit(opts, 'sampleRate', options.sampleRate ?? options.ingestion.sampleRate)
1005
1012
  opts['sampler.rateLimit'] = maybeInt(options.rateLimit ?? options.ingestion.rateLimit)
1006
1013
  setSamplingRule(opts, 'sampler.rules', options.samplingRules)
1007
- setString(opts, 'service', options.service || tags.service)
1014
+ const optService = options.service || tags.service
1015
+ setString(opts, 'service', optService)
1016
+ if (optService) {
1017
+ setBoolean(opts, 'isServiceNameInferred', false)
1018
+ }
1008
1019
  opts.serviceMapping = options.serviceMapping
1009
1020
  setString(opts, 'site', options.site)
1010
1021
  if (options.spanAttributeSchema) {
@@ -1118,6 +1129,8 @@ class Config {
1118
1129
  setBoolean(calc, 'reportHostname', true)
1119
1130
  // Clear sampling rules - server-side sampling handles this
1120
1131
  calc['sampler.rules'] = []
1132
+ // Agentless intake only accepts 64-bit trace IDs; disable 128-bit generation
1133
+ setBoolean(calc, 'traceId128BitGenerationEnabled', false)
1121
1134
  }
1122
1135
 
1123
1136
  if (this.#isCiVisibility()) {
@@ -2995,6 +2995,13 @@
2995
2995
  "default": "true"
2996
2996
  }
2997
2997
  ],
2998
+ "DD_TRACE_LANGGRAPH_ENABLED": [
2999
+ {
3000
+ "implementation": "C",
3001
+ "type": "boolean",
3002
+ "default": "true"
3003
+ }
3004
+ ],
2998
3005
  "DD_TRACE_LDAPJS_ENABLED": [
2999
3006
  {
3000
3007
  "implementation": "A",
@@ -23,6 +23,7 @@ module.exports = {
23
23
  SPAN_SAMPLING_MAX_PER_SECOND: '_dd.span_sampling.max_per_second',
24
24
  DATADOG_LAMBDA_EXTENSION_PATH: '/opt/extensions/datadog-agent',
25
25
  DECISION_MAKER_KEY: '_dd.p.dm',
26
+ SAMPLING_KNUTH_RATE: '_dd.p.ksr',
26
27
  PROCESS_ID: 'process_id',
27
28
  ERROR_TYPE: 'error.type',
28
29
  ERROR_MESSAGE: 'error.message',
@@ -74,7 +74,7 @@ class Crashtracker {
74
74
  timeout_ms: 3000,
75
75
  },
76
76
  timeout: { secs: 5, nanos: 0 },
77
- demangle_names: false,
77
+ demangle_names: true,
78
78
  signals: [],
79
79
  resolve_frames: resolveMode,
80
80
  }
@@ -2,8 +2,11 @@
2
2
 
3
3
  const { workerData: { config: parentConfig, parentThreadId, configPort } } = require('node:worker_threads')
4
4
  const { getAgentUrl } = require('../../agent/url')
5
+ const processTags = require('../../process-tags')
5
6
  const log = require('./log')
6
7
 
8
+ processTags.initialize()
9
+
7
10
  const config = module.exports = {
8
11
  ...parentConfig,
9
12
  parentThreadId,
@@ -26,6 +26,7 @@ class DogStatsDClient {
26
26
  constructor (options = {}) {
27
27
  if (options.metricsProxyUrl) {
28
28
  this._httpOptions = {
29
+ method: 'POST',
29
30
  url: options.metricsProxyUrl.toString(),
30
31
  path: '/dogstatsd/v2/proxy',
31
32
  }
@@ -16,6 +16,9 @@ const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
16
16
  function formatSpan (span, isFirstSpan) {
17
17
  span = normalizeSpan(truncateSpan(span, false))
18
18
 
19
+ // Remove _dd.p.tid (the upper 64 bits of a 128-bit trace ID) since trace_id is truncated to lower 64 bits
20
+ delete span.meta['_dd.p.tid']
21
+
19
22
  if (span.span_events) {
20
23
  span.meta.events = JSON.stringify(span.span_events)
21
24
  delete span.span_events
@@ -45,7 +48,7 @@ function formatSpan (span, isFirstSpan) {
45
48
  */
46
49
  function spanToJSON (span) {
47
50
  const result = {
48
- trace_id: span.trace_id.toString(16).toLowerCase(),
51
+ trace_id: span.trace_id.toString(16).toLowerCase().slice(-16),
49
52
  span_id: span.span_id.toString(16).toLowerCase(),
50
53
  parent_id: span.parent_id.toString(16).toLowerCase(),
51
54
  name: span.name,
@@ -15,6 +15,17 @@ class LangChainLLMObsChainHandler extends LangChainLLMObsHandler {
15
15
  // chain spans will always be workflows
16
16
  this._tagger.tagTextIO(span, input, output)
17
17
  }
18
+
19
+ getName ({ span, instance }) {
20
+ const firstCallable = instance?.first
21
+
22
+ if (firstCallable?.constructor?.name === 'ChannelWrite') return
23
+
24
+ const firstCallableIsLangGraph = firstCallable?.lc_namespace?.includes('langgraph')
25
+ const firstCallableName = firstCallable?.name
26
+
27
+ return firstCallableIsLangGraph ? firstCallableName : super.getName({ span })
28
+ }
18
29
  }
19
30
 
20
31
  module.exports = LangChainLLMObsChainHandler
@@ -54,6 +54,8 @@ class BaseLangChainLLMObsPlugin extends LLMObsPlugin {
54
54
  const handler = this._handlers[ctx.type]
55
55
  const name = handler?.getName({ span, instance })
56
56
 
57
+ if (name == null) return
58
+
57
59
  return {
58
60
  modelProvider,
59
61
  modelName,
@@ -0,0 +1,114 @@
1
+ 'use strict'
2
+
3
+ const LLMObsPlugin = require('../base')
4
+ const { spanHasError } = require('../../util')
5
+
6
+ const streamDataMap = new WeakMap()
7
+
8
+ function formatIO (data) {
9
+ if (data == null) return ''
10
+
11
+ if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
12
+ return data
13
+ }
14
+
15
+ if (data.constructor?.name === 'Object') {
16
+ const formatted = {}
17
+ for (const [key, value] of Object.entries(data)) {
18
+ formatted[key] = formatIO(value)
19
+ }
20
+ return formatted
21
+ }
22
+
23
+ if (Array.isArray(data)) {
24
+ return data.map(item => formatIO(item))
25
+ }
26
+
27
+ try {
28
+ return JSON.stringify(data)
29
+ } catch {
30
+ return String(data)
31
+ }
32
+ }
33
+
34
+ class PregelStreamLLMObsPlugin extends LLMObsPlugin {
35
+ static id = 'llmobs_langgraph_pregel_stream'
36
+ static integration = 'langgraph'
37
+ static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream'
38
+
39
+ getLLMObsSpanRegisterOptions (ctx) {
40
+ const name = ctx.self.name || 'LangGraph'
41
+
42
+ const enabled = this._tracerConfig.llmobs.enabled
43
+ if (!enabled) return
44
+
45
+ const span = ctx.currentStore?.span
46
+ if (!span) return
47
+ streamDataMap.set(span, {
48
+ streamInputs: ctx.arguments?.[0],
49
+ chunks: [],
50
+ })
51
+
52
+ return {
53
+ kind: 'workflow',
54
+ name,
55
+ }
56
+ }
57
+
58
+ asyncEnd () {}
59
+ }
60
+
61
+ class NextStreamLLMObsPlugin extends LLMObsPlugin {
62
+ static id = 'llmobs_langgraph_next_stream'
63
+ static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream_next'
64
+
65
+ start () {} // no-op: span was already registered by PregelStreamLLMObsPlugin
66
+
67
+ end () {} // no-op: context restore is handled by PregelStreamLLMObsPlugin
68
+
69
+ error (ctx) {
70
+ const span = ctx.currentStore?.span
71
+ if (!span) return
72
+
73
+ this.#tagAndCleanup(span, true)
74
+ }
75
+
76
+ setLLMObsTags (ctx) {
77
+ const span = ctx.currentStore?.span
78
+ if (!span) return
79
+
80
+ // Accumulate chunks until done
81
+ if (ctx.result?.value && !ctx.result.done) {
82
+ const streamData = streamDataMap.get(span)
83
+ if (streamData) {
84
+ streamData.chunks.push(ctx.result.value)
85
+ }
86
+ return
87
+ }
88
+
89
+ // Tag on last chunk
90
+ if (ctx.result?.done) {
91
+ const hasError = ctx.error || spanHasError(span)
92
+ this.#tagAndCleanup(span, hasError)
93
+ }
94
+ }
95
+
96
+ #tagAndCleanup (span, hasError) {
97
+ const streamData = streamDataMap.get(span)
98
+ if (!streamData) return
99
+
100
+ const { streamInputs: inputs, chunks } = streamData
101
+ const input = inputs == null ? undefined : formatIO(inputs)
102
+ const lastChunk = chunks.length > 0 ? chunks[chunks.length - 1] : undefined
103
+ const output = !hasError && lastChunk != null ? formatIO(lastChunk) : undefined
104
+
105
+ this._tagger.tagTextIO(span, input, output)
106
+
107
+ streamDataMap.delete(span)
108
+ }
109
+ }
110
+
111
+ module.exports = [
112
+ PregelStreamLLMObsPlugin,
113
+ NextStreamLLMObsPlugin,
114
+ ]
@@ -125,7 +125,7 @@ module.exports = class CiPlugin extends Plugin {
125
125
  this._pendingRequestErrorTags = []
126
126
 
127
127
  this.addSub(`ci:${this.constructor.id}:library-configuration`, (ctx) => {
128
- const { onDone, isParallel, frameworkVersion } = ctx
128
+ const { onDone, frameworkVersion } = ctx
129
129
  ctx.currentStore = storage('legacy').getStore()
130
130
 
131
131
  if (!this.tracer._exporter || !this.tracer._exporter.getLibraryConfiguration) {
@@ -143,7 +143,7 @@ module.exports = class CiPlugin extends Plugin {
143
143
  ? getSessionRequestErrorTags(this.testSessionSpan)
144
144
  : Object.fromEntries(this._pendingRequestErrorTags.map(({ tag, value }) => [tag, value]))
145
145
 
146
- const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, isParallel, frameworkVersion)
146
+ const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, frameworkVersion)
147
147
  const metadataTags = {
148
148
  test: {
149
149
  ...libraryCapabilitiesTags,
@@ -31,6 +31,7 @@ const plugins = {
31
31
  get '@redis/client' () { return require('../../../datadog-plugin-redis/src') },
32
32
  get '@smithy/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
33
33
  get '@vitest/runner' () { return require('../../../datadog-plugin-vitest/src') },
34
+ get '@langchain/langgraph' () { return require('../../../datadog-plugin-langgraph/src') },
34
35
  get aerospike () { return require('../../../datadog-plugin-aerospike/src') },
35
36
  get ai () { return require('../../../datadog-plugin-ai/src') },
36
37
  get amqp10 () { return require('../../../datadog-plugin-amqp10/src') },
@@ -150,7 +150,6 @@ const DD_CAPABILITIES_FAILED_TEST_REPLAY = '_dd.library_capabilities.failed_test
150
150
  const DD_CI_LIBRARY_CONFIGURATION_ERROR = '_dd.ci.library_configuration_error'
151
151
 
152
152
  const UNSUPPORTED_TIA_FRAMEWORKS = new Set(['playwright', 'vitest'])
153
- const UNSUPPORTED_TIA_FRAMEWORKS_PARALLEL_MODE = new Set(['cucumber', 'mocha'])
154
153
  const MINIMUM_FRAMEWORK_VERSION_FOR_EFD = {
155
154
  playwright: '>=1.38.0',
156
155
  }
@@ -170,7 +169,6 @@ const MINIMUM_FRAMEWORK_VERSION_FOR_FAILED_TEST_REPLAY = {
170
169
  playwright: '>=1.38.0',
171
170
  }
172
171
 
173
- const UNSUPPORTED_ATTEMPT_TO_FIX_FRAMEWORKS_PARALLEL_MODE = new Set(['mocha'])
174
172
  const NOT_SUPPORTED_GRANULARITY_IMPACTED_TESTS_FRAMEWORKS = new Set(['mocha', 'playwright', 'vitest'])
175
173
 
176
174
  const TEST_LEVEL_EVENT_TYPES = [
@@ -987,9 +985,8 @@ function getFormattedError (error, repositoryRoot) {
987
985
  return newError
988
986
  }
989
987
 
990
- function isTiaSupported (testFramework, isParallel) {
991
- return !(UNSUPPORTED_TIA_FRAMEWORKS.has(testFramework) ||
992
- (isParallel && UNSUPPORTED_TIA_FRAMEWORKS_PARALLEL_MODE.has(testFramework)))
988
+ function isTiaSupported (testFramework) {
989
+ return !UNSUPPORTED_TIA_FRAMEWORKS.has(testFramework)
993
990
  }
994
991
 
995
992
  function isEarlyFlakeDetectionSupported (testFramework, frameworkVersion) {
@@ -1016,12 +1013,12 @@ function isDisableSupported (testFramework, frameworkVersion) {
1016
1013
  : true
1017
1014
  }
1018
1015
 
1019
- function isAttemptToFixSupported (testFramework, isParallel, frameworkVersion) {
1016
+ function isAttemptToFixSupported (testFramework, frameworkVersion) {
1020
1017
  if (testFramework === 'playwright') {
1021
1018
  return satisfies(frameworkVersion, MINIMUM_FRAMEWORK_VERSION_FOR_ATTEMPT_TO_FIX[testFramework])
1022
1019
  }
1023
1020
 
1024
- return !(isParallel && UNSUPPORTED_ATTEMPT_TO_FIX_FRAMEWORKS_PARALLEL_MODE.has(testFramework))
1021
+ return true
1025
1022
  }
1026
1023
 
1027
1024
  function isFailedTestReplaySupported (testFramework, frameworkVersion) {
@@ -1030,9 +1027,9 @@ function isFailedTestReplaySupported (testFramework, frameworkVersion) {
1030
1027
  : true
1031
1028
  }
1032
1029
 
1033
- function getLibraryCapabilitiesTags (testFramework, isParallel, frameworkVersion) {
1030
+ function getLibraryCapabilitiesTags (testFramework, frameworkVersion) {
1034
1031
  return {
1035
- [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isTiaSupported(testFramework, isParallel)
1032
+ [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isTiaSupported(testFramework)
1036
1033
  ? '1'
1037
1034
  : undefined,
1038
1035
  [DD_CAPABILITIES_EARLY_FLAKE_DETECTION]: isEarlyFlakeDetectionSupported(testFramework, frameworkVersion)
@@ -1049,7 +1046,7 @@ function getLibraryCapabilitiesTags (testFramework, isParallel, frameworkVersion
1049
1046
  ? '1'
1050
1047
  : undefined,
1051
1048
  [DD_CAPABILITIES_TEST_MANAGEMENT_ATTEMPT_TO_FIX]:
1052
- isAttemptToFixSupported(testFramework, isParallel, frameworkVersion)
1049
+ isAttemptToFixSupported(testFramework, frameworkVersion)
1053
1050
  ? '5'
1054
1051
  : undefined,
1055
1052
  [DD_CAPABILITIES_FAILED_TEST_REPLAY]: isFailedTestReplaySupported(testFramework, frameworkVersion)
@@ -31,10 +31,21 @@ const {
31
31
  SAMPLING_LIMIT_DECISION,
32
32
  SAMPLING_AGENT_DECISION,
33
33
  DECISION_MAKER_KEY,
34
+ SAMPLING_KNUTH_RATE,
34
35
  } = require('./constants')
35
36
 
36
37
  const DEFAULT_KEY = 'service:,env:'
37
38
 
39
+ /**
40
+ * Formats a sampling rate as a string with up to 6 significant digits and no trailing zeros.
41
+ *
42
+ * @param {number} rate
43
+ * @returns {string}
44
+ */
45
+ function formatKnuthRate (rate) {
46
+ return Number(rate.toPrecision(6)).toString()
47
+ }
48
+
38
49
  const defaultSampler = new Sampler(AUTO_KEEP)
39
50
 
40
51
  /**
@@ -254,6 +265,7 @@ class PrioritySampler {
254
265
  */
255
266
  #getPriorityByRule (context, rule) {
256
267
  context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
268
+ context._trace.tags[SAMPLING_KNUTH_RATE] = formatKnuthRate(rule.sampleRate)
257
269
  context._sampling.mechanism = SAMPLING_MECHANISM_RULE
258
270
  if (rule.provenance === 'customer') context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_USER
259
271
  if (rule.provenance === 'dynamic') context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_DYNAMIC
@@ -290,9 +302,15 @@ class PrioritySampler {
290
302
  // TODO: Change underscored properties to private ones.
291
303
  const sampler = this._samplers[key] || this._samplers[DEFAULT_KEY]
292
304
 
293
- context._trace[SAMPLING_AGENT_DECISION] = sampler.rate()
305
+ const rate = sampler.rate()
306
+ context._trace[SAMPLING_AGENT_DECISION] = rate
294
307
 
295
- context._sampling.mechanism = sampler === defaultSampler ? SAMPLING_MECHANISM_DEFAULT : SAMPLING_MECHANISM_AGENT
308
+ if (sampler === defaultSampler) {
309
+ context._sampling.mechanism = SAMPLING_MECHANISM_DEFAULT
310
+ } else {
311
+ context._trace.tags[SAMPLING_KNUTH_RATE] = formatKnuthRate(rate)
312
+ context._sampling.mechanism = SAMPLING_MECHANISM_AGENT
313
+ }
296
314
 
297
315
  return sampler.isSampled(context) ? AUTO_KEEP : AUTO_REJECT
298
316
  }