2323import Monsoon .sampleEngine as sampleEngine
2424from bridge .file_storage .upload_files .file_uploader import FileUploader
2525from utils .custom_logger import getLogger
26+ from utils .power_utils import post_process_power_data
2627
2728
2829def collectPowerData (
@@ -32,8 +33,6 @@ def collectPowerData(
3233 num_iters ,
3334 method = "monsoon" ,
3435 monsoon_map = None ,
35- threshold = 300 ,
36- window_size = 1000 ,
3736):
3837 has_usb = method == "monsoon_with_usb"
3938 Mon = HVPM .Monsoon ()
@@ -105,79 +104,58 @@ def collectPowerData(
105104 repeat = repeat + 1
106105 sleep (2 )
107106 if repeat >= 5 :
108- getLogger ().info ("Failed to close device" )
109- return {}
107+ raise Exception ("Failed to close device" )
110108
111109 power_data , url = _extract_samples (samples , has_usb )
112110
113- getLogger ().info (
114- "Calculating the benchmark data range from "
115- "{} data points" .format (len (power_data ))
116- )
117- max_range , max_low_range = _calculatePowerDataRange (
118- power_data , threshold , window_size
119- )
120- if max_range is None or max_low_range is None :
121- getLogger ().error ("Metric collection failed" )
122- return {}
123-
124- getLogger ().info (
125- "Collecting baseline data from "
126- "{} to {}" .format (max_low_range ["start" ], max_low_range ["end" ])
127- )
128- getLogger ().info (
129- "Collecting data from " "{} to {}" .format (max_range ["start" ], max_range ["end" ])
130- )
131- getLogger ().info (
132- "Benchmark time: "
133- "{} - {} s" .format (
134- power_data [max_range ["start" ]]["time" ], power_data [max_range ["end" ]]["time" ]
135- )
136- )
137- data = _retrievePowerData (power_data , max_range , max_low_range , num_iters )
111+ data = post_process_power_data (power_data , sample_rate = 5000 , num_iters = num_iters )
138112 data ["power_trace" ] = url
113+
139114 return data
140115
141116
142117def _extract_samples (samples , has_usb ):
143- power_data = []
118+ power_data = {
119+ "time" : [],
120+ "current" : [],
121+ "voltage" : [],
122+ "usb_current" : [],
123+ "usb_voltage" : [],
124+ "total_power" : [],
125+ }
144126
145- prev_time_stamp = - 1
146127 for i in range (len (samples [sampleEngine .channels .timeStamp ])):
147128 time_stamp = samples [sampleEngine .channels .timeStamp ][i ]
148129 current = samples [sampleEngine .channels .MainCurrent ][i ]
149130 voltage = samples [sampleEngine .channels .MainVoltage ][i ]
150131 if has_usb :
151132 usb_current = samples [sampleEngine .channels .USBCurrent ][i ]
152133 usb_voltage = samples [sampleEngine .channels .USBVoltage ][i ]
153- # there is a bug that two consecutive time stamps may be identical
154- # patch it by evenly divide the timestamps
155- if i >= 2 and prev_time_stamp == time_stamp :
156- power_data [- 1 ]["time" ] = (power_data [- 2 ]["time" ] + time_stamp ) / 2
157- prev_time_stamp = time_stamp
158- data = {
159- "time" : time_stamp ,
160- "current" : current ,
161- "voltage" : voltage ,
162- "usb_current" : 0 ,
163- "usb_voltage" : 0 ,
164- }
134+ power_data ["time" ].append (time_stamp )
135+ power_data ["current" ].append (current )
136+ power_data ["voltage" ].append (voltage )
165137 if has_usb :
166- data ["usb_current" ] = usb_current
167- data ["usb_voltage" ] = usb_voltage
168-
169- power_data .append (data )
138+ power_data ["usb_current" ].append (usb_current )
139+ power_data ["usb_voltage" ].append (usb_voltage )
140+ power_data ["total_power" ].append (
141+ (current * voltage ) + (usb_current * usb_voltage )
142+ )
143+ else :
144+ power_data ["usb_current" ].append (0 )
145+ power_data ["usb_voltage" ].append (0 )
146+ power_data ["total_power" ].append (current * voltage )
170147
171148 with tempfile .NamedTemporaryFile (
172149 mode = "w" , delete = False , prefix = "power_data_" , suffix = ".csv"
173150 ) as f :
174151 filename = f .name
175152 getLogger ().info ("Writing power data to file: {}" .format (f .name ))
176- f .write ("time, current, voltage, usb_current, usb_voltage\n " )
177- for i in range (len (power_data )):
178- entry = power_data [i ]
153+ f .write ("time, current, voltage, usb_current, usb_voltage, total_power\n " )
154+ for i in range (len (power_data ["time" ])):
179155 f .write (
180- f"{ entry ['time' ]} , { entry ['current' ]} , { entry ['voltage' ]} , { entry ['usb_current' ]} , { entry ['usb_voltage' ]} \n "
156+ f"{ power_data ['time' ][i ]:.4f} , { power_data ['current' ][i ]:.4f} , "
157+ f"{ power_data ['voltage' ][i ]:.4f} , { power_data ['usb_current' ][i ]:.4f} , "
158+ f"{ power_data ['usb_voltage' ][i ]:.4f} , { power_data ['total_power' ][i ]:.4f} \n "
181159 )
182160
183161 getLogger ().info (f"Uploading power file { filename } " )
@@ -188,238 +166,16 @@ def _extract_samples(samples, has_usb):
188166 return power_data , url
189167
190168
191- def _get_sum_current (power_data , start , end , window_size ):
192- # Get the total current in the window
193- sum = 0
194- for i in range (start , min (start + window_size , end )):
195- sum += power_data [i ]["current" ]
196- sum += power_data [i ]["usb_current" ]
197- return i , sum
198-
199-
200- def _find_first_window_below_threshold (
201- power_data , initial_sum , start , end , window_size , threshold
202- ):
203- return _find_first_window (
204- power_data , initial_sum , start , end , window_size , threshold , False
205- )
206-
207-
208- def _find_first_window_above_threshold (
209- power_data , initial_sum , start , end , window_size , threshold
210- ):
211- return _find_first_window (
212- power_data , initial_sum , start , end , window_size , threshold , True
213- )
214-
215-
216- def _find_first_window (
217- power_data , initial_sum , start , end , window_size , threshold , above = True
218- ):
219- assert start >= window_size - 1
220- i = start
221- sum = initial_sum
222- while i < end - 1 and (
223- (sum / window_size ) < threshold if above else (sum / window_size ) > threshold
224- ):
225- # moving average advance one step
226- i = i + 1
227- sum = (
228- sum
229- - power_data [i - window_size ]["current" ]
230- - power_data [i - window_size ]["usb_current" ]
231- + power_data [i ]["current" ]
232- + power_data [i ]["usb_current" ]
233- )
234- return i , sum
235-
236-
237- def _calculateOnePowerDataRange (
238- power_data , num , i , sum , threshold = 300 , window_size = 1000
239- ):
240- # first find the average current is less than the threshold
241- i , sum = _find_first_window_below_threshold (
242- power_data , sum , i , num , window_size , threshold
243- )
244-
245- # find the first window whose average current is above the threshold
246- i , sum = _find_first_window_above_threshold (
247- power_data , sum , i , num , window_size , threshold
248- )
249-
250- window_i = i
251-
252- # find the last entry below threshold
253- while (
254- i > 0 and (power_data [i ]["current" ] + power_data [i ]["usb_current" ]) > threshold
255- ):
256- i = i - 1
257- # find the min of the constant decreasing current
258- while i > 0 and (
259- power_data [i - 1 ]["current" ] + power_data [i - 1 ]["usb_current" ]
260- ) < (power_data [i ]["current" ] + power_data [i ]["usb_current" ]):
261- i = i - 1
262-
263- # have found a possible start of the benchmark
264- start = i
265-
266- # find the first window whose current is below threshold again
267- i , sum = _find_first_window_below_threshold (
268- power_data , sum , window_i , num , window_size , threshold
269- )
270- ii = max (0 , i - window_size )
271-
272- # get the first entry below threshold
273- while (
274- ii < num
275- and (power_data [ii ]["current" ] + power_data [ii ]["usb_current" ]) > threshold
276- ):
277- ii = ii + 1
278- # get the min of the constant decreasing current
279- while ii < num - 1 and (
280- power_data [ii ]["current" ] + power_data [ii ]["usb_current" ]
281- ) > (power_data [ii + 1 ]["current" ] + power_data [ii + 1 ]["usb_current" ]):
282- ii = ii + 1
283-
284- # found a possible end of the benchmark
285- end = ii - 1
286-
287- return start , end , i , sum
288-
289-
290- # This only works in one specific scenario:
291- # In the beginning, the current is low and below threshold
292- # Then there is a sudden jump in current and the current keeps high
293- # After the benchmark, the current restores back to below threshold for some time
294- # All other scenarios are not caught
295- def _calculatePowerDataRange (power_data , threshold = 300 , window_size = 1000 ):
296- num = len (power_data )
297- if num <= window_size :
298- getLogger ().error (
299- f"Collected { num } samples from monsoon, which is less than the window size of { window_size } "
300- )
301- return None , None
302- # first get the sum of the window size values
303- i , sum = _get_sum_current (power_data , 0 , num , window_size )
304-
305- ranges = []
306- while i < num - 1 :
307- start , end , i , sum = _calculateOnePowerDataRange (
308- power_data , num , i , sum , threshold , window_size
309- )
310- if (start < num ) and (end <= num ) and (start < end ):
311- ranges .append ({"start" : start , "end" : end })
312-
313- if len (ranges ) == 0 :
314- getLogger ().error (
315- "Cannot collect any useful metric from the monsoon data. Please examine the benchmark setting."
316- )
317- return None , None
318-
319- # get the max range of all collected ranges
320- max_range = ranges [0 ]
321- r_start = 0
322- for r in ranges :
323- assert r ["end" ] >= r ["start" ]
324- assert r ["start" ] >= r_start
325- r_start = r ["end" ]
326- if r ["end" ] - r ["start" ] > max_range ["end" ] - max_range ["start" ]:
327- max_range = r
328-
329- # get the range below the threshold
330- low_ranges = [{"start" : 0 , "end" : - 1 }]
331- for r in ranges :
332- low_ranges [- 1 ]["end" ] = max (r ["start" ] - 1 , low_ranges [- 1 ]["start" ])
333- low_ranges .append ({"start" : r ["end" ] + 1 , "end" : - 1 })
334- low_ranges [- 1 ]["end" ] = num - 1
335-
336- # get the max range that is below the threshold
337- max_low_range = low_ranges [0 ]
338- for r in low_ranges :
339- if r ["end" ] - r ["start" ] > max_low_range ["end" ] - max_low_range ["start" ]:
340- max_low_range = r
341- getLogger ().info (ranges )
342- getLogger ().info (low_ranges )
343- # the test needs to be designed in a way that more than half of the collected
344- # data is executing the model.
345- """
346- assert (
347- max_range["end"] - max_range["start"] >= num / 2
348- ), f"Test needs to be designed that over half of the collected data is model execution. "
349- """
350- return max_range , max_low_range
351-
352-
353- def _retrievePowerData (power_data , high_range , low_range , num_iters ):
354- data = {}
355- if high_range ["start" ] < 0 or high_range ["end" ] < 0 :
356- return data
357-
358- # get base current. It is just an approximation
359-
360- total_current = 0
361- total_usb_current = 0
362- count = 0
363- for i in range (low_range ["start" ], low_range ["end" ]):
364- total_current += power_data [i ]["current" ]
365- total_usb_current += power_data [i ]["usb_current" ]
366- count += 1
367- base_current = total_current / count if count > 0 else 0
368- base_usb_current = total_usb_current / count if count > 0 else 0
369-
370- energy = 0
371- prev_time = power_data [max (0 , high_range ["start" ] - 1 )]["time" ]
372- for i in range (high_range ["start" ], high_range ["end" ]):
373- entry = power_data [i ]
374- curr_time = entry ["time" ]
375- energy += (
376- entry ["voltage" ] * (entry ["current" ] - base_current )
377- + entry ["usb_voltage" ] * (entry ["usb_current" ] - base_usb_current )
378- ) * (curr_time - prev_time )
379- prev_time = curr_time
380- total_time = (
381- power_data [high_range ["end" ]]["time" ] - power_data [high_range ["start" ]]["time" ]
382- )
383- power = energy / total_time
384- energy_per_inference = energy / num_iters
385- latency = total_time * 1000 * 1000 / num_iters
386- data ["energy" ] = _composeStructuredData (energy_per_inference , "energy" , "mJ" )
387- data ["power" ] = _composeStructuredData (power , "power" , "mW" )
388- data ["latency" ] = _composeStructuredData (latency , "latency" , "uS" )
389-
390- getLogger ().info (f"Number of iterations: { num_iters } " )
391- getLogger ().info ("Base current: {} mA" .format (base_current ))
392- getLogger ().info ("Energy per inference: {} mJ" .format (energy_per_inference ))
393- getLogger ().info ("Power: {} mW" .format (power ))
394- getLogger ().info ("Latency per inference: {} uS" .format (latency ))
395- return data
396-
397-
398- def _composeStructuredData (data , metric , unit ):
399- return {
400- "values" : [data ],
401- "type" : "NET" ,
402- "metric" : metric ,
403- "unit" : unit ,
404- "summary" : {
405- "p0" : data ,
406- "p10" : data ,
407- "p50" : data ,
408- "p90" : data ,
409- "p100" : data ,
410- "mean" : data ,
411- "stdev" : 0 ,
412- "MAD" : 0 ,
413- },
414- }
415-
416-
417169def _getMonsoonSerialno (device_hash , monsoon_map = None , Monsoon = None ):
418170 if monsoon_map :
419171 mapping = json .loads (monsoon_map )
420172 serialno = mapping .get (device_hash , None )
421173 if not serialno and len (Monsoon .enumerateDevices ()) > 1 :
422174 getLogger ().info (
423- f"Device { device_hash } is not associated with a specific power monitor, but there are { len (Monsoon .enumerateDevices ())} Monsoon monitors connected ({ Monsoon .enumerateDevices ()} ). Please add --monsoon_map to specify the mapping between device hash and Monsoon serial number"
175+ f"Device { device_hash } is not associated with a specific power monitor, "
176+ f"but there are { len (Monsoon .enumerateDevices ())} Monsoon monitors "
177+ f"connected: { Monsoon .enumerateDevices ()} . Please add --monsoon_map to "
178+ f"specify the mapping between device hash and Monsoon serial number"
424179 )
180+
425181 return serialno
0 commit comments