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 pprof endpoints 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-profiler Collector 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.ebpf embeds 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-render and gst-calc used 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-app in 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. profilecli can query profiles, export pprof, 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/query in 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:443 by 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.language to 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_interval defaults 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-profiler 0.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, debuginfod and 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-render if 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-render and gst-calc (CPU and allocations, version and region labels), and run Alloy’s pyroscope.ebpf on 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

  1. You have written the questions profiles must answer and the profile types each one needs.
  2. Each service has exactly one CPU profile source (SDK, pull or eBPF), so Go services do not fight over the runtime profiler.
  3. Labels are few and stable (service, namespace, version, region), and none contain personal or customer data.
  4. Retention is set deliberately (Pyroscope -retention-period, Parca storage and WAL settings), and storage growth over a week has been measured.
  5. The eBPF agent runs pinned, patched and privileged only where needed, and someone owns its upgrade.
  6. Symbolisation works for every runtime you care about, checked by opening a flame graph for each, not assumed from a support matrix.
  7. Overhead has been measured on your own service with a fixed load, profiling off and on.
  8. The UI and API are behind authentication and not on a public address.
  9. Usage statistics, debuginfod and share settings are reviewed.
  10. Versions of agent, Collector and backend are pinned together if you use OTLP profiles.

Common mistakes

  1. Choosing a backend before deciding how profiles will be collected.
  2. Running the Pyroscope Go SDK and a /debug/pprof/profile scrape on the same process, then wondering why one of them has no CPU data.
  3. Expecting heap or allocation profiles from an eBPF agent.
  4. Adding a high-cardinality label (customer, request ID) and blowing up series counts.
  5. Treating an alpha signal and a “development” receiver as production-ready because the project is part of OpenTelemetry.
  6. Running Parca without --enable-persistence and losing everything on restart, or assuming persistence without a WAL protects against a crash.
  7. Changing Pyroscope’s gRPC port without also setting the metastore and query-backend addresses (my drill, below).
  8. Leaving the profiler UI open on the network because “it only shows flame graphs”.
  9. Reading a vendor’s overhead number as if it applied to your workload.
  10. 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-port also requires -metastore.address, and my first queries timed out at 30 seconds because -query-backend.address also defaults to localhost:9095. Both are my configuration errors, but they are easy to make. Its /ready endpoint 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-push is 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

Parca and Parca Agent

OpenTelemetry eBPF profiler and OTLP Profiles

Go runtime

Security (CVE Program records and GitHub advisories)

comments powered by Disqus

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 more

Distributed 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 more

Feature 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