This document covers all profiling and benchmarking tools available in this library.
Add the profiling feature to your Cargo.toml:
[dependencies]
yt-dlp = { version = "2.8.3", features = ["profiling"] }This enables the dhat global allocator hook so heap profiling data is collected automatically when running the profiling example.
cargo install flamegraph
# Profile a real URL
cargo flamegraph --bench profiling --features profiling --release -- <URL>
# Profile without network (dry-run mode)
cargo flamegraph --bench profiling --features profiling --release -- --dry-run
# Output is written to flamegraph.svg in the current directoryOpen flamegraph.svg in any browser to explore the interactive call-graph. Artefact path: flamegraph.svg (excluded by .gitignore).
cargo install samply
# Build first, then profile
cargo build --bench profiling --features profiling --release
samply record ./target/release/deps/profiling-* <URL>
# samply opens the Firefox profiler UI automaticallysamply captures kernel-level samples with minimal overhead. Use --dry-run to profile pure-Rust paths without network.
# Run the bench β dhat-heap.json is written on exit
cargo bench --bench profiling --features profiling -- <URL>
# Dry-run (no network)
cargo bench --bench profiling --features profiling -- --dry-run
# Open the viewer at:
# https://nnethercote.github.io/dh_view/dh_view.html
# then load dhat-heap.jsonThe dhat-heap.json file is written to the working directory and excluded by .gitignore. The online viewer shows total bytes allocated, live bytes at peak, and per-call-site allocation traces.
# Build without the profiling feature (heaptrack intercepts malloc at the OS level)
cargo build --bench profiling --release
heaptrack ./target/release/deps/profiling-* <URL>
# Analyse via CLI
heaptrack --analyze heaptrack.profiling.*
# Or open the GUI
heaptrack_gui heaptrack.profiling.*heaptrack output files match heaptrack.profiling.* and are excluded by .gitignore. Output files are written to profiling-output/ if you redirect there, or to the current directory.
All benchmarks are pure Rust with no network calls.
# Run all benchmark groups
cargo bench
# Run a specific group
cargo bench -- format_selection
cargo bench -- validation
cargo bench -- model_operations
cargo bench -- config_builders
cargo bench -- chapter_ops
cargo bench -- heatmap_ops
cargo bench -- playlist_ops
cargo bench -- format_type
cargo bench -- event_filter
cargo bench -- speed_profile
# Feature-gated groups
cargo bench --features webhooks -- retry_strategy
cargo bench --features cache -- cache_ops
# HTML reports are written to:
# target/criterion/<group>/<benchmark>/report/index.htmlOpen any index.html to see Criterion's interactive charts with violin plots and regression analysis.
FFmpeg is used for several operations in the library: combining separate audio and video streams, re-encoding with post-processing filters, embedding metadata and chapters, and extracting partial ranges. These are the most CPU-intensive operations and are measured through the profiling harness.
| Scenario | FFmpeg operation |
|---|---|
download_video |
Full pipeline β includes combine (video + audio β mp4) |
postprocess |
Re-encode with H.264/AAC codec (PostProcessConfig) |
# Profile the combine step (inside download_video)
cargo flamegraph --bench profiling --features profiling --release -- <URL> --scenario download_video
# Profile post-processing only
cargo flamegraph --bench profiling --features profiling --release -- <URL> --scenario postprocess
# Or use samply for a more detailed macOS/Linux profile
cargo build --bench profiling --features profiling --release
samply record ./target/release/deps/profiling-* <URL> --scenario postprocessThe config_builders Criterion benchmark (cargo bench -- config_builders) measures the pure-Rust
cost of building PostProcessConfig argument vectors, which is useful for confirming zero-copy
argument construction before FFmpeg is even invoked.
When profiling download_video, look for these call stacks:
execute_commandβ FFmpeg subprocess spawn (combine step)metadata::postprocess::applyβ metadata injectiondownload::fetcherβ parallel HTTP segment downloads
The postprocess scenario isolates the FFmpeg re-encode step from the download phase.
benches/compare.rs benchmarks every scenario from the README performance tables. For each
scenario it measures raw yt-dlp (metadata + download in one subprocess) and the three library
speed profiles (Conservative, Balanced, Aggressive), then prints the results as
Markdown tables ready to paste into the README.
# Default: 3 runs per scenario
cargo bench --bench compare --features profiling -- <URL> --cookies-from-browser safari
# Custom run count for better statistical accuracy
cargo bench --bench compare --features profiling -- <URL> --cookies-from-browser safari --runs 5The tool iterates over 14 scenarios grouped into 4 sections:
| Section | Scenarios | yt-dlp -f |
Library API |
|---|---|---|---|
| π΅ Audio streams | 96 kbps (Low), 128 kbps (Medium), 192 kbps (High), best | bestaudio[abr<=N] |
download_audio_stream_with_quality |
| π¬ Video streams | 480p, 720p, 1080p, best | bestvideo[height<=N] |
download_video_stream_with_quality |
| π¦ Native muxed | 360p (mp4), 720p (mp4) | best[height<=N][ext=mp4] |
download_format on AudioVideo formats |
| π¦ FFmpeg muxed | 480p, 720p, 1080p, best | bestvideo[height<=N]+bestaudio |
download_video_with_quality |
All scenarios that do not require network access run fine with --dry-run. This lets you measure pure-Rust code paths (format selection, validation, config builders, event bus, etc.) without any yt-dlp or ffmpeg binaries:
cargo bench --bench profiling --features profiling -- --dry-runYou can focus on a single scenario:
cargo bench --bench profiling --features profiling -- --dry-run --scenario format_selection
cargo bench --bench profiling --features profiling -- --dry-run --scenario event_bus
cargo bench --bench profiling --features profiling -- --dry-run --scenario validationRun the benchmarks then auto-generate the Markdown tables:
# Option 1: run benchmarks + print tables in one go
./scripts/bench-table.py --run
# Option 2: run with all feature-gated groups (webhooks, cache)
./scripts/bench-table.py --run-all
# Option 3: run benchmarks manually, then parse existing results
cargo bench --features "webhooks cache-json" --release
./scripts/bench-table.pyThe script reads target/criterion/*/new/estimates.json and outputs tables ready to paste below.
| Operation | 10 formats | 50 formats | 100 formats | 500 formats |
|---|---|---|---|---|
best_video_format |
||||
best_audio_format |
||||
worst_video_format |
||||
worst_audio_format |
||||
select_video High/AVC1 |
||||
select_audio Best/Opus |
| Operation | Time |
|---|---|
validate_youtube_url (valid) |
|
validate_youtube_url (youtu.be) |
|
validate_youtube_url (non-YT) |
|
validate_youtube_url (invalid) |
|
sanitize_filename (normal) |
|
sanitize_filename (special chars) |
|
sanitize_filename (unicode) |
|
sanitize_path (simple) |
|
sanitize_path (traversal) |
| Operation | Time |
|---|---|
has_chapters |
|
get_chapters |
|
has_heatmap |
|
has_subtitle_language_en |
|
serialize_to_json |
|
deserialize_from_json |
| Operation | Time |
|---|---|
ManagerConfig::default() |
|
ManagerConfig::builder().max(5).build() |
|
PostProcessConfig H264/AAC |
| Operation | 5 chapters | 20 chapters | 50 chapters | 100 chapters |
|---|---|---|---|---|
find_by_timestamp |
||||
search_by_title |
||||
contains_timestamp |
||||
validate |
| Operation | 10 points | 100 points | 1 000 points |
|---|---|---|---|
most_engaged_segment |
|||
get_highly_engaged_segments(0.7) |
|||
get_point_at_time(42.0) |
| Operation | 10 entries | 50 entries | 200 entries |
|---|---|---|---|
available_entries |
|||
search_entries_by_title |
|||
filter_by_uploader |
| Operation | Time |
|---|---|
format_type() β video-only |
|
format_type() β audio-only |
|
format_type() β muxed |
|
format_type() β manifest |
|
is_video() |
|
is_audio() |
| Operation | Time |
|---|---|
EventFilter::all().matches() (always true) |
|
EventFilter::only_terminal().matches() (terminal event) |
|
EventFilter::only_terminal().matches() (progress event, no match) |
| Profile | 1 MB | 50 MB | 500 MB | 2 GB |
|---|---|---|---|---|
| Conservative | ||||
| Balanced | ||||
| Aggressive |
| Operation | Time |
|---|---|
delay_for_attempt(0) |
|
delay_for_attempt(1) |
|
delay_for_attempt(3) |
|
should_retry (true) |
|
should_retry (false) |
| Operation | Time |
|---|---|
cache_put_single |
|
cache_get_hit |
|
cache_get_miss |
|
cache_put_500 |
| Path | Purpose |
|---|---|
profiling-libs/ |
yt-dlp / ffmpeg binaries downloaded by the profiling example |
profiling-output/ |
Video / audio files written during profiling runs |
dhat-heap.json |
dhat-rs heap profile output |
heaptrack.profiling.* |
heaptrack output files |
flamegraph.svg |
flamegraph CPU profile |
All of the above are excluded by .gitignore.