Skip to content

Commit 64fcbf3

Browse files
authored
feat(instrumentation-aws-sdk): add gen ai metrics for bedrock (#2771)
1 parent ddf9bd5 commit 64fcbf3

File tree

14 files changed

+256
-59
lines changed

14 files changed

+256
-59
lines changed

package-lock.json

Lines changed: 9 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/node/opentelemetry-instrumentation-aws-sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"@opentelemetry/core": "^2.0.0",
4848
"@opentelemetry/instrumentation": "^0.200.0",
4949
"@opentelemetry/propagation-utils": "^0.31.0",
50-
"@opentelemetry/semantic-conventions": "^1.27.0"
50+
"@opentelemetry/semantic-conventions": "^1.31.0"
5151
},
5252
"devDependencies": {
5353
"@aws-sdk/client-bedrock-runtime": "^3.587.0",

plugins/node/opentelemetry-instrumentation-aws-sdk/src/aws-sdk.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
diag,
2222
SpanStatusCode,
2323
} from '@opentelemetry/api';
24-
import { suppressTracing } from '@opentelemetry/core';
24+
import { hrTime, suppressTracing } from '@opentelemetry/core';
2525
import { AttributeNames } from './enums';
2626
import { ServicesExtensions } from './services';
2727
import {
@@ -67,7 +67,8 @@ type V3PluginCommand = AwsV3Command<any, any, any, any, any> & {
6767

6868
export class AwsInstrumentation extends InstrumentationBase<AwsSdkInstrumentationConfig> {
6969
static readonly component = 'aws-sdk';
70-
private servicesExtensions: ServicesExtensions = new ServicesExtensions();
70+
// need declare since initialized in callbacks from super constructor
71+
private declare servicesExtensions: ServicesExtensions;
7172

7273
constructor(config: AwsSdkInstrumentationConfig = {}) {
7374
super(PACKAGE_NAME, PACKAGE_VERSION, config);
@@ -341,6 +342,7 @@ export class AwsInstrumentation extends InstrumentationBase<AwsSdkInstrumentatio
341342
self.getConfig(),
342343
self._diag
343344
);
345+
const startTime = hrTime();
344346
const span = self._startAwsV3Span(normalizedRequest, requestMetadata);
345347
const activeContextWithSpan = trace.setSpan(context.active(), span);
346348

@@ -404,7 +406,8 @@ export class AwsInstrumentation extends InstrumentationBase<AwsSdkInstrumentatio
404406
normalizedResponse,
405407
span,
406408
self.tracer,
407-
self.getConfig()
409+
self.getConfig(),
410+
startTime
408411
);
409412
self._callUserResponseHook(span, normalizedResponse);
410413
return response;
@@ -464,4 +467,11 @@ export class AwsInstrumentation extends InstrumentationBase<AwsSdkInstrumentatio
464467
return originalFunction();
465468
}
466469
}
470+
471+
override _updateMetricInstruments() {
472+
if (!this.servicesExtensions) {
473+
this.servicesExtensions = new ServicesExtensions();
474+
}
475+
this.servicesExtensions.updateMetricInstruments(this.meter);
476+
}
467477
}

plugins/node/opentelemetry-instrumentation-aws-sdk/src/semconv.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ export const ATTR_GEN_AI_RESPONSE_FINISH_REASONS =
109109
*/
110110
export const ATTR_GEN_AI_SYSTEM = 'gen_ai.system' as const;
111111

112+
/**
113+
* The type of token being counted.
114+
*
115+
* @example input
116+
* @example output
117+
*
118+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
119+
*/
120+
export const ATTR_GEN_AI_TOKEN_TYPE = 'gen_ai.token.type' as const;
121+
112122
/**
113123
* The number of tokens used in the GenAI input (prompt).
114124
*
@@ -138,3 +148,13 @@ export const GEN_AI_OPERATION_NAME_VALUE_CHAT = 'chat' as const;
138148
* Enum value "aws.bedrock" for attribute {@link ATTR_GEN_AI_SYSTEM}.
139149
*/
140150
export const GEN_AI_SYSTEM_VALUE_AWS_BEDROCK = 'aws.bedrock' as const;
151+
152+
/**
153+
* Enum value "input" for attribute {@link ATTR_GEN_AI_TOKEN_TYPE}.
154+
*/
155+
export const GEN_AI_TOKEN_TYPE_VALUE_INPUT = 'input' as const;
156+
157+
/**
158+
* Enum value "output" for attribute {@link ATTR_GEN_AI_TOKEN_TYPE}.
159+
*/
160+
export const GEN_AI_TOKEN_TYPE_VALUE_OUTPUT = 'output' as const;

plugins/node/opentelemetry-instrumentation-aws-sdk/src/services/ServiceExtension.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
import {
1717
DiagLogger,
18+
HrTime,
19+
Meter,
1820
Span,
1921
SpanAttributes,
2022
SpanKind,
@@ -49,6 +51,9 @@ export interface ServiceExtension {
4951
response: NormalizedResponse,
5052
span: Span,
5153
tracer: Tracer,
52-
config: AwsSdkInstrumentationConfig
54+
config: AwsSdkInstrumentationConfig,
55+
startTime: HrTime
5356
) => void;
57+
58+
updateMetricInstruments?: (meter: Meter) => void;
5459
}

plugins/node/opentelemetry-instrumentation-aws-sdk/src/services/ServicesExtensions.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { Tracer, Span, DiagLogger } from '@opentelemetry/api';
16+
import { Tracer, Span, DiagLogger, Meter, HrTime } from '@opentelemetry/api';
1717
import { ServiceExtension, RequestMetadata } from './ServiceExtension';
1818
import { SqsServiceExtension } from './sqs';
1919
import {
@@ -64,9 +64,16 @@ export class ServicesExtensions implements ServiceExtension {
6464
response: NormalizedResponse,
6565
span: Span,
6666
tracer: Tracer,
67-
config: AwsSdkInstrumentationConfig
67+
config: AwsSdkInstrumentationConfig,
68+
startTime: HrTime
6869
) {
6970
const serviceExtension = this.services.get(response.request.serviceName);
70-
serviceExtension?.responseHook?.(response, span, tracer, config);
71+
serviceExtension?.responseHook?.(response, span, tracer, config, startTime);
72+
}
73+
74+
updateMetricInstruments(meter: Meter) {
75+
for (const serviceExtension of this.services.values()) {
76+
serviceExtension.updateMetricInstruments?.(meter);
77+
}
7178
}
7279
}

plugins/node/opentelemetry-instrumentation-aws-sdk/src/services/bedrock-runtime.ts

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,16 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { Attributes, DiagLogger, Span, Tracer } from '@opentelemetry/api';
16+
import {
17+
Attributes,
18+
DiagLogger,
19+
Histogram,
20+
HrTime,
21+
Meter,
22+
Span,
23+
Tracer,
24+
ValueType,
25+
} from '@opentelemetry/api';
1726
import { RequestMetadata, ServiceExtension } from './ServiceExtension';
1827
import {
1928
ATTR_GEN_AI_SYSTEM,
@@ -23,19 +32,60 @@ import {
2332
ATTR_GEN_AI_REQUEST_TEMPERATURE,
2433
ATTR_GEN_AI_REQUEST_TOP_P,
2534
ATTR_GEN_AI_REQUEST_STOP_SEQUENCES,
35+
ATTR_GEN_AI_TOKEN_TYPE,
2636
ATTR_GEN_AI_USAGE_INPUT_TOKENS,
2737
ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,
2838
ATTR_GEN_AI_RESPONSE_FINISH_REASONS,
2939
GEN_AI_OPERATION_NAME_VALUE_CHAT,
3040
GEN_AI_SYSTEM_VALUE_AWS_BEDROCK,
41+
GEN_AI_TOKEN_TYPE_VALUE_INPUT,
42+
GEN_AI_TOKEN_TYPE_VALUE_OUTPUT,
3143
} from '../semconv';
3244
import {
3345
AwsSdkInstrumentationConfig,
3446
NormalizedRequest,
3547
NormalizedResponse,
3648
} from '../types';
49+
import {
50+
hrTime,
51+
hrTimeDuration,
52+
hrTimeToMilliseconds,
53+
} from '@opentelemetry/core';
3754

3855
export class BedrockRuntimeServiceExtension implements ServiceExtension {
56+
private tokenUsage!: Histogram;
57+
private operationDuration!: Histogram;
58+
59+
updateMetricInstruments(meter: Meter) {
60+
// https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/#metric-gen_aiclienttokenusage
61+
this.tokenUsage = meter.createHistogram('gen_ai.client.token.usage', {
62+
unit: '{token}',
63+
description: 'Measures number of input and output tokens used',
64+
valueType: ValueType.INT,
65+
advice: {
66+
explicitBucketBoundaries: [
67+
1, 4, 16, 64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304,
68+
16777216, 67108864,
69+
],
70+
},
71+
});
72+
73+
// https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/#metric-gen_aiclientoperationduration
74+
this.operationDuration = meter.createHistogram(
75+
'gen_ai.client.operation.duration',
76+
{
77+
unit: 's',
78+
description: 'GenAI operation duration',
79+
advice: {
80+
explicitBucketBoundaries: [
81+
0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.56, 5.12, 10.24,
82+
20.48, 40.96, 81.92,
83+
],
84+
},
85+
}
86+
);
87+
}
88+
3989
requestPreSpanHook(
4090
request: NormalizedRequest,
4191
config: AwsSdkInstrumentationConfig,
@@ -262,15 +312,22 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension {
262312
response: NormalizedResponse,
263313
span: Span,
264314
tracer: Tracer,
265-
config: AwsSdkInstrumentationConfig
315+
config: AwsSdkInstrumentationConfig,
316+
startTime: HrTime
266317
) {
267318
if (!span.isRecording()) {
268319
return;
269320
}
270321

271322
switch (response.request.commandName) {
272323
case 'Converse':
273-
return this.responseHookConverse(response, span, tracer, config);
324+
return this.responseHookConverse(
325+
response,
326+
span,
327+
tracer,
328+
config,
329+
startTime
330+
);
274331
case 'InvokeModel':
275332
return this.responseHookInvokeModel(response, span, tracer, config);
276333
}
@@ -280,16 +337,38 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension {
280337
response: NormalizedResponse,
281338
span: Span,
282339
tracer: Tracer,
283-
config: AwsSdkInstrumentationConfig
340+
config: AwsSdkInstrumentationConfig,
341+
startTime: HrTime
284342
) {
285343
const { stopReason, usage } = response.data;
344+
345+
const sharedMetricAttrs: Attributes = {
346+
[ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK,
347+
[ATTR_GEN_AI_OPERATION_NAME]: GEN_AI_OPERATION_NAME_VALUE_CHAT,
348+
[ATTR_GEN_AI_REQUEST_MODEL]: response.request.commandInput.modelId,
349+
};
350+
351+
const durationSecs =
352+
hrTimeToMilliseconds(hrTimeDuration(startTime, hrTime())) / 1000;
353+
this.operationDuration.record(durationSecs, sharedMetricAttrs);
354+
286355
if (usage) {
287356
const { inputTokens, outputTokens } = usage;
288357
if (inputTokens !== undefined) {
289358
span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, inputTokens);
359+
360+
this.tokenUsage.record(inputTokens, {
361+
...sharedMetricAttrs,
362+
[ATTR_GEN_AI_TOKEN_TYPE]: GEN_AI_TOKEN_TYPE_VALUE_INPUT,
363+
});
290364
}
291365
if (outputTokens !== undefined) {
292366
span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, outputTokens);
367+
368+
this.tokenUsage.record(outputTokens, {
369+
...sharedMetricAttrs,
370+
[ATTR_GEN_AI_TOKEN_TYPE]: GEN_AI_TOKEN_TYPE_VALUE_OUTPUT,
371+
});
293372
}
294373
}
295374

plugins/node/opentelemetry-instrumentation-aws-sdk/test/aws-sdk-v3-s3.test.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,12 @@
1515
*/
1616

1717
import {
18-
AwsInstrumentation,
1918
AwsSdkRequestHookInformation,
2019
AwsSdkResponseHookInformation,
2120
} from '../src';
22-
import {
23-
getTestSpans,
24-
registerInstrumentationTesting,
25-
} from '@opentelemetry/contrib-test-utils';
26-
const instrumentation = registerInstrumentationTesting(
27-
new AwsInstrumentation()
28-
);
21+
import { getTestSpans } from '@opentelemetry/contrib-test-utils';
22+
import { instrumentation } from './load-instrumentation';
23+
2924
import {
3025
PutObjectCommand,
3126
PutObjectCommandOutput,

plugins/node/opentelemetry-instrumentation-aws-sdk/test/aws-sdk-v3-sqs.test.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,8 @@
1818
// covered multiple `client-*` packages. Its tests could be merged into
1919
// sqs.test.ts.
2020

21-
import { AwsInstrumentation } from '../src';
22-
import {
23-
getTestSpans,
24-
registerInstrumentationTesting,
25-
} from '@opentelemetry/contrib-test-utils';
26-
registerInstrumentationTesting(new AwsInstrumentation());
21+
import { getTestSpans } from '@opentelemetry/contrib-test-utils';
22+
import './load-instrumentation';
2723

2824
import { SQS } from '@aws-sdk/client-sqs';
2925

0 commit comments

Comments
 (0)