https://github.com/polarsignals/otel-profiling-go
Easily connect distributed tracing with profiling data.
https://github.com/polarsignals/otel-profiling-go
distributed-tracing go golang opentelemetry opentelemetry-go pprof profiling tracing
Last synced: about 1 year ago
JSON representation
Easily connect distributed tracing with profiling data.
- Host: GitHub
- URL: https://github.com/polarsignals/otel-profiling-go
- Owner: polarsignals
- License: apache-2.0
- Created: 2024-03-04T16:22:39.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-10-15T16:37:12.000Z (over 1 year ago)
- Last Synced: 2025-04-20T12:15:30.098Z (about 1 year ago)
- Topics: distributed-tracing, go, golang, opentelemetry, opentelemetry-go, pprof, profiling, tracing
- Language: Go
- Homepage:
- Size: 4.33 MB
- Stars: 14
- Watchers: 3
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# otel-profiling-go
This package provides an integration between distributed tracing via OpenTelemetry with Profiling data collected via eBPF by [Parca Agent](https://github.com/parca-dev/parca-agent). The best thing about this is that it isn't actually a deep integration with Parca Agent, it just puts the trace ID into a place that Parca Agent will know how to find.
> Note: Currently Parca Agent only supports reading the trace ID in Go 1.22.
More specifically it provides three ways to do so:
* An OpenTelemetry `Tracer` implementation
* A gRPC middleware
* An HTTP middleware
What these provide are ways to automatically [set Go's goroutine labels](https://pkg.go.dev/runtime/pprof#SetGoroutineLabels). Goroutine labels are then accessible to Parca Agent via eBPF, as they are stored in thread-local-store, which the Go runtime manages, which has a well-known layout, so it is easy for the Parca Agent to know how to read them.
## Using the Tracer
The tracer is the simplest way to get started, as it can be used as a drop-in replacement for your existing tracer, and you're all set! The tracer is also the least efficient, as it causes allocations with every new span that's created.
```go
import (
otelprof "github.com/polarsignals/otel-profiling-go"
)
main() {
tp := initTracer()
otel.SetTracerProvider(otelprof.NewTracerProvider(tp))
// your application...
}
```
See a full example in [`./tracer-example`](./tracer-example).
## Middleware
Our recommendation is to use a middleware approach, where the trace ID is set once per request, causing only constant allocations, instead of allocations per span.
### HTTP
For HTTP use the handler wrapper. Make sure it is called after an initial trace ID has been set on the context.
```go
import (
"github.com/polarsignals/otel-profiling-go/otelhttpprofiling"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func main() {
handler := otelhttp.NewHandler(otelhttpprofiling.Handler(http.HandlerFunc(fibHandler)), "fibHandler")
// ... actually serve handler
}
```
See a full example in [`./http-example`](./http-example).
### gRPC
For gRPC use the gRPC middleware. Same as the HTTP middleware, ensure that it is after the otel interceptors to ensure a trace ID is already set on the context.
```go
import (
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
)
main() {
otel.SetTracerProvider(initTracer())
grpcotelprof := otelgrpcprofiling.NewMiddleware()
server := grpc.NewServer(
grpc_middleware.WithUnaryServerChain(
otelgrpc.UnaryServerInterceptor(),
grpcotelprof.GrpcUnaryInterceptor,
),
grpc_middleware.WithStreamServerChain(
otelgrpc.StreamServerInterceptor(),
grpcotelprof.GrpcStreamInterceptor,
),
)
// ... register gRPC services, serve it, etc.
}
```
See a full example in [`./grpc-example`](./grpc-example).