This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new aff5850c0a Add per-aggregate argument evaluation metrics for grouped 
hash aggregation (#24024)
aff5850c0a is described below

commit aff5850c0aa604bb03158a137d5343e4efe73ae3
Author: kosiew <[email protected]>
AuthorDate: Sun Aug 16 12:21:31 2026 +0000

    Add per-aggregate argument evaluation metrics for grouped hash aggregation 
(#24024)
    
    #### Which issue does this PR close?
    
    * Part of #23570
    
    ---
    
    ## Rationale for this change
    
    Grouped hash aggregation currently exposes a single
    `aggregate_arguments_time` metric for all aggregate argument evaluation.
    This makes it difficult to determine which aggregate expression is
    responsible for argument evaluation cost when multiple aggregates are
    present (for example, `SUM(a)` versus `SUM(b)`).
    
    This change adds per-aggregate argument evaluation metrics while
    preserving the existing operator-level metric for compatibility.
    
    ---
    
    ## What changes are included in this PR?
    
    * Add an `AggregateArgumentMetrics` helper that registers one timer per
    aggregate expression during operator construction.
    * Label per-aggregate metrics using the aggregate expression display
    text (or alias), allowing otherwise identical aggregate functions on
    different inputs (for example, `SUM(a)` and `SUM(b)`) to be
    distinguished.
    * Wrap aggregate argument evaluation with the corresponding
    per-aggregate timer in:
    
      * grouped hash aggregation stream
      * migrated hash table aggregation paths
      * ordered aggregation table paths
    * Introduce `OrderedAggregateTableMetrics` to carry both group-by and
    aggregate argument metrics through ordered aggregation replay/spill
    paths.
    * Factor out a reusable `aggregate_metric_label` helper for consistent
    metric labels.
    * Preserve the existing `aggregate_arguments_time` metric so whole-phase
    timing continues to be reported.
    
    ---
    
    ## Are these changes tested?
    
    Yes.
    
    This PR adds and updates tests including:
    
    * `test_groupby_aggregate_argument_metrics_distinguish_inputs`, which
    verifies that separate metrics are created for `SUM(a)` and `SUM(b)`
    with distinct metric names and aggregate labels.
    * Existing group-by metrics tests updated to use shared aggregate
    construction helpers.
    * Grouped hash aggregation stream tests updated to verify both:
    
    * the existing `aggregate_arguments_time` metric remains present and
    non-zero, and
    * the new per-aggregate metric (`agg_expr_0_arguments_time`) is emitted
    and records time.
    
    ---
    
    ## Are there any user-facing changes?
    
    Yes.
    
    `EXPLAIN ANALYZE` and execution plan metrics for the covered grouped
    hash aggregation paths now include per-aggregate argument evaluation
    timers (for example, `agg_expr_0_arguments_time`) with aggregate labels
    identifying the corresponding aggregate expression, while retaining the
    existing `aggregate_arguments_time` metric for compatibility.
    
    ---
    
    ## LLM-generated code disclosure
    
    This PR includes LLM-generated code and comments. All LLM-generated
    content has been manually reviewed.
---
 .../src/aggregates/aggregate_hash_table/common.rs  |  29 +++-
 .../aggregate_hash_table/common_ordered.rs         |  60 ++++++-
 .../src/aggregates/aggregate_hash_table/mod.rs     |   2 +-
 .../aggregate_hash_table/ordered_final_table.rs    |   7 +-
 .../aggregate_hash_table/ordered_partial_table.rs  |   7 +-
 .../aggregate_hash_table/ordered_single_table.rs   |   7 +-
 .../aggregate_hash_table/partial_table.rs          |   1 +
 .../src/aggregates/group_values/metrics.rs         | 190 ++++++++++++++++++---
 .../src/aggregates/group_values/mod.rs             |   2 +-
 .../src/aggregates/grouped_hash_stream.rs          |  61 ++++++-
 .../physical-plan/src/aggregates/hash_stream.rs    |  12 +-
 datafusion/physical-plan/src/aggregates/mod.rs     |   4 +
 .../src/aggregates/ordered_final_stream.rs         |  21 +--
 .../src/aggregates/ordered_single_stream.rs        |  13 +-
 .../physical-plan/src/aggregates/single_stream.rs  |  13 +-
 15 files changed, 343 insertions(+), 86 deletions(-)

diff --git 
a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/common.rs 
b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/common.rs
index 91e9d6555c..fde7024f7b 100644
--- a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/common.rs
+++ b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/common.rs
@@ -27,11 +27,14 @@ use datafusion_expr::{EmitTo, GroupsAccumulator};
 use datafusion_physical_expr::aggregate::AggregateFunctionExpr;
 
 use crate::PhysicalExpr;
-use crate::aggregates::group_values::{GroupByMetrics, GroupValues, 
new_group_values};
+use crate::aggregates::group_values::{
+    AggregateArgumentMetrics, GroupByMetrics, GroupValues, new_group_values,
+};
 use crate::aggregates::grouped_hash_stream::create_group_accumulator;
 use crate::aggregates::order::GroupOrdering;
 use crate::aggregates::{
-    AggregateExec, PhysicalGroupBy, aggregate_expressions, evaluate_group_by,
+    AggregateExec, PhysicalGroupBy, aggregate_expressions, 
aggregate_metric_label,
+    evaluate_group_by,
 };
 
 /// Marker for raw rows -> partial state aggregation.
@@ -75,6 +78,9 @@ pub(in crate::aggregates) struct AggregateHashTable<AggrMode> 
{
     /// Grouping and accumulator-specific timing metrics.
     pub(super) group_by_metrics: GroupByMetrics,
 
+    /// Per-aggregate timing metrics for evaluating aggregate arguments.
+    pub(super) aggregate_argument_metrics: AggregateArgumentMetrics,
+
     /// Raw input schema, used to evaluate expressions and synthesize empty
     /// grouping-set rows.
     pub(super) input_schema: SchemaRef,
@@ -134,8 +140,17 @@ impl<AggrMode> AggregateHashTable<AggrMode> {
         let group_schema = agg.group_by.group_schema(&input_schema)?;
         let group_values = new_group_values(group_schema, 
&GroupOrdering::None)?;
 
+        let aggregate_argument_metrics = AggregateArgumentMetrics::new(
+            &agg.metrics,
+            partition,
+            agg.aggr_expr
+                .iter()
+                .map(|agg_expr| aggregate_metric_label(agg_expr)),
+        );
+
         Ok(Self {
             group_by_metrics: GroupByMetrics::new(&agg.metrics, partition),
+            aggregate_argument_metrics,
             input_schema,
             output_schema,
             state_schema,
@@ -169,7 +184,11 @@ impl<AggrMode> AggregateHashTable<AggrMode> {
             .building()
             .accumulators
             .iter()
-            .map(|acc| acc.evaluate_acc_args(batch))
+            .enumerate()
+            .map(|(idx, acc)| {
+                self.aggregate_argument_metrics
+                    .time(idx, || acc.evaluate_acc_args(batch))
+            })
             .collect::<Result<Vec<_>>>()?;
         drop(timer);
 
@@ -288,10 +307,6 @@ impl<AggrMode> AggregateHashTable<AggrMode> {
         }
     }
 
-    pub(in crate::aggregates) fn group_by_metrics(&self) -> &GroupByMetrics {
-        &self.group_by_metrics
-    }
-
     /// Returns the number of distinct groups accumulated so far.
     pub(in crate::aggregates) fn building_group_count(&self) -> usize {
         self.state.building().group_values.len()
diff --git 
a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/common_ordered.rs
 
b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/common_ordered.rs
index dac0d4b7c5..22fe8f5433 100644
--- 
a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/common_ordered.rs
+++ 
b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/common_ordered.rs
@@ -30,19 +30,52 @@ use datafusion_expr::EmitTo;
 
 use crate::InputOrderMode;
 use crate::PhysicalExpr;
-use crate::aggregates::group_values::{GroupByMetrics, GroupValues, 
new_group_values};
+use crate::aggregates::group_values::{
+    AggregateArgumentMetrics, GroupByMetrics, GroupValues, new_group_values,
+};
 use crate::aggregates::grouped_hash_stream::create_group_accumulator;
 use crate::aggregates::order::GroupOrdering;
 use crate::aggregates::{
     AggregateExec, AggregateMode, PhysicalGroupBy, aggregate_expressions,
-    evaluate_group_by,
+    aggregate_metric_label, evaluate_group_by,
 };
 
 use super::common::{
-    AggregateAccumulator, AggregateBatchFn, EvaluatedAggregateBatch,
+    AggregateAccumulator, AggregateBatchFn, AggregateHashTable, 
EvaluatedAggregateBatch,
     MaterializeAccumulatorFn,
 };
 
+#[derive(Clone)]
+pub(in crate::aggregates) struct OrderedAggregateTableMetrics {
+    pub(super) group_by: GroupByMetrics,
+    pub(super) aggregate_arguments: AggregateArgumentMetrics,
+}
+
+impl OrderedAggregateTableMetrics {
+    pub(in crate::aggregates) fn new(agg: &AggregateExec, partition: usize) -> 
Self {
+        let aggregate_arguments = AggregateArgumentMetrics::new(
+            &agg.metrics,
+            partition,
+            agg.aggr_expr
+                .iter()
+                .map(|agg_expr| aggregate_metric_label(agg_expr)),
+        );
+        Self {
+            group_by: GroupByMetrics::new(&agg.metrics, partition),
+            aggregate_arguments,
+        }
+    }
+
+    pub(in crate::aggregates) fn from_hash_table<AggrMode>(
+        table: &AggregateHashTable<AggrMode>,
+    ) -> Self {
+        Self {
+            group_by: table.group_by_metrics.clone(),
+            aggregate_arguments: table.aggregate_argument_metrics.clone(),
+        }
+    }
+}
+
 /// Aggregate table shared by the ordered single, partial and final paths.
 ///
 /// # Ordering optimization
@@ -100,6 +133,9 @@ pub(in crate::aggregates) struct 
OrderedAggregateTable<OrderedAggrMode> {
     /// Grouping and accumulator-specific timing metrics.
     pub(super) group_by_metrics: GroupByMetrics,
 
+    /// Per-aggregate timing metrics for evaluating aggregate arguments.
+    pub(super) aggregate_argument_metrics: AggregateArgumentMetrics,
+
     /// Group keys, ordering state, and accumulator states.
     pub(super) buffer: OrderedAggregateTableBuffer,
 
@@ -149,7 +185,7 @@ impl<AggrMode> OrderedAggregateTable<AggrMode> {
         input_order_mode: &InputOrderMode,
         aggregate_mode: &AggregateMode,
         filters: Vec<Option<Arc<dyn PhysicalExpr>>>,
-        group_by_metrics: GroupByMetrics,
+        metrics: OrderedAggregateTableMetrics,
     ) -> Result<Self> {
         assert_or_internal_err!(
             batch_size > 0,
@@ -184,7 +220,8 @@ impl<AggrMode> OrderedAggregateTable<AggrMode> {
             output_schema,
             state_schema,
             batch_size,
-            group_by_metrics,
+            group_by_metrics: metrics.group_by,
+            aggregate_argument_metrics: metrics.aggregate_arguments,
             buffer: OrderedAggregateTableBuffer {
                 group_by: Arc::clone(&agg.group_by),
                 group_ordering,
@@ -213,7 +250,11 @@ impl<AggrMode> OrderedAggregateTable<AggrMode> {
             .buffer
             .accumulators
             .iter()
-            .map(|acc| acc.evaluate_acc_args(batch))
+            .enumerate()
+            .map(|(idx, acc)| {
+                self.aggregate_argument_metrics
+                    .time(idx, || acc.evaluate_acc_args(batch))
+            })
             .collect::<Result<Vec<_>>>()?;
         drop(timer);
 
@@ -259,8 +300,11 @@ impl<AggrMode> OrderedAggregateTable<AggrMode> {
             + self.buffer.group_indices.allocated_size()
     }
 
-    pub(in crate::aggregates) fn group_by_metrics(&self) -> GroupByMetrics {
-        self.group_by_metrics.clone()
+    pub(in crate::aggregates) fn metrics(&self) -> 
OrderedAggregateTableMetrics {
+        OrderedAggregateTableMetrics {
+            group_by: self.group_by_metrics.clone(),
+            aggregate_arguments: self.aggregate_argument_metrics.clone(),
+        }
     }
 
     /// Takes every intermediate aggregate state and resets the table so it can
diff --git 
a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/mod.rs 
b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/mod.rs
index fbf3ccc738..435289aa30 100644
--- a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/mod.rs
+++ b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/mod.rs
@@ -29,4 +29,4 @@ pub(super) use common::{
     AggregateHashTable, FinalMarker, PartialMarker, PartialReduceMarker,
     PartialSkipMarker, SingleMarker,
 };
-pub(super) use common_ordered::OrderedAggregateTable;
+pub(super) use common_ordered::{OrderedAggregateTable, 
OrderedAggregateTableMetrics};
diff --git 
a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/ordered_final_table.rs
 
b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/ordered_final_table.rs
index 1b7d419dd5..f3e22cdd0c 100644
--- 
a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/ordered_final_table.rs
+++ 
b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/ordered_final_table.rs
@@ -27,11 +27,10 @@ use datafusion_common::Result;
 
 use crate::InputOrderMode;
 use crate::aggregates::aggregate_hash_table::FinalMarker;
-use crate::aggregates::group_values::GroupByMetrics;
 use crate::aggregates::{AggregateExec, AggregateMode};
 
 use super::common::HashAggregateAccumulator;
-use super::common_ordered::OrderedAggregateTable;
+use super::common_ordered::{OrderedAggregateTable, 
OrderedAggregateTableMetrics};
 
 /// Implementation specific to final aggregation, where the table stores 
partial
 /// aggregate states and the input rows are also partial states.
@@ -49,7 +48,7 @@ impl OrderedAggregateTable<FinalMarker> {
         output_schema: SchemaRef,
         batch_size: usize,
         input_order_mode: &InputOrderMode,
-        group_by_metrics: GroupByMetrics,
+        metrics: OrderedAggregateTableMetrics,
     ) -> Result<Self> {
         Self::new_for_mode(
             agg,
@@ -60,7 +59,7 @@ impl OrderedAggregateTable<FinalMarker> {
             input_order_mode,
             &AggregateMode::Final,
             vec![None; agg.aggr_expr.len()],
-            group_by_metrics,
+            metrics,
         )
     }
 
diff --git 
a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/ordered_partial_table.rs
 
b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/ordered_partial_table.rs
index 8545289f99..ea38346729 100644
--- 
a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/ordered_partial_table.rs
+++ 
b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/ordered_partial_table.rs
@@ -37,11 +37,10 @@ use datafusion_common::Result;
 
 use crate::aggregates::{
     AggregateExec, AggregateMode, aggregate_hash_table::PartialMarker,
-    group_values::GroupByMetrics,
 };
 
 use super::common::HashAggregateAccumulator;
-use super::common_ordered::OrderedAggregateTable;
+use super::common_ordered::{OrderedAggregateTable, 
OrderedAggregateTableMetrics};
 
 /// Implementation specific to partial aggregation, where the table stores
 /// partial aggregate states and the input rows are raw rows.
@@ -61,7 +60,7 @@ impl OrderedAggregateTable<PartialMarker> {
     ) -> Result<Self> {
         let input_schema = agg.input().schema();
         let state_schema = Arc::clone(&output_schema);
-        let group_by_metrics = GroupByMetrics::new(&agg.metrics, partition);
+        let metrics = OrderedAggregateTableMetrics::new(agg, partition);
         Self::new_for_mode(
             agg,
             &input_schema,
@@ -71,7 +70,7 @@ impl OrderedAggregateTable<PartialMarker> {
             &agg.input_order_mode,
             &AggregateMode::Partial,
             agg.filter_expr.iter().cloned().collect(),
-            group_by_metrics,
+            metrics,
         )
     }
 
diff --git 
a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/ordered_single_table.rs
 
b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/ordered_single_table.rs
index 8ba50e2a59..db53e2822e 100644
--- 
a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/ordered_single_table.rs
+++ 
b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/ordered_single_table.rs
@@ -24,11 +24,10 @@ use arrow::record_batch::RecordBatch;
 use datafusion_common::Result;
 
 use crate::aggregates::aggregate_hash_table::SingleMarker;
-use crate::aggregates::group_values::GroupByMetrics;
 use crate::aggregates::{AggregateExec, AggregateMode};
 
 use super::common::HashAggregateAccumulator;
-use super::common_ordered::OrderedAggregateTable;
+use super::common_ordered::{OrderedAggregateTable, 
OrderedAggregateTableMetrics};
 
 /// Implementation specific to single aggregation, where the table stores final
 /// aggregate values and the input rows are raw rows.
@@ -53,7 +52,7 @@ impl OrderedAggregateTable<SingleMarker> {
         ));
 
         let input_schema = agg.input().schema();
-        let group_by_metrics = GroupByMetrics::new(&agg.metrics, partition);
+        let metrics = OrderedAggregateTableMetrics::new(agg, partition);
         Self::new_for_mode(
             agg,
             &input_schema,
@@ -63,7 +62,7 @@ impl OrderedAggregateTable<SingleMarker> {
             &agg.input_order_mode,
             &agg.mode,
             agg.filter_expr.iter().cloned().collect(),
-            group_by_metrics,
+            metrics,
         )
     }
 
diff --git 
a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/partial_table.rs 
b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/partial_table.rs
index a64fd32536..915e7f1beb 100644
--- 
a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/partial_table.rs
+++ 
b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/partial_table.rs
@@ -86,6 +86,7 @@ impl AggregateHashTable<PartialMarker> {
 
         Ok(AggregateHashTable {
             group_by_metrics: self.group_by_metrics.clone(),
+            aggregate_argument_metrics: 
self.aggregate_argument_metrics.clone(),
             input_schema: Arc::clone(&self.input_schema),
             output_schema: Arc::clone(&self.output_schema),
             state_schema: Arc::clone(&self.state_schema),
diff --git a/datafusion/physical-plan/src/aggregates/group_values/metrics.rs 
b/datafusion/physical-plan/src/aggregates/group_values/metrics.rs
index 1c6285d793..0011150970 100644
--- a/datafusion/physical-plan/src/aggregates/group_values/metrics.rs
+++ b/datafusion/physical-plan/src/aggregates/group_values/metrics.rs
@@ -19,6 +19,43 @@
 
 use crate::metrics::{ExecutionPlanMetricsSet, MetricBuilder, Time};
 
+#[derive(Clone)]
+pub(crate) struct AggregateArgumentMetrics {
+    argument_times: Vec<Time>,
+}
+
+impl AggregateArgumentMetrics {
+    pub(crate) fn new<T>(
+        metrics: &ExecutionPlanMetricsSet,
+        partition: usize,
+        aggregate_labels: impl IntoIterator<Item = T>,
+    ) -> Self
+    where
+        T: Into<String>,
+    {
+        let argument_times = aggregate_labels
+            .into_iter()
+            .enumerate()
+            .map(|(idx, label)| {
+                MetricBuilder::new(metrics)
+                    .with_new_label("aggregate", label.into())
+                    .subset_time(format!("agg_expr_{idx}_arguments_time"), 
partition)
+            })
+            .collect();
+
+        Self { argument_times }
+    }
+
+    pub(crate) fn time<R>(&self, index: usize, f: impl FnOnce() -> R) -> R {
+        debug_assert!(
+            index < self.argument_times.len(),
+            "aggregate argument metric index {index} out of range"
+        );
+        let _timer = self.argument_times.get(index).map(Time::timer);
+        f()
+    }
+}
+
 #[derive(Clone)]
 pub(crate) struct GroupByMetrics {
     /// Time spent calculating the group IDs from the evaluated grouping 
columns.
@@ -52,7 +89,7 @@ impl GroupByMetrics {
 #[cfg(test)]
 mod tests {
     use crate::aggregates::{AggregateExec, AggregateMode, PhysicalGroupBy};
-    use crate::metrics::MetricsSet;
+    use crate::metrics::{MetricValue, MetricsSet};
     use crate::test::TestMemoryExec;
     use crate::{ExecutionPlan, collect};
     use arrow::array::{Float64Array, UInt32Array};
@@ -63,7 +100,9 @@ mod tests {
     use datafusion_execution::runtime_env::RuntimeEnvBuilder;
     use datafusion_functions_aggregate::count::count_udaf;
     use datafusion_functions_aggregate::sum::sum_udaf;
-    use datafusion_physical_expr::aggregate::AggregateExprBuilder;
+    use datafusion_physical_expr::aggregate::{
+        AggregateExprBuilder, AggregateFunctionExpr,
+    };
     use datafusion_physical_expr::expressions::col;
     use std::sync::Arc;
 
@@ -82,6 +121,55 @@ mod tests {
         assert!(emitting_time.unwrap().as_usize() > 0);
     }
 
+    fn aggregate_argument_metric_names_and_labels(
+        metrics: &MetricsSet,
+    ) -> Vec<(String, String)> {
+        metrics
+            .iter()
+            .filter_map(|metric| match metric.value() {
+                MetricValue::Time { name, .. }
+                    if name.starts_with("agg_expr_")
+                        && name.ends_with("_arguments_time") =>
+                {
+                    let aggregate_label = metric
+                        .labels()
+                        .iter()
+                        .find(|label| label.name() == "aggregate")?
+                        .value()
+                        .to_string();
+                    Some((name.to_string(), aggregate_label))
+                }
+                _ => None,
+            })
+            .collect()
+    }
+
+    fn sum_aggregate(
+        schema: &Arc<Schema>,
+        column: &str,
+        alias: &str,
+    ) -> Result<Arc<AggregateFunctionExpr>> {
+        Ok(Arc::new(
+            AggregateExprBuilder::new(sum_udaf(), vec![col(column, schema)?])
+                .schema(Arc::clone(schema))
+                .alias(alias)
+                .build()?,
+        ))
+    }
+
+    fn count_aggregate(
+        schema: &Arc<Schema>,
+        column: &str,
+        alias: &str,
+    ) -> Result<Arc<AggregateFunctionExpr>> {
+        Ok(Arc::new(
+            AggregateExprBuilder::new(count_udaf(), vec![col(column, schema)?])
+                .schema(Arc::clone(schema))
+                .alias(alias)
+                .build()?,
+        ))
+    }
+
     #[tokio::test]
     async fn test_groupby_metrics_partial_mode() -> Result<()> {
         let schema = Arc::new(Schema::new(vec![
@@ -114,18 +202,8 @@ mod tests {
             PhysicalGroupBy::new_single(vec![(col("a", &schema)?, 
"a".to_string())]);
 
         let aggregates = vec![
-            Arc::new(
-                AggregateExprBuilder::new(sum_udaf(), vec![col("b", &schema)?])
-                    .schema(Arc::clone(&schema))
-                    .alias("SUM(b)")
-                    .build()?,
-            ),
-            Arc::new(
-                AggregateExprBuilder::new(count_udaf(), vec![col("b", 
&schema)?])
-                    .schema(Arc::clone(&schema))
-                    .alias("COUNT(b)")
-                    .build()?,
-            ),
+            sum_aggregate(&schema, "b", "SUM(b)")?,
+            count_aggregate(&schema, "b", "COUNT(b)")?,
         ];
 
         let aggregate_exec = Arc::new(AggregateExec::try_new(
@@ -153,6 +231,83 @@ mod tests {
         Ok(())
     }
 
+    #[tokio::test]
+    async fn test_groupby_aggregate_argument_metrics_distinguish_inputs() -> 
Result<()> {
+        let schema = Arc::new(Schema::new(vec![
+            Field::new("k", DataType::UInt32, false),
+            Field::new("a", DataType::Float64, false),
+            Field::new("b", DataType::Float64, false),
+        ]));
+
+        let batches = (0..5)
+            .map(|i| {
+                RecordBatch::try_new(
+                    Arc::clone(&schema),
+                    vec![
+                        Arc::new(UInt32Array::from(vec![1, 2, 1, 2])),
+                        Arc::new(Float64Array::from(vec![
+                            i as f64,
+                            (i + 1) as f64,
+                            (i + 2) as f64,
+                            (i + 3) as f64,
+                        ])),
+                        Arc::new(Float64Array::from(vec![
+                            (i + 4) as f64,
+                            (i + 5) as f64,
+                            (i + 6) as f64,
+                            (i + 7) as f64,
+                        ])),
+                    ],
+                )
+                .unwrap()
+            })
+            .collect::<Vec<_>>();
+
+        let input = TestMemoryExec::try_new_exec(&[batches], 
Arc::clone(&schema), None)?;
+        let group_by =
+            PhysicalGroupBy::new_single(vec![(col("k", &schema)?, 
"k".to_string())]);
+        let aggregates = vec![
+            sum_aggregate(&schema, "a", "SUM(a)")?,
+            sum_aggregate(&schema, "b", "SUM(b)")?,
+        ];
+
+        let aggregate_exec = Arc::new(AggregateExec::try_new(
+            AggregateMode::Partial,
+            group_by,
+            aggregates,
+            vec![None, None],
+            input,
+            schema,
+        )?);
+
+        let runtime = RuntimeEnvBuilder::new()
+            .with_memory_limit(10 * 1024 * 1024, 1.0)
+            .build_arc()?;
+        let task_ctx = Arc::new(TaskContext::default().with_runtime(runtime));
+        let _result =
+            collect(Arc::clone(&aggregate_exec) as _, 
Arc::clone(&task_ctx)).await?;
+
+        let metrics = aggregate_exec.metrics().unwrap();
+        let mut metric_names_and_labels =
+            aggregate_argument_metric_names_and_labels(&metrics);
+        metric_names_and_labels.sort();
+        assert_eq!(
+            metric_names_and_labels,
+            vec![
+                (
+                    "agg_expr_0_arguments_time".to_string(),
+                    "SUM(a)".to_string(),
+                ),
+                (
+                    "agg_expr_1_arguments_time".to_string(),
+                    "SUM(b)".to_string(),
+                ),
+            ]
+        );
+
+        Ok(())
+    }
+
     #[tokio::test]
     async fn test_groupby_metrics_final_mode() -> Result<()> {
         let schema = Arc::new(Schema::new(vec![
@@ -183,12 +338,7 @@ mod tests {
         let group_by =
             PhysicalGroupBy::new_single(vec![(col("a", &schema)?, 
"a".to_string())]);
 
-        let aggregates = vec![Arc::new(
-            AggregateExprBuilder::new(sum_udaf(), vec![col("b", &schema)?])
-                .schema(Arc::clone(&schema))
-                .alias("SUM(b)")
-                .build()?,
-        )];
+        let aggregates = vec![sum_aggregate(&schema, "b", "SUM(b)")?];
 
         // Create partial aggregate
         let partial_aggregate = Arc::new(AggregateExec::try_new(
diff --git a/datafusion/physical-plan/src/aggregates/group_values/mod.rs 
b/datafusion/physical-plan/src/aggregates/group_values/mod.rs
index 1101d53531..bd5b92747e 100644
--- a/datafusion/physical-plan/src/aggregates/group_values/mod.rs
+++ b/datafusion/physical-plan/src/aggregates/group_values/mod.rs
@@ -49,7 +49,7 @@ use crate::aggregates::{
 mod metrics;
 mod null_builder;
 
-pub(crate) use metrics::GroupByMetrics;
+pub(crate) use metrics::{AggregateArgumentMetrics, GroupByMetrics};
 
 /// Stores the group values during hash aggregation.
 ///
diff --git a/datafusion/physical-plan/src/aggregates/grouped_hash_stream.rs 
b/datafusion/physical-plan/src/aggregates/grouped_hash_stream.rs
index 99c1011994..340df57e5d 100644
--- a/datafusion/physical-plan/src/aggregates/grouped_hash_stream.rs
+++ b/datafusion/physical-plan/src/aggregates/grouped_hash_stream.rs
@@ -24,12 +24,14 @@ use std::vec;
 use super::order::GroupOrdering;
 use super::skip_partial::SkipAggregationProbe;
 use super::{AggregateExec, format_human_display};
-use crate::aggregates::group_values::{GroupByMetrics, GroupValues, 
new_group_values};
+use crate::aggregates::group_values::{
+    AggregateArgumentMetrics, GroupByMetrics, GroupValues, new_group_values,
+};
 use crate::aggregates::order::GroupOrderingFull;
 use crate::aggregates::{
     AggregateInputMode, AggregateMode, AggregateOutputMode, PhysicalGroupBy,
-    create_schema, evaluate_group_by, evaluate_many, evaluate_optional, 
group_id_array,
-    max_duplicate_ordinal,
+    aggregate_metric_label, create_schema, evaluate_group_by, 
evaluate_optional,
+    group_id_array, max_duplicate_ordinal,
 };
 use crate::metrics::{BaselineMetrics, MetricBuilder, MetricCategory, 
RecordOutput};
 use crate::sorts::streaming_merge::{SortedSpillFile, StreamingMergeBuilder};
@@ -52,6 +54,7 @@ use 
datafusion_physical_expr::aggregate::AggregateFunctionExpr;
 use datafusion_physical_expr::expressions::Column;
 use datafusion_physical_expr::{GroupsAccumulatorAdapter, PhysicalSortExpr};
 use datafusion_physical_expr_common::sort_expr::LexOrdering;
+use datafusion_physical_expr_common::utils::evaluate_expressions_to_arrays;
 
 use crate::sorts::IncrementalSortIterator;
 use datafusion_common::instant::Instant;
@@ -372,6 +375,9 @@ pub(crate) struct GroupedHashAggregateStream {
     /// Aggregation-specific metrics
     group_by_metrics: GroupByMetrics,
 
+    /// Per-aggregate timing metrics for evaluating aggregate arguments.
+    aggregate_argument_metrics: AggregateArgumentMetrics,
+
     /// Reduction factor metric, calculated as `output_rows/input_rows` (only 
for partial aggregation)
     reduction_factor: Option<metrics::RatioMetrics>,
 }
@@ -392,6 +398,13 @@ impl GroupedHashAggregateStream {
         let input = agg.input.execute(partition, Arc::clone(context))?;
         let baseline_metrics = BaselineMetrics::new(&agg.metrics, partition);
         let group_by_metrics = GroupByMetrics::new(&agg.metrics, partition);
+        let aggregate_argument_metrics = AggregateArgumentMetrics::new(
+            &agg.metrics,
+            partition,
+            agg.aggr_expr
+                .iter()
+                .map(|agg_expr| aggregate_metric_label(agg_expr)),
+        );
 
         let timer = baseline_metrics.elapsed_compute().timer();
 
@@ -598,6 +611,7 @@ impl GroupedHashAggregateStream {
             exec_state,
             baseline_metrics,
             group_by_metrics,
+            aggregate_argument_metrics,
             batch_size,
             group_ordering,
             input_done: false,
@@ -857,11 +871,19 @@ impl GroupedHashAggregateStream {
         };
 
         // Evaluate the aggregation expressions.
-        let input_values = if self.spill_state.is_stream_merging {
-            evaluate_many(&self.spill_state.merging_aggregate_arguments, 
batch)?
+        let aggregate_arguments = if self.spill_state.is_stream_merging {
+            &self.spill_state.merging_aggregate_arguments
         } else {
-            evaluate_many(&self.aggregate_arguments, batch)?
+            &self.aggregate_arguments
         };
+        let input_values = aggregate_arguments
+            .iter()
+            .enumerate()
+            .map(|(idx, expr)| {
+                self.aggregate_argument_metrics
+                    .time(idx, || evaluate_expressions_to_arrays(expr, batch))
+            })
+            .collect::<Result<Vec<_>>>()?;
         drop(timer);
 
         // Evaluate the filter expressions, if any, against the inputs
@@ -1368,7 +1390,17 @@ impl GroupedHashAggregateStream {
     /// Transforms input batch to intermediate aggregate state, without 
grouping it
     fn transform_to_states(&self, batch: &RecordBatch) -> Result<RecordBatch> {
         let mut group_values = evaluate_group_by(&self.group_by, batch)?;
-        let input_values = evaluate_many(&self.aggregate_arguments, batch)?;
+        let timer = self.group_by_metrics.aggregate_arguments_time.timer();
+        let input_values = self
+            .aggregate_arguments
+            .iter()
+            .enumerate()
+            .map(|(idx, expr)| {
+                self.aggregate_argument_metrics
+                    .time(idx, || evaluate_expressions_to_arrays(expr, batch))
+            })
+            .collect::<Result<Vec<_>>>()?;
+        drop(timer);
         let filter_values = evaluate_optional(&self.filter_expressions, 
batch)?;
 
         assert_eq_or_internal_err!(
@@ -1398,6 +1430,7 @@ impl GroupedHashAggregateStream {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::ExecutionPlan;
     use crate::InputOrderMode;
     use crate::test::TestMemoryExec;
     use arrow::array::{Int32Array, Int64Array};
@@ -1439,7 +1472,7 @@ mod tests {
             ],
         )?;
 
-        let input_partitions = vec![vec![batch]];
+        let input_partitions = vec![vec![batch.clone(), batch]];
 
         // Create constrained memory to trigger early emission but not 
completely fail
         let runtime = RuntimeEnvBuilder::default()
@@ -1508,10 +1541,20 @@ mod tests {
         }
 
         assert_eq!(
-            total_output_groups, num_groups,
+            total_output_groups,
+            num_groups * 2,
             "Unexpected number of groups",
         );
 
+        let metrics = aggregate_exec.metrics().unwrap();
+        let agg_arguments_time = 
metrics.sum_by_name("aggregate_arguments_time");
+        assert!(agg_arguments_time.is_some());
+        assert!(agg_arguments_time.unwrap().as_usize() > 0);
+
+        let per_aggregate_time = 
metrics.sum_by_name("agg_expr_0_arguments_time");
+        assert!(per_aggregate_time.is_some());
+        assert!(per_aggregate_time.unwrap().as_usize() > 0);
+
         Ok(())
     }
 
diff --git a/datafusion/physical-plan/src/aggregates/hash_stream.rs 
b/datafusion/physical-plan/src/aggregates/hash_stream.rs
index f697e5a394..2df5960188 100644
--- a/datafusion/physical-plan/src/aggregates/hash_stream.rs
+++ b/datafusion/physical-plan/src/aggregates/hash_stream.rs
@@ -42,9 +42,9 @@ use futures::stream::{Stream, StreamExt};
 
 use super::AggregateExec;
 use super::aggregate_hash_table::{
-    AggregateHashTable, FinalMarker, PartialMarker, PartialSkipMarker,
+    AggregateHashTable, FinalMarker, OrderedAggregateTableMetrics, 
PartialMarker,
+    PartialSkipMarker,
 };
-use super::group_values::GroupByMetrics;
 use super::ordered_final_stream::OrderedFinalAggregateStream;
 use super::skip_partial::SkipAggregationProbe;
 use crate::metrics::{
@@ -382,7 +382,7 @@ impl FinalSpillContext {
     fn into_replay_stream(
         self,
         baseline_metrics: &BaselineMetrics,
-        group_by_metrics: GroupByMetrics,
+        metrics: OrderedAggregateTableMetrics,
         reservation: MemoryReservation,
     ) -> Result<SendableRecordBatchStream> {
         let Self {
@@ -416,7 +416,7 @@ impl FinalSpillContext {
             merged,
             &InputOrderMode::Sorted,
             baseline_metrics.clone(),
-            group_by_metrics,
+            metrics,
             None,
             reservation,
         )?;
@@ -1325,12 +1325,12 @@ impl FinalHashAggregateStream {
         let timer = elapsed_compute.timer();
         let replay = match spill_context.spill_table(&mut hash_table) {
             Ok(()) => {
-                let group_by_metrics = hash_table.group_by_metrics().clone();
+                let metrics = 
OrderedAggregateTableMetrics::from_hash_table(&hash_table);
                 drop(hash_table);
                 match self.reservation.try_resize(0) {
                     Ok(()) => (*spill_context).into_replay_stream(
                         &self.baseline_metrics,
-                        group_by_metrics,
+                        metrics,
                         self.reservation.new_empty(),
                     ),
                     Err(e) => Err(e),
diff --git a/datafusion/physical-plan/src/aggregates/mod.rs 
b/datafusion/physical-plan/src/aggregates/mod.rs
index f9dd90f6f9..1671735ec3 100644
--- a/datafusion/physical-plan/src/aggregates/mod.rs
+++ b/datafusion/physical-plan/src/aggregates/mod.rs
@@ -1996,6 +1996,10 @@ fn format_tree_aggregate_expr(agg: 
&AggregateFunctionExpr) -> Cow<'_, str> {
         .unwrap_or_else(|| Cow::Borrowed(agg.name()))
 }
 
+fn aggregate_metric_label(agg: &AggregateFunctionExpr) -> String {
+    format_tree_aggregate_expr(agg).into_owned()
+}
+
 fn format_human_display<'a>(
     human_display: Option<&'a str>,
     alias: Option<&'a str>,
diff --git a/datafusion/physical-plan/src/aggregates/ordered_final_stream.rs 
b/datafusion/physical-plan/src/aggregates/ordered_final_stream.rs
index 19deedc258..2c26b74da7 100644
--- a/datafusion/physical-plan/src/aggregates/ordered_final_stream.rs
+++ b/datafusion/physical-plan/src/aggregates/ordered_final_stream.rs
@@ -32,8 +32,9 @@ use datafusion_physical_expr_common::sort_expr::LexOrdering;
 use futures::stream::{Stream, StreamExt};
 
 use super::AggregateExec;
-use super::aggregate_hash_table::{FinalMarker, OrderedAggregateTable};
-use super::group_values::GroupByMetrics;
+use super::aggregate_hash_table::{
+    FinalMarker, OrderedAggregateTable, OrderedAggregateTableMetrics,
+};
 use crate::aggregates::AggregateMode;
 use crate::metrics::{BaselineMetrics, RecordOutput, SpillMetrics};
 use crate::sorts::IncrementalSortIterator;
@@ -216,7 +217,7 @@ impl OrderedFinalSpillContext {
     fn into_replay_stream(
         self,
         baseline_metrics: &BaselineMetrics,
-        group_by_metrics: GroupByMetrics,
+        metrics: OrderedAggregateTableMetrics,
         reservation: MemoryReservation,
     ) -> Result<SendableRecordBatchStream> {
         let Self {
@@ -250,7 +251,7 @@ impl OrderedFinalSpillContext {
             merged,
             &InputOrderMode::Sorted,
             baseline_metrics.clone(),
-            group_by_metrics,
+            metrics,
             None,
             reservation,
         )?;
@@ -282,7 +283,7 @@ impl OrderedFinalAggregateStream {
         input_order_mode: &InputOrderMode,
     ) -> Result<Self> {
         let baseline_metrics = BaselineMetrics::new(&agg.metrics, partition);
-        let group_by_metrics = GroupByMetrics::new(&agg.metrics, partition);
+        let metrics = OrderedAggregateTableMetrics::new(agg, partition);
         let spill_metrics = SpillMetrics::new(&agg.metrics, partition);
         let reservation =
             
MemoryConsumer::new(format!("OrderedFinalAggregateStream[{partition}]"))
@@ -300,7 +301,7 @@ impl OrderedFinalAggregateStream {
             input,
             input_order_mode,
             baseline_metrics,
-            group_by_metrics,
+            metrics,
             Some(spill_metrics),
             reservation,
         )
@@ -320,7 +321,7 @@ impl OrderedFinalAggregateStream {
         input: SendableRecordBatchStream,
         input_order_mode: &InputOrderMode,
         baseline_metrics: BaselineMetrics,
-        group_by_metrics: GroupByMetrics,
+        metrics: OrderedAggregateTableMetrics,
         spill_metrics: Option<SpillMetrics>,
         reservation: MemoryReservation,
     ) -> Result<Self> {
@@ -359,7 +360,7 @@ impl OrderedFinalAggregateStream {
             Arc::clone(&schema),
             batch_size,
             input_order_mode,
-            group_by_metrics,
+            metrics,
         )?;
         Ok(Self {
             schema,
@@ -654,12 +655,12 @@ impl OrderedFinalAggregateStream {
         let timer = elapsed_compute.timer();
         let replay = match spill_context.spill_table(&mut table) {
             Ok(()) => {
-                let group_by_metrics = table.group_by_metrics();
+                let metrics = table.metrics();
                 drop(table);
                 match self.reservation.try_resize(0) {
                     Ok(()) => (*spill_context).into_replay_stream(
                         &self.baseline_metrics,
-                        group_by_metrics,
+                        metrics,
                         self.reservation.new_empty(),
                     ),
                     Err(e) => Err(e),
diff --git a/datafusion/physical-plan/src/aggregates/ordered_single_stream.rs 
b/datafusion/physical-plan/src/aggregates/ordered_single_stream.rs
index 2025bdce30..da00b42e5c 100644
--- a/datafusion/physical-plan/src/aggregates/ordered_single_stream.rs
+++ b/datafusion/physical-plan/src/aggregates/ordered_single_stream.rs
@@ -31,8 +31,9 @@ use datafusion_physical_expr::expressions::Column;
 use datafusion_physical_expr_common::sort_expr::LexOrdering;
 use futures::stream::{Stream, StreamExt};
 
-use super::aggregate_hash_table::{OrderedAggregateTable, SingleMarker};
-use super::group_values::GroupByMetrics;
+use super::aggregate_hash_table::{
+    OrderedAggregateTable, OrderedAggregateTableMetrics, SingleMarker,
+};
 use super::ordered_final_stream::OrderedFinalAggregateStream;
 use super::{AggregateExec, create_schema};
 use crate::aggregates::AggregateMode;
@@ -276,7 +277,7 @@ impl OrderedSingleSpillContext {
     fn into_replay_stream(
         self,
         baseline_metrics: &BaselineMetrics,
-        group_by_metrics: GroupByMetrics,
+        metrics: OrderedAggregateTableMetrics,
         reservation: MemoryReservation,
     ) -> Result<SendableRecordBatchStream> {
         let Self {
@@ -310,7 +311,7 @@ impl OrderedSingleSpillContext {
             merged,
             &InputOrderMode::Sorted,
             baseline_metrics.clone(),
-            group_by_metrics,
+            metrics,
             None,
             reservation,
         )?;
@@ -635,12 +636,12 @@ impl OrderedSingleAggregateStream {
         let timer = elapsed_compute.timer();
         let replay = match spill_context.spill_table(&mut table) {
             Ok(()) => {
-                let group_by_metrics = table.group_by_metrics();
+                let metrics = table.metrics();
                 drop(table);
                 match self.reservation.try_resize(0) {
                     Ok(()) => (*spill_context).into_replay_stream(
                         &self.baseline_metrics,
-                        group_by_metrics,
+                        metrics,
                         self.reservation.new_empty(),
                     ),
                     Err(e) => Err(e),
diff --git a/datafusion/physical-plan/src/aggregates/single_stream.rs 
b/datafusion/physical-plan/src/aggregates/single_stream.rs
index c6f25dc2cf..3e306d72a7 100644
--- a/datafusion/physical-plan/src/aggregates/single_stream.rs
+++ b/datafusion/physical-plan/src/aggregates/single_stream.rs
@@ -36,8 +36,9 @@ use datafusion_physical_expr::expressions::Column;
 use datafusion_physical_expr_common::sort_expr::LexOrdering;
 use futures::stream::{Stream, StreamExt};
 
-use super::aggregate_hash_table::{AggregateHashTable, SingleMarker};
-use super::group_values::GroupByMetrics;
+use super::aggregate_hash_table::{
+    AggregateHashTable, OrderedAggregateTableMetrics, SingleMarker,
+};
 use super::ordered_final_stream::OrderedFinalAggregateStream;
 use super::{AggregateExec, create_schema};
 use crate::aggregates::AggregateMode;
@@ -267,7 +268,7 @@ impl SingleSpillContext {
     fn into_replay_stream(
         self,
         baseline_metrics: &BaselineMetrics,
-        group_by_metrics: GroupByMetrics,
+        metrics: OrderedAggregateTableMetrics,
         reservation: MemoryReservation,
     ) -> Result<SendableRecordBatchStream> {
         let Self {
@@ -301,7 +302,7 @@ impl SingleSpillContext {
             merged,
             &InputOrderMode::Sorted,
             baseline_metrics.clone(),
-            group_by_metrics,
+            metrics,
             None,
             reservation,
         )?;
@@ -591,12 +592,12 @@ impl SingleHashAggregateStream {
         let timer = elapsed_compute.timer();
         let replay = match spill_context.spill_table(&mut hash_table) {
             Ok(()) => {
-                let group_by_metrics = hash_table.group_by_metrics().clone();
+                let metrics = 
OrderedAggregateTableMetrics::from_hash_table(&hash_table);
                 drop(hash_table);
                 match self.reservation.try_resize(0) {
                     Ok(()) => (*spill_context).into_replay_stream(
                         &self.baseline_metrics,
-                        group_by_metrics,
+                        metrics,
                         self.reservation.new_empty(),
                     ),
                     Err(e) => Err(e),


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to