@@ -52,6 +52,8 @@ pub struct DynamicEngine {
5252 HashMap < String , mpsc:: Sender < streamkit_core:: pins:: PinManagementMessage > > ,
5353 /// Map of node pin metadata: NodeId -> Pin Metadata (for runtime type validation)
5454 pub ( super ) node_pin_metadata : HashMap < String , NodePinMetadata > ,
55+ /// Map of node_id -> node_kind for labeling metrics
56+ pub ( super ) node_kinds : HashMap < String , String > ,
5557 pub ( super ) batch_size : usize ,
5658 /// Session ID for gateway registration (if applicable)
5759 pub ( super ) session_id : Option < String > ,
@@ -75,11 +77,11 @@ pub struct DynamicEngine {
7577 pub ( super ) nodes_active_gauge : opentelemetry:: metrics:: Gauge < u64 > ,
7678 pub ( super ) node_state_transitions_counter : opentelemetry:: metrics:: Counter < u64 > ,
7779 pub ( super ) engine_operations_counter : opentelemetry:: metrics:: Counter < u64 > ,
78- // Node-level packet metrics
79- pub ( super ) node_packets_received_gauge : opentelemetry:: metrics:: Gauge < u64 > ,
80- pub ( super ) node_packets_sent_gauge : opentelemetry:: metrics:: Gauge < u64 > ,
81- pub ( super ) node_packets_discarded_gauge : opentelemetry:: metrics:: Gauge < u64 > ,
82- pub ( super ) node_packets_errored_gauge : opentelemetry:: metrics:: Gauge < u64 > ,
80+ // Node-level packet metrics (counters, not gauges - for proper rate() calculation)
81+ pub ( super ) node_packets_received_counter : opentelemetry:: metrics:: Counter < u64 > ,
82+ pub ( super ) node_packets_sent_counter : opentelemetry:: metrics:: Counter < u64 > ,
83+ pub ( super ) node_packets_discarded_counter : opentelemetry:: metrics:: Counter < u64 > ,
84+ pub ( super ) node_packets_errored_counter : opentelemetry:: metrics:: Counter < u64 > ,
8385 // Node state metric (1=running, 0=not running)
8486 pub ( super ) node_state_gauge : opentelemetry:: metrics:: Gauge < u64 > ,
8587}
@@ -363,16 +365,50 @@ impl DynamicEngine {
363365 "Node stats updated"
364366 ) ;
365367
366- // Store the current stats
367- self . node_stats . insert ( update. node_id . clone ( ) , update. stats . clone ( ) ) ;
368+ let node_kind =
369+ self . node_kinds . get ( & update. node_id ) . map_or ( "unknown" , std:: string:: String :: as_str) ;
370+ let labels = & [
371+ KeyValue :: new ( "node_id" , update. node_id . clone ( ) ) ,
372+ KeyValue :: new ( "node_kind" , node_kind. to_string ( ) ) ,
373+ ] ;
374+
375+ let prev_stats = self . node_stats . get ( & update. node_id ) ;
376+
377+ let delta_received = prev_stats. map_or ( update. stats . received , |prev| {
378+ if update. stats . received < prev. received {
379+ update. stats . received
380+ } else {
381+ update. stats . received - prev. received
382+ }
383+ } ) ;
384+ let delta_sent = prev_stats. map_or ( update. stats . sent , |prev| {
385+ if update. stats . sent < prev. sent {
386+ update. stats . sent
387+ } else {
388+ update. stats . sent - prev. sent
389+ }
390+ } ) ;
391+ let delta_discarded = prev_stats. map_or ( update. stats . discarded , |prev| {
392+ if update. stats . discarded < prev. discarded {
393+ update. stats . discarded
394+ } else {
395+ update. stats . discarded - prev. discarded
396+ }
397+ } ) ;
398+ let delta_errored = prev_stats. map_or ( update. stats . errored , |prev| {
399+ if update. stats . errored < prev. errored {
400+ update. stats . errored
401+ } else {
402+ update. stats . errored - prev. errored
403+ }
404+ } ) ;
368405
369- // Record metrics with node_id label
370- let labels = & [ KeyValue :: new ( "node_id" , update. node_id . clone ( ) ) ] ;
406+ self . node_packets_received_counter . add ( delta_received, labels) ;
407+ self . node_packets_sent_counter . add ( delta_sent, labels) ;
408+ self . node_packets_discarded_counter . add ( delta_discarded, labels) ;
409+ self . node_packets_errored_counter . add ( delta_errored, labels) ;
371410
372- self . node_packets_received_gauge . record ( update. stats . received , labels) ;
373- self . node_packets_sent_gauge . record ( update. stats . sent , labels) ;
374- self . node_packets_discarded_gauge . record ( update. stats . discarded , labels) ;
375- self . node_packets_errored_gauge . record ( update. stats . errored , labels) ;
411+ self . node_stats . insert ( update. node_id . clone ( ) , update. stats . clone ( ) ) ;
376412
377413 // Broadcast to all subscribers
378414 self . stats_subscribers . retain ( |subscriber| {
@@ -862,6 +898,7 @@ impl DynamicEngine {
862898 self . node_stats . remove ( node_id) ;
863899 self . node_pin_metadata . remove ( node_id) ;
864900 self . pin_management_txs . remove ( node_id) ;
901+ self . node_kinds . remove ( node_id) ;
865902 self . nodes_active_gauge . record ( self . live_nodes . len ( ) as u64 , & [ ] ) ;
866903 }
867904
@@ -881,6 +918,7 @@ impl DynamicEngine {
881918 tracing:: info!( name = %node_id, kind = %kind, "Adding node to graph" ) ;
882919 match self . registry . create_node ( & kind, params. as_ref ( ) ) {
883920 Ok ( node) => {
921+ self . node_kinds . insert ( node_id. clone ( ) , kind. clone ( ) ) ;
884922 // Delegate initialization to helper function
885923 // Pass by reference to avoid unnecessary clones
886924 if let Err ( e) = self
0 commit comments