Continuous profiling in 2026: revisit Grafana Pyroscope, Parca and the OpenTelemetry eBPF profiler before you store another flame graph
Picture a GST invoicing SaaS in Pune with about 1,800 business customers (an invented example, not a real client). Its platform team of six runs about 40 services, mostly Go with a few Java and Python ones, on a 12-node Kubernetes cluster. Prometheus and Grafana are in place, traces go to a tracing backend, and logs are under control. Two things keep coming back. Every month-end, when customers file returns, invoice generation slows down and nobody can say which function is eating the CPU. And the cloud bill for the cluster has grown faster than the customer count. Someone took a pprof profile by hand during the last incident, but by the time it was captured the spike had passed. The team has agreed to run a continuous profiler. One engineer wants Grafana Pyroscope because the team already lives in Grafana. A second wants Parca because its eBPF agent needs no code changes and everything is Apache 2.0. A third says both are the wrong first question: the OpenTelemetry eBPF profiler is the new standard collector, so pick that and send OTLP profiles to whatever backend wins.
Continuous profiling means sampling stack traces from production processes all the time, at low frequency, and storing them so you can ask later “which code used the CPU (or memory) between 10:05 and 10:20, and how is that different from last week?” This post compares the three open-source options the Pune team is looking at: Grafana Pyroscope (a profiling database with SDKs and an agent in Grafana Alloy), Parca (a server plus the eBPF-based Parca Agent) and the OpenTelemetry eBPF profiler (a vendor-neutral collection agent that ships as an OpenTelemetry Collector distribution). They are not three equal products, and that is the main point: two are backends with their own collectors, and the third is a collector that both of them now build on. I cover history, collection methods, language coverage, storage and retention, querying, the OTLP profiles signal, Kubernetes, overhead claims, licences, telemetry defaults and security. Facts come from release notes, official documentation, licence files and CVE records, checked on 13 October 2026. The drill near the end is my own run on this Linux box, and it could not run the eBPF agents, which I explain.
The short version
- Decide first what a profile must answer for you (month-end CPU spikes, slow memory growth, cost per service, regressions between releases), then choose how to collect it. The collection method matters more than the backend.
- Grafana Pyroscope 2.4.0 (10 October 2026, 1:16 AM IST) is the current release. Since 2.0.0 (20 April 2026) its new v2 storage writes straight to object storage, with a Raft-based metastore and background compaction. The server is AGPL-3.0; the Go SDK and Grafana Alloy are Apache 2.0.
- Parca 0.29.1 (28 September 2026, 7:23 PM IST) and Parca Agent 0.50.0 (22 September 2026, 4:42 AM IST) are the current releases, both Apache 2.0. Parca can pull
pprofendpoints itself or receive profiles from its eBPF agent, which is built on a fork of the OpenTelemetry eBPF profiler and can now export OTLP profiles. - The OpenTelemetry eBPF profiler is a whole-host, no-instrumentation profiler for Linux, donated by Elastic in 2024. It ships as the
otelcol-ebpf-profilerCollector distribution (0.162.0, 29 September 2026, 6:04 PM IST). It is not a backend. The OpenTelemetry Profiles signal entered public alpha on 26 March 2026, and the project itself says it should not be used for critical production workloads yet. - The agent layer is converging. Grafana Alloy’s
pyroscope.ebpfembeds a Grafana fork of the OpenTelemetry profiler, and Parca Agent embeds a parca-dev fork. The real choice is between two backends and between SDK, pull and eBPF collection. - Security in 2025 to 2026 is light but not empty: CVE-2025-41118 (Pyroscope 1.x exposed a Tencent COS secret key through its API, CNA score 9.1) and CVE-2026-48496 (a local denial of service in the OpenTelemetry eBPF profiler). I found no CVE or GitHub advisory for Parca or Parca Agent. All three eBPF agents need root or equivalent capabilities, which is a bigger risk than either CVE.
- In my drill (no root on this VM), Pyroscope and Parca both found the same two hot functions in a small Go service, and each cost the service about 3 percent throughput in a rough test. Both eBPF agents refused to start without privileges, and running the Pyroscope SDK stopped Parca from collecting CPU profiles from the same Go process.
Where things stand on 13 October 2026
Dates are from each project’s GitHub release page unless marked otherwise; times are converted from UTC to IST. The OpenTelemetry eBPF profiler publishes Git tags but no GitHub releases, so its row shows the date of the commit the tag points to. Licences are from the LICENSE files at the tagged release.
Project
Release
Date (IST)
Notes
Licence
Grafana Pyroscope
2.4.0
10 October 2026, 1:16 AM
Current release; experimental deterministic profile IDs, distributor in-flight bytes limit, Go 1.26.9 and golang.org/x/net security updates
AGPL-3.0
Grafana Pyroscope
2.3.2 and 2.2.3
8 October 2026, 3:07 AM and 2:23 AM
Security dependency updates on the two previous minor lines
AGPL-3.0
Grafana Pyroscope
2.3.0
25 August 2026, 4:05 AM
Balanced query planner, resumable asynchronous queries, profilecli replay
AGPL-3.0
Grafana Pyroscope
2.0.0
20 April 2026, 2:45 PM
v2 storage becomes the default; Go module path /v2; label sanitisation off by default
AGPL-3.0
Pyroscope Helm chart
2.4.0
10 October 2026, 2:38 AM (chart index)
Single binary or microservices values
AGPL-3.0 (same repository)
Grafana Alloy
1.20.1
28 September 2026, 11:24 PM
Collector with pyroscope.ebpf, pyroscope.java and pyroscope.scrape components
Apache 2.0
Parca
0.29.1
28 September 2026, 7:23 PM
Current release; relabel config validation fix
Apache 2.0
Parca
0.29.0
23 September 2026, 11:00 PM
Source view on by default, GPU PC sampling profile types, pprof validation fixes, dependency security updates
Apache 2.0
Parca
0.28.0
7 May 2026, 1:38 PM
OpenTelemetry eBPF Profiler profile type, native v2 schema writes
Apache 2.0
Parca Agent
0.50.0
22 September 2026, 4:42 AM
Current release; OTLP profiles as a third export format, uprobe duration probes, fix for CVE-2026-39821 in a dependency
Apache 2.0
Parca Agent
0.48.0
10 June 2026, 6:06 PM
v2 writes on by default, GPU PC sampling
Apache 2.0
OTel Collector eBPF distribution (otelcol-ebpf-profiler)
0.162.0
29 September 2026, 6:04 PM
Embeds go.opentelemetry.io/ebpf-profiler v0.0.202636
Apache 2.0
OpenTelemetry eBPF profiler (Go module)
v0.0.202640
Tag commit 2 October 2026, 12:14 PM
Version 0.x; tags named v0.0.<year><week>
Apache 2.0, eBPF code GPL-2.0
Two practical notes on the table. Pyroscope maintains more than one minor line at a time (2.2.x and 2.3.x both received fixes on 8 October), so you can take security patches without jumping a minor version. And the OpenTelemetry profiler’s own versioning document says the project is under active development and “anything MAY change at any time” while the major version is zero. Pin exact versions.
Timeline
Date (IST)
Event
2 January 2021
First Pyroscope release (v0.0.10), from the Pyroscope company and open-source project
22 September 2021
First Parca Agent pre-release (v0.0.1-alpha)
8 October 2021
Parca v0.1.0
March 2023
Grafana Labs acquires Pyroscope and merges it with Grafana Phlare under the name Grafana Pyroscope
29 August 2023
Grafana Pyroscope 1.0.0
15 April 2024
open-telemetry/opentelemetry-ebpf-profiler repository created
7 June 2024
OpenTelemetry announces it has accepted Elastic’s donation of its eBPF profiling agent
26 March 2026
OpenTelemetry Profiles signal enters public alpha
16 April 2026
CVE-2025-41118 (Pyroscope 1.x, COS secret exposure) published
20 April 2026
Grafana Pyroscope 2.0.0 makes v2 storage the default
7 May 2026
Parca 0.28.0 adds an OpenTelemetry eBPF Profiler profile type
11 May 2026
OpenTelemetry moves to the CNCF Graduated maturity level
24 June 2026
GitHub advisory for CVE-2026-48496 (OpenTelemetry eBPF profiler denial of service) published; CVE record followed on 12 September
22 September 2026
Parca Agent 0.50.0 adds OTLP profiles export
29 September 2026
otelcol-ebpf-profiler 0.162.0
10 October 2026
Grafana Pyroscope 2.4.0
Pyroscope and Parca are both about five years old and each is led by one company (Grafana Labs for Pyroscope, Polar Signals for Parca), and each company sells a hosted service on the same code. The OpenTelemetry profiler is younger as an OpenTelemetry project, but its code ran in Elastic’s commercial product before the donation; the donation proposal says the agent had been used in large customer production environments since August 2021.
First question: what should a profile answer?
Before comparing tools, write down two or three questions you want profiles to answer in the next quarter. For the Pune team they are:
- During the month-end slowdown, which functions in
invoice-renderandgst-calcused the most CPU, and was it our code, a library, the garbage collector or the kernel? - Which five services cost the most CPU across the cluster, and what is the biggest single function in each?
- After a release, did CPU per request go up, and in which function?
These questions decide three things. First, profile types: CPU is enough for the first two, but slow memory growth needs heap or allocation profiles, and eBPF agents today collect CPU (and some off-CPU) only. Second, coverage: “the whole cluster” points to an eBPF agent on every node; “these two services in depth” points to SDKs or pull. Third, labels: if you want to compare by customer tier, endpoint or release, you need the application to attach labels, which only SDKs can do well.
Profiling is not tracing. A trace tells you that one request spent 800 ms in gst-calc; a profile tells you which functions burned CPU across all requests. They work best together, and both Pyroscope and the OpenTelemetry Profiles data model can link profile samples to trace and span IDs. If you are still choosing a tracing backend, the distributed tracing post covers that side.
What these three actually are
Grafana Pyroscope 2.4.0
Pyroscope describes itself as a multi-tenant continuous profiling aggregation system whose architecture follows Grafana Mimir, Loki and Tempo. It is a database and query service for profiles, with a web UI, a CLI (profilecli) and a Grafana data source and app.
Version 2.0 changed the storage design. In the v2 architecture, profiles enter through distributors, go to stateless segment-writers that batch them into small segments in object storage, and a metastore (the only stateful component, replicated with Raft) keeps an index of those objects. Compaction-workers merge small segments into larger blocks in the background; the docs give a median time to first compaction of no more than 15 seconds. Queries go through a query-frontend to stateless query-backends that read object storage directly. The docs say ingestion is synchronous: a client is blocked until data is durably in object storage and indexed, with median latency expected under 500 ms using default settings. In 2.x the default -architecture.storage is v1-v2-dual, which writes to v2 while still reading old v1 data, so upgrades from 1.x do not lose history.
Profiles reach Pyroscope in three ways, per its client documentation: language SDKs that push (Go, Java, Python, Ruby, Node.js, .NET, Rust), Grafana Alloy pulling profiles (Go pprof endpoints, Java through async-profiler) or collecting them with eBPF, and SDKs that push through Alloy. It also accepts pprof and JFR uploads on its /ingest HTTP API, and OTLP profiles over gRPC on its HTTP port.
Parca 0.29.1 and Parca Agent 0.50.0
Parca is a server that stores profiles and lets you query them over time, plus Parca Agent, an eBPF-based whole-system profiler. Parca’s data model is openly inspired by Prometheus: a series of profiles is identified by its profile type and labels, and you select with label matchers such as process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{job="invoice-render"}. The server can scrape pprof HTTP endpoints like Prometheus scrapes metrics (static or service-discovered targets), or receive profiles pushed by Parca Agent.
Parca’s storage, per its docs, has two parts: a metadata store (mappings, functions, locations) in the embedded key-value store Badger, and a sample store in FrostDB, a columnar database originally written for Parca. In my run, the server also wrote Parquet blocks to the configured bucket on shutdown.
Parca Agent samples user-space and kernel-space stacks at 19 Hz per logical CPU by default (--profiling-cpu-sampling-frequency=19). The design page explains the prime number: 100 Hz could line up with periodic work in your code and skew the profile. Release notes for 0.45 to 0.50 show the agent now builds on parca-dev/opentelemetry-ebpf-profiler, a fork of the OpenTelemetry profiler, and 0.50.0 added OTLP profiles as an export format. Some pages on parca.dev still describe the older libbpf-based design and different kernel requirements, so trust the release notes and the README at the tag you deploy.
The OpenTelemetry eBPF profiler (otelcol-ebpf-profiler 0.162.0)
This is a whole-system, cross-language profiler for Linux built on eBPF. According to its README it unwinds native code without frame pointers or on-host debug symbols (using .eh_frame data), follows mixed stacks from the kernel through system libraries into high-level runtimes, and supports HotSpot JVM, Python, Ruby, PHP, Node.js and V8, Perl, Erlang and .NET without loading anything into the profiled processes.
It does not store or display anything. Since the alpha release it runs as a receiver inside an OpenTelemetry Collector, and the supported way to deploy it is the otelcol-ebpf-profiler distribution, which contains the profiling receiver plus a small set of processors (Kubernetes attributes, resource detection, transform, batch, memory limiter) and OTLP exporters only. You point the OTLP exporter at a backend. The profiler README lists two open-source backends: Elastic’s devfiler (a desktop app, “not a real production backend”) and Pyroscope. Parca’s 0.28.0 release added a matching profile type, and Parca Agent itself is built on a fork of this code.
So the fair way to include it is as the third collection path, not as a third database. Its receiver’s stability is marked “development” for profiles, and the Pyroscope docs page for it calls the setup “suitable for development and testing”.
Why not Elastic Universal Profiling?
Elastic donated the agent, and its Universal Profiling product is still the backend that agent was built for. But Elastic’s documentation says that to run Universal Profiling on a self-hosted Elastic Stack “you need an appropriate license”, and its self-managed pricing page lists Universal Profiling among paid features. That is a fair commercial product, but not an open-source, self-hosted option for this comparison. The same applies to Grafana Cloud Profiles and Polar Signals Cloud: useful if you do not want to run storage, but out of scope here.
Three ways to collect a profile
SDK in the process (push)
Pull a pprof endpoint
eBPF agent on the node
Who does it
Pyroscope SDKs for Go, Java, Python, Ruby, Node.js, .NET, Rust
Parca server scrape; Alloy pyroscope.scrape for Pyroscope
Parca Agent; Alloy pyroscope.ebpf; otelcol-ebpf-profiler
Code change
Yes, add the library and a start call
Expose the runtime’s endpoint (Go net/http/pprof)
None
Profile types
CPU, wall, allocations, heap, locks, mutex, block, goroutines, exceptions (varies by language)
Whatever the endpoint serves (Go: CPU, heap, allocs, block, mutex, goroutine)
On-CPU (OpenTelemetry profiler also has an off-CPU probe); no heap
Custom labels (tenant, endpoint)
Yes, in code
Only target labels
Only process, container and Kubernetes labels
Privileges
None beyond the app
Network access to the endpoint
Root or equivalent capabilities, host PID namespace
Coverage
Only instrumented services
Only services exposing endpoints
Every process on the node, including ones you did not write
Typical sampling
Language default (Go uses 100 Hz)
Runtime default
19 Hz (Parca Agent, Alloy) or 20 Hz (OpenTelemetry receiver code default)
For the Pune team this table already narrows things. The Go services can be covered by any of the three methods. The Java services can use the Pyroscope Java agent (async-profiler, which gives allocation and lock profiles too) or an eBPF agent for CPU only. The Python services can use the Pyroscope Python SDK, which by default only samples threads holding the GIL (gil_only = True), or an eBPF agent. And if the month-end CPU is going to a sidecar, the kernel or an unexpected binary, only an eBPF agent will see it.
One trap from my drill deserves its own line: a Go process can run only one CPU profile at a time. If the Pyroscope Go SDK is pushing CPU profiles, a scrape of /debug/pprof/profile from Parca or Alloy fails with “cpu profiling already in use”. Pick one CPU collection method per Go service.
Language and runtime coverage
Runtime
Pyroscope SDK
eBPF (Alloy, Parca Agent, OpenTelemetry profiler)
Pull
Go
Yes (push, Apache 2.0 SDK)
Yes; native unwinding, Go symbolisation on target in the OpenTelemetry profiler
Yes, net/http/pprof
Java (HotSpot)
Yes, Java agent on async-profiler; JFR upload supported
Yes (HotSpot unwinder)
Alloy pyroscope.java runs async-profiler against JVMs; Parca’s docs list CPU-only pprof libraries
Python
Yes, pyroscope-io package
Yes
pprof libraries (Parca’s docs list CPU and heap)
Node.js
Yes
Yes (V8)
pprof libraries (Parca’s docs list CPU and heap)
.NET
Yes
Yes (.NET 9 and 10 support noted in the OpenTelemetry alpha post)
Not listed
Ruby
Yes
Yes
Not listed
Rust, C, C++
Rust SDK
Yes, without frame pointers using .eh_frame
Rust pprof crates
PHP, Perl, Erlang
No SDK listed
Yes (Parca Agent README also lists Deno, Julia and Wasmtime)
Not listed
Coverage claims come from each project’s own docs. Treat “supported” as “worth testing on your exact runtime version”: symbolisation is where profilers fail quietly, and the Pyroscope page for the OpenTelemetry profiler warns that “function names may not resolve in flamegraphs for some programs”.
Storage, retention and cost
Grafana Pyroscope 2.x
Parca 0.29
Storage
Object storage (S3, GCS, Azure, Swift; COS also listed in flags); local filesystem only for single-node
Badger metadata store plus FrostDB columnar samples; Parquet blocks to a bucket (filesystem or object storage)
Persistence default
Writes are durable in object storage before the client is acknowledged
In memory unless --enable-persistence; WAL off unless --storage-enable-wal
Memory knob
Per component
--storage-active-memory, default 512 MiB
Retention
-retention-period, default 31 days (from pyroscope -help)
I found no retention flag in parca --help for 0.29.1
Scale-out
Microservices mode: distributors, segment-writers, 3 or 5 metastores (Raft), compaction-workers, query-frontends and query-backends
Single server; a scraper-only --mode forwards scraped profiles to a remote gRPC store
Multi-tenancy
Built in, off by default (X-Scope-OrgID header)
No tenant setting in parca --help
Data reduction
Server-side write-path sampling per tenant, optional totals-only for sampled-out profiles
Agent sampling frequency, relabelling to drop series
Two points matter for cost. Pyroscope’s v2 design puts almost everything in object storage and makes most components stateless, which suits a team that already runs MinIO-style storage or a cloud bucket; the docs note that local filesystem storage is not supported in microservices mode. Parca is simpler to run (one binary, one data directory or bucket) but keeps its hot data in memory, so size that memory and decide on the WAL before you depend on it. If the long-term storage problem sounds familiar, it is the same trade-off as for metrics, covered in the Prometheus long-term storage post.
Pyroscope’s write-path sampling is worth knowing about because it is easy to misread. The distributor can drop a fraction of incoming profiles per tenant. By default dropped profiles simply leave gaps; with keep_stripped_profiles the totals are kept but stacks are removed, so flame graphs exclude them while time series can include them. This is separate from the sampling rate of the profiler itself.
Querying: flame graphs, diffs and labels
Both backends show flame graphs (Parca draws them upside down as icicle graphs), tables of top functions, and comparisons between two time ranges or label sets. The differences are in workflow:
- Pyroscope’s main UI is Grafana, through the Pyroscope data source and the Grafana Pyroscope app plugin (
grafana-pyroscope-appin its examples), and the server has its own UI as well. Linking a trace span to the profile samples taken during that span (“span profiles”) is documented for Go, Java, Python, Ruby and .NET SDKs.profileclican query profiles, exportpprof, list top label values and, since 2.3 and 2.4, dump and replay ingestion traffic. - Parca’s UI is in the server and talks gRPC-web; the same API is exposed over HTTP under
/api(I used/api/profiles/queryin the drill). Recent releases added a sandwich view, flame charts (limited to 15 minutes per view since 0.27.0), source view on by default (0.29.0) and GPU profile types. The share button sends a profile to the configured share server,api.pprof.me:443by default.
Labels decide how useful any of this is. Keep them few and stable: service, namespace, version, region, maybe a coarse endpoint. A label per customer or per request ID will explode series counts in either backend. Pyroscope’s docs note that a period is not valid in tag names, while 2.0 turned label sanitisation off by default so UTF-8 names with dots are accepted. That kind of change between versions is exactly why you pin versions and test queries after upgrades.
OTLP profiles: a standard in alpha
The OpenTelemetry Profiles signal is the reason the third tool is in this post. The alpha announcement (26 March 2026) describes a data model that deduplicates stacks, links samples to trace and span IDs, and round-trips with pprof without loss, plus a conformance checker. The Collector gained a pprof receiver, Kubernetes attribute enrichment for profiles and OTTL transforms.
What that means in practice on 13 October 2026:
- Collectors: the OpenTelemetry profiler, Parca Agent 0.50.0 and Pyroscope’s Java agent (experimental) can all emit OTLP profiles.
- Backends: Pyroscope ingests OTLP profiles and, since 2.4.0, reads
telemetry.sdk.languageto detect the language. Parca’s 0.27 and 0.28 releases list OTLP and OpenTelemetry profiler support. The OpenTelemetry alpha post itself says production-ready backends “have not yet emerged”. - Stability: the signal is alpha, the receiver is “development”, the Collector must run with
--feature-gates=+service.profilesSupport, and Pyroscope’s docs warn that breaking protocol changes “have occurred and may continue”. Versions of profiler, Collector and backend must be managed together.
A sensible stance for a team like Pune’s: use OTLP profiles if your collector of choice already speaks it, but do not make “OTLP everywhere” a requirement this year. The format will stabilise; the collection method and backend you choose now will outlive that decision. If you already run an OpenTelemetry Collector fleet for logs and traces, the telemetry pipelines post covers the Collector itself; the profiler distribution is a separate binary meant to run as a node agent.
Running on Kubernetes
Pyroscope
Parca
OpenTelemetry eBPF profiler
Server install
Helm chart grafana/pyroscope (2.4.0), single binary or microservices values file
kubernetes-manifest.yaml attached to each Parca release
Not applicable (agent only)
Agent install
Grafana Alloy Helm chart as a DaemonSet for eBPF; SDKs need no agent
kubernetes-manifest.yaml attached to each Parca Agent release (DaemonSet)
DaemonSet using the otelcol-ebpf-profiler image
Agent privileges
Alloy must run as root in the host PID namespace for pyroscope.ebpf; the docs suggest privileged: true or specific capabilities
Agent needs kernel access and runs as a privileged user (Parca Kubernetes guide)
Privileged, hostPID: true, host /proc and /sys/kernel mounts (Pyroscope’s example DaemonSet)
Kubernetes metadata
Alloy discovery and relabelling
Agent reads it from the CRI (CRI v1, for example containerd 1.6+)
k8sattributes processor with a service account that can read pods, namespaces and nodes
Kernel
Not stated on the Alloy component page
README: Linux 5.3+ with BTF
README: newer code may need 5.10+; follows the oldest kernel in maintained major distributions
The eBPF agent is the part that needs a security review. It runs on every node with privileges close to root, reads every process’s memory maps, and sees kernel stacks. That is the price of “no code changes”. Restrict who can change its configuration, keep the image pinned and patched, and do not mount more of the host than the documented paths.
The kernel rows also disagree with each other because the docs are of different ages. Check the README at the exact tag you deploy, and test on the oldest node image you still run.
Overhead: what the projects claim and what to measure
The only numeric overhead claim I found in primary sources is the OpenTelemetry profiler README: “1% CPU and 250MB memory are our upper limits in testing and the agent typically manages to stay way below that”. That sentence came with the code from Elastic and is the project’s own measurement, not an independent one. Parca says “very low overhead” and explains its 19 Hz choice; Pyroscope says “minimal CPU overhead”. Neither gives numbers I could check.
What drives overhead in practice:
- Sampling frequency. Doubling it roughly doubles the samples to unwind, symbolise, send and store. At 19 Hz, one fully busy core produces about 190 samples in 10 seconds.
- Symbolisation and unwinding for interpreted and JIT runtimes, which is more work than native code.
- Upload interval and profile size (the OpenTelemetry receiver reports every 5 seconds by default; Alloy’s
collect_intervaldefaults to 15 seconds). - For SDKs, the extra profile types. Go’s block and mutex profiles stay off until a rate is set, and their cost grows with the rate you choose.
- The backend, which is often the larger cost: object storage requests, compaction and query memory.
Measure it yourself: run a fixed load against one service with profiling off, then on, two or three times each, and compare throughput and CPU per request. My small version of this is in the drill below.
Licences, governance and telemetry defaults
Grafana Pyroscope
Parca and Parca Agent
OpenTelemetry eBPF profiler
Licence
Server AGPL-3.0; Go SDK and Alloy Apache 2.0
Apache 2.0 (agent’s eBPF code comes from the OpenTelemetry profiler fork)
Apache 2.0; eBPF code GPL-2.0
Steward
Grafana Labs
Polar Signals
OpenTelemetry Profiling SIG (maintainers from Datadog, Elastic and independents) under CNCF
Commercial sibling
Grafana Cloud Profiles
Polar Signals Cloud
Vendors are adding OTLP profiles support (per the alpha post)
Usage statistics
Anonymous usage stats on by default (-usage-stats.enabled, documented)
Agent sends anonymous usage stats unless --analytics-opt-out; default endpoint in source is analytics.parca.dev
None that I found in the README or distribution manifest
Other outbound calls
Self-profiling push to itself in single-binary mode (-self-profiling.disable-push)
Server’s default upstream for missing debug information is debuginfod.elfutils.org; share button uses api.pprof.me; agent sends its crash output to the configured server unless --telemetry-disable-panic-reporting
OTLP exporter only
AGPL-3.0 for the Pyroscope server mostly matters if you modify it and let others use your modified version over a network. Running it unmodified inside your company is a common pattern, but have your legal team read the licence once rather than guess. Parca and the OpenTelemetry profiler are permissive, with GPL-2.0 applying to the eBPF programs loaded into the kernel, which is normal for eBPF code.
Telemetry defaults are worth one hour of review for an Indian SaaS handling customer financial data. None of these send profile contents to a vendor by default, but usage statistics, symbol server lookups and a share button that uploads a profile to an external service should be decisions, not surprises. In an air-gapped or regulated cluster, switch off what you do not need and point debuginfod at an internal server or none.
Security and CVEs
I searched the GitHub advisory database (by Go module), each repository’s security advisories and the NVD keyword search on 13 October 2026. Scores below are as published in the CVE record by the CNA.
Grafana Pyroscope
CVE
Score
Issue
Fixed in
CVE-2025-41118
9.1 (CNA, Grafana)
With Tencent Cloud Object Storage (COS) as the backend, the COS secret_key could be read in plain text from the configuration API by anyone with access to the Pyroscope API
1.15.2 and 1.16.1; 1.17.0 and later not affected
The record was published on 16 April 2026 (IST) and lists 1.0.0 up to (but not including) 1.16.0 as affected. Grafana’s own advice in the record is the important line: “We highly recommend limiting the public internet exposure of all our databases.” Pyroscope’s other 2026 security items are dependency and toolchain updates listed in release notes (for example Go 1.26.9 and golang.org/x/net v0.60.0 in 2.4.0, gRPC and undici in 2.3.2). These matter because the server is a Go and JavaScript application with a large dependency tree, and the fix is the same: stay on a maintained minor line.
Parca and Parca Agent
I found no CVE and no GitHub security advisory for github.com/parca-dev/parca or github.com/parca-dev/parca-agent, and the NVD keyword search for “parca” returned no results. Parca Agent 0.50.0 notes a fix for CVE-2026-39821 (an IDNA validation issue in golang.org/x/net and the Go standard library) and other dependency advisories, and Parca 0.29.0 carried a batch of dependency security updates. No advisories is not the same as no vulnerabilities; it may also reflect a smaller user base and fewer researchers looking.
OpenTelemetry eBPF profiler
CVE
Score
Issue
Fixed in
CVE-2026-48496
6.2 (CNA, GitHub)
An unprivileged local process could make the profiler open a non-regular file such as a FIFO from its memory mappings and block forever, stopping ELF analysis (denial of service of the agent)
v0.0.202622 (affected from v0.0.202527)
The advisory states there is no data exfiltration and no data loss. Because Parca Agent and Alloy embed forks of this code, check that their versions include the fix too; the Collector distribution 0.162.0 embeds v0.0.202636, which is later than the fixed tag.
Practical controls
- Run Pyroscope 2.4.0 (or the latest patch of the 2.3 or 2.2 line), Parca 0.29.1, Parca Agent 0.50.0 and
otelcol-ebpf-profiler0.162.0 or later, and subscribe to each project’s releases. - Keep profiling servers off the public internet and put an authenticating proxy in front of the UI and API. Profiles reveal function names, file paths, library versions and sometimes argument-derived labels.
- Treat the eBPF DaemonSet as privileged infrastructure: pinned image digest, restricted namespace, admission policy that allows privilege only for that workload, and change review for its config.
- Do not put customer identifiers, GSTINs, email addresses or tokens into profile labels or span attributes that reach profiles.
- Store object storage credentials in a secrets manager and check that no API endpoint prints configuration with secrets; CVE-2025-41118 was exactly that.
- Decide telemetry,
debuginfodand share-server settings on purpose.
Decision guide for the Pune scenario
If your situation is
Lean towards
Why
Already on Grafana, Mimir or Tempo, want profiles next to metrics and traces, several teams
Pyroscope with SDKs for key services and Alloy eBPF for the rest
Same operational model as the rest of the Grafana stack; multi-tenancy; span profiles
Want one small server, Apache 2.0 everywhere, mostly CPU, Prometheus-style label queries
Parca with Parca Agent
Simple to run; whole-host eBPF; permissive licence
Already run an OpenTelemetry Collector fleet and want vendor-neutral collection
OpenTelemetry eBPF profiler sending OTLP to Pyroscope (or Parca)
Standard collector and data model; backend can change later
Need allocation, heap, lock or wall-clock profiles for Java or Go
Pyroscope SDKs or pull, not eBPF alone
eBPF agents collect CPU (and off-CPU) only
Cannot run privileged pods (strict multi-tenant cluster, some managed platforms)
SDKs or pull into either backend
eBPF agents need root or equivalent capabilities
Need object storage, horizontal scale-out and retention policy from day one
Pyroscope v2
Designed around object storage and stateless components
For the invented Pune team, I would do this:
- Write down the three questions from the start of this post and the profile types they need (CPU for the month-end spike and for cost; allocations for
invoice-renderif memory becomes a question). - Start with Pyroscope, because the team already operates Grafana and wants traces and profiles linked. Use the Go SDK on
invoice-renderandgst-calc(CPU and allocations, version and region labels), and run Alloy’spyroscope.ebpfon every node for whole-cluster CPU. - Run a two-week trial of Parca with Parca Agent on two nodes in parallel. If its simpler operations and query model win the team over, the SDK-instrumented services can be pulled by Parca instead, but not both at once for CPU.
- Treat the OpenTelemetry profiler as the likely long-term node agent. Revisit when the Profiles signal reaches beta and the receiver leaves “development”.
- Before month-end, compare one release against the previous one in the profiler, and note the top three functions. That is the habit that pays back the setup.
Checklist before you store another flame graph
- You have written the questions profiles must answer and the profile types each one needs.
- Each service has exactly one CPU profile source (SDK, pull or eBPF), so Go services do not fight over the runtime profiler.
- Labels are few and stable (service, namespace, version, region), and none contain personal or customer data.
- Retention is set deliberately (Pyroscope
-retention-period, Parca storage and WAL settings), and storage growth over a week has been measured. - The eBPF agent runs pinned, patched and privileged only where needed, and someone owns its upgrade.
- Symbolisation works for every runtime you care about, checked by opening a flame graph for each, not assumed from a support matrix.
- Overhead has been measured on your own service with a fixed load, profiling off and on.
- The UI and API are behind authentication and not on a public address.
- Usage statistics,
debuginfodand share settings are reviewed. - Versions of agent, Collector and backend are pinned together if you use OTLP profiles.
Common mistakes
- Choosing a backend before deciding how profiles will be collected.
- Running the Pyroscope Go SDK and a
/debug/pprof/profilescrape on the same process, then wondering why one of them has no CPU data. - Expecting heap or allocation profiles from an eBPF agent.
- Adding a high-cardinality label (customer, request ID) and blowing up series counts.
- Treating an alpha signal and a “development” receiver as production-ready because the project is part of OpenTelemetry.
- Running Parca without
--enable-persistenceand losing everything on restart, or assuming persistence without a WAL protects against a crash. - Changing Pyroscope’s gRPC port without also setting the metastore and query-backend addresses (my drill, below).
- Leaving the profiler UI open on the network because “it only shows flame graphs”.
- Reading a vendor’s overhead number as if it applied to your workload.
- Collecting profiles for months and never comparing two releases.
A drill I ran on 13 October 2026
Environment: shared Linux VM (Debian 13, kernel 6.12), 8 vCPU, about 15.6 GiB RAM with 4 to 6 GiB available, no root, no Linux capabilities, perf_event_paranoid set to 2 and unprivileged BPF disabled, no Docker. Between about 6:30 and 6:55 PM IST I downloaded the release binaries of Pyroscope 2.4.0, profilecli 2.4.0, Parca 0.29.1, Parca Agent 0.50.0 and otelcol-ebpf-profiler 0.162.0 from GitHub and ran them as an ordinary user. Other work shares this VM, so treat every number as indicative only.
The test service was a small Go program (built with Go 1.25.0 and pyroscope-go 1.4.3) with two busy goroutines. Each loop renders a 40-line invoice to JSON and upper-cases it (renderInvoice), then hashes the result 201 times with SHA-256 (checksumInvoice). It exposes net/http/pprof, and a flag turns on the Pyroscope SDK (CPU plus four memory profile types). Pyroscope ran as a single binary with v2 storage on the local filesystem; Parca ran with --enable-persistence and scraped the service’s pprof endpoint every 10 seconds.
Overhead smoke test: 60 s per run, same binary, ops/s (not a benchmark)
A1 no profiling 53,914 A2 no profiling 54,282
B1 Pyroscope Go SDK push 51,485 B2 Pyroscope Go SDK push 52,953
C1 Parca scraping pprof 51,875 C2 Parca scraping pprof 52,988
mean vs baseline: SDK -3.5%, scrape -3.1%; the two baselines differ by 0.7%
sys CPU per run: ~2.0 s baseline, ~2.9 to 3.1 s SDK, ~3.2 s scrape
What each backend recorded (go tool pprof -top -cum on exported profiles)
Pyroscope B1 122.2 s CPU checksumInvoice 48.3% renderInvoice 44.8%
Parca C1 102.8 s CPU checksumInvoice 47.6% renderInvoice 45.2%
process used about 123.6 s CPU per run (bash time)
SDK push and Parca scrape on the same process, 180 s
Pyroscope 367.5 s CPU checksumInvoice 48.9% renderInvoice 44.3%
Parca 0 s CPU for the window; memory profiles still arrived
/debug/pprof/profile -> HTTP 500 "cpu profiling already in use"
Start-up and operations
Parca /ready 200 after 0.33 s; RSS about 217 MiB after 15 min
Pyroscope /ready 200 after about 62 s (deliberate min-ready waits; twice)
Pyroscope query for the 3 min data-run window: 0.08 s once configured
Parca after SIGTERM and restart: same 205.42 s CPU total still queryable
eBPF agents without root
otelcol-ebpf-profiler 0.162.0 exit 1 "failed to load eBPF tracer: failed to read
kernel symbols: unable to read kallsyms addresses
- check capabilities"
parca-agent 0.50.0 exit 2, same error; then sent its crash output
to my Parca server ("agent panic'ed with")
What this shows:
- Both backends agreed on the answer: about 48 percent of CPU in the checksum path and 44 to 45 percent in rendering, with the same functions at the top. For a question like the Pune team’s month-end spike, either would have pointed at the right code.
- Parca’s 10-second scrapes covered about 103 of the 124 CPU seconds the process used in a 60-second run, because scrape windows start and stop at their own times. Pyroscope’s SDK, running inside the process from start to exit, covered nearly all of it. For long-running services this edge effect does not matter much; for short jobs it does.
- The roughly 3 percent throughput cost in both modes is in line with “low overhead”, but my test used a CPU-bound loop on a shared VM, and the SDK runs also collected memory profiles. Measure your own service.
- The Go CPU profiler conflict is real and silent from Parca’s side: the target showed as scraped, but no CPU data arrived while the SDK held the profiler.
- Pyroscope 2.x as a single binary is not “download and run” if you change ports. My first start failed because changing
-server.grpc-listen-portalso requires-metastore.address, and my first queries timed out at 30 seconds because-query-backend.addressalso defaults tolocalhost:9095. Both are my configuration errors, but they are easy to make. Its/readyendpoint also waits on purpose (the 503 response says the waits slow rolling updates and can be set to zero for single-node setups), and it pushes its own profiles to itself unless-self-profiling.disable-pushis set. - Parca Agent’s crash report went to the Parca server I had configured, not to an outside service. Knowing that the agent reports crashes and usage statistics, and how to switch each off, is part of reviewing it.
Limits of the drill: one VM, one Go service, no root, so no eBPF profile was captured by any agent and Grafana Alloy was not tested. No Java, Python or Node.js targets, no OTLP ingestion into either backend, no object storage, no Kubernetes or Helm, no multi-tenancy, no hard-kill test of Parca without a WAL. The SDK and scrape runs collected different profile types. Artefacts (README, scripts, configs, logs, exported profiles) are under /workspace/revisit/drill35/. All processes were stopped, binaries and data directories deleted, and the Go toolchains and modules downloaded for the drill removed.
Unlearn / re-learn
Unlearn
- Profiling is something you do by hand during an incident.
- One profiler choice covers everything: CPU, memory, all languages, every node.
- eBPF means zero cost and zero risk.
- “Part of OpenTelemetry” means production-ready.
- If the UI shows a flame graph, the symbols and the coverage must be right.
Re-learn
- Keep low-frequency profiles running all the time, so the incident is already recorded when you need it.
- Choose a collection method per service (SDK, pull or eBPF), and only one CPU source per Go process.
- Treat node agents as privileged infrastructure with owners, pinning and review.
- Check the maturity label of each piece: OTLP Profiles is alpha, the profiler receiver is “development”, the backends are stable releases.
- Practise reading a diff between two releases until it is a habit.
Closing
Continuous profiling in 2026 comes down to two mature backends and one collector that is turning into the shared engine underneath them. Learn their models: Pyroscope’s v2 object-storage design with SDKs, Alloy and tenants; Parca’s Prometheus-like labels, scraping and single-binary simplicity; the OpenTelemetry profiler’s whole-host eBPF collection as a Collector receiver. Unlearn the habit of reaching for pprof only after a spike, and of assuming one method sees everything. Re-learn the current versions: Pyroscope 2.4.0 under AGPL-3.0, Parca 0.29.1 and Parca Agent 0.50.0 under Apache 2.0, otelcol-ebpf-profiler 0.162.0 with the Profiles signal still in alpha. Practise with a drill like the one above: one service, profiling off and on, two backends, one comparison. Apply one rule before you store another flame graph: know which question it answers, and who will look at it before the next month-end.
Sources
Grafana Pyroscope and Grafana Alloy
- Grafana Pyroscope releases
- Pyroscope 2.4.0 release
- Pyroscope 2.0.0 release
- Pyroscope LICENSE (AGPL-3.0) at v2.4.0
- Pyroscope introduction
- About the Pyroscope v2 architecture (docs source at v2.4.0)
- Pyroscope v2 deployment modes (docs source at v2.4.0)
- Pyroscope write-path sampling (docs source at v2.4.0)
- Pyroscope v2.0 release notes
- Configure the client to send profiles
- Pyroscope language SDKs (docs source at v2.4.0)
- Pyroscope Java SDK (docs source at v2.4.0)
- Pyroscope Python SDK (docs source at v2.4.0)
- Pyroscope profile types (docs source at v2.4.0)
- Pyroscope with the OpenTelemetry eBPF profiler (docs source at v2.4.0)
- Pyroscope span profiles (docs source at v2.4.0)
- Pyroscope server HTTP API (docs source at v2.4.0)
- Pyroscope tenant IDs (docs source at v2.4.0)
- Pyroscope anonymous usage statistics
- Deploy Pyroscope using the Helm chart
- Grafana Helm charts index
- pyroscope-go SDK
- Grafana Alloy releases
- Grafana Alloy
pyroscope.ebpfreference - Grafana Labs: Pyroscope and Grafana Phlare join together (March 2023)
Parca and Parca Agent
- Parca releases
- Parca 0.29.1 release
- Parca 0.28.0 release
- Parca LICENSE at v0.29.1
- Parca Agent releases
- Parca Agent 0.50.0 release
- Parca Agent README at v0.50.0
- Parca Agent LICENSE at v0.50.0
- Parca Agent analytics client at v0.50.0
- Parca overview
- Parca storage
- Parca ingestion
- Parca Agent
- Parca Agent design
- Parca Agent language support
- Parca on Kubernetes
OpenTelemetry eBPF profiler and OTLP Profiles
- opentelemetry-ebpf-profiler repository and README
- opentelemetry-ebpf-profiler tags
- Versioning policy
- Collector receiver README
- Receiver defaults (factory_linux.go)
- LICENSE (Apache 2.0)
- eBPF code LICENSE (GPL-2.0)
- OpenTelemetry Collector releases (0.162.0)
- otelcol-ebpf-profiler distribution
- OpenTelemetry Profiles enters public alpha (26 March 2026)
- OpenTelemetry Profiles concepts
- Elastic contributes its continuous profiling agent to OpenTelemetry (7 June 2024)
- Donation proposal: continuous profiling agent
- OpenTelemetry on the CNCF project page
- Run Universal Profiling on a self-hosted Elastic Stack
Go runtime
Security (CVE Program records and GitHub advisories)
- CVE-2025-41118 (Pyroscope)
- GHSA-m9hq-h476-h2g8 (CVE-2025-41118 advisory)
- Grafana security advisory for CVE-2025-41118
- CVE-2026-48496 (OpenTelemetry eBPF profiler)
- GHSA-f2r5-5m7w-p5cx (CVE-2026-48496 advisory)
- CVE-2026-39821 (golang.org/x/net and Go standard library)
- NVD keyword search: parca
Internal Revisit.Tech links used in the body
Releted Posts
Data pipeline orchestration in 2026: revisit Apache Airflow, Dagster and Prefect before you schedule another DAG
Picture a two-wheeler loan company in Coimbatore with about ninety branches (an invented example, not a real client). Its data team of five runs about 140 cron jobs on two Linux VMs: shell scripts that pull collections files from a payment gateway SFTP, Python scripts that clean them, and SQL that loads a PostgreSQL reporting database every night.
Read moreDistributed tracing in 2026: revisit Jaeger, Grafana Tempo and SigNoz before you store another span
Picture a bus, train and hotel booking platform in Kochi (an invented example, not a real client). Customers book from an Android app, an iOS app and a web app.
Read moreFeature flags in 2026: revisit Unleash, Flagsmith and GrowthBook before you hard-code another release toggle
Picture a test-preparation and online classes platform in Jaipur (an invented example, not a real client). Students use an Android app, an iOS app and a React web app.
Read more