· TechMeetup Ostrava #23 · Ostrava · given in Czech

Application Performance Monitoring with OpenTelemetry

Application Performance Monitoring (APM) s OpenTelemetry. Why your app is slow, and how to find out before your users tell you. What observability means, how tracing works, and why OpenTelemetry is the way to instrument your code once and send the data anywhere.

Update, 2026OpenTelemetry for PHP has matured since this talk. Its traces, metrics and logs are now all marked stable, and there’s zero-code instrumentation through a PECL extension, which does much of the manual work shown below for you. Also, OpenTelemetry deprecated its Jaeger exporters: Jaeger accepts OTLP natively, so export OTLP instead.

How to measure performance

There are a few ways to find out your app is slow:

This talk is about the last one.

Observability first

To understand OpenTelemetry, you have to understand observability first. Observability means you can understand the system from the outside. You can answer the question “why is this happening?” and easily troubleshoot the problem.

To call an application observable, it must be properly instrumented, which means it emits signals. Properly instrumented means you don’t need to add more instrumentation when you’re troubleshooting an issue. The signals come in three overlapping kinds: metrics, which you can aggregate, tracing, which is scoped to a request, and logging, which records events.

What APM gives you

Application Performance Monitoring gathers data about requests, called traces: framework boot, SQL queries, Redis, HTTP calls, business logic and so on. It saves the data into storage, so you can aggregate and analyse it and set up alerts. The result is a high-level picture of what’s happening in your requests.

I showed what that looks like in Elastic APM: a service overview with latency, throughput and failed transaction rate compared with the day before, a waterfall of a single checkout request across services, latency distribution with automatic correlations (for example, slow requests correlating with large orders and HTTP 500 responses), and a service map of how all the services call each other.

Traces and spans

Two terms matter:

Trace diagram drawn as nested bars over time: a client span contains /api, which contains /authN, /authZ, /paymentGateway with DB and Ext. Merchant calls, and /dispatch with /dispatch/search, three /poll calls and /pollDriver/{id}.
One trace, many spans. Each bar is a span, nested under the span that called it.

A span is just data. Here’s an example span as JSON:

{
  "trace_id": "7bba9f33312b3dbb8b2c2c62bb7abe2d",
  "parent_id": "",
  "span_id": "086e83747d0e381e",
  "name": "/v1/sys/health",
  "start_time": "2021-10-22 16:04:01.209458162 +0000 UTC",
  "end_time": "2021-10-22 16:04:01.209514132 +0000 UTC",
  "status_code": "STATUS_CODE_OK",
  "status_message": "",
  "attributes": {
    "http.method": "GET",
    "http.target": "/v1/sys/health",
    "http.scheme": "http",
    "http.host": "10.177.2.152:26040"
  },
  "events": {
    "name": "",
    "message": "OK",
    "timestamp": "2021-10-22 16:04:01.209512872 +0000 UTC"
  }
}

No silver bullet

There are many APM tools: Datadog, New Relic, SolarWinds, AppDynamics, Blackfire, Dynatrace, Sentry, Elastic APM. Choosing one used to mean committing to it:

OpenTelemetry to the rescue

The community recognised the need for a standard. In May 2019 two open-source projects merged: OpenTracing (CNCF) and OpenCensus (Google). The result, OpenTelemetry (OTel), is a collection of tools, APIs and SDKs that help you instrument, generate, collect and export telemetry data. In its own words: “OpenTelemetry is the mechanism by which application code is instrumented, to help make a system observable.”

The architecture

OpenTelemetry architecture: inside the application, code feeds the SDK, which feeds an exporter. The exporter sends data to the collector’s receiver, and the collector’s exporter sends it on to the telemetry backend.
Code, SDK and exporter live in your app. The collector sits between your app and the backend.

Inside the collector is a pipeline: several receivers, a chain of processors, and a fan-out to several exporters. Without a collector, every VM process, Kubernetes pod or node runs its own agent that pushes data to each backend separately. With a collector service in the middle, the apps only talk to the collector, and it talks to Jaeger, Prometheus or whatever else you use.

At Sharry we used exactly that: apps in Node, Java, .NET, Go and Python, instrumented with OpenTelemetry agents, send data to OpenTelemetry collectors, which export it over the OpenTelemetry protocol into Elastic Observability: APM Server, Elasticsearch and the Kibana observability apps.

Instrumenting a PHP app

Instrumentation differs per language. There’s automatic instrumentation, and there’s manual. Manually, you create a span around the code you care about:

class HelloController
{
    private OpenTelemetry\SDK\Trace\Tracer $tracer;

    /**
     * @Route("/hello", name="hello")
     */
    public function index(): Response
    {
        $span = $this->tracer->spanBuilder(__METHOD__)->startSpan();
        /* Do some stuff */
        $span->end();

        return new Response('Hello')
    }
}

For a Symfony app you want one root span per request. A kernel event subscriber starts it on the request and ends it on terminate:

class TracingKernelSubscriber implements EventSubscriberInterface
{
    private Tracer $tracer;
    private SpanInterface|null $mainSpan = null;

    public function onRequestEvent(RequestEvent $event)
    {
        $this->mainSpan = $this->tracer->spanBuilder('main')->startSpan();
        $this->mainSpan->activate();
    }

    public function onTerminateEvent(TerminateEvent $event): void
    {
        $this->mainSpan->end();
    }

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::REQUEST => 'onRequestEvent',
            KernelEvents::TERMINATE => 'onTerminateEvent',
        ];
    }
}

Where the data goes is just configuration. The same spans can go to several backends at once:

trace:
  exporters:

    exporter1: zipkin+https://zipkinhost:1234

    exporter2: jaeger+https://foo:bar@jaegerhost:1234

    exporter3:
      type: newrelic
      url: http://newrelichost:1234
      options:
        license_key: abcde

The talk ended with a live demo, which isn’t in the slides. The code for it is on GitHub in opentelemetry-apm-php-demo, and the demo itself is in the meetup stream linked below.

All 31 slides
  1. Slide 1: Title: Application Performance Monitoring with OpenTelemetry, over a photo of slow snails
  2. Slide 2: Speaker slide: Jan Mikeš, #TechMeetupOstrava 08.06.2022, peon.dev and Sharry logos
  3. Slide 3: How to measure performance: users tell you, profiling, performance testing, performance monitoring
  4. Slide 4: Observability: understand the system from outside, answer "Why is this happening?"
  5. Slide 5: Meme: elderly woman squinting at a computer screen, captioned "I can't see anything!"
  6. Slide 6: Instrumented apps emit signals; Venn diagram of metrics, tracing, and logging
  7. Slide 7: Elastic APM overview of advertService: latency chart, throughput, and transactions
  8. Slide 8: Elastic APM trace waterfall of a POST /checkout request across microservices
  9. Slide 9: Elastic APM latency distribution histogram and failed transaction correlations table
  10. Slide 10: Elastic APM service map showing frontend, checkout, payment, and other microservices
  11. Slide 11: Application Performance Monitoring: gather request traces, save and aggregate data, high-level picture
  12. Slide 12: Tracing: trace (transaction) and span, with a span timeline diagram of API calls
  13. Slide 13: Example span JSON with trace_id, span_id, timestamps, status, and HTTP attributes
  14. Slide 14: APM tools logos: Datadog, New Relic, SolarWinds, AppDynamics, Blackfire.io, Dynatrace, Sentry, Elastic APM
  15. Slide 15: Morpheus from The Matrix offering the red and blue pills
  16. Slide 16: No silver bullet: each backend has different instrumentation, no standard data format
  17. Slide 17: South Park "Captain Hindsight" meme captioned "To the rescue"
  18. Slide 18: OpenTelemetry: OpenTracing and OpenCensus merged in 05/2019 into a standard set of tools, APIs, and SDKs
  19. Slide 19: OpenTelemetry is vendor-neutral, pluggable, and NOT an observability backend like Datadog or New Relic
  20. Slide 20: OTel architecture: agent, exporter, collector, and backend of your choice, with pipeline diagram
  21. Slide 21: OTel collector: optional, vendor-agnostic receive, process, export pipeline diagram
  22. Slide 22: Deployment diagram: agents as binary, sidecar, or daemonset exporting to multiple backends
  23. Slide 23: Deployment diagram: agents sending data through a central OpenTelemetry Collector service to backends
  24. Slide 24: Sharry setup: apps instrumented with OpenTelemetry agents sending to Elastic APM and Kibana
  25. Slide 25: Instrumentation: differs per language, automatic or manual
  26. Slide 26: PHP code: manually starting and ending an OpenTelemetry span in a controller
  27. Slide 27: PHP code: Symfony event subscriber starting a span on request and ending it on terminate
  28. Slide 28: YAML config of OpenTelemetry trace exporters: Zipkin, Jaeger, and New Relic
  29. Slide 29: Resources: opentelemetry.io, opentelemetry-apm-php-demo on GitHub, jaegertracing.io, Elastic APM
  30. Slide 30: Disaster Girl meme: "Live demo! What could possibly go wrong?"
  31. Slide 31: Homer Simpson asking "Questions?" and Thank you! @honza_mikes, janmikes.cz, sharry.tech

Related talks