Skip to content

Commit 46567a4

Browse files
committed
contextvars bpftrace examples
1 parent 62da90e commit 46567a4

11 files changed

Lines changed: 261 additions & 0 deletions

File tree

uprobe-contextvars/.python-version

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

uprobe-contextvars/README.md

Whitespace-only changes.

uprobe-contextvars/contextvars.bt

Whitespace-only changes.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Docker compose file for bpftrace and some python webapps
2+
3+
services:
4+
bpftrace:
5+
build:
6+
context: .
7+
dockerfile: Dockerfile.bpftrace
8+
privileged: true
9+
pid: "host"
10+
network_mode: "host"
11+
volumes:
12+
- ./bpftrace:/app/bpftrace
13+
working_dir: /app/bpftrace
14+
entrypoint: ["/bin/bash", "-c", "tail -f /dev/null"] # Keep container running
15+
16+
flask_app:
17+
build:
18+
context: .
19+
dockerfile: Dockerfile.flask
20+
network_mode: "host"
21+
environment:
22+
- FLASK_APP=app.py
23+
- FLASK_RUN_HOST=0.0.0.0
24+
- FLASK_RUN_PORT=5000
25+
volumes:
26+
- ./flask_app:/app/flask_app
27+
working_dir: /app/flask_app
28+
command: ["flask", "run"]
29+
30+
django_app:
31+
build:
32+
context: .
33+
dockerfile: Dockerfile.django
34+
network_mode: "host"
35+
environment:
36+
- PYTHONUNBUFFERED=1
37+
volumes:
38+
- ./django_app:/app/django_app
39+
working_dir: /app/django_app
40+
command: ["python", "manage.py", "runserver", "0.0.0.0:8000"]

uprobe-contextvars/fastapi_main.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from typing import Union
2+
3+
from fastapi import FastAPI
4+
5+
app = FastAPI()
6+
7+
8+
@app.get("/")
9+
def read_root():
10+
return {"Hello": "World"}
11+
12+
13+
@app.get("/items/{item_id}")
14+
def read_item(item_id: int, q: Union[str, None] = None):
15+
return {"item_id": item_id, "q": q}

uprobe-contextvars/main.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import asyncio
2+
import urllib.request
3+
import urllib.error
4+
import sys
5+
6+
7+
async def fetch_url(url):
8+
"""Fetches a URL using urllib.request in a non-blocking way."""
9+
try:
10+
# urlopen is blocking, so run it in a thread pool executor
11+
response_obj = await asyncio.to_thread(urllib.request.urlopen, url)
12+
with response_obj as response:
13+
return response.read().decode(
14+
"utf-8", errors="ignore"
15+
) # Use errors='ignore' for robust decoding
16+
except urllib.error.URLError as e:
17+
return f"Error fetching {url}: {e.reason}"
18+
except Exception as e:
19+
return f"An unexpected error occurred while fetching {url}: {e}"
20+
21+
22+
async def handle_client(reader, writer):
23+
addr = writer.get_extra_info("peername")
24+
print(f"Connection from {addr}")
25+
26+
try:
27+
request_data = await reader.read(
28+
4096
29+
) # Read more data for potentially larger requests
30+
if not request_data:
31+
print(f"Client {addr} disconnected before sending data.")
32+
writer.close()
33+
await writer.wait_closed()
34+
return
35+
36+
request_line = request_data.decode().splitlines()[0]
37+
print(f"Received request: {request_line}")
38+
39+
response_body = ""
40+
status_code = 200
41+
status_message = "OK"
42+
content_type = "text/plain"
43+
44+
# For simplicity, we'll just check if it's a GET request
45+
if request_line.startswith("GET"):
46+
github_content = await fetch_url("https://github.com")
47+
48+
# Truncate for brevity, as github.com's homepage can be very large
49+
display_content = github_content
50+
if len(github_content) > 1000:
51+
display_content = github_content[:1000] + "\n\n... (content truncated)"
52+
53+
response_body = f"Hello from the Asyncio Server!\n\nFetched content from github.com:\n\n{display_content}"
54+
else:
55+
status_code = 405
56+
status_message = "Method Not Allowed"
57+
response_body = "Only GET requests are supported."
58+
59+
response = (
60+
f"HTTP/1.1 {status_code} {status_message}\r\n"
61+
f"Content-Type: {content_type}\r\n"
62+
f"Content-Length: {len(response_body.encode('utf-8'))}\r\n" # Encode to get byte length
63+
f"\r\n"
64+
f"{response_body}"
65+
)
66+
writer.write(response.encode("utf-8"))
67+
await writer.drain()
68+
69+
except Exception as e:
70+
print(f"Error handling client {addr}: {e}", file=sys.stderr)
71+
# Attempt to send an error response if possible
72+
error_body = f"Internal Server Error: {e}"
73+
error_response = (
74+
f"HTTP/1.1 500 Internal Server Error\r\n"
75+
f"Content-Type: text/plain\r\n"
76+
f"Content-Length: {len(error_body.encode('utf-8'))}\r\n"
77+
f"\r\n"
78+
f"{error_body}"
79+
)
80+
writer.write(error_response.encode("utf-8"))
81+
await writer.drain()
82+
finally:
83+
print(f"Closing connection for {addr}")
84+
writer.close()
85+
await writer.wait_closed()
86+
87+
88+
async def main():
89+
host = "127.0.0.1"
90+
port = 8080
91+
92+
server = await asyncio.start_server(handle_client, host, port)
93+
94+
print(f"Serving on {host}:{port}")
95+
96+
async with server:
97+
await server.serve_forever()
98+
99+
100+
if __name__ == "__main__":
101+
try:
102+
asyncio.run(main())
103+
except KeyboardInterrupt:
104+
print("Server stopped by user.")
105+
except Exception as e:
106+
print(f"An unexpected error occurred in main: {e}", file=sys.stderr)
107+
sys.exit(1)

uprobe-contextvars/plain.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import contextvars
2+
3+
foovar = contextvars.ContextVar("foovar")
4+
root = contextvars.copy_context()
5+
6+
c1 = root.copy()
7+
c2 = root.copy()
8+
9+
c1.run(foovar.set, "c1")
10+
c2.run(foovar.set, "c2")
11+
12+
print(f"{c1=} {c1.get(foovar, None)=}")
13+
print(f"{c2=} {c2.get(foovar, None)=}")
14+
15+
16+
def a():
17+
foovar.set("foo")
18+
print(foovar.get())
19+
20+
21+
def b():
22+
print(foovar.get(None))
23+
24+
25+
c1.run(b)
26+
c2.run(b)

uprobe-contextvars/print_calls.bt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
uretprobe:/usr/lib/libpython3.13.so:PyContext_CopyCurrent {
3+
printf("%s retval = %#x\n", func, retval);
4+
}
5+
6+
uretprobe:/usr/lib/libpython3.13.so:PyContext_Copy {
7+
printf("%s retval = %#x\n", func, retval);
8+
}
9+
*/
10+
11+
uprobe:/usr/lib/libpython3.13.so:PyContextVar_Get {
12+
printf("%s %s\n", func, args);
13+
}
14+
15+
uprobe:/usr/lib/libpython3.13.so:PyContextVar_Set {
16+
printf("%s %s\n", func, args);
17+
}
18+
19+
uprobe:/usr/lib/libpython3.13.so:PyContext_Enter {
20+
printf("%s %s\n", func, args);
21+
}
22+
23+
uprobe:/usr/lib/libpython3.13.so:PyContext_Exit {
24+
printf("%s %s\n", func, args);
25+
}
26+
27+
uprobe:/usr/lib/libpython3.13.so:_PyContext_Enter {
28+
printf("%s %s\n", func, args);
29+
}
30+
31+
uprobe:/usr/lib/libpython3.13.so:_PyContext_Exit {
32+
printf("%s %s\n", func, args);
33+
}

uprobe-contextvars/pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[project]
2+
name = "uprobe-contextvars"
3+
version = "0.1.0"
4+
description = "Add your description here"
5+
readme = "README.md"
6+
requires-python = ">=3.13"
7+
dependencies = []

uprobe-contextvars/sample.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import asyncio
2+
import contextvars
3+
4+
5+
foovar = contextvars.ContextVar("foovar")
6+
7+
8+
async def print_foo():
9+
foovar.set("foo")
10+
await asyncio.sleep(0.1)
11+
print(foovar.get())
12+
13+
14+
async def main():
15+
async with asyncio.TaskGroup() as tg:
16+
foovar.set("bar")
17+
tg.create_task(print_foo())
18+
await asyncio.sleep(0.01)
19+
print(foovar.get())
20+
print()
21+
22+
23+
if __name__ == "__main__":
24+
loop = asyncio.run(main(), debug=True)

0 commit comments

Comments
 (0)