@@ -294,7 +294,7 @@ def highpass(signal, fs, cutoff, order=3):
294
294
plt .ylabel (measurementUnit )
295
295
296
296
fig .suptitle (f"Preview of { sensorName } signal noise (mean={ noiseScale :.1f} { measurementUnit } , threshold={ noiseThreshold :.1f} { measurementUnit } )" )
297
- fig .tight_layout (rect = [ 0 , 0.03 , 1 , 0.95 ] )
297
+ fig .tight_layout ()
298
298
self .images .append (base64 (fig ))
299
299
300
300
if noiseScale > noiseThreshold :
@@ -365,6 +365,50 @@ def analyzeAccelerometerSignalHasGravity(self, signal):
365
365
"This suggests the signal may be missing gravitational acceleration."
366
366
)
367
367
368
+ def analyzeCpuUsage (self , signal , timestamps , processes ):
369
+ CPU_USAGE_THRESHOLD = 90.0
370
+
371
+ mean = np .mean (signal )
372
+ p95 = np .percentile (signal , 95 )
373
+ p99 = np .percentile (signal , 99 )
374
+
375
+ if mean > CPU_USAGE_THRESHOLD :
376
+ self .__addIssue (DiagnosisLevel .WARNING ,
377
+ f"Average CPU usage { mean :.1f} % is above the threshold ({ CPU_USAGE_THRESHOLD :.1f} %)."
378
+ )
379
+ elif p95 > CPU_USAGE_THRESHOLD :
380
+ self .__addIssue (DiagnosisLevel .WARNING ,
381
+ f"95th percentile CPU usage { p95 :.1f} % is above the threshold ({ CPU_USAGE_THRESHOLD :.1f} %)."
382
+ )
383
+ elif p99 > CPU_USAGE_THRESHOLD :
384
+ self .__addIssue (DiagnosisLevel .WARNING ,
385
+ f"99th percentile CPU usage { p99 :.1f} % is above the threshold ({ CPU_USAGE_THRESHOLD :.1f} %)."
386
+ )
387
+
388
+ import matplotlib .pyplot as plt
389
+ fig , ax = plt .subplots (figsize = (8 , 6 ))
390
+
391
+ ax .plot (timestamps , signal , label = "System total" , linestyle = '-' )
392
+ ax .set_title ("CPU usage" )
393
+ ax .set_ylabel ("CPU usage (%)" )
394
+ ax .set_xlabel ("Time (s)" )
395
+
396
+ legend = ['System total' ]
397
+ ylim = 100
398
+ for name , data in processes .items ():
399
+ if len (data ['v' ]) == 0 : continue
400
+ ax .plot (data ['t' ], data ['v' ], label = name , linestyle = '--' )
401
+ legend .append (name )
402
+ ylim = max (ylim , np .max (data ['v' ]) * 1.1 )
403
+
404
+ ax .set_ylim (0 , ylim )
405
+
406
+ leg = ax .legend (legend , fontsize = 'large' , markerscale = 10 )
407
+ for line in leg .get_lines (): line .set_linewidth (2 )
408
+
409
+ fig .tight_layout ()
410
+ self .images .append (base64 (fig ))
411
+
368
412
def serializeIssues (self ):
369
413
self .issues = sorted (self .issues , key = lambda x : x [0 ], reverse = True )
370
414
return [{
@@ -705,13 +749,22 @@ def diagnoseGNSS(data, output):
705
749
if status .diagnosis == DiagnosisLevel .ERROR :
706
750
output ["passed" ] = False
707
751
708
- def diagnoseCpu (data , output ):
709
- data = data ["cpu" ]
710
- timestamps = np .array (data ["t" ])
711
- values = data ["v" ]
752
+ def diagnoseCPU (data , output ):
753
+ sensor = data ["cpu" ]
754
+ timestamps = np .array (sensor ["t" ])
755
+ deltaTimes = np .array (sensor ["td" ])
756
+ signal = np .array (sensor ['v' ])
757
+ processes = sensor ["processes" ]
712
758
713
759
if len (timestamps ) == 0 : return
714
760
715
- output ["cpu" ] = {
716
- "image" : plotFrame (timestamps , values , "CPU system load (%)" , ymin = 0 , ymax = 100 )
761
+ status = Status ()
762
+ status .analyzeCpuUsage (signal , timestamps , processes )
763
+
764
+ output ["CPU" ] = {
765
+ "diagnosis" : status .diagnosis .toString (),
766
+ "issues" : status .serializeIssues (),
767
+ "frequency" : computeSamplingRate (deltaTimes ),
768
+ "count" : len (timestamps ),
769
+ "images" : status .images
717
770
}
0 commit comments