Skip to content

Commit 6dc1f76

Browse files
authored
Merge branch 'main' into fix/split-lint-ci-for-faster-feedback
2 parents 958b8bb + 345cd74 commit 6dc1f76

15 files changed

Lines changed: 361 additions & 58 deletions

File tree

.github/workflows/benchmark.yml

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,35 @@
1-
# This workflow runs a Criterion benchmark on a PR and compares the results against the base branch.
2-
# It is triggered on a PR or a push to main.
1+
# This workflow has two jobs:
2+
# 1. compareBenchmark: Runs on PRs with the "performance" label, comparing Criterion
3+
# benchmark results against the base branch using criterion-compare-action.
4+
# 2. continuousBenchmark: Runs daily on main via schedule, storing benchmark results
5+
# in the gh-pages branch and publishing a dashboard via github-action-benchmark.
6+
# Skips runs where the HEAD commit has already been benchmarked.
37
#
4-
# The workflow is gated on the presence of the "performance" label on the PR.
5-
#
6-
# The workflow runs on a self-hosted runner pool. We can't use the shared runners for this,
7-
# because they are only permitted to run on the default branch to preserve resources.
8-
#
9-
# In the future, we might like to consider using bencher.dev or the framework used by otel-golang here.
10-
on:
8+
# The PR job runs on shared GitHub runners to save resources.
9+
# The continuous job runs on a self-hosted bare-metal runner for consistent, accurate results.
10+
on:
1111
pull_request:
1212
types: [labeled, synchronize]
13-
push:
14-
branches:
15-
- main
16-
name: benchmark pull requests
13+
schedule:
14+
- cron: '0 6 * * *' # daily at 06:00 UTC
15+
workflow_dispatch:
16+
name: benchmark
1717
permissions:
1818
contents: read
1919
jobs:
20-
runBenchmark:
21-
name: run benchmark
20+
# ---------------------------------------------------------------------------
21+
# PR benchmark comparison
22+
# ---------------------------------------------------------------------------
23+
compareBenchmark:
24+
name: compare benchmarks (PR)
25+
if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'performance')
26+
runs-on: ubuntu-latest
2227
permissions:
2328
pull-requests: write
24-
25-
# If we're running on main, use our oracle bare-metal runner for accuracy.
26-
# If we're running on a PR, use github's shared workers to save resources.
27-
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'oracle-bare-metal-64cpu-512gb-x86-64' }}
28-
if: ${{ (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'performance')) || github.event_name == 'push' }}
2929
container:
3030
image: rust:slim-bullseye
3131
env:
32-
# For PRs, compare against the base branch - e.g., 'main'.
33-
# For pushes to main, compare against the previous commit
34-
BRANCH_NAME: ${{ github.event_name == 'pull_request' && github.base_ref || github.event.before }}
32+
BRANCH_NAME: ${{ github.base_ref }}
3533
GIT_DISCOVERY_ACROSS_FILESYSTEM: 1
3634
steps:
3735
- name: Harden the runner (Audit all outbound calls)
@@ -41,15 +39,15 @@ jobs:
4139

4240
- name: Setup container environment
4341
run: |
44-
apt-get update && apt-get install --fix-missing -y unzip cmake build-essential pkg-config curl git
42+
apt-get update && apt-get install --fix-missing -y unzip cmake build-essential pkg-config curl git libssl-dev
4543
cargo install cargo-criterion
4644
4745
- name: Make repo safe for Git inside container
4846
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
4947

5048
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
5149
with:
52-
fetch-depth: 10 # Fetch a bit of history so we can do perf diffs
50+
fetch-depth: 10
5351

5452
- uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0
5553
with:
@@ -58,3 +56,67 @@ jobs:
5856
- uses: boa-dev/criterion-compare-action@adfd3a94634fe2041ce5613eb7df09d247555b87 # v3.2.4
5957
with:
6058
branchName: ${{ env.BRANCH_NAME }}
59+
60+
# ---------------------------------------------------------------------------
61+
# Continuous benchmark tracking (daily schedule)
62+
# ---------------------------------------------------------------------------
63+
continuousBenchmark:
64+
name: continuous benchmark tracking
65+
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
66+
# Use bare-metal runner on the upstream repo for consistent results; fall back to shared runners elsewhere
67+
runs-on: ${{ github.repository == 'open-telemetry/opentelemetry-rust' && 'oracle-bare-metal-64cpu-512gb-x86-64' || 'ubuntu-latest' }}
68+
permissions:
69+
contents: write
70+
container:
71+
image: rust:slim-bullseye
72+
env:
73+
GIT_DISCOVERY_ACROSS_FILESYSTEM: 1
74+
steps:
75+
- name: Harden the runner (Audit all outbound calls)
76+
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
77+
with:
78+
egress-policy: audit
79+
80+
- name: Setup container environment
81+
run: |
82+
apt-get update && apt-get install --fix-missing -y unzip cmake build-essential pkg-config curl git libssl-dev
83+
84+
- name: Make repo safe for Git inside container
85+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
86+
87+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
88+
89+
- name: Check if commit already benchmarked
90+
id: check_duplicate
91+
run: |
92+
# Fetch the benchmark data file from gh-pages and see if this commit is already recorded
93+
DATA_URL="https://raw.githubusercontent.com/${{ github.repository }}/gh-pages/dev/bench/data.js"
94+
if curl -sf "$DATA_URL" | grep -q "${{ github.sha }}"; then
95+
echo "skip=true" >> "$GITHUB_OUTPUT"
96+
echo "Commit ${{ github.sha }} already benchmarked, skipping."
97+
else
98+
echo "skip=false" >> "$GITHUB_OUTPUT"
99+
fi
100+
101+
- uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0
102+
if: steps.check_duplicate.outputs.skip != 'true'
103+
with:
104+
repo-token: ${{ secrets.GITHUB_TOKEN }}
105+
106+
- name: Run benchmarks
107+
if: steps.check_duplicate.outputs.skip != 'true'
108+
run: cargo bench --workspace --all-features -- --output-format bencher | tee output.txt
109+
110+
- name: Store benchmark result
111+
if: steps.check_duplicate.outputs.skip != 'true'
112+
uses: benchmark-action/github-action-benchmark@a7bc2366eda11037936ea57d811a43b3418d3073 # v1.21.0
113+
with:
114+
tool: 'cargo'
115+
output-file-path: output.txt
116+
github-token: ${{ secrets.GITHUB_TOKEN }}
117+
auto-push: true
118+
benchmark-data-dir-path: dev/bench
119+
# Alert if a benchmark regresses by more than 20%
120+
alert-threshold: '120%'
121+
comment-on-alert: true
122+
fail-on-alert: false

CONTRIBUTING.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,14 @@ All such features should adhere to naming convention `<signal>_<feature_name>`
324324
- Run `cargo test --all` - this will execute code and doc tests for all
325325
projects in this workspace.
326326
- Run `cargo bench` - this will run benchmarks to show performance
327-
- Run `cargo bench` - this will run benchmarks to show performance
328327
regressions
329328

329+
Benchmarks are run daily against `main` and results are tracked over time.
330+
The continuous benchmark dashboard is published at
331+
<https://open-telemetry.github.io/opentelemetry-rust/dev/bench/>.
332+
PRs with the `performance` label will also get a benchmark comparison
333+
comment showing any regressions or improvements.
334+
330335
## FAQ
331336

332337
### Where should I put third party propagators/exporters, contrib or standalone crates?

docs/metrics.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -730,10 +730,12 @@ Follow these guidelines when deciding where to attach metric attributes:
730730

731731
```rust
732732
// Example: Setting resource-level attributes
733-
let resource = Resource::new(vec![
734-
KeyValue::new("service.name", "payment-processor"),
735-
KeyValue::new("deployment.environment", "production"),
736-
]);
733+
// Use Resource::builder() to preserve SDK-provided defaults
734+
// (telemetry.sdk.*, service.name).
735+
let resource = Resource::builder()
736+
.with_service_name("payment-processor")
737+
.with_attributes([KeyValue::new("deployment.environment.name", "production")])
738+
.build();
737739
```
738740

739741
* **Meter-level attributes**: If the dimension applies only to a subset of

opentelemetry-appender-log/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ experimental_metadata_attributes = ["dep:opentelemetry-semantic-conventions"]
2828
opentelemetry_sdk = { workspace = true, features = ["testing"] }
2929
opentelemetry-stdout = { workspace = true, features = ["logs"] }
3030
log = { workspace = true, features = ["kv_serde"] }
31-
tokio = { workspace = true }
31+
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
3232
serde = { workspace = true, features = ["std", "derive"] }
3333

3434
[lints]

opentelemetry-otlp/src/exporter/tonic/mod.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,4 +962,96 @@ mod tests {
962962
result.unwrap_err()
963963
);
964964
}
965+
966+
#[test]
967+
#[cfg(not(feature = "gzip-tonic"))]
968+
fn test_gzip_compression_errors_without_feature() {
969+
use crate::exporter::ExporterBuildError;
970+
use crate::SpanExporter;
971+
use crate::WithTonicConfig;
972+
973+
let result = SpanExporter::builder()
974+
.with_tonic()
975+
.with_compression(crate::Compression::Gzip)
976+
.build();
977+
978+
assert!(result.is_err());
979+
let err = result.unwrap_err();
980+
assert!(
981+
matches!(
982+
err,
983+
ExporterBuildError::FeatureRequiredForCompressionAlgorithm(..)
984+
),
985+
"expected FeatureRequiredForCompressionAlgorithm error, got: {err:?}"
986+
);
987+
let msg = err.to_string();
988+
assert!(
989+
msg.contains("gzip-tonic"),
990+
"error message should mention 'gzip-tonic' feature, got: {msg}"
991+
);
992+
}
993+
994+
#[tokio::test]
995+
#[cfg(feature = "gzip-tonic")]
996+
async fn test_gzip_compression_succeeds_with_feature() {
997+
use crate::SpanExporter;
998+
use crate::WithTonicConfig;
999+
1000+
let result = SpanExporter::builder()
1001+
.with_tonic()
1002+
.with_compression(crate::Compression::Gzip)
1003+
.build();
1004+
1005+
assert!(
1006+
result.is_ok(),
1007+
"gzip compression should succeed when gzip-tonic feature is enabled, got: {:?}",
1008+
result.unwrap_err()
1009+
);
1010+
}
1011+
1012+
#[test]
1013+
#[cfg(not(feature = "zstd-tonic"))]
1014+
fn test_zstd_compression_errors_without_feature() {
1015+
use crate::exporter::ExporterBuildError;
1016+
use crate::SpanExporter;
1017+
use crate::WithTonicConfig;
1018+
1019+
let result = SpanExporter::builder()
1020+
.with_tonic()
1021+
.with_compression(crate::Compression::Zstd)
1022+
.build();
1023+
1024+
assert!(result.is_err());
1025+
let err = result.unwrap_err();
1026+
assert!(
1027+
matches!(
1028+
err,
1029+
ExporterBuildError::FeatureRequiredForCompressionAlgorithm(..)
1030+
),
1031+
"expected FeatureRequiredForCompressionAlgorithm error, got: {err:?}"
1032+
);
1033+
let msg = err.to_string();
1034+
assert!(
1035+
msg.contains("zstd-tonic"),
1036+
"error message should mention 'zstd-tonic' feature, got: {msg}"
1037+
);
1038+
}
1039+
1040+
#[tokio::test]
1041+
#[cfg(feature = "zstd-tonic")]
1042+
async fn test_zstd_compression_succeeds_with_feature() {
1043+
use crate::SpanExporter;
1044+
use crate::WithTonicConfig;
1045+
1046+
let result = SpanExporter::builder()
1047+
.with_tonic()
1048+
.with_compression(crate::Compression::Zstd)
1049+
.build();
1050+
1051+
assert!(
1052+
result.is_ok(),
1053+
"zstd compression should succeed when zstd-tonic feature is enabled, got: {:?}",
1054+
result.unwrap_err()
1055+
);
1056+
}
9651057
}

opentelemetry-sdk/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## vNext
44

5+
- **Breaking** The SDK `testing` feature is now runtime agnostic. [#3407][3407]
6+
- `TokioSpanExporter` and `new_tokio_test_exporter` have been renamed to `TestSpanExporter` and `new_test_exporter`.
7+
- The following transitive dependencies and features have been removed: `tokio/rt`, `tokio/time`, `tokio/macros`, `tokio/rt-multi-thread`, `tokio-stream`, `experimental_async_runtime`
58
- Add 32-bit platform support by using `portable-atomic` for `AtomicI64` and `AtomicU64` in the metrics module. This enables compilation on 32-bit ARM targets (e.g., `armv5te-unknown-linux-gnueabi`, `armv7-unknown-linux-gnueabihf`).
69
- `Aggregation` enum and `StreamBuilder::with_aggregation()` are now stable and no longer require the `spec_unstable_metrics_views` feature flag.
710
- Fix `service.name` Resource attribute fallback to follow OpenTelemetry
@@ -29,6 +32,7 @@
2932
[3312]: https://github.com/open-telemetry/opentelemetry-rust/pull/3312
3033
[3248]: https://github.com/open-telemetry/opentelemetry-rust/pull/3248
3134
[3262]: https://github.com/open-telemetry/opentelemetry-rust/pull/3262
35+
[3407]: https://github.com/open-telemetry/opentelemetry-rust/pull/3407
3236

3337
- "spec_unstable_logs_enabled" feature flag is removed. The capability (and the
3438
backing specification) is now stable and is enabled by default.

opentelemetry-sdk/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ rustdoc-args = ["--cfg", "docsrs"]
3737
criterion = { workspace = true, features = ["html_reports"] }
3838
rstest = { workspace = true }
3939
temp-env = { workspace = true }
40+
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
4041

4142
[target.'cfg(not(target_os = "windows"))'.dev-dependencies]
4243
pprof = { workspace = true }
@@ -47,7 +48,7 @@ trace = ["opentelemetry/trace", "rand", "percent-encoding"]
4748
jaeger_remote_sampler = ["trace", "opentelemetry-http", "http", "serde", "serde_json", "url", "experimental_async_runtime"]
4849
logs = ["opentelemetry/logs"]
4950
metrics = ["opentelemetry/metrics"]
50-
testing = ["opentelemetry/testing", "trace", "metrics", "logs", "rt-tokio", "rt-tokio-current-thread", "tokio/macros", "tokio/rt-multi-thread"]
51+
testing = ["opentelemetry/testing", "trace", "metrics", "logs", "tokio/sync"]
5152
experimental_async_runtime = []
5253
rt-tokio = ["tokio/rt", "tokio/time", "tokio-stream", "experimental_async_runtime"]
5354
rt-tokio-current-thread = ["tokio/rt", "tokio/time", "tokio-stream", "experimental_async_runtime"]

opentelemetry-sdk/src/logs/batch_log_processor.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,7 @@ mod tests {
756756
use opentelemetry::logs::LogRecord;
757757
use opentelemetry::InstrumentationScope;
758758
use opentelemetry::KeyValue;
759-
use std::sync::atomic::{AtomicUsize, Ordering};
759+
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
760760
use std::sync::{Arc, Mutex};
761761
use std::time::Duration;
762762

@@ -839,6 +839,29 @@ mod tests {
839839
assert_eq!(config.max_queue_size, 4096);
840840
assert_eq!(config.max_export_batch_size, 1024);
841841
}
842+
#[test]
843+
fn test_force_flush_being_called() {
844+
#[derive(Debug, Clone)]
845+
struct MockExporter {
846+
export_called: Arc<AtomicBool>,
847+
}
848+
impl LogExporter for MockExporter {
849+
async fn export(&self, _batch: LogBatch<'_>) -> OTelSdkResult {
850+
self.export_called.store(true, Ordering::SeqCst);
851+
Ok(())
852+
}
853+
}
854+
let exporter = MockExporter {
855+
export_called: Arc::new(AtomicBool::new(false)),
856+
};
857+
let processor = BatchLogProcessor::new(exporter.clone(), BatchConfig::default());
858+
let scope = opentelemetry::InstrumentationScope::builder("my-crate")
859+
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
860+
.build();
861+
processor.emit(&mut SdkLogRecord::new(), &scope);
862+
processor.force_flush().unwrap();
863+
assert!(exporter.export_called.load(Ordering::SeqCst));
864+
}
842865

843866
#[test]
844867
fn test_batch_config_max_export_batch_size_validation() {

opentelemetry-sdk/src/logs/log_processor_with_async_runtime.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -775,13 +775,17 @@ mod tests {
775775
BatchLogProcessor::new(exporter.clone(), BatchConfig::default(), runtime::Tokio);
776776
let provider = SdkLoggerProvider::builder()
777777
.with_log_processor(processor)
778-
.with_resource(Resource::new(vec![
779-
KeyValue::new("k1", "v1"),
780-
KeyValue::new("k2", "v3"),
781-
KeyValue::new("k3", "v3"),
782-
KeyValue::new("k4", "v4"),
783-
KeyValue::new("k5", "v5"),
784-
]))
778+
.with_resource(
779+
Resource::builder_empty()
780+
.with_attributes(vec![
781+
KeyValue::new("k1", "v1"),
782+
KeyValue::new("k2", "v3"),
783+
KeyValue::new("k3", "v3"),
784+
KeyValue::new("k4", "v4"),
785+
KeyValue::new("k5", "v5"),
786+
])
787+
.build(),
788+
)
785789
.build();
786790
provider.force_flush().unwrap();
787791
assert_eq!(exporter.get_resource().unwrap().into_iter().count(), 5);

0 commit comments

Comments
 (0)