Skip to content
MacrofoldDocs
Deploy and operate

Performance profiling

Capture bounded, content-free profiles of API and worker execution with an optional startup flag.

Temporarily measure ordinary API and worker execution with content-free process profiles. Profiling is off by default and independent of Langfuse execution tracing. Required deadlines, billing and public receipt timings remain active in every mode.

Capture a slow operation

  1. Set PERF_PROFILING_ENABLED=true in the environment of the API or worker you want to inspect.
  2. Restart that process through its existing supervisor or redeploy it. Reproduce the operation using its normal API and credentials.
  3. Collect JSONL records with component: "performance" from the process output. Collection stops automatically after five minutes. Each process has its own capture and instance IDs.
  4. Set the flag back to false and restart to restore the normal configuration. Restart with it enabled when another capture is needed.

For a local API started directly from source:

sh
PERF_PROFILING_ENABLED=true pnpm dev

For a busy worker, collect one percent of whole operations for ten minutes:

sh
PERF_PROFILING_ENABLED=true PERF_PROFILING_SAMPLE_RATE=0.01 \
  PERF_PROFILING_DURATION_SECONDS=600 pnpm worker

Use the existing process and port; do not start a competing preview or worker. Changing environment values does not alter an already running process. There is no customer request flag or live administrator toggle.

The capture window begins when its serving composition initializes: during self-hosted API readiness, at the first transport/step entry when no shared readiness ran, or at standalone worker startup. Repeated readiness probes and requests reuse that collector; expiry does not start another window. Next instrumentation does not create a second collector, because its separately compiled module graph could wrap the same database pools twice and interfere with cleanup.

Settings and output

SettingDefaultAccepted values
PERF_PROFILING_ENABLEDfalseLiteral true or false
PERF_PROFILING_SAMPLE_RATE1Number from 0 through 1; the whole operation is selected together
PERF_PROFILING_DURATION_SECONDS300Integer from 1 through 3600
PERF_PROFILING_OUTPUT_DIRUnset; stdoutOptional private local directory; unsupported on hosted processes

Invalid optional settings emit performance_profile_configuration_invalid and leave profiling disabled. They do not prevent the application from serving requests.

For private local files:

sh
PERF_PROFILING_ENABLED=true PERF_PROFILING_OUTPUT_DIR=.data/performance pnpm dev

The directory must be private and free of symlink components. Newly created directories/files use modes 0700/0600 and unique file names. Hosted captures should use the deployment's existing log collection; an ephemeral filesystem is not durable storage.

Profiles contain fixed operation/stage names, generated request/Run correlation IDs, timings and numeric counters. They exclude prompts, answers, tool arguments, file paths, SQL text/parameters, raw errors and credentials. Restrict access to logs separately from customer API access.

Read the measurements

  • Operation records distinguish foreground request handling, retained stream producers, jobs and optional background work. Background records link to their initiating operation without adding their SQL to its foreground count.
  • http.total starts inside the API handler, after the framework loads the route. It excludes framework compilation/module evaluation and socket delivery. http.parameter_validation includes first-use header/path schema compilation; http.body_validation measures body validation; http.response aggregates response projection and JSON construction. These stages are contained in http.total, not additional latency to add to it.
  • trace.initialize measures synchronous first-use tracing-backend construction. trace.record measures synchronous enqueue/serialization and application logging, separately from initialization. SDK module loading before route entry and asynchronous network export are outside these stages.
  • Database counters distinguish submitted statements, ordered wait groups, actual transaction outcomes and pool checkout. Group/transaction examples are bounded; aggregate counts continue after their example budget is exhausted.
  • Query and checkout durations include queueing, network and local scheduling. They do not isolate PostgreSQL lock wait or CPU. Unknown parameter sizes remain unknown; profiling never serializes objects just to count them.
  • A provider span measures the adapter/transport interval. Provider computation time is normally unknown. Subtracting that entire interval does not establish pure Macrofold overhead; the synthetic performance fixture separately measures its upstream wait.
  • Stream response readiness and retained producer completion are separate lifetimes. Returning response headers is not completion of a streamed answer.
  • Capture summaries expose sampling, unattributed statement/checkout totals and drops. Missing profiles are not evidence of zero traffic. Reports may be incomplete after overload, expiry, a slow sink or process death.

Collection admits at most 256 active scopes, eight simultaneous stage spans per scope and bounded group examples. Output admits at most 256 records and 1 MiB including writes in flight, with an 8-KiB record ceiling and one writer. Expiry detaches instrumentation and drains for at most two seconds. Overflow loses diagnostics rather than delaying or rejecting customer work. Stdout itself may block depending on its destination; use the asynchronous private-file sink for a slow local terminal.

Separate startup, HTTP and application latency

Keep the first request in your report. Measure the caller's POST through full body decoding, correlate its X-Request-Id with the API profile, then measure any caller-side journaling, retrieval or result processing separately. A terminal inference already carries usage/billing and does not require another Run or billing request. Use result.inference.timings_ms.provider for the adapter interval; Run completed_at - started_at also contains platform work and is not provider latency.

next dev compiles routes on demand and can spend seconds before entering the handler, including after its initial readiness message. Compare a fresh process's first call and subsequent calls in both development and a compiled local server. Do not remove the cold sample or report handler-only timing as full HTTP timing. A warm synthetic p95 below 100 ms is a qualification for that workload and topology, not a cold-start or per-request guarantee.

Next’s startup “Ready” log is not an HTTP readiness probe. Self-hosted /health checks the database/configuration, awaits the shared REST handler dependencies and initializes trusted contract validators before returning success; probe each serving instance before routing traffic to it. A self-hosted rewrite routes health through the same compiled REST wrapper and one shared health-response owner, including its Next/background dependencies without registering work. Matched route parameters distinguish the operational path; health bypasses API authentication, trace flushing and dispatch. It also initializes the configured trace sink without recording/exporting a span; disabled tracing remains a no-op and configuration failures retain best-effort handling. This pays reusable compilation/setup costs during startup. Caller-supplied schemas still undergo bounded validation on each request and use the existing compilation cache. Vercel skips that eager initialization because its route functions need not share a process. Measure process start through readiness separately, then keep the first inference in the request report: new transport connections, caller schemas and runtime code can still initialize on first use. Any non-2xx health result fails readiness, including framework module-load failures outside the health handler. Never send a hidden model warmup or discard that first inference to meet a budget.

Synthetic upstream fixtures must keep their own observation work outside the timed response path. Buffer bounded timing records and drain them after measurement; a synchronous file write between the upstream timer stopping and the response returning falsely increases measured platform overhead. Report observer changes and retain earlier results with their limitations.

When the framework-to-handler gap is large, profile framework startup and module evaluation in an isolated source/build and port. The ordinary profiler intentionally does not install a global framework tracer or CPU inspector. SDKs used only for connectors or remote object storage load at their provider port's first actual use, and the Workflow dispatcher loads its optional runtime only for a matching deployment; unrelated inline inferences do not pay that initialization cost. Preserve authorization, financial settlement, validation and default tracing when comparing changes.

Other diagnostic choices

For native scheduling, include admission commit, candidate/claim queries and phase publication in the measured interval. phaseTimings.activeMs excludes the initial claim and final transaction; it cannot establish scheduling overhead by itself. Poller admission, candidates, claim and advance jobs use the same optional collector. The native qualification compares an actual API/separate-poller path and full cross-pool boundaries, with small and near-limit prompts, rather than treating provider work as total latency.

Full execution tracing remains the default when configured. TRACING_CAPTURE_CONTENT=false keeps execution timings, IDs, usage and costs while avoiding optional content capture. TRACING_ENABLED=false disables that integration. Both are independent of performance profiling.

REQUEST_ANALYTICS_ENABLED=false disables optional retained HTTP request observations. API_PRODUCT_EVENTS_ENABLED=false disables successful API-operation observations. Both default to true. These settings preserve billing, authorization/rate accounting, administrative audits, actor activity and durable domain milestones. Request reports describe observed activity and cannot establish exact total traffic, especially after collection is disabled.

Restart all affected processes when changing diagnostic policy. Customer input cannot enable profiling or change these settings.