Skip to content

Commit 31e2b0c

Browse files
committed
add documentation and tests for metric_doc macro
1 parent 26c622b commit 31e2b0c

File tree

8 files changed

+270
-10
lines changed

8 files changed

+270
-10
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

datafusion/doc/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@ name = "datafusion_doc"
4242

4343
[dependencies]
4444
inventory = "0.3"
45+
46+
[dev-dependencies]
47+
datafusion-macros = { workspace = true }
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//! Integration tests for the `#[metric_doc]` macro.
19+
//!
20+
//! These tests verify:
21+
//! 1. Cross-file usage: metrics structs defined in one file, exec in another
22+
//! 2. Multiple metrics groups: one exec referencing multiple metrics structs
23+
24+
mod test_helpers;
25+
26+
use datafusion_doc::metric_doc_sections::{DocumentedExec, DocumentedMetrics};
27+
use test_helpers::separate_exec::UserDefinedExec;
28+
use test_helpers::separate_metrics::{MetricsGroupA, MetricsGroupB};
29+
30+
/// Test that metrics structs in a separate file correctly implement DocumentedMetrics
31+
#[test]
32+
fn test_cross_file_metrics_have_documented_metrics_trait() {
33+
// MetricsGroupA should implement DocumentedMetrics
34+
let doc_a = MetricsGroupA::metric_doc();
35+
assert_eq!(doc_a.name, "MetricsGroupA");
36+
assert!(doc_a.doc.contains("First group of metrics"));
37+
assert_eq!(doc_a.fields.len(), 2);
38+
39+
// MetricsGroupB should implement DocumentedMetrics
40+
let doc_b = MetricsGroupB::metric_doc();
41+
assert_eq!(doc_b.name, "MetricsGroupB");
42+
assert!(doc_b.doc.contains("Second group of metrics"));
43+
assert_eq!(doc_b.fields.len(), 2);
44+
}
45+
46+
/// Test that an exec with multiple metrics groups correctly implements DocumentedExec
47+
#[test]
48+
fn test_exec_with_multiple_metrics_groups() {
49+
let exec_doc = UserDefinedExec::exec_doc();
50+
51+
// Verify exec documentation
52+
assert_eq!(exec_doc.name, "UserDefinedExec");
53+
assert!(exec_doc.doc.contains("user-defined execution plan"));
54+
55+
// Verify that both metrics groups are linked
56+
assert_eq!(
57+
exec_doc.metrics.len(),
58+
2,
59+
"Expected 2 metrics groups, got {}",
60+
exec_doc.metrics.len()
61+
);
62+
63+
// Verify the metrics are the correct ones (order should match declaration order)
64+
let metric_names: Vec<&str> = exec_doc.metrics.iter().map(|m| m.name).collect();
65+
assert_eq!(
66+
metric_names[0], "MetricsGroupA",
67+
"Expected MetricsGroupA in metrics, got {}",
68+
metric_names[0]
69+
);
70+
assert_eq!(
71+
metric_names[1], "MetricsGroupB",
72+
"Expected MetricsGroupB in metrics, got {}",
73+
metric_names[1]
74+
);
75+
}
76+
77+
/// Test that field documentation is correctly extracted from metrics structs
78+
#[test]
79+
fn test_metrics_field_documentation() {
80+
let doc_a = MetricsGroupA::metric_doc();
81+
82+
// Check that field docs are extracted
83+
let field_names: Vec<&str> = doc_a.fields.iter().map(|f| f.name).collect();
84+
assert_eq!(field_names[0], "phase_a_time");
85+
assert_eq!(field_names[1], "phase_a_rows");
86+
87+
// Check that field descriptions are captured
88+
let time_field = doc_a.fields.iter().find(|f| f.name == "phase_a_time");
89+
assert_eq!(
90+
time_field.unwrap().doc.trim(),
91+
"Time spent executing phase A"
92+
);
93+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//! Test helper modules for metric_doc macro tests.
19+
20+
pub mod separate_exec;
21+
pub mod separate_metrics;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//! Exec struct that references metrics from a different file.
19+
//! This demonstrates cross-file usage and multiple metrics groups.
20+
21+
#![allow(dead_code)]
22+
23+
use datafusion_macros::metric_doc;
24+
25+
// Import metrics from the separate file
26+
use super::separate_metrics::{MetricsGroupA, MetricsGroupB};
27+
28+
/// A user-defined execution plan that demonstrates:
29+
/// 1. Referencing metrics from a different file
30+
/// 2. Having multiple metrics groups
31+
#[metric_doc(MetricsGroupA, MetricsGroupB)]
32+
pub struct UserDefinedExec {
33+
/// Some internal state
34+
pub state: String,
35+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//! Metrics structs defined in a separate file from the exec.
19+
//! This demonstrates that metric_doc works across file boundaries.
20+
21+
#![allow(dead_code)]
22+
23+
use datafusion_macros::metric_doc;
24+
25+
/// First group of metrics for UserDefinedExec.
26+
/// Tracks phase A execution statistics.
27+
#[metric_doc]
28+
pub struct MetricsGroupA {
29+
/// Time spent executing phase A
30+
pub phase_a_time: u64,
31+
/// Number of rows processed in phase A
32+
pub phase_a_rows: usize,
33+
}
34+
35+
/// Second group of metrics for UserDefinedExec.
36+
/// Tracks phase B execution statistics.
37+
#[metric_doc]
38+
pub struct MetricsGroupB {
39+
/// Time spent executing phase B
40+
pub phase_b_time: u64,
41+
/// Number of rows processed in phase B
42+
pub phase_b_rows: usize,
43+
}

datafusion/ffi/tests/utils/mod.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use std::sync::Arc;
19-
20-
use datafusion::prelude::SessionContext;
21-
use datafusion_execution::TaskContextProvider;
22-
use datafusion_ffi::execution::FFI_TaskContextProvider;
23-
use datafusion_ffi::proto::logical_extension_codec::FFI_LogicalExtensionCodec;
24-
use datafusion_proto::logical_plan::DefaultLogicalExtensionCodec;
18+
#[cfg(feature = "integration-tests")]
19+
use {
20+
datafusion::prelude::SessionContext, datafusion_execution::TaskContextProvider,
21+
datafusion_ffi::execution::FFI_TaskContextProvider,
22+
datafusion_ffi::proto::logical_extension_codec::FFI_LogicalExtensionCodec,
23+
datafusion_proto::logical_plan::DefaultLogicalExtensionCodec, std::sync::Arc,
24+
};
2525

26+
#[cfg(feature = "integration-tests")]
2627
pub fn ctx_and_codec() -> (Arc<SessionContext>, FFI_LogicalExtensionCodec) {
2728
let ctx = Arc::new(SessionContext::default());
2829
let task_ctx_provider = Arc::clone(&ctx) as Arc<dyn TaskContextProvider>;

datafusion/macros/src/lib.rs

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,72 @@ pub fn user_doc(args: TokenStream, input: TokenStream) -> TokenStream {
110110
}
111111

112112
/// Helper attribute to register metrics structs or execs for documentation generation.
113-
/// Usage:
114-
/// - `#[metric_doc(common)]` on `*_Metrics` structs (derives `DocumentedMetrics`)
115-
/// - `#[metric_doc(M1, M2)]` on `*_Exec` structs to link metrics (derives `DocumentedExec`)
113+
///
114+
/// # Usage
115+
///
116+
/// ## On Metrics Structs
117+
///
118+
/// Use `#[metric_doc]` (no arguments) for operator-specific metrics:
119+
/// ```ignore
120+
/// #[metric_doc]
121+
/// struct FilterExecMetrics {
122+
/// /// Common metrics for most operators
123+
/// baseline_metrics: BaselineMetrics,
124+
/// /// Selectivity of the filter
125+
/// selectivity: RatioMetrics,
126+
/// }
127+
/// ```
128+
///
129+
/// Use `#[metric_doc(common)]` for reusable metrics shared across operators:
130+
/// ```ignore
131+
/// #[metric_doc(common)]
132+
/// pub struct BaselineMetrics {
133+
/// /// Amount of time the operator was actively using the CPU
134+
/// elapsed_compute: Time,
135+
/// /// Total output rows
136+
/// output_rows: Count,
137+
/// }
138+
/// ```
139+
///
140+
/// ## On Exec Structs
141+
///
142+
/// Reference a single metrics struct:
143+
/// ```ignore
144+
/// #[metric_doc(FilterExecMetrics)]
145+
/// pub struct FilterExec { /* ... */ }
146+
/// ```
147+
///
148+
/// Reference multiple metrics structs (for operators with multiple metric groups):
149+
/// ```ignore
150+
/// #[metric_doc(MetricsGroupA, MetricsGroupB)]
151+
/// pub struct UserDefinedExec { /* ... */ }
152+
/// ```
153+
///
154+
/// ## Cross-File Usage
155+
///
156+
/// Metrics structs can be defined in different files/modules from the exec.
157+
/// The macro resolves references at compile time, so metrics can live anywhere
158+
/// as long as they are in scope where the exec is defined:
159+
/// ```ignore
160+
/// // In metrics/groups.rs
161+
/// #[metric_doc]
162+
/// pub struct MetricsGroupA {
163+
/// /// Time spent in phase A
164+
/// phase_a_time: Time,
165+
/// }
166+
///
167+
/// #[metric_doc]
168+
/// pub struct MetricsGroupB {
169+
/// /// Time spent in phase B
170+
/// phase_b_time: Time,
171+
/// }
172+
///
173+
/// // In exec/user_defined.rs
174+
/// use crate::metrics::groups::{MetricsGroupA, MetricsGroupB};
175+
///
176+
/// #[metric_doc(MetricsGroupA, MetricsGroupB)]
177+
/// pub struct UserDefinedExec { /* ... */ }
178+
/// ```
116179
#[proc_macro_attribute]
117180
pub fn metric_doc(args: TokenStream, input: TokenStream) -> TokenStream {
118181
metric_doc_impl::metric_doc(&args, input)

0 commit comments

Comments
 (0)