Skip to content

Commit bfa596a

Browse files
authored
Observability: OpenTelemetry GenAI spans for turns + tool calls (closes #41) (#47)
1 parent d2b5a48 commit bfa596a

6 files changed

Lines changed: 944 additions & 0 deletions

File tree

bin/bench-tracer.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
/**
3+
* Tracer overhead micro-benchmark.
4+
*
5+
* Drives one synthetic turn + one tool-call through OpenclaWP_Tracer 1000 times
6+
* and reports avg/p50/p95/p99/max overhead. Used to assert the <5ms-per-turn
7+
* budget from issue #41.
8+
*
9+
* Run: php bin/bench-tracer.php
10+
*
11+
* @package OpenclaWP
12+
*/
13+
14+
require __DIR__ . '/../tests/bootstrap.php';
15+
16+
$GLOBALS['openclawp_test_filters']['openclawp_otel_endpoint'] = static function () {
17+
return 'https://collector.example/v1/traces';
18+
};
19+
20+
$samples = array();
21+
for ( $i = 0; $i < 1000; $i++ ) {
22+
OpenclaWP_Tracer::reset();
23+
OpenclaWP_Tracer::set_runtime_context(
24+
array(
25+
'openclawp.session.id' => 'sess-bench',
26+
'openclawp.agent.slug' => 'bench-agent',
27+
'openclawp.user.id' => '1',
28+
'openclawp.channel' => 'chat-block',
29+
)
30+
);
31+
OpenclaWP_Tracer::on_turn_completed(
32+
array(
33+
'agent_slug' => 'bench-agent',
34+
'session_id' => 'sess-bench',
35+
'provider' => 'anthropic',
36+
'model' => 'claude-opus-4-7',
37+
'token_usage' => array( 'input' => 1200, 'output' => 350 ),
38+
'duration_ms' => 480,
39+
'success' => true,
40+
'error' => null,
41+
'tool_call_count' => 1,
42+
)
43+
);
44+
OpenclaWP_Tracer::on_loop_event( 'tool_call', array( 'turn' => 1, 'tool_name' => 'echo' ) );
45+
OpenclaWP_Tracer::on_loop_event( 'tool_result', array( 'turn' => 1, 'tool_name' => 'echo', 'success' => true ) );
46+
$samples[] = OpenclaWP_Tracer::overhead_us();
47+
}
48+
sort( $samples );
49+
$n = count( $samples );
50+
$p50 = $samples[ (int) ( $n * 0.50 ) ];
51+
$p95 = $samples[ (int) ( $n * 0.95 ) ];
52+
$p99 = $samples[ (int) ( $n * 0.99 ) ];
53+
$avg = array_sum( $samples ) / $n;
54+
printf(
55+
"samples=%d avg=%.1fus p50=%dus p95=%dus p99=%dus max=%dus\n",
56+
$n,
57+
$avg,
58+
$p50,
59+
$p95,
60+
$p99,
61+
end( $samples )
62+
);

includes/class-openclawp-bootstrap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public static function init(): void {
3434
OpenclaWP_Routine_Registrar::register();
3535
OpenclaWP_Abilities::register();
3636
OpenclaWP_Event_Sink::register();
37+
OpenclaWP_Tracer::register();
3738
OpenclaWP_Usage_Recorder::register();
3839
OpenclaWP_Mcp_Rest::register();
3940
OpenclaWP_Rest::register();

includes/class-openclawp-runner.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,26 @@ public static function run_turn( string $agent_slug, string $message, ?string $s
121121
return $result;
122122
}
123123

124+
// Stamp session/agent/user/channel onto every span emitted during
125+
// this turn. Tracer is a no-op when no OTLP endpoint is configured,
126+
// so this is cheap when disabled.
127+
if ( class_exists( 'OpenclaWP_Tracer' ) ) {
128+
$channel = '';
129+
if ( isset( $runtime_context['channel'] ) && is_string( $runtime_context['channel'] ) ) {
130+
$channel = $runtime_context['channel'];
131+
} elseif ( isset( $runtime_context['client_context']['channel'] ) && is_string( $runtime_context['client_context']['channel'] ) ) {
132+
$channel = $runtime_context['client_context']['channel'];
133+
}
134+
OpenclaWP_Tracer::set_runtime_context(
135+
array(
136+
'openclawp.session.id' => $session_id,
137+
'openclawp.agent.slug' => $agent_slug,
138+
'openclawp.user.id' => (string) $user_id,
139+
'openclawp.channel' => $channel,
140+
)
141+
);
142+
}
143+
124144
$messages = $session['messages'];
125145
$messages[] = array(
126146
'role' => 'user',

0 commit comments

Comments
 (0)