11package net .laprun .sustainability .power .sensors .macos .powermetrics ;
22
3- import java .io .BufferedReader ;
4- import java .io .IOException ;
53import java .io .InputStream ;
6- import java .io .InputStreamReader ;
7- import java .util .ArrayList ;
8- import java .util .Arrays ;
9- import java .util .HashMap ;
10- import java .util .HashSet ;
11- import java .util .Map ;
124
135import io .quarkus .logging .Log ;
146import net .laprun .sustainability .power .SensorMetadata ;
15- import net .laprun .sustainability .power .measures .NoDurationSensorMeasure ;
16- import net .laprun .sustainability .power .measures .PartialSensorMeasure ;
177import net .laprun .sustainability .power .sensors .AbstractPowerSensor ;
188import net .laprun .sustainability .power .sensors .Measures ;
199import net .laprun .sustainability .power .sensors .PowerSensor ;
@@ -50,10 +40,6 @@ public abstract class MacOSPowermetricsSensor extends AbstractPowerSensor {
5040 * The extracted CPU share component name, this represents the process' share of the measured power consumption
5141 */
5242 public static final String CPU_SHARE = "cpuShare" ;
53- private static final String DURATION_SUFFIX = "ms elapsed) ***" ;
54- private static final int DURATION_SUFFIX_LENGTH = DURATION_SUFFIX .length ();
55- private static final String TASKS_SECTION_MARKER = "*** Running tasks ***" ;
56- private static final String CPU_USAGE_SECTION_MARKER = "**** Processor usage ****" ;
5743
5844 private CPU cpu ;
5945 private long lastCalled ;
@@ -64,41 +50,7 @@ public boolean supportsProcessAttribution() {
6450 }
6551
6652 void initMetadata (InputStream inputStream ) {
67- try (BufferedReader input = new BufferedReader (new InputStreamReader (inputStream ))) {
68- String line ;
69- var components = new ArrayList <SensorMetadata .ComponentMetadata >();
70- while ((line = input .readLine ()) != null ) {
71- if (cpu == null ) {
72- // if we reached the OS line while cpu is still null, we're looking at an Apple Silicon CPU
73- if (line .startsWith ("OS " )) {
74- cpu = new AppleSiliconCPU ();
75- } else if (line .startsWith ("EFI " )) {
76- cpu = new IntelCPU ();
77- }
78-
79- if (cpu != null && cpu .doneAfterComponentsInitialization (components )) {
80- break ;
81- }
82- } else {
83- // skip empty / header lines
84- if (line .isEmpty () || line .startsWith ("*" )) {
85- continue ;
86- }
87-
88- cpu .addComponentIfFound (line , components );
89- }
90- }
91-
92- if (cpu == null ) {
93- throw new IllegalStateException ("Couldn't determine CPU family from powermetrics output" );
94- }
95-
96- final var metadata = new SensorMetadata (components ,
97- "macOS powermetrics derived information, see https://firefox-source-docs.mozilla.org/performance/powermetrics.html" );
98- cpu .setMetadata (metadata );
99- } catch (IOException e ) {
100- throw new RuntimeException (e );
101- }
53+ cpu = PowerMetricsParser .initCPU (inputStream );
10254 }
10355
10456 @ Override
@@ -114,137 +66,17 @@ protected void doStart() {
11466 }
11567 }
11668
117- private static class Section {
118- boolean done ;
119- }
120-
12169 Measures extractPowerMeasure (InputStream powerMeasureInput , long lastUpdateEpoch , long newUpdateEpoch ) {
12270 if (Log .isDebugEnabled ()) {
12371 final var start = System .currentTimeMillis ();
12472 Log .debugf ("powermetrics measure extraction last called %dms ago" , (start - lastCalled ));
12573 lastCalled = start ;
12674 }
127- final long startMs = lastUpdateEpoch ;
128- try {
129- // Should not be closed since it closes the process
130- BufferedReader input = new BufferedReader (new InputStreamReader (powerMeasureInput ));
131- String line ;
132-
133- double totalSampledCPU = -1 ;
134- double totalSampledGPU = -1 ;
135- // copy the pids so that we can remove them as soon as we've processed them
136- final var pidsToProcess = new HashSet <>(measures .trackedPIDs ());
137- // remove total system "pid"
138- pidsToProcess .remove (RegisteredPID .SYSTEM_TOTAL_REGISTERED_PID );
139- // start measure
140- final var pidMeasures = new HashMap <RegisteredPID , ProcessRecord >(measures .numberOfTrackedPIDs ());
141- final var metadata = metadata ();
142- final var powerComponents = new HashMap <String , Number >(metadata .componentCardinality ());
143- var duration = -1L ;
144- Section processes = null ;
145- Section cpuSection = null ;
146- while ((line = input .readLine ()) != null ) {
147- if (line .isEmpty ()) {
148- continue ;
149- }
150-
151- if (line .charAt (0 ) == '*' ) {
152- // check if we have the sample duration
153- if (duration == -1 && line .endsWith (DURATION_SUFFIX )) {
154- final var startLookingIndex = line .length () - DURATION_SUFFIX_LENGTH ;
155- final var lastOpenParenIndex = line .lastIndexOf ('(' , startLookingIndex );
156- if (lastOpenParenIndex > 0 ) {
157- duration = Math .round (Float .parseFloat (line .substring (lastOpenParenIndex + 1 , startLookingIndex )));
158- }
159- continue ;
160- }
161-
162- // check for the beginning of tasks section
163- if (processes == null && line .equals (TASKS_SECTION_MARKER )) {
164- // make flag null to indicate it needs to be set to true on the next iteration, to avoid processing the marker line for processes
165- processes = new Section ();
166- continue ;
167- }
168-
169- if (cpuSection == null && line .equals (CPU_USAGE_SECTION_MARKER )) {
170- cpuSection = new Section ();
171- continue ;
172- }
173-
174- continue ;
175- }
176-
177- // first, look for process line detailing share
178- if (processes != null && !processes .done && !pidsToProcess .isEmpty ()) {
179- if (line .startsWith ("ALL_TASKS" )) {
180- processes .done = true ; // we reached the end of the process section
181- } else {
182- for (RegisteredPID pid : pidsToProcess ) {
183- if (line .contains (pid .stringForMatching ())) {
184- pidMeasures .put (pid , new ProcessRecord (line ));
185- pidsToProcess .remove (pid );
186- break ;
187- }
188- }
189- }
190- }
191-
192- // then skip all lines until we get the totals
193- if (totalSampledCPU < 0 && line .startsWith ("ALL_TASKS" )) {
194- final var totals = new ProcessRecord (line );
195- // compute ratio
196- totalSampledCPU = totals .cpu ;
197- totalSampledGPU = totals .gpu > 0 ? totals .gpu : 0 ;
198- if (!pidsToProcess .isEmpty ()) {
199- Log .warnf ("Couldn't find processes: %s" ,
200- Arrays .toString (pidsToProcess .stream ().map (RegisteredPID ::pid ).toArray (Long []::new )));
201- }
202- }
203-
204- // we need an exit condition to break out of the loop, otherwise we'll just keep looping forever since there are always new lines since the process is periodical
205- // fixme: perhaps we should relaunch the process on each update loop instead of keeping it running? Not sure which is more efficient
206- if (cpuSection != null && !cpuSection .done && cpu .doneExtractingPowerComponents (line , powerComponents )) {
207- break ;
208- }
209- }
210-
211- double finalTotalSampledGPU = totalSampledGPU ;
212- double finalTotalSampledCPU = totalSampledCPU ;
213- final var endMs = newUpdateEpoch ;
214- final var durationMs = duration ;
215-
216- // handle total system measure separately
217- final var systemTotalMeasure = getSystemTotalMeasure (metadata , powerComponents );
218- recordMeasure (RegisteredPID .SYSTEM_TOTAL_REGISTERED_PID , systemTotalMeasure , startMs , endMs , durationMs );
219-
220- pidMeasures .forEach ((pid , record ) -> {
221- final var attributedMeasure = record .asAttributedMeasure (metadata , powerComponents , finalTotalSampledCPU ,
222- finalTotalSampledGPU );
223- recordMeasure (pid , attributedMeasure , startMs , endMs , durationMs );
224- });
225- } catch (Exception exception ) {
226- throw new RuntimeException (exception );
227- }
75+ PowerMetricsParser .extractPowerMeasure (powerMeasureInput , measures , lastUpdateEpoch , newUpdateEpoch ,
76+ measures .trackedPIDs (), metadata (), cpu );
22877 return measures ;
22978 }
23079
231- private void recordMeasure (RegisteredPID pid , double [] components , long startMs , long endMs , long duration ) {
232- measures .record (pid , duration > 0 ? new PartialSensorMeasure (components , startMs , endMs , duration )
233- : new NoDurationSensorMeasure (components , startMs , endMs ));
234- }
235-
236- private static double [] getSystemTotalMeasure (SensorMetadata metadata , Map <String , Number > powerComponents ) {
237- final var measure = new double [metadata .componentCardinality ()];
238- metadata .components ().forEach ((name , cm ) -> {
239- final var index = cm .index ();
240- final var value = CPU_SHARE .equals (name ) ? 1.0
241- : powerComponents .getOrDefault (name , 0 ).doubleValue ();
242- measure [index ] = value ;
243- });
244-
245- return measure ;
246- }
247-
24880 @ Override
24981 protected Measures doUpdate (long lastUpdateEpoch , long newUpdateStartEpoch ) {
25082 return extractPowerMeasure (getInputStream (), lastUpdateEpoch , newUpdateStartEpoch );
0 commit comments