Skip to content

Measured performance

Field measurements of a Kiln v1.1.0 candidate against a real upstream at real bitrates - a one-hour soak, viewer fan-out, traffic planning, and the limits of this data.

Updated View as Markdown

The numbers in Lite, Core and Full come from deterministic in-repo fixtures and answer one question: which variant costs less. This page reports observed behaviour under real bitrates, a real upstream, and continuous load. The two runs differ in method and in reproducibility, so do not read them as one dataset.

Everything below was measured on a v1.1.0 candidate after the upstream TLS, fallback-option and connection-pool corrections described in this release. Build differences between that candidate and the published v1.1.0 artifact are still possible.

One hour of continuous dual-channel playback

Two encrypted DASH live sources were consumed simultaneously for one hour: a 4K channel at roughly 15 Mbps on a same-host path, and a 1080p channel at roughly 5 Mbps across an additional network hop. The consumer was ffmpeg performing a full fetch and demux, not a single manifest request.

Metric Value
4K channel 3551 seconds of media timeline consumed in 3600 seconds of wall clock
1080p channel 3519 seconds
Buffering and underrun events 0
CPU 3.07% mean, 13.9% peak (123 samples)
Container memory 106 MiB mean, 27 floor, 241 peak (1 GiB limit)
Upstream ingest 8.76 GB over roughly 59 minutes, recorded mean 19.7 Mbps
Egress 8.63 GB over the same window, recorded mean 19.4 Mbps
Server-side warnings 0

CPU percentages treat one core as 100%. The run did not retain a separate first-packet timestamp or a steady-state PTS slope, so the gap between media duration and wall clock cannot be divided between startup, playlist-edge offset, and lag accumulated during the run. This figure alone does not prove that steady state always kept pace with real time.

What the run supports:

  • Ingest and egress are nearly equal (19.7 against 19.4). That is consistent with fetching the primary video clock plus requested non-primary renditions, but the run had no control with rendition idling disabled and therefore does not quantify the bytes saved.
  • Memory is a sawtooth, not a climb. A 27 MiB floor, a 241 MiB peak and a 106 MiB mean, with no monotonic rise across the hour, is the expected shape for segment buffers plus GC. The peak is about 24% of the 1 GiB limit, leaving roughly 783 MiB.
  • Every server-side counter stayed at zero: slow segments, re-anchors, stalls, session restarts and handshake failures.
  • Native-path logs recorded twelve track pauses and five rewarms, direct evidence that rendition_idle_sec triggered during the run, but the run did not quantify its byte savings.

The ffmpeg input path logged 2 Packet corrupt entries at the same timestamp for the 4K channel. The second path logged 13 entries, 7 of them End of file. Because -c copy performs no decode, these messages only establish that the input or demux path observed an anomaly; their counts alone cannot attribute the cause. Neither consumer process was interrupted.

Viewer fan-out

One channel was consumed by 1, 5, 10 and 20 concurrent clients to test whether upstream pulling grows with the audience.

Concurrent viewers Upstream ingest Egress CPU Container memory
1 2.01 Mbps 1.98 Mbps 0.79% 67 to 119 MiB
5 4.37 Mbps 21.2 Mbps 1.74% 124 to 156 MiB
10 5.75 Mbps 14.9 Mbps 1.96% 150 to 214 MiB
20 3.84 Mbps 72.6 Mbps 2.27% 143 to 165 MiB

This run did not show upstream pulling growing in proportion to viewer count. Ingest stayed in the same 2 to 6 Mbps band from 1 to 20 viewers, and was lower at 20 viewers (3.84) than at 10 (5.75), while egress over the same period reached 72.6 Mbps.

The server-side counters make the sharing more direct. kiln_sessions stayed at 1 throughout. Upstream segment fetches ran at 4.3 per 10 seconds with 10 viewers, 3.9 per 10 seconds with 20 viewers, and 3 per 10 seconds in gaps where no viewer was connected but the channel had not yet idled out. These rates are of the same order and show that the clients shared one upstream session. With only one 60-second window per tier, the data does not establish statistical independence between viewer count and upstream bytes.

In this sample CPU rose from 0.79% with one viewer to 2.27% with twenty, slower than the viewer count. Memory showed no monotonic relationship. Treating either relationship as a capacity result would require longer windows and repeated runs.

Every client selected the first rendition in the master playlist (576p50, roughly 2 Mbps including audio), so this demonstrates same-rendition fan-out. When viewers select non-primary renditions, Kiln keeps one primary video pull as the clock and adds one pull for each requested non-primary rendition.

Traffic planning

When the selected rendition is the primary video, its nominal bitrate gives this order of magnitude for one viewer. Selecting a non-primary rendition also retains the primary video clock:

Rendition Per hour Per month at 2 hours a day
About 2 Mbps (576p plus audio) 0.9 GB About 54 GB
About 5 Mbps (1080p plus audio) 2.3 GB About 135 GB
About 15 Mbps (4K plus audio) 6.8 GB About 405 GB

The traffic counter covered roughly 59 minutes rather than a full hour, so its 8.76 GB total cannot be compared directly with the 9.0 GB hourly estimate. The recorded 19.7 Mbps mean corresponds to about 8.9 GB per hour; because the exact window duration and raw byte count were not retained here, no further cause is assigned to the difference. Use the table as a preliminary data-transfer allowance estimate and leave margin based on a long measurement of your own deployment. Multiple viewers on the same rendition of one channel share one upstream session.

Live latency

This run sampled how far the playlist edge trailed the upstream timestamp, not end-to-end latency at the player’s screen. End-to-end results also depend on the output engine, playlist window, and player buffering. The native and ffmpeg compatibility paths produce different output structures, so one set of TARGETDURATION or LL-HLS part settings cannot describe both.

Environment and method

The measurements ran in an arm64 container limited to 1 GiB of memory. The build under test was a v1.1.0 candidate.

The sources were two encrypted DASH live streams reached through an HTTP forward proxy, with 8 second upstream segments. Every consumer used ffmpeg’s -c copy -f null -, fetching and demuxing in full without transcoding, so the measurement covers the real segment download and packaging path. The 4K stream used a same-host path and served as the primary criterion; the 1080p stream crossed an additional network hop and is reference only because that path adds unrelated jitter.

CPU and memory come from the container’s cgroup v2 counters (usage_usec in cpu.stat, and memory.current); traffic comes from exact byte counters for the container’s eth0 in /proc/net/dev. Summary network counters rounded to three significant digits are not precise enough for short-window differences at GB scale.

Each fan-out step waited 75 seconds before counting, so that the clients’ catch-up burst was over and they had reached steady state, then counted over a 60 second window, with a check at both ends of the window confirming the clients were still alive. Buffering was judged from both sides: underruns and stalls in the client ffmpeg logs, and Kiln’s own counters for slow segments, re-anchors, stalls, session restarts and handshake failures.

Reproducing this on your own deployment

The path above cannot be reproduced directly. The repository ships scripts/live-performance.sh for a similar short, single-channel run against your own source. It measures cold start, time to the first manifest, throughput as a ratio of real time, peak RSS and peak CPU. It starts Kiln on the host and samples through ps; it does not reproduce this page’s soak, fan-out, or container cgroup method:

KILN_PERF_CONFIG=/path/to/your/kiln.toml \
KILN_PERF_CHANNELS=my-channel \
KILN_PERF_CAPTURE_SECONDS=60 \
  sh scripts/live-performance.sh

There is no packaged script for soak and fan-out, but the method is not complicated: consume your own channel with ffmpeg’s -c copy -f null - while sampling the container’s cgroup counters at a fixed interval. What matters is waiting for steady state before opening the counting window and confirming the consumers are still alive at both ends of it, otherwise you measure the catch-up burst instead of the steady state.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close