4343
4444from __future__ import annotations
4545
46+ import time
4647from dataclasses import dataclass
4748from typing import Any , Dict , List , Optional
4849
5354 totals_with_multiplier ,
5455)
5556from ngraph .explorer import NetworkExplorer
57+ from ngraph .logging import get_logger
5658from ngraph .workflow .base import WorkflowStep , register_workflow_step
5759
60+ logger = get_logger (__name__ )
61+
5862
5963@dataclass
6064class CostPowerEfficiency (WorkflowStep ):
@@ -90,6 +94,18 @@ def run(self, scenario: Any) -> None:
9094 Returns:
9195 None
9296 """
97+ t0 = time .perf_counter ()
98+ logger .info (
99+ "Starting cost/power efficiency analysis: name=%s include_disabled=%s "
100+ "node_hw=%s link_hw=%s denom_key=%s denom_explicit=%s" ,
101+ self .name ,
102+ str (self .include_disabled ),
103+ str (self .collect_node_hw_entries ),
104+ str (self .collect_link_hw_entries ),
105+ self .delivered_bandwidth_key ,
106+ str (self .delivered_bandwidth_gbps is not None ),
107+ )
108+
93109 # Build explorer FIRST to trigger all validations before collecting metrics
94110 explorer = NetworkExplorer .explore_network (
95111 scenario .network , components_library = scenario .components_library
@@ -168,6 +184,8 @@ def run(self, scenario: Any) -> None:
168184 scenario .results .put (step_name , "hardware_bom_by_path" , bom_by_path )
169185
170186 # Optional hardware inventory
187+ node_entries : List [Dict [str , Any ]] = []
188+ link_entries : List [Dict [str , Any ]] = []
171189 if self .collect_node_hw_entries or self .collect_link_hw_entries :
172190 node_entries , link_entries = self ._collect_hw_entries (
173191 scenario .components_library , scenario
@@ -177,6 +195,51 @@ def run(self, scenario: Any) -> None:
177195 if self .collect_link_hw_entries :
178196 scenario .results .put (step_name , "link_hw_entries" , link_entries )
179197
198+ # INFO-level outcome summary for quick visual inspection
199+ try :
200+ duration_sec = time .perf_counter () - t0
201+ bom_count = len (bom_total ) if isinstance (bom_total , dict ) else 0
202+ # Top-N components by count
203+ top_n = 3
204+ top_items = []
205+ try :
206+ top_items = sorted (
207+ bom_total .items (), key = lambda kv : (- float (kv [1 ]), kv [0 ])
208+ )[:top_n ]
209+ except Exception :
210+ top_items = []
211+ top_str = (
212+ ", " .join (f"{ k } :{ float (v ):.3f} " for k , v in top_items )
213+ if top_items
214+ else "-"
215+ )
216+ logger .info (
217+ (
218+ "CostPowerEfficiency summary: name=%s include_disabled=%s "
219+ "capex=%.3f power_watts=%.3f delivered_gbps=%.6g "
220+ "dollars_per_gbit=%.6g watts_per_gbit=%.6g "
221+ "bom_components=%d top=[%s] node_hw_entries=%d link_hw_entries=%d "
222+ "duration=%.3fs"
223+ ),
224+ self .name ,
225+ str (self .include_disabled ),
226+ total_capex ,
227+ total_power_watts ,
228+ denom ,
229+ dollars_per_gbit ,
230+ watts_per_gbit ,
231+ bom_count ,
232+ top_str ,
233+ len (node_entries ) if self .collect_node_hw_entries else 0 ,
234+ len (link_entries ) if self .collect_link_hw_entries else 0 ,
235+ duration_sec ,
236+ )
237+ except Exception :
238+ # Logging must not raise
239+ pass
240+
241+ logger .info ("Cost/power efficiency analysis completed: %s" , self .name )
242+
180243 def _collect_hw_entries (
181244 self , library : ComponentsLibrary , scenario : Any
182245 ) -> tuple [List [Dict [str , Any ]], List [Dict [str , Any ]]]:
0 commit comments