Skip to content

Commit 328863e

Browse files
cdeckerrustyrussell
authored andcommitted
tracing: Add an example exporter to zipkin and document it
1 parent 3fa2ec7 commit 328863e

File tree

5 files changed

+151
-24
lines changed

5 files changed

+151
-24
lines changed

contrib/cln-tracer/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
poetry.lock

contrib/cln-tracer/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# cln-tracer
2+
3+
Utilities to export USDT traces from CLN.
4+
5+
This directory contains the following utilities:
6+
7+
- `cln_tracer/trace.py` instruments and exports traces from a single binary.
8+
9+
## Installation
10+
11+
Since the `bcc` library depends heavily on its binary extension
12+
matching the version of the kernel `systemtap-sdt-dev` version, it is
13+
strongly suggested to install the `libpbfcc` dependency via your
14+
package manager and _not_ use a virtualenv to run these tracers.
15+
16+
The tracers also require kernel access, and will most likely have to
17+
be run by `root`
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env python3
2+
3+
"""A simple way to receive spans from an instrumented binary.
4+
5+
Currently only supports a single binary given as the first command
6+
line argument.
7+
"""
8+
9+
import threading
10+
from urllib import request
11+
from bcc import BPF, USDT
12+
import json
13+
import sys
14+
from pathlib import Path
15+
import time
16+
from queue import Queue
17+
18+
if len(sys.argv) != 3:
19+
print(f"Usage: {sys.argv[0]} [zipkin_url] [binary]")
20+
sys.exit(1)
21+
22+
zipkin_url = sys.argv[1]
23+
binaries = [sys.argv[2]]
24+
25+
# Path to the eBPF program
26+
bpf_text = """
27+
#include <uapi/linux/ptrace.h>
28+
29+
int do_trace(struct pt_regs *ctx) {
30+
uint64_t addr;
31+
char path[495];
32+
bpf_usdt_readarg(2, ctx, &addr);
33+
bpf_probe_read(&path, sizeof(path), (void *)addr);
34+
bpf_trace_printk("%s\\n", path);
35+
return 0;
36+
};
37+
"""
38+
39+
40+
def instrument(binary: Path) -> USDT:
41+
"""Given a binary isntrument it with the USDT context."""
42+
path = binary.resolve()
43+
usdt_ctx = USDT(path=str(path))
44+
45+
usdt_ctx.enable_probe(
46+
probe="span_emit",
47+
fn_name="do_trace"
48+
)
49+
return usdt_ctx
50+
51+
52+
def submit_once(queue: Queue):
53+
"""Submit accumulated spans to an otelcol."""
54+
spans = []
55+
while not queue.empty():
56+
span = queue.get_nowait()
57+
spans.append(span)
58+
queue.task_done()
59+
60+
print(f"Submitting a batch of {len(spans)} spans")
61+
req = request.Request(
62+
zipkin_url,
63+
data=json.dumps(spans).encode('ASCII'),
64+
headers={
65+
'Content-Type': 'application/json',
66+
},
67+
method='POST'
68+
)
69+
request.urlopen(req).read()
70+
71+
72+
def submit(queue: Queue):
73+
"""Repeatedly submit batches of spans to otelcol."""
74+
while True:
75+
time.sleep(1)
76+
try:
77+
submit_once(queue)
78+
except Exception as e:
79+
print(f"Error submitting spans to otelcol: {e}")
80+
81+
82+
# An queue to accumulate spans in.
83+
queue = Queue()
84+
85+
86+
# Run the submission to otelcol in the background.
87+
thread = threading.Thread(
88+
target=submit,
89+
args=(queue,),
90+
daemon=True
91+
)
92+
thread.start()
93+
94+
binaries = [Path(i) for i in binaries]
95+
ctxs = [instrument(b) for b in binaries]
96+
b = BPF(
97+
text=bpf_text,
98+
usdt_contexts=ctxs,
99+
debug=0,
100+
)
101+
102+
while True:
103+
try:
104+
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
105+
queue.put(json.loads(msg)[0])
106+
except Exception as e:
107+
print(f"Failed to parse message {msg}: {e}")

contrib/cln-tracer/pyproject.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[tool.poetry]
2+
name = "cln-tracer"
3+
version = "0.1.0"
4+
description = ""
5+
authors = ["Christian Decker <[email protected]>"]
6+
readme = "README.md"
7+
8+
[tool.poetry.dependencies]
9+
python = "^3.8"
10+
bcc = "^0.1.10"
11+
opentelemetry-proto = "^1.21.0"
12+
13+
14+
[build-system]
15+
requires = ["poetry-core"]
16+
build-backend = "poetry.core.masonry.api"

doc/developers-guide/tracing-cln-performance.md

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -53,34 +53,20 @@ usdt:lightningd/lightningd:lightningd:span_start
5353
usdt:lightningd/lightningd:lightningd:span_suspend
5454
```
5555

56-
We want to attach to the `span_emit` probe, as that's the one getting the opentracing-compatible JSON string passed as an argument, and we'd like to extract that.
57-
58-
```bash
59-
$ export BPFTRACE_STRLEN=200
60-
$ sudo -E bpftrace -e 'U:../lightning/lightningd/lightningd:span_emit {printf("%s\n", str(arg1, 10240));}'
61-
62-
Attaching 1 probe...
63-
[{"id": "5006000000000000", "name": "lightningd/jsonrpc", "timestamp": 1690202283751653, "duration": 154,"localEndpoint": { "serviceName": "lightningd"}, "tags": {"method": "sql"}, "traceId": "b7f9b1
64-
[{"id": "5106000000000000", "name": "lightningd/jsonrpc", "timestamp": 1690202283752515, "duration": 146,"localEndpoint": { "serviceName": "lightningd"}, "tags": {"method": "listnodes"}, "traceId": "
65-
[{"id": "8206000000000000", "name": "lightningd/jsonrpc", "timestamp": 1690202283759889, "duration": 657,"localEndpoint": { "serviceName": "lightningd"}, "tags": {"method": "dev-memleak"}, "traceId":
66-
[{"id": "8306000000000000", "name": "lightningd/jsonrpc", "timestamp": 1690202283784095, "duration": 135,"localEndpoint": { "serviceName": "lightningd"}, "tags": {"method": "dev-report-fds"}, "traceI
67-
[{"id": "8406000000000000", "name": "lightningd/jsonrpc", "timestamp": 1690202283785116, "duration": 204,"localEndpoint": { "serviceName": "lightningd"}, "tags": {"method": "stop"}, "traceId": "f6d42
68-
[{"id": "3100000000000000", "name": "lightningd/jsonrpc", "timestamp": 1690202283860017, "duration": 770,"localEndpoint": { "serviceName": "lightningd"}, "tags": {"method": "dev-memleak"}, "traceId":
69-
[{"id": "3200000000000000", "name": "lightningd/jsonrpc", "timestamp": 1690202283992271, "duration": 155,"localEndpoint": { "serviceName": "lightningd"}, "tags": {"method": "dev-report-fds"}, "traceI
70-
[{"id": "3300000000000000", "name": "lightningd/jsonrpc", "timestamp": 1690202283993745, "duration": 370,"localEndpoint": { "serviceName": "lightningd"}, "tags": {"method": "stop"}, "traceId": "92576
71-
[{"id": "5206000000000000", "name": "lightningd/jsonrpc", "timestamp": 1690202284070125, "duration": 911,"localEndpoint": { "serviceName": "lightningd"}, "tags": {"method": "dev-memleak"}, "traceId":
72-
[{"id": "5506000000000000", "name": "lightningd/jsonrpc", "timestamp": 1690202284313462, "duration": 62,"localEndpoint": { "serviceName": "lightningd"}, "tags": {"method": "dev-report-fds"}, "traceId
73-
[{"id": "5606000000000000", "name": "lightningd/jsonrpc", "timestamp": 1690202284314014, "duration": 117,"localEndpoint": { "serviceName": "lightningd"}, "tags": {"method": "stop"}, "traceId": "b7f9b
74-
[{"id": "5406000000000000", "name": "plugin/bitcoind", "timestamp": 1690202284312469, "duration": 4774,"localEndpoint": { "serviceName": "lightningd"}, "parentId": "5306000000000000","tags": {"method
75-
[{"id": "5306000000000000", "name": "extend_tip", "timestamp": 1690202284312428, "duration": 4902,"localEndpoint": { "serviceName": "lightningd"}, "tags": {}, "traceId": "b7f9b1e8af12d252"}]
76-
77-
```
78-
56+
There is a sample exporter that can be used to instrument a single
57+
binary, batch the spans it receives and submit them as a batch to an
58+
`otelcol` or `tempo` instance in [contrib/cln-tracer][cln-tracer]
59+
using the zipkin format for spans and traces.
7960

61+
[cln-tracer]: https://github.com/ElementsProject/lightning/tree/master/contrib/cln-tracer
8062

8163

64+
Notice that due to a [limitation][bpftracer305] in the way the eBPF
65+
script is handled you'll at most get the first 495 bytes of the
66+
payload. This is due to the 512 byte limitation for eBPF programs out
67+
of the box.
8268

83-
Notice that due to a [limitation](https://github.com/iovisor/bpftrace/issues/305) in `bpftrace` you'll at most get the first 200 bytes of the payload. If you write your own exporter you'll be able to specify the size of the buffer that is being used, and can extract the entire span.
69+
[bpftracer]: https://github.com/iovisor/bpftrace/issues/305
8470

8571
## Tracing Overhead
8672

0 commit comments

Comments
 (0)