View Javadoc
1   /*
2    * Copyright 2018-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.util.platform.windows;
6   
7   import java.util.Locale;
8   
9   import org.slf4j.Logger;
10  import org.slf4j.LoggerFactory;
11  
12  import com.sun.jna.platform.win32.BaseTSD.DWORD_PTR;
13  import com.sun.jna.platform.win32.Pdh;
14  import com.sun.jna.platform.win32.PdhMsg;
15  import com.sun.jna.platform.win32.VersionHelpers;
16  import com.sun.jna.platform.win32.WinDef.DWORD;
17  import com.sun.jna.platform.win32.WinDef.DWORDByReference;
18  import com.sun.jna.platform.win32.WinError;
19  import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
20  
21  import oshi.annotation.concurrent.Immutable;
22  import oshi.annotation.concurrent.ThreadSafe;
23  import oshi.jna.ByRef.CloseableLONGLONGByReference;
24  import oshi.jna.Struct.CloseablePdhRawCounter;
25  import oshi.util.FormatUtil;
26  import oshi.util.ParseUtil;
27  import oshi.util.Util;
28  
29  /**
30   * Helper class to centralize the boilerplate portions of PDH counter setup and allow applications to easily add, query,
31   * and remove counters.
32   */
33  @ThreadSafe
34  public final class PerfDataUtil {
35  
36      private static final Logger LOG = LoggerFactory.getLogger(PerfDataUtil.class);
37  
38      private static final DWORD_PTR PZERO = new DWORD_PTR(0);
39      private static final DWORDByReference PDH_FMT_RAW = new DWORDByReference(new DWORD(Pdh.PDH_FMT_RAW));
40      private static final Pdh PDH = Pdh.INSTANCE;
41  
42      private static final boolean IS_VISTA_OR_GREATER = VersionHelpers.IsWindowsVistaOrGreater();
43  
44      /**
45       * Encapsulates the three string components of a performance counter
46       */
47      @Immutable
48      public static class PerfCounter {
49          private final String object;
50          private final String instance;
51          private final String counter;
52          private final boolean baseCounter;
53  
54          public PerfCounter(String objectName, String instanceName, String counterName) {
55              this.object = objectName;
56              this.instance = instanceName;
57              int baseIdx = counterName.indexOf("_Base");
58              if (baseIdx > 0) {
59                  this.counter = counterName.substring(0, baseIdx);
60                  this.baseCounter = true;
61              } else {
62                  this.counter = counterName;
63                  this.baseCounter = false;
64              }
65          }
66  
67          /**
68           * @return Returns the object.
69           */
70          public String getObject() {
71              return object;
72          }
73  
74          /**
75           * @return Returns the instance.
76           */
77          public String getInstance() {
78              return instance;
79          }
80  
81          /**
82           * @return Returns the counter.
83           */
84          public String getCounter() {
85              return counter;
86          }
87  
88          /**
89           * @return Returns whether the counter is a base counter
90           */
91          public boolean isBaseCounter() {
92              return baseCounter;
93          }
94  
95          /**
96           * Returns the path for this counter
97           *
98           * @return A string representing the counter path
99           */
100         public String getCounterPath() {
101             StringBuilder sb = new StringBuilder();
102             sb.append('\\').append(object);
103             if (instance != null) {
104                 sb.append('(').append(instance).append(')');
105             }
106             sb.append('\\').append(counter);
107             return sb.toString();
108         }
109     }
110 
111     private PerfDataUtil() {
112     }
113 
114     /**
115      * Create a Performance Counter
116      *
117      * @param object   The object/path for the counter
118      * @param instance The instance of the counter, or null if no instance
119      * @param counter  The counter name
120      * @return A PerfCounter object encapsulating the object, instance, and counter
121      */
122     public static PerfCounter createCounter(String object, String instance, String counter) {
123         return new PerfCounter(object, instance, counter);
124     }
125 
126     /**
127      * Update a query and get the timestamp
128      *
129      * @param query The query to update all counters in
130      * @return The update timestamp of the first counter in the query
131      */
132     public static long updateQueryTimestamp(HANDLEByReference query) {
133         try (CloseableLONGLONGByReference pllTimeStamp = new CloseableLONGLONGByReference()) {
134             int ret = IS_VISTA_OR_GREATER ? PDH.PdhCollectQueryDataWithTime(query.getValue(), pllTimeStamp)
135                     : PDH.PdhCollectQueryData(query.getValue());
136             // Due to race condition, initial update may fail with PDH_NO_DATA.
137             int retries = 0;
138             while (ret == PdhMsg.PDH_NO_DATA && retries++ < 3) {
139                 // Exponential fallback.
140                 Util.sleep(1 << retries);
141                 ret = IS_VISTA_OR_GREATER ? PDH.PdhCollectQueryDataWithTime(query.getValue(), pllTimeStamp)
142                         : PDH.PdhCollectQueryData(query.getValue());
143             }
144             if (ret != WinError.ERROR_SUCCESS) {
145                 if (LOG.isWarnEnabled()) {
146                     LOG.warn("Failed to update counter. Error code: {}",
147                             String.format(Locale.ROOT, FormatUtil.formatError(ret)));
148                 }
149                 return 0L;
150             }
151             // Perf Counter timestamp is in local time
152             return IS_VISTA_OR_GREATER ? ParseUtil.filetimeToUtcMs(pllTimeStamp.getValue().longValue(), true)
153                     : System.currentTimeMillis();
154         }
155     }
156 
157     /**
158      * Open a pdh query
159      *
160      * @param q pointer to the query
161      * @return true if successful
162      */
163     public static boolean openQuery(HANDLEByReference q) {
164         int ret = PDH.PdhOpenQuery(null, PZERO, q);
165         if (ret != WinError.ERROR_SUCCESS) {
166             if (LOG.isErrorEnabled()) {
167                 LOG.error("Failed to open PDH Query. Error code: {}",
168                         String.format(Locale.ROOT, FormatUtil.formatError(ret)));
169             }
170             return false;
171         }
172         return true;
173     }
174 
175     /**
176      * Close a pdh query
177      *
178      * @param q pointer to the query
179      * @return true if successful
180      */
181     public static boolean closeQuery(HANDLEByReference q) {
182         return WinError.ERROR_SUCCESS == PDH.PdhCloseQuery(q.getValue());
183     }
184 
185     /**
186      * Get value of pdh counter
187      *
188      * @param counter The counter to get the value of
189      * @return long value of the counter, or negative value representing an error code
190      */
191     public static long queryCounter(HANDLEByReference counter) {
192         try (CloseablePdhRawCounter counterValue = new CloseablePdhRawCounter()) {
193             int ret = PDH.PdhGetRawCounterValue(counter.getValue(), PDH_FMT_RAW, counterValue);
194             if (ret != WinError.ERROR_SUCCESS) {
195                 if (LOG.isWarnEnabled()) {
196                     LOG.warn("Failed to get counter. Error code: {}",
197                             String.format(Locale.ROOT, FormatUtil.formatError(ret)));
198                 }
199                 return ret;
200             }
201             return counterValue.FirstValue;
202         }
203     }
204 
205     /**
206      * Get value of pdh counter's second value (base counters)
207      *
208      * @param counter The counter to get the value of
209      * @return long value of the counter's second value, or negative value representing an error code
210      */
211     public static long querySecondCounter(HANDLEByReference counter) {
212         try (CloseablePdhRawCounter counterValue = new CloseablePdhRawCounter()) {
213             int ret = PDH.PdhGetRawCounterValue(counter.getValue(), PDH_FMT_RAW, counterValue);
214             if (ret != WinError.ERROR_SUCCESS) {
215                 if (LOG.isWarnEnabled()) {
216                     LOG.warn("Failed to get counter. Error code: {}",
217                             String.format(Locale.ROOT, FormatUtil.formatError(ret)));
218                 }
219                 return ret;
220             }
221             return counterValue.SecondValue;
222         }
223     }
224 
225     /**
226      * Adds a pdh counter to a query
227      *
228      * @param query Pointer to the query to add the counter
229      * @param path  String name of the PerfMon counter. For Vista+, must be in English. Must localize this path for
230      *              pre-Vista.
231      * @param p     Pointer to the counter
232      * @return true if successful
233      */
234     public static boolean addCounter(HANDLEByReference query, String path, HANDLEByReference p) {
235         int ret = IS_VISTA_OR_GREATER ? PDH.PdhAddEnglishCounter(query.getValue(), path, PZERO, p)
236                 : PDH.PdhAddCounter(query.getValue(), path, PZERO, p);
237         if (ret != WinError.ERROR_SUCCESS) {
238             if (LOG.isWarnEnabled()) {
239                 LOG.warn("Failed to add PDH Counter: {}, Error code: {}", path,
240                         String.format(Locale.ROOT, FormatUtil.formatError(ret)));
241             }
242             return false;
243         }
244         return true;
245     }
246 
247     /**
248      * Remove a pdh counter
249      *
250      * @param p pointer to the counter
251      * @return true if successful
252      */
253     public static boolean removeCounter(HANDLEByReference p) {
254         return WinError.ERROR_SUCCESS == PDH.PdhRemoveCounter(p.getValue());
255     }
256 }