What Is the Difference Between Grafana Beyla and OpenTelemetry eBPF Instrumentation (OBI)?
Originally published in Japanese at https://zenn.dev/ymotongpoo/articles/20260916-beyla-obi-diff.
Test environment
- Host: Ubuntu, kernel 7.0.0-31-generic, with
/sys/kernel/btf/vmlinux - Instrumented target:
nginx:1.27-alpine(port 80 exposed to the host) - Agents:
otel/ebpf-instrument:v0.13.0(released 2026-09-04),grafana/beyla:3.35.0(released 2026-09-09, OBI submodule861d907) - Startup method:
docker run --privileged --pid=host --network=host, with the Prometheus endpoint scraped bycurl
Introduction
I gave a presentation about OpenTelemetry eBPF Instrumentation (OBI) at the recent Go Conference and published the details in a Zenn book. However, I barely mentioned that OBI started as Beyla, a project that Grafana Labs developed.
Grafana Beyla was donated to OpenTelemetry in 2025. Development now continues on the CNCF side under the name OpenTelemetry eBPF Instrumentation. Grafana Labs did not retire Beyla. It remains as a downstream distribution of OBI. What changes when you replace one with the other today?
On my Linux machine, I ran the latest OBI and Beyla container images at the time of writing under the same conditions. Then I compared the metrics that each one emitted and how each one accepted configuration.
TL;DR
The telemetry core (HTTP server metrics, route inference, protocol analysis, and eBPF probes) comes entirely from OBI and is the same in both. The differences are in metric name prefixes, output paths that connect to Grafana products, several extra features, and the configuration file version.
Repository relationship
The Beyla repository includes the OBI repository as the Git submodule .obi-src and replaces it in go.mod with replace go.opentelemetry.io/obi => ./.obi-src. Beyla is therefore a thin wrapper that vendors OBI as a library. The code volume reflects this relationship.
| Item | OBI | Beyla | Difference |
|---|---|---|---|
| Go lines of code (excluding tests, vendor, and generated code) | 153,939 | 10,275 | Beyla is 6.7% |
| Binary size (bytes) | 127,759,366 | 129,898,523 | 1.7% increase |
Beyla 3.35.0 pins OBI to commit 861d907, which is two commits after OBI tag v0.13.0. Releases always start upstream in OBI. Most entries in the Beyla release notes are OBI updates such as “Update OBI submodule to …”.
The Go packages that exist only on the Beyla side show the scope of its extra features.
| Package | Role |
|---|---|
pkg/webhook | A Kubernetes mutating webhook that injects the OpenTelemetry SDK into Pods (experimental feature) |
pkg/export/otel | OTLP configuration for Grafana Cloud, a separate output path for GenAI spans, and survey metrics |
cmd | Entry points for beyla, beyla-schema, and k8s-cache |
pkg/internal/infraolly | Collection of process CPU, memory, disk, and network metrics |
pkg/beyla | Conversion between Beyla configuration types and OBI configuration types |
pkg/export/prom | Prometheus output for process metrics |
pkg/services | Beyla-specific exclusion rules and survey selection conditions |
pkg/export/alloy | Integration with the Grafana Alloy trace receiver |
Differences in metric names and identifiers
I enabled only features: [application] for the same nginx instance and compared the metrics in Prometheus format. Metrics that follow semantic conventions have identical names. Only custom metrics outside those conventions have different prefixes.
| Metric or attribute | OBI | Beyla |
|---|---|---|
| HTTP server metrics | http_server_request_duration_seconds and others | Same |
| Build information | obi_build_info | beyla_build_info |
| Network flow | obi_network_flow_bytes_total | beyla_network_flow_bytes_total |
| Flow attribute | obi.ip | beyla.ip |
source in target_info | obi | beyla |
telemetry_sdk_name in target_info | opentelemetry | beyla |
telemetry_distro_version in target_info | v0.13.0 | unset |
OBI exposes the prefixes as variables such as attr.VendorPrefix so that external code can replace them. Beyla overwrites them with beyla at startup 1.
Features unique to Beyla
Process metrics
The application_process feature exists only in Beyla. When I passed the same value to OBI, it refused to start and listed the valid feature names.
$ docker run --rm ... -e OTEL_EBPF_PROMETHEUS_FEATURES=application,application_process \
otel/ebpf-instrument:v0.13.0
level=ERROR msg="wrong configuration" error="... unknown metrics feature \"application_process\"
(valid features: all, application, application_host, application_runtime,
application_service_graph, application_span, application_span_otel, application_span_sizes,
ebpf, network, network_flow_packets, network_inter_zone, stats,
stats_tcp_failed_connections, stats_tcp_io, stats_tcp_retransmits, stats_tcp_rtt)"
When I enabled it in Beyla, six additional metrics appeared for each instrumented process.
process_cpu_time_seconds_total
process_cpu_utilization_ratio
process_disk_io_bytes_total
process_memory_usage_bytes
process_memory_virtual_bytes
process_network_io_bytes_total
As their names indicate, these are operating system process metrics, not values collected by eBPF. Unlike a host-wide agent, Beyla limits their scope to the PIDs of the services that it selected for instrumentation. Be careful with process_network_io_bytes_total. The source, /proc/<pid>/net/dev, reports interface statistics per network namespace. Multiple processes in the same namespace therefore report the same value.
Survey mode
discovery.survey discovers processes and detects their languages, but does not instrument them. Instead, it lists the targets in the survey_info metric. The intended use is to give an external auto-instrumentation platform an inventory of the targets that it can instrument. When I gave both tools the same YAML, Beyla produced survey_info, while OBI ignored this key and performed normal instrumentation.
# Beyla
survey_info{...,job="nginx",service_name="nginx",source="beyla",...} 1
Output paths for Grafana products
The Beyla configuration has a grafana.otlp section. You only set cloud_zone, cloud_instance_id, and cloud_api_key, and Beyla builds the Grafana Cloud OTLP endpoint and headers. OBI is a general-purpose eBPF instrumentation tool. For OBI, you configure the OTLP endpoint and its authentication in the standard way: you specify the OTLP endpoint URL and pass the finished headers in the configuration.
Beyla has three more features of this kind:
- A receiver integration that passes spans directly to Grafana Alloy (
pkg/export/alloy) - Connection spans that let Tempo build a service graph across clusters (
BEYLA_TOPOLOGY_SPANS=inter_cluster) - A path that extracts only GenAI spans and sends them to a separate OTLP destination (
BEYLA_GRAFANA_AI_*)
Beyla implements each of them as an additional node that subscribes to the output queue of the OBI pipeline. None of them changes OBI itself.
SDK injection for Kubernetes
pkg/webhook is the largest of the Beyla-specific packages. As a mutating webhook, it receives Pod creation requests, detects the language, and injects the OpenTelemetry SDK. Beyla’s own documentation does not mention it. The code explicitly marks it as experimental and says that it may be removed in the future. For now, it is less a feature to evaluate and more material to read: it shows how Grafana is trying to implement operations that use eBPF and SDKs together.
Differences in defaults
In one place, the same input produces different results: the mode that infers routes from HTTP paths. OBI defaults to heuristic, and Beyla defaults to low-cardinality. In low-cardinality mode, Beyla keeps the inferred routes in a tree for each process. When the number of distinct segments at one level exceeds the default limit of 10, Beyla replaces that whole level with a wildcard.
I sent requests to 26 different /shop/<word> paths and counted the http_route values that appeared in http_server_request_duration_seconds_count.
# OBI: all 26 paths remain as they are
/shop/alpha /shop/bravo /shop/charlie ... /shop/zulu
# Beyla: in processes over the limit, they collapse into /shop/*
/shop/*
/shop/alpha /shop/bravo ...
Both tools correctly grouped /users/12345/orders/98 as /users/*/orders/*. They share the heuristic that turns numeric or hash-like segments into wildcards. The only difference is whether the tool stops when new, previously unseen string segments keep appearing. In environments that must limit cardinality, Beyla’s default is the safer one. To get the same behavior in OBI, set routes.unmatch: low-cardinality explicitly.
The default exclusion patterns for avoiding self-instrumentation also differ. OBI excludes obi and otelcol*. Beyla adds Grafana process names (*beyla, *alloy, and *prometheus-config-reloader), Kubernetes namespaces (grafana-alloy, monitoring, and others), and container names.
The environment variable prefixes differ as well. OBI uses OTEL_EBPF_*, and Beyla uses BEYLA_*. However, at startup Beyla takes each variable that starts with BEYLA_ and adds a copy with the OTEL_EBPF_ prefix. You can therefore also use the variable names from the upstream documentation as they are.
Configuration file versions
Today, the largest difference between the two is the configuration file version. Since v0.11.0, OBI can read Config v2. Config v2 follows OpenTelemetry declarative configuration, and its documents have file_format and extensions.obi. OBI also includes subcommands for validation and migration.
$ docker run --rm -v /tmp/v2.yaml:/cfg.yaml otel/ebpf-instrument:v0.13.0 config validate /cfg.yaml
configuration is valid
$ docker run -d ... -e OTEL_EBPF_CONFIG_PATH=/cfg.yaml otel/ebpf-instrument:v0.13.0
level=INFO msg="configuration loaded" version=v2
This is the v2 configuration that I tested.
file_format: "1.0"
meter_provider:
readers:
- pull:
exporter:
prometheus/development:
port: 9414
extensions:
obi:
version: "2.0"
capture:
policy:
default_action: exclude
rules:
- action: include
match:
process:
open_ports: "80"
When I gave the same file to Beyla, it tried to interpret it as v1 and failed.
$ docker run --rm ... -e BEYLA_CONFIG_PATH=/cfg.yaml grafana/beyla:3.35.0
level=ERROR msg="wrong Beyla configuration"
error="missing application discovery section or network metrics configuration."
You can confirm the reason in Beyla’s cmd/beyla/main.go. The v2 loader is in OBI’s internal/config/{schema,convert}. A local replace cannot bypass Go’s rule for internal packages, so Beyla can read only v1 until OBI exposes a public loader under pkg/. Beyla also has no subcommand such as beyla config validate.
OBI as an embedded component
OBI has a collector/ package that exposes a receiver factory for the obi component type. Inside an OpenTelemetry Collector pipeline, this receiver delivers traces and metrics. OBI can therefore run as part of the Collector instead of as a standalone agent process. Beyla’s corresponding path is its Alloy receiver integration.
The two projects also handle the telemetry contract differently. OBI publishes the metrics and spans that it emits as a Weaver-compatible schema registry under site/schemas/obi/<version>. It places that URL in the resource’s schema_url. The project records renames and removals as schema transformations. If you need to track changes to attribute or metric names mechanically, OBI is easier to manage because it provides this system.
OBI also documents its stability policy. Its VERSIONING.md classifies the current state as Development. It says that minor releases within v0 may introduce breaking changes to configuration, defaults, behavior, and output telemetry. It also says not to treat latest as a stable tag. The 2026 goals include 1.0, expanded protocol support, .NET support, and use alongside SDKs.
Which one should you choose?
The instrumentation engine is the same. Choose based on the destination and operational constraints.
If you connect to Grafana Cloud, Alloy, or Tempo service graphs, Beyla is the natural choice because it provides the configuration paths and process metrics for those products. Use OBI directly when you want to embed it as an OpenTelemetry Collector receiver, adopt declarative configuration (Config v2), or track output changes through telemetry schemas.
Whichever one you choose, bug reports and fixes go to the OBI repository. Beyla’s README also asks contributors to send pull requests other than documentation changes upstream. When you investigate a problem that you hit in Beyla, first check the commit that .obi-src points to. Then search the upstream issues. This is the fastest route.
Conclusion
This comparison confirmed that Beyla is a distribution that includes OBI as a library. On top of OBI, Beyla adds output paths to Grafana products and several extra features. The instrumentation itself is OBI. Beyla’s own parts are limited to the periphery: connections to Grafana Cloud and Alloy, process metrics, survey mode, and SDK injection for Kubernetes.
OBI is upstream, and Beyla follows it by updating the submodule. OBI aims for 1.0 in 2026 and is moving to declarative configuration and expanding protocol support. Beyla still has parts that have not caught up, such as its configuration loader, which reads only v1. For now, if you care about eBPF instrumentation itself, following OBI is enough. Choose Beyla when you combine it with Grafana products.
Only the last row does not seem to be an intended difference. In Beyla,
telemetry_distro_versionisunset. However,beyla_build_infocorrectly hasversion="v3.35.0", so build-time version embedding itself works. OBI’sTelemetryDistroVersioncopies OBI’sbuildinfo.Versionwhen the package variables initialize. Beyla overwrites that value later, insideOverrideOBIGlobalConfig. The copied initial value,unset, therefore remains. If your dashboards or alerts use this attribute, assume that it has no value in Beyla. A fix for this issue is merged into OBI itself. At the time of writing, however, neither a released OBI version nor the OBI commit that Beyla pins includes it. ↩︎