Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
# 2. Compile the C/C++ examples using CMake
# 3. Run each example and report pass/fail
ffi-test = "run --package tools --bin ffi_test --"

62 changes: 62 additions & 0 deletions .github/workflows/pr-binary-size.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Binary Size

on:
pull_request:

jobs:
binary-size:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0 # need full history to check out base ref

- name: Install nightly + rust-src
run: rustup toolchain install nightly --component rust-src

- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
prefix-key: v0-rust-binary-size
cache-targets: true
cache-on-failure: true
workspaces: ". -> target"

- name: Compare binary size
run: |
BASE=$(git merge-base origin/main HEAD)
bash size-benchmark/compare-size.sh \
--base "$BASE" \
--head ${{ github.sha }} \
--output size-report.md

- name: Post PR comment
uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0
with:
script: |
const fs = require('fs');
const body = fs.readFileSync('size-report.md', 'utf8');
const marker = '<!-- binary-size-report -->';
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.data.find(c => c.body.includes(marker));
const fullBody = marker + '\n' + body;
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: fullBody,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: fullBody,
});
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ examples/cxx/exporter_manager.exe
examples/cxx/profiling
examples/cxx/profiling.exe
profile.pprof
.worktree-size-*
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ members = [
"libdd-http-client",
"libdd-log",
"libdd-log-ffi",
"size-benchmark",
]

# https://doc.rust-lang.org/cargo/reference/resolver.html
Expand Down Expand Up @@ -101,6 +102,14 @@ debug = false
incremental = false
opt-level = 3

# Profile used exclusively by the size-benchmark crate.
# Inherits release then tightens every size knob that cannot be set per-package.
[profile.release-size]
inherits = "release"
opt-level = "z" # "z" vs "s": skip loop vectorization too
strip = true
panic = "abort"

# https://camshaft.github.io/bolero/library-installation.html
[profile.fuzz]
inherits = "dev"
Expand Down
9 changes: 9 additions & 0 deletions libdd-ddsketch-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ pub extern "C" fn ddog_Vec_U8_drop(_vec: ffi::Vec<u8>) {
// The Vec will be automatically dropped when it goes out of scope
}

/// Dummy function for size-benchmark verification.
///
/// # Safety
/// Always safe to call.
#[no_mangle]
pub unsafe extern "C" fn ddog_ddsketch_dummy_size_bench() -> u64 {
42
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
31 changes: 31 additions & 0 deletions size-benchmark/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
# SPDX-License-Identifier: Apache-2.0

[package]
name = "size-benchmark"
edition.workspace = true
version.workspace = true
rust-version.workspace = true
license.workspace = true
publish = false

[[bin]]
name = "size-benchmark"
path = "src/main.rs"

[build-dependencies]
glob = "0.3.1"
syn = { version = "2.0.87", features = ["full", "parsing"] }

[dependencies]
libdd-common-ffi = { path = "../libdd-common-ffi" }
libdd-profiling-ffi = { path = "../libdd-profiling-ffi" }
libdd-crashtracker-ffi = { path = "../libdd-crashtracker-ffi" }
libdd-telemetry-ffi = { path = "../libdd-telemetry-ffi" }
libdd-data-pipeline-ffi = { path = "../libdd-data-pipeline-ffi" }
libdd-ddsketch-ffi = { path = "../libdd-ddsketch-ffi" }
libdd-library-config-ffi = { path = "../libdd-library-config-ffi" }
libdd-log-ffi = { path = "../libdd-log-ffi" }
datadog-ffe-ffi = { path = "../datadog-ffe-ffi" }
symbolizer-ffi = { path = "../symbolizer-ffi" }
libdd-shared-runtime-ffi = { path = "../libdd-shared-runtime-ffi" }
47 changes: 47 additions & 0 deletions size-benchmark/build-size-optimized.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# Build the size-benchmark binary with the same aggressive size optimizations
# that our most critical users apply, so the measured size is representative.
#
# On Linux → builds for {host-arch}-unknown-linux-musl (static, musl libc)
# On macOS → builds for the native Darwin target (no musl available on macOS)
#
# Requires: rustup with nightly toolchain + the resolved target installed.
# On Linux the musl target also needs a musl C toolchain (e.g. musl-tools package).
#
# Usage: ./size-benchmark/build-size-optimized.sh [extra cargo args]
# Output: binary size in bytes on stdout (last line)

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="${WORKSPACE_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}"

ARCH="$(uname -m | sed 's/arm64/aarch64/')"
OS="$(uname -s)"

case "$OS" in
Linux) TARGET="${ARCH}-unknown-linux-gnu" ;;
Darwin) TARGET="${ARCH}-apple-darwin" ;;
*) echo "Unsupported OS: $OS" >&2; exit 1 ;;
esac

rustup target add "$TARGET" --toolchain nightly >/dev/null 2>&1 || true

RUSTFLAGS="\
-Zunstable-options \
-Cpanic=immediate-abort \
-Zlocation-detail=none \
-Zfmt-debug=none \
" \
cargo +nightly build \
-Z build-std=std,panic_abort \
-Z build-std-features= \
--target "$TARGET" \
--profile release-size \
-p size-benchmark \
--manifest-path "$WORKSPACE_ROOT/Cargo.toml" \
"$@"

TARGET_DIR="${CARGO_TARGET_DIR:-$WORKSPACE_ROOT/target}"
BINARY="$TARGET_DIR/$TARGET/release-size/size-benchmark"
wc -c < "$BINARY"
106 changes: 106 additions & 0 deletions size-benchmark/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

//! Scans all *-ffi/src/**/*.rs files, finds every `#[no_mangle] pub extern "C"` function,
//! and emits $OUT_DIR/fptrs.rs:
//!
//! extern "C" { fn ddog_foo(...); fn ddog_bar(...); ... }
//! static FPTRS: &[unsafe extern "C" fn()] = &[ddog_foo as _, ddog_bar as _, ...];
//!
//! Storing every symbol in a non-dead static forces the linker to include every function
//! body (and its transitive call graph) in the final binary, which is what we want for
//! measuring realistic binary size after LTO.

use std::fmt::Write as _;
use std::path::Path;
use std::{env, fs};
use syn::{Item, Visibility};

const FFI_DIRS: &[&str] = &[
"libdd-common-ffi/src",
"libdd-profiling-ffi/src",
"libdd-crashtracker-ffi/src",
"libdd-telemetry-ffi/src",
"libdd-data-pipeline-ffi/src",
"libdd-ddsketch-ffi/src",
"libdd-library-config-ffi/src",
"libdd-log-ffi/src",
"datadog-ffe-ffi/src",
"symbolizer-ffi/src",
"libdd-shared-runtime-ffi/src",
];

fn main() {
let manifest = env::var("CARGO_MANIFEST_DIR").unwrap();
let workspace = Path::new(&manifest).parent().unwrap();
let current_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();

let mut names: Vec<String> = Vec::new();

for dir in FFI_DIRS {
println!("cargo:rerun-if-changed=../{dir}");
let src = workspace.join(dir);
let pattern = format!("{}/**/*.rs", src.display());
for path in glob::glob(&pattern).unwrap().flatten() {
let Ok(source) = fs::read_to_string(&path) else {
continue;
};
let Ok(file) = syn::parse_file(&source) else {
continue;
};
for item in &file.items {
let Item::Fn(f) = item else { continue };
if !matches!(f.vis, Visibility::Public(_)) {
continue;
}
let Some(abi) = &f.sig.abi else { continue };
if !matches!(&abi.name, Some(n) if n.value() == "C") {
continue;
}
if !f.attrs.iter().any(|a| a.path().is_ident("no_mangle")) {
continue;
}
// Skip items gated to windows on non-windows builds
let is_windows_only = f.attrs.iter().any(|a| {
if !a.path().is_ident("cfg") {
return false;
}
let Ok(list) = a.meta.require_list() else {
return false;
};
list.tokens.to_string().contains("windows")
});
if is_windows_only && current_os != "windows" {
continue;
}
names.push(f.sig.ident.to_string());
}
}
}

names.sort();
names.dedup();

let mut out = String::new();
writeln!(
out,
"// Auto-generated by size-benchmark/build.rs — DO NOT EDIT"
)
.unwrap();
writeln!(out, "extern \"C\" {{").unwrap();
for name in &names {
writeln!(out, " fn {name}();").unwrap();
}
writeln!(out, "}}").unwrap();
writeln!(out).unwrap();
writeln!(out, "#[used]").unwrap();
writeln!(out, "static FPTRS: &[unsafe extern \"C\" fn()] = &[").unwrap();
for name in &names {
writeln!(out, " {name} as _,").unwrap();
}
writeln!(out, "];").unwrap();
writeln!(out, "// {} symbols", names.len()).unwrap();

let out_dir = env::var("OUT_DIR").unwrap();
fs::write(Path::new(&out_dir).join("fptrs.rs"), out).unwrap();
}
43 changes: 43 additions & 0 deletions size-benchmark/cargo-bloat-optimized.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# Build the size-benchmark binary with the same aggressive size optimizations
# that our most critical users apply, so the measured size is representative.
#
# On Linux → builds for {host-arch}-unknown-linux-musl (static, musl libc)
# On macOS → builds for the native Darwin target (no musl available on macOS)
#
# Requires: rustup with nightly toolchain + the resolved target installed.
# On Linux the musl target also needs a musl C toolchain (e.g. musl-tools package).
#
# Usage: ./size-benchmark/build-size-optimized.sh [extra cargo args]
# Output: binary size in bytes on stdout (last line)

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

ARCH="$(uname -m | sed 's/arm64/aarch64/')"
OS="$(uname -s)"

case "$OS" in
Linux) TARGET="${ARCH}-unknown-linux-musl" ;;
Darwin) TARGET="${ARCH}-apple-darwin" ;;
*) echo "Unsupported OS: $OS" >&2; exit 1 ;;
esac

rustup target add "$TARGET" --toolchain nightly 2>/dev/null || true

RUSTFLAGS="\
-Zunstable-options \
-Cpanic=immediate-abort \
-Zlocation-detail=none \
-Zfmt-debug=none \
" \
cargo +nightly bloat \
-Z build-std=std,panic_abort \
-Z build-std-features= \
--target "$TARGET" \
--profile release-size \
-p size-benchmark \
--manifest-path "$WORKSPACE_ROOT/Cargo.toml" \
"$@"
Loading
Loading