View Javadoc
1   /*
2    * Copyright 2020-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.driver.windows.registry;
6   
7   import java.util.Collection;
8   import java.util.Collections;
9   import java.util.HashMap;
10  import java.util.List;
11  import java.util.Map;
12  
13  import com.sun.jna.platform.win32.WinBase.FILETIME;
14  
15  import oshi.annotation.concurrent.Immutable;
16  import oshi.annotation.concurrent.ThreadSafe;
17  import oshi.driver.windows.perfmon.PerfmonDisabled;
18  import oshi.driver.windows.perfmon.ThreadInformation;
19  import oshi.driver.windows.perfmon.ThreadInformation.ThreadPerformanceProperty;
20  import oshi.util.Util;
21  import oshi.util.tuples.Pair;
22  import oshi.util.tuples.Triplet;
23  
24  /**
25   * Utility to read thread data from HKEY_PERFORMANCE_DATA information with backup from Performance Counters or WMI
26   */
27  @ThreadSafe
28  public final class ThreadPerformanceData {
29  
30      private static final String THREAD = "Thread";
31  
32      private ThreadPerformanceData() {
33      }
34  
35      /**
36       * Query the registry for thread performance counters
37       *
38       * @param pids An optional collection of thread IDs to filter the list to. May be null for no filtering.
39       * @return A map with Thread ID as the key and a {@link PerfCounterBlock} object populated with performance counter
40       *         information if successful, or null otherwise.
41       */
42      public static Map<Integer, PerfCounterBlock> buildThreadMapFromRegistry(Collection<Integer> pids) {
43          // Grab the data from the registry.
44          Triplet<List<Map<ThreadPerformanceProperty, Object>>, Long, Long> threadData = HkeyPerformanceDataUtil
45                  .readPerfDataFromRegistry(THREAD, ThreadPerformanceProperty.class);
46          if (threadData == null) {
47              return null;
48          }
49          List<Map<ThreadPerformanceProperty, Object>> threadInstanceMaps = threadData.getA();
50          long perfTime100nSec = threadData.getB(); // 1601
51          long now = threadData.getC(); // 1970 epoch
52  
53          // Create a map and fill it
54          Map<Integer, PerfCounterBlock> threadMap = new HashMap<>();
55          // Iterate instances.
56          for (Map<ThreadPerformanceProperty, Object> threadInstanceMap : threadInstanceMaps) {
57              int pid = ((Integer) threadInstanceMap.get(ThreadPerformanceProperty.IDPROCESS)).intValue();
58              if ((pids == null || pids.contains(pid)) && pid > 0) {
59                  int tid = ((Integer) threadInstanceMap.get(ThreadPerformanceProperty.IDTHREAD)).intValue();
60                  String name = (String) threadInstanceMap.get(ThreadPerformanceProperty.NAME);
61                  long upTime = (perfTime100nSec - (Long) threadInstanceMap.get(ThreadPerformanceProperty.ELAPSEDTIME))
62                          / 10_000L;
63                  if (upTime < 1) {
64                      upTime = 1;
65                  }
66                  long user = ((Long) threadInstanceMap.get(ThreadPerformanceProperty.PERCENTUSERTIME)).longValue()
67                          / 10_000L;
68                  long kernel = ((Long) threadInstanceMap.get(ThreadPerformanceProperty.PERCENTPRIVILEGEDTIME))
69                          .longValue() / 10_000L;
70                  int priority = ((Integer) threadInstanceMap.get(ThreadPerformanceProperty.PRIORITYCURRENT)).intValue();
71                  int threadState = ((Integer) threadInstanceMap.get(ThreadPerformanceProperty.THREADSTATE)).intValue();
72                  int threadWaitReason = ((Integer) threadInstanceMap.get(ThreadPerformanceProperty.THREADWAITREASON))
73                          .intValue();
74                  // Start address is pointer sized when fetched from registry, so this could be
75                  // either Integer (uint32) or Long depending on OS bitness
76                  Object addr = threadInstanceMap.get(ThreadPerformanceProperty.STARTADDRESS);
77                  long startAddr = addr.getClass().equals(Long.class) ? (Long) addr
78                          : Integer.toUnsignedLong((Integer) addr);
79                  int contextSwitches = ((Integer) threadInstanceMap.get(ThreadPerformanceProperty.CONTEXTSWITCHESPERSEC))
80                          .intValue();
81                  threadMap.put(tid, new PerfCounterBlock(name, tid, pid, now - upTime, user, kernel, priority,
82                          threadState, threadWaitReason, startAddr, contextSwitches));
83              }
84          }
85          return threadMap;
86      }
87  
88      /**
89       * Query PerfMon for thread performance counters
90       *
91       * @param pids An optional collection of process IDs to filter the list to. May be null for no filtering.
92       * @return A map with Thread ID as the key and a {@link PerfCounterBlock} object populated with performance counter
93       *         information.
94       */
95      public static Map<Integer, PerfCounterBlock> buildThreadMapFromPerfCounters(Collection<Integer> pids) {
96          return buildThreadMapFromPerfCounters(pids, null, -1);
97      }
98  
99      /**
100      * Query PerfMon for thread performance counters
101      *
102      * @param pids      An optional collection of process IDs to filter the list to. May be null for no filtering.
103      * @param procName  Limit the matches to processes matching the given name.
104      * @param threadNum Limit the matches to threads matching the given thread. Use -1 to match all threads.
105      * @return A map with Thread ID as the key and a {@link PerfCounterBlock} object populated with performance counter
106      *         information.
107      */
108     public static Map<Integer, PerfCounterBlock> buildThreadMapFromPerfCounters(Collection<Integer> pids,
109             String procName, int threadNum) {
110         if (PerfmonDisabled.PERF_PROC_DISABLED) {
111             return Collections.emptyMap();
112         }
113         Map<Integer, PerfCounterBlock> threadMap = new HashMap<>();
114         Pair<List<String>, Map<ThreadPerformanceProperty, List<Long>>> instanceValues = Util.isBlank(procName)
115                 ? ThreadInformation.queryThreadCounters()
116                 : ThreadInformation.queryThreadCounters(procName, threadNum);
117         long now = System.currentTimeMillis(); // 1970 epoch
118         List<String> instances = instanceValues.getA();
119         Map<ThreadPerformanceProperty, List<Long>> valueMap = instanceValues.getB();
120         List<Long> tidList = valueMap.get(ThreadPerformanceProperty.IDTHREAD);
121         List<Long> pidList = valueMap.get(ThreadPerformanceProperty.IDPROCESS);
122         List<Long> userList = valueMap.get(ThreadPerformanceProperty.PERCENTUSERTIME); // 100-nsec
123         List<Long> kernelList = valueMap.get(ThreadPerformanceProperty.PERCENTPRIVILEGEDTIME); // 100-nsec
124         List<Long> startTimeList = valueMap.get(ThreadPerformanceProperty.ELAPSEDTIME); // filetime
125         List<Long> priorityList = valueMap.get(ThreadPerformanceProperty.PRIORITYCURRENT);
126         List<Long> stateList = valueMap.get(ThreadPerformanceProperty.THREADSTATE);
127         List<Long> waitReasonList = valueMap.get(ThreadPerformanceProperty.THREADWAITREASON);
128         List<Long> startAddrList = valueMap.get(ThreadPerformanceProperty.STARTADDRESS);
129         List<Long> contextSwitchesList = valueMap.get(ThreadPerformanceProperty.CONTEXTSWITCHESPERSEC);
130 
131         int nameIndex = 0;
132         for (int inst = 0; inst < instances.size(); inst++) {
133             int pid = pidList.get(inst).intValue();
134             if (pids == null || pids.contains(pid)) {
135                 int tid = tidList.get(inst).intValue();
136                 String name = Integer.toString(nameIndex++);
137                 long startTime = startTimeList.get(inst);
138                 int lowerStartTimeLimit = (int) (startTime >> 32);
139                 int higherStartTimeLimit = (int) (startTime & 0xffffffffL);
140                 startTime = FILETIME.filetimeToDate(lowerStartTimeLimit, higherStartTimeLimit).getTime();
141                 if (startTime > now) {
142                     startTime = now - 1;
143                 }
144                 long user = userList.get(inst) / 10_000L;
145                 long kernel = kernelList.get(inst) / 10_000L;
146                 int priority = priorityList.get(inst).intValue();
147                 int threadState = stateList.get(inst).intValue();
148                 int threadWaitReason = waitReasonList.get(inst).intValue();
149                 long startAddr = startAddrList.get(inst).longValue();
150                 int contextSwitches = contextSwitchesList.get(inst).intValue();
151 
152                 // if creation time value is less than current millis, it's in 1970 epoch,
153                 // otherwise it's 1601 epoch and we must convert
154                 threadMap.put(tid, new PerfCounterBlock(name, tid, pid, startTime, user, kernel, priority, threadState,
155                         threadWaitReason, startAddr, contextSwitches));
156             }
157         }
158         return threadMap;
159     }
160 
161     /**
162      * Class to encapsulate data from the registry performance counter block
163      */
164     @Immutable
165     public static class PerfCounterBlock {
166         private final String name;
167         private final int threadID;
168         private final int owningProcessID;
169         private final long startTime;
170         private final long userTime;
171         private final long kernelTime;
172         private final int priority;
173         private final int threadState;
174         private final int threadWaitReason;
175         private final long startAddress;
176         private final int contextSwitches;
177 
178         public PerfCounterBlock(String name, int threadID, int owningProcessID, long startTime, long userTime,
179                 long kernelTime, int priority, int threadState, int threadWaitReason, long startAddress,
180                 int contextSwitches) {
181             this.name = name;
182             this.threadID = threadID;
183             this.owningProcessID = owningProcessID;
184             this.startTime = startTime;
185             this.userTime = userTime;
186             this.kernelTime = kernelTime;
187             this.priority = priority;
188             this.threadState = threadState;
189             this.threadWaitReason = threadWaitReason;
190             this.startAddress = startAddress;
191             this.contextSwitches = contextSwitches;
192         }
193 
194         /**
195          * @return the name
196          */
197         public String getName() {
198             return name;
199         }
200 
201         /**
202          * @return the threadID
203          */
204         public int getThreadID() {
205             return threadID;
206         }
207 
208         /**
209          * @return the owningProcessID
210          */
211         public int getOwningProcessID() {
212             return owningProcessID;
213         }
214 
215         /**
216          * @return the startTime
217          */
218         public long getStartTime() {
219             return startTime;
220         }
221 
222         /**
223          * @return the userTime
224          */
225         public long getUserTime() {
226             return userTime;
227         }
228 
229         /**
230          * @return the kernelTime
231          */
232         public long getKernelTime() {
233             return kernelTime;
234         }
235 
236         /**
237          * @return the priority
238          */
239         public int getPriority() {
240             return priority;
241         }
242 
243         /**
244          * @return the threadState
245          */
246         public int getThreadState() {
247             return threadState;
248         }
249 
250         /**
251          * @return the threadWaitReason
252          */
253         public int getThreadWaitReason() {
254             return threadWaitReason;
255         }
256 
257         /**
258          * @return the startMemoryAddress
259          */
260         public long getStartAddress() {
261             return startAddress;
262         }
263 
264         /**
265          * @return the contextSwitches
266          */
267         public int getContextSwitches() {
268             return contextSwitches;
269         }
270     }
271 }