View Javadoc
1   /*
2    * Copyright 2019-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.util.platform.windows;
6   
7   import java.util.HashMap;
8   import java.util.Locale;
9   import java.util.Map;
10  
11  import org.slf4j.Logger;
12  import org.slf4j.LoggerFactory;
13  
14  import oshi.annotation.concurrent.NotThreadSafe;
15  import oshi.jna.ByRef.CloseableHANDLEByReference;
16  import oshi.util.FormatUtil;
17  import oshi.util.platform.windows.PerfDataUtil.PerfCounter;
18  
19  /**
20   * Utility to handle Performance Counter Queries
21   * <p>
22   * This class is not thread safe. Each query handler instance should only be used in a single thread, preferably in a
23   * try-with-resources block.
24   */
25  @NotThreadSafe
26  public final class PerfCounterQueryHandler implements AutoCloseable {
27  
28      private static final Logger LOG = LoggerFactory.getLogger(PerfCounterQueryHandler.class);
29  
30      // Map of counter handles
31      private Map<PerfCounter, CloseableHANDLEByReference> counterHandleMap = new HashMap<>();
32      // The query handle
33      private CloseableHANDLEByReference queryHandle = null;
34  
35      /**
36       * Begin monitoring a Performance Data counter.
37       *
38       * @param counter A PerfCounter object.
39       * @return True if the counter was successfully added to the query.
40       */
41      public boolean addCounterToQuery(PerfCounter counter) {
42          // Open a new query or get the handle to an existing one
43          if (this.queryHandle == null) {
44              this.queryHandle = new CloseableHANDLEByReference();
45              if (!PerfDataUtil.openQuery(this.queryHandle)) {
46                  LOG.warn("Failed to open a query for PDH counter: {}", counter.getCounterPath());
47                  this.queryHandle.close();
48                  this.queryHandle = null;
49                  return false;
50              }
51          }
52          // Get a new handle for the counter
53          CloseableHANDLEByReference p = new CloseableHANDLEByReference();
54          if (!PerfDataUtil.addCounter(this.queryHandle, counter.getCounterPath(), p)) {
55              LOG.warn("Failed to add counter for PDH counter: {}", counter.getCounterPath());
56              p.close();
57              return false;
58          }
59          counterHandleMap.put(counter, p);
60          return true;
61      }
62  
63      /**
64       * Stop monitoring a Performance Data counter.
65       *
66       * @param counter A PerfCounter object
67       * @return True if the counter was successfully removed.
68       */
69      public boolean removeCounterFromQuery(PerfCounter counter) {
70          boolean success = false;
71          try (CloseableHANDLEByReference href = counterHandleMap.remove(counter)) {
72              // null if handle wasn't present
73              if (href != null) {
74                  success = PerfDataUtil.removeCounter(href);
75              }
76          }
77          if (counterHandleMap.isEmpty()) {
78              PerfDataUtil.closeQuery(this.queryHandle);
79              this.queryHandle.close();
80              this.queryHandle = null;
81          }
82          return success;
83      }
84  
85      /**
86       * Stop monitoring all Performance Data counters and release their resources
87       */
88      public void removeAllCounters() {
89          // Remove all counters from counterHandle map
90          for (CloseableHANDLEByReference href : counterHandleMap.values()) {
91              PerfDataUtil.removeCounter(href);
92              href.close();
93          }
94          counterHandleMap.clear();
95          // Remove query
96          if (this.queryHandle != null) {
97              PerfDataUtil.closeQuery(this.queryHandle);
98              this.queryHandle.close();
99              this.queryHandle = null;
100         }
101     }
102 
103     /**
104      * Update all counters on this query.
105      *
106      * @return The timestamp for the update of all the counters, in milliseconds since the epoch, or 0 if the update
107      *         failed
108      */
109     public long updateQuery() {
110         if (this.queryHandle == null) {
111             LOG.warn("Query does not exist to update.");
112             return 0L;
113         }
114         return PerfDataUtil.updateQueryTimestamp(queryHandle);
115     }
116 
117     /**
118      * Query the raw counter value of a Performance Data counter. Further mathematical manipulation/conversion is left
119      * to the caller.
120      *
121      * @param counter The counter to query
122      * @return The raw value of the counter
123      */
124     public long queryCounter(PerfCounter counter) {
125         if (!counterHandleMap.containsKey(counter)) {
126             if (LOG.isWarnEnabled()) {
127                 LOG.warn("Counter {} does not exist to query.", counter.getCounterPath());
128             }
129             return 0;
130         }
131         long value = counter.isBaseCounter() ? PerfDataUtil.querySecondCounter(counterHandleMap.get(counter))
132                 : PerfDataUtil.queryCounter(counterHandleMap.get(counter));
133         if (value < 0) {
134             if (LOG.isWarnEnabled()) {
135                 LOG.warn("Error querying counter {}: {}", counter.getCounterPath(),
136                         String.format(Locale.ROOT, FormatUtil.formatError((int) value)));
137             }
138             return 0L;
139         }
140         return value;
141     }
142 
143     @Override
144     public void close() {
145         removeAllCounters();
146     }
147 }