Pyroscope
Starting from the Version-1.67.0 release, DataKit has added a collector named Pyroscope. It supports the ingestion of data reported by the Grafana Pyroscope Agent, assisting users in identifying performance bottlenecks in aspects such as CPU, memory, and IO within applications.
Configuration¶
Go to the conf.d/pyroscope directory under the DataKit installation directory, copy pyroscope.conf.sample and name it pyroscope.conf. Examples are as follows:
[[inputs.pyroscope]]
## pyroscope Agent endpoints register by version respectively.
## Endpoints can be skipped listen by remove them from the list.
## Default value set as below. DO NOT MODIFY THESE ENDPOINTS if not necessary.
endpoints = ["/ingest"]
## set true to enable election, pull mode only
election = true
## the max allowed size of http request body (of MB), 32MB by default.
body_size_limit_mb = 32 # MB
## set false to stop generating apm metrics from ddtrace output.
generate_metrics = true
## io_config is used to control profiling uploading behavior.
## cache_path set the disk directory where temporarily cache profiling data.
## cache_capacity_mb specify the max storage space (in MiB) that profiling cache can use.
## clear_cache_on_start set whether we should clear all previous profiling cache on restarting Datakit.
## upload_workers set the count of profiling uploading workers.
## send_timeout specify the http timeout when uploading profiling data to dataway.
## send_retry_count set the max retry count when sending every profiling request.
# [inputs.pyroscope.io_config]
# cache_path = "/usr/local/datakit/cache/pyroscope_inputs" # C:\Program Files\datakit\cache\pyroscope_inputs by default on Windows
# cache_capacity_mb = 10240 # 10240MB
# clear_cache_on_start = false
# upload_workers = 8
# send_timeout = "75s"
# send_retry_count = 4
## set custom tags for profiling data
# [inputs.pyroscope.tags]
# some_tag = "some_value"
# more_tag = "some_other_value"
After configuration, restart DataKit to enable the Pyroscope profiler.
Currently, the profiler can be enabled by injecting the profiler configuration through the ConfigMap method.
Custom Tag¶
You can specify other tags in the configuration through [inputs.pyroscope.tags]
:
Agent Configuration¶
The Pyroscope profiler currently supports the access of Pyroscope Agents in three languages: Java, Python, Go and Rust. Other languages are being added:
Download the latest pyroscope.jar package from Github and start your application as a Java Agent:
PYROSCOPE_APPLICATION_NAME="java-pyro-demo" \
PYROSCOPE_LOG_LEVEL=debug \
PYROSCOPE_FORMAT="jfr" \
PYROSCOPE_PROFILER_EVENT="cpu" \
PYROSCOPE_LABELS="host=$(hostname),service=java-pyro-demo,version=1.2.3,env=dev,some_other_tag=other_value" \
PYROSCOPE_UPLOAD_INTERVAL="60s" \
PYROSCOPE_JAVA_STACK_DEPTH_MAX=512 \
PYROSCOPE_PROFILING_INTERVAL="10ms" \
PYROSCOPE_PROFILER_ALLOC=128k \
PYROSCOPE_PROFILER_LOCK=10ms \
PYROSCOPE_ALLOC_LIVE=false \
PYROSCOPE_GC_BEFORE_DUMP=true \
PYROSCOPE_SERVER_ADDRESS="http://127.0.0.1:9529" \
java -javaagent:pyroscope.jar -jar your-app.jar
Install the pyroscope-io
dependency package:
Import the pyroscope-io
package in the code:
import os
import pyroscope
import socket
pyroscope.configure(
server_address="http://127.0.0.1:9529",
detect_subprocesses=True,
oncpu=True,
enable_logging=True,
report_pid=True,
report_thread_id=True,
report_thread_name=True,
tags={
"host": socket.gethostname(),
"service": 'python-pyro-demo',
"version": 'v1.2.3',
"env": "testing",
"process_id": os.getpid(),
}
)
Start the application:
Add the pyroscope-go
module:
Import the module and initialize it:
import (
"log"
"os"
"runtime"
"strconv"
"time"
"github.com/grafana/pyroscope-go"
)
func Must[T any](t T, _ error) T {
return t
}
runtime.SetMutexProfileFraction(5)
runtime.SetBlockProfileRate(5)
profiler, err := pyroscope.Start(pyroscope.Config{
ApplicationName: "go-pyroscope-demo",
// replace this with the address of pyroscope server
ServerAddress: "http://127.0.0.1:9529",
// you can disable logging by setting this to nil
Logger: pyroscope.StandardLogger,
// uploading interval period
UploadRate: time.Minute,
// you can provide static tags via a map:
Tags: map[string]string{
"service": "go-pyroscope-demo",
"env": "demo",
"version": "1.2.3",
"host": Must(os.Hostname()),
"process_id": strconv.Itoa(os.Getpid()),
},
ProfileTypes: []pyroscope.ProfileType{
// these profile types are enabled by default:
pyroscope.ProfileCPU,
pyroscope.ProfileAllocObjects,
pyroscope.ProfileAllocSpace,
pyroscope.ProfileInuseObjects,
pyroscope.ProfileInuseSpace,
// these profile types are optional:
pyroscope.ProfileGoroutines,
pyroscope.ProfileMutexCount,
pyroscope.ProfileMutexDuration,
pyroscope.ProfileBlockCount,
pyroscope.ProfileBlockDuration,
},
})
if err!= nil {
log.Fatal("unable to bootstrap pyroscope profiler: ", err)
}
defer profiler.Stop()
-
Add the dependency crates
pyroscope
andpyroscope_pprofrs
to your project: -
Initialize and start the Pyroscope Rust profiling task in your code:
use pyroscope::{PyroscopeAgent, Result}; use pyroscope_pprofrs::{pprof_backend, PprofConfig}; fn main() -> Result<()> { let pprof_config = PprofConfig::new().sample_rate(100).report_thread_id().report_thread_name(); // Basic configuration such as sampling rate let backend_impl = pprof_backend(pprof_config); // Pyroscope agent configuration let agent = PyroscopeAgent::builder("http://127.0.0.1:9529", "pyroscope-rust-app") .backend(backend_impl) .tags([("version", "1.23.4"), ("env", "demo"), ("host", "<your-hostname>")].to_vec()) .build()?; // start the Pyroscope agent let agent_running = agent.start()?; // your application code... // gracefully shutdown the Pyroscope agent let agent_ready = agent_running.stop()?; agent_ready.shutdown(); Ok(()) }
Info
The Pyroscope Rust SDK currently only works properly on the Linux platform.
Association with OpenTelemetry Tracing Data¶
By associating with tracing data (which Grafana calls Span profiles), users can more easily identify system performance bottlenecks. Pyroscope provides relevant OpenTelemetry plugins to associate data between the two. Currently, it supports languages such as Java, Python, and Go. The following sections introduce them respectively.
Note
To facilitate the differentiation of different instances of the same service, we can randomly generate a UUID when the process is started, and then set this UUID as the runtime_id
tag on all the tracing and profiling data throughout the entire lifecycle of the process. In this way, the observability cloud can correlate the two sets of data. In addition to the runtime_id
tag, it is also recommended that all applications add tags such as host
(hostname), service
(service name), version
(service version), env
(deployment environment), and process_id
(process ID of the service startup process) to make it easier for the observability cloud to correlate various collected metrics and data.
- Download the Java agent package opentelemetry-javaagent.jar provided by the OpenTelemetry official.
- Download the Pyroscope OpenTelemetry plugin pyroscope-otel.jar.
- Make the corresponding settings and start your Java application:
uuid=$(uuidgen); OTEL_SERVICE_NAME="java-pyro-demo" \ OTEL_RESOURCE_ATTRIBUTES="runtime_id=$uuid,host=$(hostname),service.name=java-pyro-demo,service.version=1.3.55,service.env=dev" \ OTEL_JAVAAGENT_EXTENSIONS=./pyroscope-otel.jar \ OTEL_PYROSCOPE_ADD_PROFILE_URL=false \ OTEL_PYROSCOPE_ADD_PROFILE_BASELINE_URL=false \ OTEL_PYROSCOPE_START_PROFILING=true \ OTEL_TRACES_EXPORTER=otlp \ OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://127.0.0.1:9529/otel/v1/traces" \ OTEL_EXPORTER_OTLP_METRICS_ENDPOINT="http://127.0.0.1:9529/otel/v1/metrics" \ OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="http://127.0.0.1:9529/otel/v1/logs" \ OTEL_EXPORTER_OTLP_COMPRESSION=gzip \ PYROSCOPE_APPLICATION_NAME="java-pyro-demo" \ PYROSCOPE_LOG_LEVEL=debug \ PYROSCOPE_FORMAT="jfr" \ PYROSCOPE_PROFILER_EVENT="cpu" \ PYROSCOPE_LABELS="runtime_id=$uuid,host=$(hostname),service=java-pyro-demo,version=1.2.3,env=dev,other_tag=other_value" \ PYROSCOPE_UPLOAD_INTERVAL="60s" \ PYROSCOPE_JAVA_STACK_DEPTH_MAX=512 \ PYROSCOPE_PROFILING_INTERVAL="10ms" \ PYROSCOPE_PROFILER_ALLOC=128k \ PYROSCOPE_PROFILER_LOCK=10ms \ PYROSCOPE_ALLOC_LIVE=false \ PYROSCOPE_GC_BEFORE_DUMP=true \ PYROSCOPE_SERVER_ADDRESS="http://127.0.0.1:9529" \ java -javaagent:opentelemetry-javaagent.jar -jar your-app.jar
Tips
The above uses the uuidgen
command to randomly generate a UUID, and sets the runtime_id
tag for tracing and profiling through the environment variables OTEL_RESOURCE_ATTRIBUTES
and PYROSCOPE_LABELS
respectively. Other environment variable settings are for reference only. Please modify them according to the actual situation or refer to the official documentation.
-
Install the
pyroscope-otel
dependency library: -
Import the
pyroscope-otel
andopentelemetry
libraries and make the corresponding configurations:import uuid import socket import os import pyroscope from opentelemetry import trace from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from pyroscope.otel import PyroscopeSpanProcessor from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter UUID = uuid.uuid1() # Randomly generate a UUID when the process starts otelExporter = OTLPSpanExporter(endpoint='http://127.0.0.1:4317', insecure=True, timeout=30) tracerProvider = TracerProvider(resource=Resource(attributes={ "service.name": "python-pyro-demo", "service.version": "v3.5.7", "service.env": "dev", "host": socket.gethostname(), "process_id": os.getpid(), "runtime_id": str(UUID), # Set the runtime_id tag for tracing })) tracerProvider.add_span_processor(PyroscopeSpanProcessor()) tracerProvider.add_span_processor(BatchSpanProcessor(span_exporter=otelExporter, max_queue_size=100, max_export_batch_size=30)) trace.set_tracer_provider(tracerProvider) tracer = trace.get_tracer("python-pyro-demo") pyroscope.configure( server_address="http://127.0.0.1:9529", detect_subprocesses=True, oncpu=True, enable_logging=True, report_pid=True, report_thread_id=True, report_thread_name=True, tags={ "runtime_id": str(UUID), # Set the runtime_id tag for profiling "host": socket.gethostname(), "service": 'python-pyro-demo', "version": 'v0.2.3', "env": "testing", "process_id": os.getpid(), } ) if __name__ == '__main__': # your app code pyroscope.shutdown()
-
Add the
pyroscope-go
library: -
Configure and start OpenTelemetry and Pyroscope:
package main import ( "github.com/google/uuid" otelpyroscope "github.com/grafana/otel-profiling-go" "github.com/grafana/pyroscope-go" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" tracesdk "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) var ( UUID = uuid.NewString() // Generate a global UUID otelTracer trace.Tracer ) func hostname() string { host, _ := os.Hostname() return host } func main() { otelExporter, err := otlptracehttp.New(context.Background(), otlptracehttp.WithEndpointURL("http://127.0.0.1:9529/otel/v1/traces"), otlptracehttp.WithInsecure(), otlptracehttp.WithTimeout(time.Second*15), ) if err != nil { log.Fatal("unable to init otel tracing exporter: ", err) } tracerProvider := tracesdk.NewTracerProvider(tracesdk.WithBatcher(otelExporter, tracesdk.WithBatchTimeout(time.Second*3)), tracesdk.WithResource(resource.NewSchemaless( attribute.String("runtime_id", UUID), // Set the runtime_id tag for tracing attribute.String("service.name", "go-pyroscope-demo"), attribute.String("service.version", "v0.0.1"), attribute.String("service.env", "dev"), attribute.String("host", hostname()), attribute.String("process_id", strconv.Itoa(os.Getpid())) )), ) defer tracerProvider.Shutdown(context.Background()) otel.SetTracerProvider(otelpyroscope.NewTracerProvider(tracerProvider)) otelTracer = otel.Tracer("go-pyroscope-demo") log.Printf("otel tracing started....\n") runtime.SetMutexProfileFraction(5) runtime.SetBlockProfileRate(5) profiler, err := pyroscope.Start(pyroscope.Config{ ApplicationName: "go-pyroscope-demo", // replace this with the address of pyroscope server ServerAddress: "http://127.0.0.1:9529", // you can disable logging by setting this to nil Logger: pyroscope.StandardLogger, // uploading interval period UploadRate: time.Minute, // you can provide static tags via a map: Tags: map[string]string{ "runtime_id": UUID, // Set the runtime_id tag for profiling "env": "demo", "version": "0.0.1", "host": hostname(), "process_id": strconv.Itoa(os.Getpid()), }, ProfileTypes: []pyroscope.ProfileType{ // these profile types are enabled by default: pyroscope.ProfileCPU, pyroscope.ProfileAllocObjects, pyroscope.ProfileAllocSpace, pyroscope.ProfileInuseObjects, pyroscope.ProfileInuseSpace, // these profile types are optional: pyroscope.ProfileGoroutines, pyroscope.ProfileMutexCount, pyroscope.ProfileMutexDuration, pyroscope.ProfileBlockCount, pyroscope.ProfileBlockDuration, }, }) if err != nil { log.Fatal("unable to bootstrap pyroscope profiler: ", err) } log.Printf("pyroscope profiler started....\n") defer profiler.Stop() // your app code... }