Skip to content
This repository was archived by the owner on Oct 15, 2026. It is now read-only.
DefinetlyNotAIPublic archive

About

Local runtime tracing, profiling, and debugging for Python, JavaScript/TypeScript, and C with live execution visualization and performance analysis.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

26 Commits

Folders and files

Repository files navigation

EventHorizon

See what your application is doing.

Local runtime tracing, performance analysis, and debugging for
Python · JavaScript / TypeScript · Instrumented C


Follow execution from a request to the functions, files, and database calls behind it. Explore a live call tree, inspect exceptions in context, find expensive work, and return to a recording later. Everything stays on your machine in SQLite.

Start here

Requires Python 3.11+. Node tracing also requires Node.js 22.15+ and npm. C tracing requires a GCC-compatible compiler and binutils; on Windows, use MSYS2 UCRT64 GCC.

./scripts/setup.ps1
./scripts/run.ps1 trace python examples/python/workload.py

Or install directly from this checkout:

python -m venv .venv
# Activate .venv using your shell, then:
python -m pip install -e .
npm ci
eventhorizon trace python examples/python/workload.py

The terminal explorer opens in an interactive terminal. Redirected output automatically uses a readable summary. Add --headless to record without the interface.

Example scenarios

Examples are grouped under examples/python, examples/js, and examples/c. The workload examples finish normally; the error examples deliberately fail.

Scenario Python JavaScript / TypeScript C
Finishes normally python/workload.py js/workload.cjs, js/workload.ts c/workload.c
Fails after successful work python/runtime_error.py js/runtime_error.cjs c/crash.c
Invalid syntax; no application code runs python/syntax_error.py js/syntax_error.cjs c/syntax_error.c (build fails)
Immediate runtime failure — — c/startup_error.c
Runs until interrupted python/server.py js/server.cjs c/long_running.c

Run the following commands from the repository root, one scenario at a time. Close the explorer with q before starting the next scenario.

Python

./scripts/run.ps1 trace python examples/python/workload.py
# Optional workload arguments:
./scripts/run.ps1 trace python examples/python/workload.py --iterations 12 --delay 0.05

# Deliberate failures: after successful work, then during parsing.
./scripts/run.ps1 trace python examples/python/runtime_error.py
./scripts/run.ps1 trace python examples/python/syntax_error.py

./scripts/run.ps1 trace python examples/python/server.py

While the Python server is running, use another PowerShell terminal:

Invoke-RestMethod http://127.0.0.1:8765/health
Invoke-RestMethod http://127.0.0.1:8765/work
Invoke-WebRequest http://127.0.0.1:8765/error  # Deliberate HTTP 500; server continues.
Invoke-RestMethod http://127.0.0.1:8765/health

JavaScript / TypeScript

./scripts/run.ps1 trace node examples/js/workload.cjs
./scripts/run.ps1 trace node examples/js/workload.ts

# Deliberate failures: after successful work, then during parsing.
./scripts/run.ps1 trace node examples/js/runtime_error.cjs
./scripts/run.ps1 trace node examples/js/syntax_error.cjs

./scripts/run.ps1 trace node examples/js/server.cjs

While the JavaScript server is running, use another PowerShell terminal:

Invoke-RestMethod http://127.0.0.1:8766/health
Invoke-RestMethod http://127.0.0.1:8766/work
Invoke-WebRequest http://127.0.0.1:8766/error  # Deliberate HTTP 500; server continues.
Invoke-RestMethod http://127.0.0.1:8766/health

C

Build each example before tracing its executable:

./scripts/run.ps1 build-c examples/c/workload.c -o .eventhorizon/workload.exe
./scripts/run.ps1 trace native .eventhorizon/workload.exe

# Fatal signal after successful work; unfinished calls are expected.
./scripts/run.ps1 build-c examples/c/crash.c -o .eventhorizon/crash.exe
./scripts/run.ps1 trace native .eventhorizon/crash.exe

# Immediate nonzero exit at runtime.
./scripts/run.ps1 build-c examples/c/startup_error.c -o .eventhorizon/startup-error.exe
./scripts/run.ps1 trace native .eventhorizon/startup-error.exe

# Expected compiler failure; there is no executable to trace.
./scripts/run.ps1 build-c examples/c/syntax_error.c -o .eventhorizon/syntax-error.exe

# Continuous worker; stop with q in the explorer.
./scripts/run.ps1 build-c examples/c/long_running.c -o .eventhorizon/worker.exe
./scripts/run.ps1 trace native .eventhorizon/worker.exe

In the TUI, Ctrl+X or Stop ends a launched server/worker and keeps its recording open for inspection; the target cannot be resumed. q stops the target and quits the explorer. In headless mode, use Ctrl+C. The C crash example deliberately raises a fatal signal; unfinished calls are expected. C syntax errors occur during compilation, so startup_error.c separately demonstrates failure at runtime before useful work.

Record, replay, and export an example

./scripts/run.ps1 trace --headless python examples/python/workload.py
./scripts/run.ps1 sessions
./scripts/run.ps1 show latest
./scripts/run.ps1 show latest --tree
./scripts/run.ps1 show latest --replay 2
./scripts/run.ps1 export latest --format json -o .eventhorizon/example-trace.json
./scripts/run.ps1 export latest --format folder -o .eventhorizon/example-trace-items

latest selects the most recently started session. The JSON command writes one complete blob; the folder command writes indexed JSON files per item and requires a new or empty destination. Add --headless before python, node, or native in any trace command above to record without opening the explorer.

Trace an application

eventhorizon trace python app.py --your-app-argument
eventhorizon trace node server.js
eventhorizon trace node server.mjs
eventhorizon trace node examples/js/workload.ts

eventhorizon build-c examples/c/workload.c -o .eventhorizon/workload.exe
eventhorizon trace native .eventhorizon/workload.exe

Use an executable path appropriate for your platform; .exe is only needed on Windows. C sources link against the bundled helper automatically. Add compiler flags with --cflag=-pthread, for example. Existing native builds can compile their own sources with -g -finstrument-functions, compile eventhorizon/backends/native.c separately with -fno-instrument-functions, and link both. Windows needs -lws2_32; Unix needs -pthread and a non-PIE executable for address resolution.

Tracing defaults to files under the current directory. Set --root when launching from elsewhere. Collector options go before the runtime; arguments after the script belong to your application.

eventhorizon trace --root ./src --exclude '*cache*' python src/app.py
eventhorizon trace --mode filtered --include '*process_order*' python examples/python/workload.py
eventhorizon trace --libraries --depth 32 --sample 5 node server.js
eventhorizon trace --mode sample python examples/python/workload.py
eventhorizon trace --mode sample node examples/js/workload.ts

--include and --exclude accept repeatable glob fragments matching file, module, or function names. --sample N records every Nth eligible call; it is call thinning, not a CPU profiler. --mode sample instead collects nominal 10 ms statistical stack samples: Python samples wall stacks, while Node uses the V8 profiler. Samples are estimates, not exact call counts. Native hooks support call thinning and depth limits; native name filters apply at collection after symbol resolution.

Explore the recording

View What it shows
Calls Expandable execution order, durations, and observed exceptions
Hotspots Call counts, inclusive/self time, percentiles, CPU where available
Samples Sampled locations and estimated time in statistical mode
Timeline Calls placed on a common time axis, with thread and task context
Requests Inbound/outbound HTTP and custom request spans; Enter opens the call branch
Events / Errors Correlated I/O, logs, exceptions, stacks, and source locations
Output Last 500 chunks of the launched process's stdout/stderr; live only, never saved or exported
Bookmarks Saved developer annotations attached to calls or the session
Compare Mean latency and call-count changes against another session
Diagnostics Capabilities, collection loss, incomplete calls, and process metrics
Key Action
Arrow keys / Enter Navigate and expand calls; inspect a table row
/ Search
Space Pause the display; collection continues
Ctrl+X / click Stop End the launched target; keep the explorer open
b Annotate the selected call; Enter saves
e / click Export Choose an export format and destination in a popup
r Toggle replay of a completed recording
[ / ] Newer / older history page
Escape Clear search, thread/task selection, and request scope
d / q Toggle details / quit

Drag to select text in calls, tables, output, or details; Ctrl+C or right-click copies the selection. The view holds steady while text is selected; click to clear the selection and resume live display updates. Collection continues throughout. The details pane has both scrollbars and does not wrap long lines. Terminals that reserve right-click for their own menu may require Shift+right-click or Ctrl+C. Theme choices made through the command palette (Ctrl+P → theme) are saved in your per-user EventHorizon configuration and restored next launch.

Live updates preserve scroll positions, expanded calls, selections, and detail context. Output follows new lines while you are at the bottom; scrolling up keeps your place. Stopped targets are saved with a stopped session status. For cooperative listeners, Stop ends collection; the independently launched application keeps running.

If a program fails before recording any calls, empty tabs are hidden and the explorer opens Errors or Output when available. Populated events, samples, bookmarks, and diagnostics remain accessible. Failures after successful calls keep the normal views, and an empty search does not hide tabs.

JSON in output, details, and diagnostics is colored. Ctrl+click or double-click a source file path in details or a Source column to open it for editing; its visual appearance stays unchanged. On Windows, the configured Edit application is used, with Notepad as a fallback. Requests clicks inspect a row; Enter opens its call branch. Leaving Calls clears that request scope.

Search plain text or combine name:, file:, thread:, task:, status:, category:, trace:, and min: (milliseconds). Events also accept kind: and level:. For example, category:http min:20 finds slow HTTP calls; kind:exception filters the event stream. Use the thread/task selector to isolate concurrent work.

The live interface reads 1,000 calls and 1,000 events per page. Hotspots and distributions describe that visible window; the window is labeled in the header. CLI summaries, comparisons, and exports use the full retained recording. A parent outside the page or tracing filter appears as a separate visible root. Collection detail, depth, and sampling are selected when launching; view filters can change during recording.

Save, compare, export

Sessions are recorded automatically in .eventhorizon/traces.sqlite relative to your working directory. Supply --db PATH before the command to use another database.

eventhorizon sessions
eventhorizon show latest
eventhorizon show latest --tree
eventhorizon show latest --replay 2
eventhorizon compare BEFORE_ID AFTER_ID
eventhorizon note SESSION_ID "Investigate the slow inventory request"
eventhorizon note SESSION_ID "Retry path" --span SPAN_ID

eventhorizon export latest --format json -o trace.json
eventhorizon export latest --format folder -o trace-items
eventhorizon export latest --format jsonl -o events.jsonl
eventhorizon export latest --format chrome -o timeline.json
eventhorizon export latest --format text -o report.txt

Session IDs may be unambiguous prefixes. JSON includes session metadata, spans, raw normalized events, and annotations. JSONL streams raw events. Chrome trace JSON opens in compatible trace viewers such as Perfetto. Replay reads the saved SQLite recording; exported JSON is portable evidence, not an import format.

In the explorer, click Export in the footer or press e, choose a format, and edit the prefilled destination before confirming. Changing formats updates the suggested filename unless you have entered a custom path. Escape cancels; export errors stay in the popup so you can correct the destination.

--format json creates one complete JSON blob. --format folder requires a new or empty folder and creates session.json, one JSON file per span/event/note under spans/, events/, and notes/, plus index.json linking the items. The index is written last so an unfinished export does not appear complete.

Comparison flags a potential regression when mean observed latency rises by at least 20% and 1 ms; change the percentage with --threshold. It matches function name, source path, line, and category. Code moves can appear as added/removed functions. Results are observations, not statistical proof of a regression; use comparable workloads and tracing settings.

Instrumentation coverage

Backend Automatic visibility Explicit limits
Python Python calls/returns, exceptions including handled exceptions, asyncio tasks, new threads, file opens, sqlite3, http.client, http.server, logging Native extension internals, arbitrary database/HTTP clients, and threads already running before cooperative connection need their own integration. File payload reads/writes are not copied.
Node.js / TypeScript Loaded CJS/ESM/TypeScript source functions, Promise results, async context, thrown exceptions, http/https, sync and Promise file operations, console Constructors, generators, eval, native internals, browser JS, native fetch, and database clients need explicit instrumentation. Async return tracing changes Promise identity and attaches rejection observers. TypeScript is stripped, not type-checked; JSX requires a precompiled application.
C Functions compiled with GCC hooks, per-thread call stacks, named manual spans, live symbols and source locations Uninstrumented libraries, arbitrary attachment, arguments/return values, C++ exception unwinding, longjmp, and asynchronous correlation are not automatic. Use unstripped debug symbols. ELF PIE/shared-library symbolization is outside the native launch backend.

Node requires the npm dependencies installed in this source checkout. Source instrumentation executes applications; it is not a sandbox. Python and Node dependencies are included only with --libraries or an explicit include where supported. The profiler's own implementation is excluded. Handled exceptions remain available in the exception explorer; calls are marked failed when an exception escapes them.

Timing: durations use monotonic clocks anchored to wall time in each process. Self wall time subtracts the union of observed direct child intervals, so overlapping work is never subtracted twice. Inclusive totals can exceed session elapsed time through concurrency and recursion. CPU counters remain separate; Python thread CPU resolution depends on the OS, and Node/C function CPU time is unavailable. Process RSS, CPU utilization, and cumulative CPU time are sampled separately. Cross-process ordering is approximate; clock anchors and an estimated 1 ms uncertainty are recorded. HTTP durations end at response headers for clients and request handling/response completion for supported servers, not after consuming an entire response body.

Connect cooperatively

To trace a process you control without relaunching it, start a listener:

eventhorizon listen

It prints a local address, token, and configuration. In the running Python application, connect deliberately and wrap operations as needed:

from eventhorizon.sdk import connect, span, annotate

connection = connect("127.0.0.1:PORT", "TOKEN", root="/path/to/project")
with span("checkout", category="request", order_id=42):
    # Your application code
    annotate("Inspect the retry decision")
connection.close()  # Stop tracing and flush pending events when finished.

Automatic tracing starts in the connecting thread and threads subsequently created. It cannot reconstruct earlier execution. In Node, applications launched with the collector's EH_ADDRESS, EH_TOKEN, and EH_CONFIG environment variables can preload eventhorizon/backends/node.cjs plus --import eventhorizon/backends/register.mjs. A running Node process can require the collector after setting those variables and use globalThis[Symbol.for('eventhorizon')].span(name, callback) for cooperative spans. Existing loaded code is not retroactively rewritten. C hooks use the same launch environment. HTTP integrations propagate W3C traceparent IDs between supported components. There is no arbitrary PID injection.

Privacy and resource limits

Argument values, returns, launch arguments after the entry point, and free-form log/error messages are off by default. Enable them deliberately with --values. Sensitive named fields and command switches such as passwords, tokens, cookies, credentials, and authorization are masked; HTTP query strings, fragments, headers, bodies, and SQL parameters are not recorded. Free-form strings can still contain secrets when value capture is enabled. Paths, function names, source locations, developer annotations, and your application's ordinary terminal output may also contain sensitive information.

Collection uses authenticated, loopback-only UDP and bounded queues, keeping the interface off the target's execution path. UDP is best-effort: sequence gaps, producer drops, queue drops, retention drops, malformed events, and unfinished spans are reported rather than silently treated as complete execution. Abrupt exits can lose final packets and summaries. Reported producer overhead measures transport/enqueue work, not the full instrumentation cost. Detailed tracing can substantially slow an application; use filters or statistical mode for lower overhead.

Defaults: 8,192 queued events · 500,000 stored events per session · 30 retained sessions. Change them with --buffer, --max-events, and --max-sessions. Old completed sessions are removed when a new recording starts; active sessions are retained. SQLite reuses freed pages. Quit stops processes launched by this recording; cooperative listener shutdown leaves independently owned applications running.

Development and releases

./scripts/diagnose.ps1
./scripts/release.ps1

The release command archives the current committed source into dist/; it does not publish or push. Run setup after extracting it. The bundled workloads exercise concurrent requests, file operations, database calls, recoverable exceptions, and native recursion. They are runnable examples, not a test suite.

About

Local runtime tracing, profiling, and debugging for Python, JavaScript/TypeScript, and C with live execution visualization and performance analysis.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages