What it was
A game server running at 60 Hz has about 16 ms for each tick. Adding metrics code takes some of that time, and if the collector occasionally spikes then you've introduced the sort of problem you're trying to measure. I worked on the observability system across C#, C++ and Go, including the metric definitions, OpenTelemetry pipeline and default dashboards. I built the Unity SDK end to end and wrote the public metrics documentation. The wider work covered Unity and Unreal servers on Windows and Linux.
Build
- We designed the metric names, units and behaviour together, so the C#, C++ and Go implementations could feed the same dashboards.
- OpenTelemetry carries the instrumentation, with exports to Amazon Managed Grafana and CloudWatch. Studios can use their existing observability tools.
- The collection path needed to take a fraction of a millisecond consistently. Aggregation and export happen away from the server tick.
- The C# SDK includes a DogStatsD-style API for custom metrics, plus server connections, network traffic, CPU, memory and frame timings.
- A Unity-specific processor works with the engine's runtime constraints to collect accurate measurements.
- I also reviewed parity across the C++ and Go SDKs and the Unreal integration, and tested dedicated builds on Linux and Windows.
- Preconfigured dashboards and the metrics documentation ship with the telemetry, which was folded into the main server SDKs.
Key decisions
Timing data
An average frame time can look healthy while some frames are very slow. Histograms preserve the distribution, so the percentiles can show that tail.
OpenTelemetry
I wanted the game-server metrics to work with the same tooling a studio already used elsewhere. OpenTelemetry gave us that without inventing another export format.
Installation
We folded telemetry into the main server SDKs. That removed a separate package for studios to install and keep at the right version.
Lesson learned
The averages looked fine while the bad frames were still there.the rest of it ↓fold it back up ↑
Moving timings to histograms and percentiles showed bad frames that the averages had hidden. We had to be just as careful with the collector's own timings, including the occasional slow tick. There was a lot to agree across the three SDKs too. A metric with the same name needs the same units and behaviour in each language, or a studio looking at the shared dashboard can end up comparing different things.