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.
:::message 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. I did not cover much about the fact that OBI originated from Beyla, which Grafana Labs developed.
Grafana Beyla was donated to OpenTelemetry in 2025. The CNCF community now develops it under the name OpenTelemetry eBPF Instrumentation. Beyla did not disappear. It remains as a downstream distribution of OBI. What changes when you replace one with the other today?
I ran the latest OBI and Beyla container images available when I wrote this article on my Linux machine under the same conditions. I compared their output metrics and their configuration handling.
TL;DR
The telemetry core, including HTTP server metrics, route inference, protocol analysis, and eBPF probes, all comes from OBI and does not differ. The differences center on metric name prefixes, output paths for connecting to Grafana products, several extra features, and configuration file generations.
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. OBI always releases upstream. Most Beyla release notes describe 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 through variables such as attr.VendorPrefix, so external code can replace them. Beyla overrides the prefix 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, OBI rejected startup 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. Their scope covers only the PIDs of selected instrumented services, unlike an agent that covers the entire host. Take care 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 identifies their languages without instrumenting them. It lists the targets through the survey_info metric. The intended use is to provide an external auto-instrumentation system with an inventory of instrumentable targets. 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
Beyla provides a grafana.otlp section. You can write cloud_zone, cloud_instance_id, and cloud_api_key, and Beyla builds the Grafana Cloud OTLP endpoint and headers. OBI is published as a general-purpose eBPF instrumentation tool. You must configure its OTLP endpoint and authentication through the standard method: specify the OTLP endpoint URL and pass the constructed headers in the configuration.
Beyla also includes a receiver integration that sends spans directly to Grafana Alloy (pkg/export/alloy), connection spans for building a service graph across clusters on the Tempo side (BEYLA_TOPOLOGY_SPANS=inter_cluster), and a path that extracts only GenAI spans and sends them to another OTLP destination (BEYLA_GRAFANA_AI_*). Each feature is implemented as an additional node that subscribes to the output queue of the OBI pipeline. None of them modifies OBI itself.
SDK injection for Kubernetes
pkg/webhook is the largest Beyla-specific package. It receives Pod creation requests as a mutating webhook, identifies the language, and injects the OpenTelemetry SDK. Beyla’s own documentation does not describe it. The code explicitly labels it as experimental and says that it may be removed in the future. For now, it serves more as a way to study how Grafana plans to combine eBPF and SDK instrumentation than as a feature to evaluate for production use.
Differences in defaults
The same input can produce different results. One difference is the mode for inferring routes from HTTP paths. OBI defaults to heuristic. Beyla defaults to low-cardinality. In the latter mode, Beyla stores inferred routes in a tree for each process. When the number of segment types at the same level exceeds the default of 10, it replaces that level with a wildcard.
I sent 26 types of /shop/<word> requests and counted the http_route values that appeared in http_server_request_duration_seconds_count.
# OBI: The 26 types remain as they are
/shop/alpha /shop/bravo /shop/charlie ... /shop/zulu
# Beyla: Processes above the limit group them under /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 they stop when unknown string segments keep increasing. Beyla’s default is safer in environments that must control cardinality. 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_*, while Beyla uses BEYLA_*. Beyla also creates a corresponding variable that translates each variable beginning with BEYLA_ to the same name beginning with OTEL_EBPF_. You can therefore use the variable names in the upstream documentation unchanged.
Configuration file versions
The largest current difference is the configuration file version. OBI has read Config v2, which follows OpenTelemetry declarative configuration and contains file_format and extensions.obi, since v0.11.0. It 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}. Go’s internal package rules cannot be bypassed by a local replace, 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. It can receive traces and metrics as part of an OpenTelemetry Collector pipeline. This lets it run as part of the Collector instead of as an independent 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 a minor release 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.
Regardless of your choice, report and fix bugs in the OBI repository. Beyla’s README also asks contributors to send non-documentation pull requests upstream. When you investigate a problem found in Beyla, first check the commit that .obi-src points to, then search for the issue upstream.
Conclusion
Beyla is a distribution that includes OBI as a library and adds output paths and several extra features for connecting to Grafana products. OBI provides the instrumentation core. Beyla’s additions remain around that core: 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 is moving toward declarative configuration and expanded protocol support as it aims for 1.0 in 2026. Beyla still has areas that have not caught up, such as a configuration loader that reads only v1. For now, use OBI when you care about eBPF instrumentation itself. Choose Beyla when you combine it with Grafana products.
The last difference does not seem intentional. In Beyla,
telemetry_distro_versionbecomesunset, whilebeyla_build_infocorrectly containsversion=\"v3.35.0\". Build-time version embedding itself works. OBI copiesbuildinfo.VersionintoTelemetryDistroVersionwhen it initializes the package variable. Beyla overwrites that value later, insideOverrideOBIGlobalConfig. The copied initial value,unset, therefore remains. If your dashboards or alerts use this attribute, treat it as unset in Beyla. A fix for this issue has been merged into OBI itself, but it was not included in the released OBI or in the OBI that Beyla included when I wrote this article. ↩︎