-
Notifications
You must be signed in to change notification settings - Fork 150
Expand file tree
/
Copy pathnodejs.c
More file actions
186 lines (152 loc) · 5.98 KB
/
Copy pathnodejs.c
File metadata and controls
186 lines (152 loc) · 5.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build obi_bpf_ignore
#include <bpfcore/vmlinux.h>
#include <bpfcore/bpf_helpers.h>
#include <bpfcore/bpf_tracing.h>
#include <common/common.h>
#include <common/ringbuf.h>
#include <common/strings.h>
#include <common/tracing.h>
#include <logger/bpf_dbg.h>
#include <maps/fd_to_connection.h>
#include <maps/nodejs_fd_map.h>
#include <pid/pid.h>
#include <shared/obi_ctx.h>
enum {
k_delim_offset = 13,
k_variant_offset = 14,
k_fd1_offset = 14,
k_fd2_offset = 18,
k_ctx_fd_offset = 18,
k_max_fd_digits = 4,
// strlen("/dev/null/obi-span/") — the JSON span payload starts here
k_span_payload_offset = 19,
};
static __always_inline int handle_async_switch(char *buf, const u64 pid_tgid) {
u32 fd = 0;
for (u8 i = 0; i < k_max_fd_digits; ++i) {
fd *= 10;
fd += buf[k_ctx_fd_offset + i] - '0';
}
bpf_dbg_printk("nodejs_async_switch: %s, pid_tgid = %llx, fd = %u", buf, pid_tgid, fd);
const fd_key fkey = {.pid_tgid = pid_tgid, .fd = (s32)fd};
const connection_info_t *conn = bpf_map_lookup_elem(&fd_to_connection, &fkey);
if (!conn) {
obi_ctx__del(pid_tgid);
return 0;
}
const tp_info_pid_t *tp = trace_info_for_connection(conn, TRACE_TYPE_SERVER);
if (tp && tp->valid) {
obi_ctx__set(pid_tgid, &tp->tp);
} else {
obi_ctx__del(pid_tgid);
}
return 0;
}
// Manual span emitted by the injected span bridge (spanbridge.js):
// /dev/null/obi-span/<json>
// The JSON document (name, ids, duration, attributes...) is copied verbatim
// into a node_span_event_t; user space parses it (ReadNodeSpanEventIntoSpan).
// We stamp the event with bpf_ktime_get_ns() (the sentinel fires inside
// span.end(), so this is the span end time in the same monotonic domain the
// rest of the pipeline uses) and with the current request trace context from
// traces_ctx_v1 — maintained by the async-context sentinels above — so the
// span can be parented under OBI's automatic server span.
static __always_inline int handle_node_span(const char *path, const u64 pid_tgid) {
node_span_event_t *ev = bpf_ringbuf_reserve(&events, sizeof(node_span_event_t), 0);
if (!ev) {
return 0;
}
ev->type = EVENT_NODE_SPAN;
ev->_pad[0] = 0;
ev->_pad[1] = 0;
ev->end_ktime = bpf_ktime_get_ns();
task_pid(&ev->pid);
const obi_ctx_info_t *octx = obi_ctx__get(pid_tgid);
if (octx) {
ev->has_parent_ctx = 1;
bpf_memcpy(ev->parent_trace_id, (void *)octx->trace_id, TRACE_ID_SIZE_BYTES);
bpf_memcpy(ev->parent_span_id, (void *)octx->span_id, SPAN_ID_SIZE_BYTES);
} else {
ev->has_parent_ctx = 0;
bpf_memset(ev->parent_trace_id, 0, TRACE_ID_SIZE_BYTES);
bpf_memset(ev->parent_span_id, 0, SPAN_ID_SIZE_BYTES);
}
const long len = bpf_probe_read_user_str(
ev->payload, NODE_SPAN_PAYLOAD_MAX_LEN, path + k_span_payload_offset);
if (len <= 1) { // empty or unreadable payload
bpf_ringbuf_discard(ev, 0);
return 0;
}
ev->payload_len = (u32)(len - 1); // exclude the NUL terminator
bpf_dbg_printk("nodejs_manual_span: len=%d", ev->payload_len);
bpf_ringbuf_submit(ev, get_flags());
return 0;
}
static __always_inline int handle_fd_correlation(char *buf, const u64 pid_tgid) {
u32 fd1 = 0;
u32 fd2 = 0;
for (u8 i = 0; i < k_max_fd_digits; ++i) {
fd1 *= 10;
fd1 += buf[k_fd1_offset + i] - '0';
fd2 *= 10;
fd2 += buf[k_fd2_offset + i] - '0';
}
bpf_dbg_printk("nodejs_fd_correlation: %s, fd1 = %u, fd2 = %u", buf, fd1, fd2);
const u64 key = (pid_tgid << 32) | fd2;
bpf_map_update_elem(&nodejs_fd_map, &key, &fd1, BPF_ANY);
return 0;
}
SEC("uprobe/node:uv_fs_access")
int BPF_KPROBE(obi_uv_fs_access, void *loop, void *req, const char *path) {
(void)ctx;
(void)loop;
(void)req;
// the obi nodejs agents (fdextractor.js, spanbridge.js) pass signals to
// the ebpf layer by invoking uv_fs_access() with a fake path. Three
// formats are used:
//
// 1. fd pair correlation (outgoing -> incoming):
// /dev/null/obi/<fd1><fd2> — each fd is a left-zero-padded 4-digit number
//
// 2. async context switch (before-hook fires before each JS callback):
// /dev/null/obi-ctx/<fd> — 4-digit incoming fd for the current async context
//
// 3. manual span end (spanbridge.js):
// /dev/null/obi-span/<json> — serialized manual span, variable length
//
// All paths share the prefix "/dev/null/obi" (13 chars). The characters at
// positions 13-14 distinguish the formats:
// '/' -> format 1 (fd pair)
// '-', 'c' -> format 2 (context switch, "-ctx/" follows)
// '-', 's' -> format 3 (manual span, "-span/" follows)
static const char prefix[] = "/dev/null/obi";
static const u8 prefix_size = sizeof(prefix) - 1;
// Buffer sized to hold the longest fixed-size path + null terminator.
// Formats 1 and 2 are exactly 22 characters long; format 3 is read
// directly from user memory in handle_node_span.
char buf[] = "/dev/null/obi/00000000";
if (bpf_probe_read_user(buf, sizeof(buf), path) != 0) {
return 0;
}
if (obi_bpf_memcmp(prefix, buf, prefix_size) != 0) {
return 0;
}
const u64 pid_tgid = bpf_get_current_pid_tgid();
if (buf[k_delim_offset] == '-') {
// Manual span: /dev/null/obi-span/<json>
if (buf[k_variant_offset] == 's') {
if (!valid_pid(pid_tgid)) {
return 0;
}
return handle_node_span(path, pid_tgid);
}
// Async context switch: /dev/null/obi-ctx/XXXX
// Fires from the async_hooks 'before' callback in fdextractor.js to
// refresh traces_ctx_v1 before each JS callback.
return handle_async_switch(buf, pid_tgid);
}
// fd pair correlation: /dev/null/obi/<fd1><fd2>
return handle_fd_correlation(buf, pid_tgid);
}