Scrape application pprof endpoints with Parca and send profiles to Dash0 through the OpenTelemetry Collector.
Parca can periodically scrape application endpoints that return profiles in the pprof format.
In otlp-forwarder mode, Parca converts these profiles to OpenTelemetry profiles and sends them to an OpenTelemetry
Collector for export to Dash0. You do not need to run a Parca database or UI.
This integration demonstrates Go heap profiling on Kubernetes. It collects live allocated bytes from an application's
/debug/pprof/allocs endpoint and preserves the application's service and pod identity.
Unlike eBPF profiling, pprof scraping requires an application profiling endpoint, but does not require a privileged container or host PID access. You can use both approaches together.
Parca can scrape any HTTP endpoint that returns binary pprof profiles. The application or a profiling library must collect the data and expose that endpoint; installing the scraper does not enable profiling inside the application.
The table lists profiling options for common application runtimes. The setup guide collects only Go live-heap profiles; use its runtime-adaptation section when configuring other languages or profile types.
| Language/runtime | Profile types and source | Requirements for scraping |
|---|---|---|
| Go | CPU, live heap, cumulative allocations, blocking, mutex contention, and goroutines via net/http/pprof. | Register and serve the HTTP handlers. Enable block and mutex sampling explicitly with runtime.SetBlockProfileRate and runtime.SetMutexProfileFraction. |
| Python | Heap, CPU, wall-clock time, and thread snapshots via pypprof. | Start its HTTP server. Heap collection requires the optional mprofile dependency and enabled memory tracing. Check dependency compatibility with your Python version. |
| Node.js | Sampled heap and wall-clock time via pprof-nodejs. | Start the profiler and add an HTTP handler that returns pprof.encode(...) output. The library does not provide the scrape server. |
| Rust | Heap via jemalloc_pprof; CPU via pprof-rs. | For heap, use Linux and a profiling-enabled jemalloc allocator, then expose dump_pprof() over HTTP. For CPU, enable pprof protobuf output and serve it from your application. |
| Java/JVM | CPU, allocations, and lock contention via AP-Agent, which wraps async-profiler. | Load the Java agent with -Dap-agent.handler.go-mode=true to serve pprof endpoints. Requires Java 11+ and a supported HotSpot JVM; its default CPU sampling engine requires Linux. |
| C/C++ | Heap via the jemalloc_pprof C API. | Use profiling-enabled jemalloc, link the C API library, and serve the binary profile from dump_jemalloc_pprof() over HTTP. |
Go's goroutine, blocking, and mutex profiles are runtime-specific. They are not automatically available in other languages just because a library can produce a heap or CPU profile. Heap profiles describe allocations tracked by the profiler or allocator, not necessarily all process memory or RSS. Wall-clock profiles include waiting time and are not CPU profiles.
Go can also expose wall-clock profiles with fgprof. Add its HTTP handler and a custom
pprof_config entry pointing at that handler rather than treating it as a CPU-only profile.
Java's AP-Agent converts recordings to binary pprof before returning them over HTTP. Its allocation profile describes allocations during a recording, not a live-heap snapshot. Async-profiler or Java Flight Recorder alone is not a pprof HTTP server; producing a JFR file or sending profiles to a backend is different from exposing a scrape endpoint.
.NET's dotnet-monitor trace API exposes
HTTP endpoints, but returns .nettrace, not pprof. dotnet-trace also records runtime diagnostics rather than serving a
pprof endpoint. These tools need a separate pprof conversion and HTTP bridge for this integration; they are not drop-in
scrape targets. A profiler that pushes pprof to another backend is not automatically a pull-based exporter.
Ruby Vernier collects time, allocation, and retained-memory profiles in Firefox Profiler format. PHP Excimer provides sampling data with Speedscope and collapsed-stack output. These outputs are not directly scrapeable by this integration. Using these tools with Parca requires an adapter that converts the data to binary pprof and exposes it over HTTP; serving the original recording or JSON response is not sufficient.
Run Parca as a DaemonSet to scrape application pprof endpoints on each Kubernetes node. Parca converts the profiles to OTLP and sends them to a node-local collector.
The Dash0 operator for Kubernetes manages the collector, which enriches and exports the profiles using its existing Dash0 endpoint and authorization token.
This example collects live allocated bytes, inuse_space, every 60 seconds. It does not collect CPU, goroutine, block,
or mutex profiles. The eBPF profiler is not required for this setup.
operator.profilingEnabled=true in your existing Helm values. Keep your existing
export and authorization settings. You do not need to enable operator.ebpfProfilerEnabled for pprof scraping.Dash0Monitoring resource, as described in the operator setup
guide. This example uses the default namespace and an operator installed as dash0-operator in dash0-system.6060, reachable from other pods. See the
Go pprof documentation for how to enable the endpoint.Keep pprof endpoints private. They expose application internals and can trigger profiling work. Restrict access with your cluster's network policies; do not expose them through a public Service or Ingress.
The example discovers pods labeled app.kubernetes.io/name: my-go-service in default, and selects the container port
named pprof. Add the following labels and port declaration to your application's existing pod template:
spec:
template:
metadata:
labels:
app.kubernetes.io/name: my-go-service
spec:
containers:
- name: my-go-service
ports:
- name: pprof
containerPort: 6060This is a pod-template fragment, not a complete Deployment. A port declaration does not start the pprof HTTP server; the application must listen on that port. No Kubernetes Service is required because Parca scrapes pod IPs directly.
Change the namespace, application label, and port name below to match your workload. Set service.name and
service.namespace to the same values used by your application's other telemetry.
Save the following as parca-config.yaml and apply it with kubectl apply -f parca-config.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: parca-pprof
namespace: dash0-system
data:
parca.yaml: |
object_storage:
bucket:
type: FILESYSTEM
config:
directory: /tmp/data
scrape_configs:
- job_name: my-go-service-memory
scrape_interval: 60s
kubernetes_sd_configs:
- role: pod
namespaces:
names: [default]
selectors:
- role: pod
field: spec.nodeName=__NODE_NAME__
label: app.kubernetes.io/name=my-go-service
profiling_config:
pprof_config:
memory:
enabled: true
path: /debug/pprof/allocs
keep_sample_type:
- type: inuse_space
unit: bytes
block:
enabled: false
goroutine:
enabled: false
mutex:
enabled: false
process_cpu:
enabled: false
relabel_configs:
- action: keep
source_labels: [__meta_kubernetes_pod_phase]
regex: Running
- action: keep
source_labels: [__meta_kubernetes_pod_container_port_name]
regex: pprof
- target_label: service.name
replacement: my-go-service
- target_label: service.namespace
replacement: default
- source_labels: [__meta_kubernetes_pod_uid, __meta_kubernetes_pod_container_name]
separator: /
target_label: service.instance.id
- source_labels: [__meta_kubernetes_namespace]
target_label: k8s.namespace.name
- source_labels: [__meta_kubernetes_pod_name]
target_label: k8s.pod.name
- source_labels: [__meta_kubernetes_pod_uid]
target_label: k8s.pod.uid
- source_labels: [__meta_kubernetes_pod_node_name]
target_label: k8s.node.name
- source_labels: [__meta_kubernetes_pod_container_name]
target_label: k8s.container.nameThe filesystem entry is required by Parca's configuration validation. Forwarding mode does not start a local profile database. All other profile types are explicitly disabled because omitted pprof settings inherit Parca's defaults.
Although the endpoint is named allocs, keep_sample_type selects only inuse_space in bytes. These are snapshots of
live heap memory, not cumulative allocated bytes or allocation rates.
The deployment below can also scrape other runtimes once you expose a compatible pprof endpoint. Use a separate scrape job for each runtime or profiling configuration, and keep the node-local discovery and application identity labels. Change the target selector, named port, and service attributes to match that application.
Set memory.path to the endpoint you actually expose, and match keep_sample_type to its sample type and unit:
| Profile source | Heap endpoint | Byte sample type |
|---|---|---|
Go net/http/pprof | /debug/pprof/allocs or /debug/pprof/heap | inuse_space / bytes for live heap. |
Rust jemalloc_pprof | /debug/pprof/allocs in its HTTP example. | inuse_space / bytes. |
Python pypprof | /debug/pprof/heap | HEAP / bytes. |
Node.js pprof-nodejs | The path registered by your application's heap handler. | space / bytes. |
Java AP-Agent also exposes /debug/pprof/allocs, but records allocations over a timed interval rather than returning a
live-heap snapshot. Configure its duration and sample types separately; do not reuse the Go inuse_space filter.
The Python and Node.js sample names come from their respective
pypprof and
pprof-nodejs implementations. Inspect a
profile from your library version before choosing a filter. Keeping Go's inuse_space filter for an exporter that uses
HEAP or space discards those samples. You can omit keep_sample_type to retain all sample types while checking them.
Keep the other default profiles disabled unless your application exposes those endpoints. Go's block, mutex, and
goroutine endpoints are not portable runtime APIs. For timed CPU or wall-clock endpoints, use delta: true only if the
handler accepts a seconds query parameter to control the recording duration. Leave CPU scraping disabled if you already
collect CPU profiles with eBPF and do not want duplicate collection.
Return binary pprof, not an HTML profile index, a text stack dump, a flame graph, or a runtime-native recording. Native
exporters must include resolved stack symbols, or symbolize the profiles before serving them; this setup does not deploy
a separate symbolization service. For Rust heap profiles, enable jemalloc_pprof's symbolize feature.
Save the following as parca-deployment.yaml and apply it with kubectl apply -f parca-deployment.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: parca-pprof
namespace: dash0-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: parca-pprof
namespace: default
rules:
- apiGroups: [""]
resources: [pods]
verbs: [get, list, watch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: parca-pprof
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: parca-pprof
subjects:
- kind: ServiceAccount
name: parca-pprof
namespace: dash0-system
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: parca-pprof
namespace: dash0-system
spec:
selector:
matchLabels:
app.kubernetes.io/name: parca-pprof
template:
metadata:
labels:
app.kubernetes.io/name: parca-pprof
spec:
serviceAccountName: parca-pprof
tolerations:
- operator: Exists
securityContext:
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
fsGroup: 65534
seccompProfile:
type: RuntimeDefault
containers:
- name: parca
image: ghcr.io/parca-dev/parca:v0.30.0
command: [/bin/sh, -ec]
args:
- |
sed "s/__NODE_NAME__/${K8S_NODE_NAME}/g" /etc/parca/parca.yaml > /tmp/parca.yaml
exec /parca --mode=otlp-forwarder --config-path=/tmp/parca.yaml --insecure \
--store-address=dash0-operator-opentelemetry-collector-service.dash0-system.svc.cluster.local:4317
env:
- name: K8S_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: GOMEMLIMIT
value: 400MiB
ports:
- name: http
containerPort: 7070
readinessProbe:
tcpSocket:
port: http
livenessProbe:
tcpSocket:
port: http
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 512Mi
volumeMounts:
- name: config
mountPath: /etc/parca
readOnly: true
- name: tmp
mountPath: /tmp
volumes:
- name: config
configMap:
name: parca-pprof
- name: tmp
emptyDir: {}This example uses Parca v0.30.0, which supports --mode=otlp-forwarder.
The startup command substitutes the node name in the scrape configuration; Parca does not expand that placeholder itself.
If your operator release name or namespace differs, change --store-address to its collector Service. --insecure
selects plaintext gRPC for the internal collector connection, not for the collector's authenticated export to Dash0.
Parca does not need a Dash0 authorization token because the collector handles that export.
Keep this as a node-local DaemonSet when using the operator's node-local collector Service. The node selector in discovery prevents duplicate scraping, and the target pod UID and container name identify the application rather than the scraper. Align Parca and collector scheduling so each scraper has a local collector endpoint. Profiles use the operator's default export, not namespace-specific exports.
Check that the DaemonSet is ready and inspect its logs:
kubectl rollout status daemonset/parca-pprof --namespace dash0-system
kubectl logs --namespace dash0-system --selector app.kubernetes.io/name=parca-pprof --tail=100After at least one scrape interval, open Telemetry > Profiling in Dash0 and select my-go-service. Look for the
inuse_space profile type with unit bytes. An idle application with no retained allocations may produce an empty profile;
exercise the application before checking its flame graph.
If profiles are missing, check the application label, namespace, and named port against the pod spec. Confirm that
http://<pod-ip>:6060/debug/pprof/allocs is reachable from the scraper and that a collector is ready on the same node.
DaemonSet readiness alone does not prove that application scrapes or profile export are succeeding.
After changing the ConfigMap, restart Parca so the generated configuration picks up your changes:
kubectl rollout restart daemonset/parca-pprof --namespace dash0-system