1818use std:: collections:: HashMap ;
1919use std:: sync:: Arc ;
2020
21- use datafusion:: physical_plan:: metrics:: { MetricValue , MetricsSet , Metric } ;
21+ use datafusion:: physical_plan:: metrics:: { MetricValue , MetricsSet , Metric , Timestamp } ;
2222use pyo3:: prelude:: * ;
2323
2424#[ pyclass( frozen, name = "MetricsSet" , module = "datafusion" ) ]
@@ -81,6 +81,32 @@ impl PyMetric {
8181 pub fn new ( metric : Arc < Metric > ) -> Self {
8282 Self { metric }
8383 }
84+
85+ fn timestamp_to_pyobject < ' py > (
86+ py : Python < ' py > ,
87+ ts : & Timestamp ,
88+ ) -> PyResult < Option < Bound < ' py , PyAny > > > {
89+ match ts. value ( ) {
90+ Some ( dt) => {
91+ let nanos = dt. timestamp_nanos_opt ( ) . ok_or_else ( || {
92+ PyErr :: new :: < pyo3:: exceptions:: PyOverflowError , _ > (
93+ "timestamp out of range" ,
94+ )
95+ } ) ?;
96+ let datetime_mod = py. import ( "datetime" ) ?;
97+ let datetime_cls = datetime_mod. getattr ( "datetime" ) ?;
98+ let tz_utc = datetime_mod. getattr ( "timezone" ) ?. getattr ( "utc" ) ?;
99+ let secs = nanos / 1_000_000_000 ;
100+ let micros = ( nanos % 1_000_000_000 ) / 1_000 ;
101+ let result = datetime_cls. call_method1 (
102+ "fromtimestamp" ,
103+ ( secs as f64 + micros as f64 / 1_000_000.0 , tz_utc) ,
104+ ) ?;
105+ Ok ( Some ( result) )
106+ }
107+ None => Ok ( None ) ,
108+ }
109+ }
84110}
85111
86112#[ pymethods]
@@ -90,62 +116,30 @@ impl PyMetric {
90116 self . metric . value ( ) . name ( ) . to_string ( )
91117 }
92118
93- /// Returns the numeric value of this metric as a `usize`, or `None` when the
94- /// value is not representable as an integer.
95- ///
96- /// # Note
97- /// `StartTimestamp` and `EndTimestamp` metrics are returned as nanoseconds
98- /// since the Unix epoch (via `timestamp_nanos_opt`), which may overflow
99- /// a `usize` on 32-bit platforms or return `None` if the timestamp is out
100- /// of range. Non-numeric metric variants (unrecognised future variants)
101- /// also return `None`.
102119 #[ getter]
103- fn value ( & self ) -> Option < usize > {
120+ fn value < ' py > ( & self , py : Python < ' py > ) -> PyResult < Option < Bound < ' py , PyAny > > > {
104121 match self . metric . value ( ) {
105- MetricValue :: OutputRows ( c) => Some ( c. value ( ) ) ,
106- MetricValue :: OutputBytes ( c) => Some ( c. value ( ) ) ,
107- MetricValue :: ElapsedCompute ( t) => Some ( t. value ( ) ) ,
108- MetricValue :: SpillCount ( c) => Some ( c. value ( ) ) ,
109- MetricValue :: SpilledBytes ( c) => Some ( c. value ( ) ) ,
110- MetricValue :: SpilledRows ( c) => Some ( c. value ( ) ) ,
111- MetricValue :: CurrentMemoryUsage ( g) => Some ( g. value ( ) ) ,
112- MetricValue :: Count { count, .. } => Some ( count. value ( ) ) ,
113- MetricValue :: Gauge { gauge, .. } => Some ( gauge. value ( ) ) ,
114- MetricValue :: Time { time, .. } => Some ( time. value ( ) ) ,
115- MetricValue :: StartTimestamp ( ts) => {
116- ts. value ( ) . and_then ( |dt| dt. timestamp_nanos_opt ( ) . map ( |n| n as usize ) )
117- }
118- MetricValue :: EndTimestamp ( ts) => {
119- ts. value ( ) . and_then ( |dt| dt. timestamp_nanos_opt ( ) . map ( |n| n as usize ) )
122+ MetricValue :: OutputRows ( c) => Ok ( Some ( c. value ( ) . into_pyobject ( py) ?. into_any ( ) ) ) ,
123+ MetricValue :: OutputBytes ( c) => Ok ( Some ( c. value ( ) . into_pyobject ( py) ?. into_any ( ) ) ) ,
124+ MetricValue :: ElapsedCompute ( t) => Ok ( Some ( t. value ( ) . into_pyobject ( py) ?. into_any ( ) ) ) ,
125+ MetricValue :: SpillCount ( c) => Ok ( Some ( c. value ( ) . into_pyobject ( py) ?. into_any ( ) ) ) ,
126+ MetricValue :: SpilledBytes ( c) => Ok ( Some ( c. value ( ) . into_pyobject ( py) ?. into_any ( ) ) ) ,
127+ MetricValue :: SpilledRows ( c) => Ok ( Some ( c. value ( ) . into_pyobject ( py) ?. into_any ( ) ) ) ,
128+ MetricValue :: CurrentMemoryUsage ( g) => Ok ( Some ( g. value ( ) . into_pyobject ( py) ?. into_any ( ) ) ) ,
129+ MetricValue :: Count { count, .. } => Ok ( Some ( count. value ( ) . into_pyobject ( py) ?. into_any ( ) ) ) ,
130+ MetricValue :: Gauge { gauge, .. } => Ok ( Some ( gauge. value ( ) . into_pyobject ( py) ?. into_any ( ) ) ) ,
131+ MetricValue :: Time { time, .. } => Ok ( Some ( time. value ( ) . into_pyobject ( py) ?. into_any ( ) ) ) ,
132+ MetricValue :: StartTimestamp ( ts) | MetricValue :: EndTimestamp ( ts) => {
133+ Self :: timestamp_to_pyobject ( py, ts)
120134 }
121- _ => None ,
135+ _ => Ok ( None ) ,
122136 }
123137 }
124138
125- /// Returns the value as a Python `datetime` for `StartTimestamp` / `EndTimestamp`
126- /// metrics, or `None` for all other metric types.
127139 fn value_as_datetime < ' py > ( & self , py : Python < ' py > ) -> PyResult < Option < Bound < ' py , PyAny > > > {
128140 match self . metric . value ( ) {
129141 MetricValue :: StartTimestamp ( ts) | MetricValue :: EndTimestamp ( ts) => {
130- match ts. value ( ) {
131- Some ( dt) => {
132- let nanos = dt. timestamp_nanos_opt ( )
133- . ok_or_else ( || PyErr :: new :: < pyo3:: exceptions:: PyOverflowError , _ > (
134- "timestamp out of range"
135- ) ) ?;
136- let datetime_mod = py. import ( "datetime" ) ?;
137- let datetime_cls = datetime_mod. getattr ( "datetime" ) ?;
138- let tz_utc = datetime_mod. getattr ( "timezone" ) ?. getattr ( "utc" ) ?;
139- let secs = nanos / 1_000_000_000 ;
140- let micros = ( nanos % 1_000_000_000 ) / 1_000 ;
141- let result = datetime_cls. call_method1 (
142- "fromtimestamp" ,
143- ( secs as f64 + micros as f64 / 1_000_000.0 , tz_utc) ,
144- ) ?;
145- Ok ( Some ( result) )
146- }
147- None => Ok ( None ) ,
148- }
142+ Self :: timestamp_to_pyobject ( py, ts)
149143 }
150144 _ => Ok ( None ) ,
151145 }
0 commit comments