View Javadoc
1   /*
2    * Copyright 2022 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.driver.windows.perfmon;
6   
7   import java.util.Arrays;
8   import java.util.List;
9   import java.util.Map;
10  
11  import oshi.annotation.concurrent.ThreadSafe;
12  import oshi.driver.windows.perfmon.ProcessInformation.IdleProcessorTimeProperty;
13  import oshi.driver.windows.perfmon.SystemInformation.ProcessorQueueLengthProperty;
14  import oshi.util.tuples.Pair;
15  
16  /**
17   * Utility to calculate a load average equivalent metric on Windows. Starts a daemon thread to collect the necessary
18   * counters and averages in 5-second intervals.
19   */
20  @ThreadSafe
21  public final class LoadAverage {
22  
23      // Daemon thread for Load Average
24      private static Thread loadAvgThread = null;
25  
26      private static double[] loadAverages = new double[] { -1d, -1d, -1d };
27      private static final double[] EXP_WEIGHT = new double[] {
28              // 1-, 5-, and 15-minute exponential smoothing weight
29              Math.exp(-5d / 60d), Math.exp(-5d / 300d), Math.exp(-5d / 900d) };
30  
31      private LoadAverage() {
32      }
33  
34      public static double[] queryLoadAverage(int nelem) {
35          synchronized (loadAverages) {
36              return Arrays.copyOf(loadAverages, nelem);
37          }
38      }
39  
40      public static synchronized void stopDaemon() {
41          if (loadAvgThread != null) {
42              loadAvgThread.interrupt();
43              loadAvgThread = null;
44          }
45      }
46  
47      public static synchronized void startDaemon() {
48          if (loadAvgThread != null) {
49              return;
50          }
51          loadAvgThread = new Thread("OSHI Load Average daemon") {
52              @Override
53              public void run() {
54                  // Initialize tick counters
55                  Pair<Long, Long> nonIdlePair = LoadAverage.queryNonIdleTicks();
56                  long nonIdleTicks0 = nonIdlePair.getA();
57                  long nonIdleBase0 = nonIdlePair.getB();
58                  long nonIdleTicks;
59                  long nonIdleBase;
60  
61                  // Use nanoTime to synchronize queries at 5 seconds
62                  long initNanos = System.nanoTime();
63                  long delay;
64  
65                  // The two components of load average
66                  double runningProcesses;
67                  long queueLength;
68  
69                  try {
70                      Thread.sleep(2500L);
71                  } catch (InterruptedException e) {
72                      Thread.currentThread().interrupt();
73                  }
74                  while (!Thread.currentThread().isInterrupted()) {
75                      // get non-idle ticks, proxy for average processes running
76                      nonIdlePair = LoadAverage.queryNonIdleTicks();
77                      nonIdleTicks = nonIdlePair.getA() - nonIdleTicks0;
78                      nonIdleBase = nonIdlePair.getB() - nonIdleBase0;
79                      if (nonIdleBase > 0 && nonIdleTicks > 0) {
80                          runningProcesses = (double) nonIdleTicks / nonIdleBase;
81                      } else {
82                          runningProcesses = 0d;
83                      }
84                      nonIdleTicks0 = nonIdlePair.getA();
85                      nonIdleBase0 = nonIdlePair.getB();
86                      // get processes waiting
87                      queueLength = SystemInformation.queryProcessorQueueLength()
88                              .getOrDefault(ProcessorQueueLengthProperty.PROCESSORQUEUELENGTH, 0L);
89  
90                      synchronized (loadAverages) {
91                          // Init to running procs the first time
92                          if (loadAverages[0] < 0d) {
93                              Arrays.fill(loadAverages, runningProcesses);
94                          }
95                          // Use exponential smoothing to update values
96                          for (int i = 0; i < loadAverages.length; i++) {
97                              loadAverages[i] *= EXP_WEIGHT[i];
98                              loadAverages[i] += (runningProcesses + queueLength) * (1d - EXP_WEIGHT[i]);
99                          }
100                     }
101 
102                     delay = 5000L - (System.nanoTime() - initNanos) % 5_000_000_000L / 1_000_000;
103                     if (delay < 500L) {
104                         delay += 5000L;
105                     }
106                     try {
107                         Thread.sleep(delay);
108                     } catch (InterruptedException e) {
109                         Thread.currentThread().interrupt();
110                     }
111                 }
112             }
113         };
114         loadAvgThread.setDaemon(true);
115         loadAvgThread.start();
116     }
117 
118     private static Pair<Long, Long> queryNonIdleTicks() {
119         Pair<List<String>, Map<IdleProcessorTimeProperty, List<Long>>> idleValues = ProcessInformation
120                 .queryIdleProcessCounters();
121         List<String> instances = idleValues.getA();
122         Map<IdleProcessorTimeProperty, List<Long>> valueMap = idleValues.getB();
123         List<Long> proctimeTicks = valueMap.get(IdleProcessorTimeProperty.PERCENTPROCESSORTIME);
124         List<Long> proctimeBase = valueMap.get(IdleProcessorTimeProperty.ELAPSEDTIME);
125         long nonIdleTicks = 0L;
126         long nonIdleBase = 0L;
127         for (int i = 0; i < instances.size(); i++) {
128             if ("_Total".equals(instances.get(i))) {
129                 nonIdleTicks += proctimeTicks.get(i);
130                 nonIdleBase += proctimeBase.get(i);
131             } else if ("Idle".equals(instances.get(i))) {
132                 nonIdleTicks -= proctimeTicks.get(i);
133             }
134         }
135         return new Pair<>(nonIdleTicks, nonIdleBase);
136     }
137 }