-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add PDH counter lookup and enumeration functions #973
Changes from 3 commits
aa8d54a
c06a68f
01aeb2a
9401f05
efcd4d3
72925a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
/* Copyright (c) 2018 Daniel Widdis, All Rights Reserved | ||
* | ||
* The contents of this file is dual-licensed under 2 | ||
* alternative Open Source/Free licenses: LGPL 2.1 or later and | ||
* Apache License 2.0. (starting with JNA version 4.0.0). | ||
* | ||
* You can freely decide which license you want to apply to | ||
* the project. | ||
* | ||
* You may obtain a copy of the LGPL License at: | ||
* | ||
* http://www.gnu.org/licenses/licenses.html | ||
* | ||
* A copy is also included in the downloadable source code package | ||
* containing JNA, in file "LGPL2.1". | ||
* | ||
* You may obtain a copy of the Apache License at: | ||
* | ||
* http://www.apache.org/licenses/ | ||
* | ||
* A copy is also included in the downloadable source code package | ||
* containing JNA, in file "AL2.0". | ||
*/ | ||
package com.sun.jna.platform.win32; | ||
|
||
import java.util.ArrayList; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
|
||
import com.sun.jna.Memory; | ||
import com.sun.jna.Native; | ||
import com.sun.jna.platform.win32.WinDef.DWORD; | ||
import com.sun.jna.platform.win32.WinDef.DWORDByReference; | ||
|
||
/** | ||
* Pdh utility API. | ||
* | ||
* @author widdis[at]gmail[dot]com | ||
*/ | ||
public abstract class PdhUtil { | ||
|
||
/** | ||
* Utility method to call Pdh's PdhLookupPerfNameByIndex that allocates the | ||
* required memory for the szNameBuffer parameter based on the type mapping | ||
* used, calls to PdhLookupPerfNameByIndex, and returns the received string. | ||
* | ||
* @param szMachineName | ||
* Null-terminated string that specifies the name of the computer | ||
* where the specified performance object or counter is located. | ||
* The computer name can be specified by the DNS name or the IP | ||
* address. If NULL, the function uses the local computer. | ||
* @param dwNameIndex | ||
* Index of the performance object or counter. | ||
* @return Returns the name of the performance object or counter. | ||
*/ | ||
public static String PdhLookupPerfNameByIndex(String szMachineName, int dwNameIndex) { | ||
int charToBytes = Boolean.getBoolean("w32.ascii") ? 1 : Native.WCHAR_SIZE; | ||
|
||
// Call once to get required buffer size | ||
DWORDByReference pcchNameBufferSize = new DWORDByReference(new DWORD(0)); | ||
Pdh.INSTANCE.PdhLookupPerfNameByIndex(null, dwNameIndex, null, pcchNameBufferSize); | ||
|
||
// Allocate buffer and call again | ||
Memory mem = new Memory(pcchNameBufferSize.getValue().intValue() * charToBytes); | ||
Pdh.INSTANCE.PdhLookupPerfNameByIndex(null, dwNameIndex, mem, pcchNameBufferSize); | ||
|
||
// Convert buffer to Java String | ||
if (charToBytes == 1) { | ||
return mem.getString(0); | ||
} else { | ||
return mem.getWideString(0); | ||
} | ||
} | ||
|
||
/** | ||
* Utility method to call Pdh's PdhEnumObjectItems that allocates the | ||
* required memory for the mszCounterList and mszInstanceList parameters | ||
* based on the type mapping used, calls to PdhEnumObjectItems, and returns | ||
* the received lists of strings. | ||
* | ||
* @param szDataSource | ||
* String that specifies the name of the log file used to | ||
* enumerate the counter and instance names. If NULL, the | ||
* function uses the computer specified in the szMachineName | ||
* parameter to enumerate the names. | ||
* @param szMachineName | ||
* String that specifies the name of the computer that contains | ||
* the counter and instance names that you want to enumerate. | ||
* Include the leading slashes in the computer name, for example, | ||
* \\computername. If the szDataSource parameter is NULL, you can | ||
* set szMachineName to NULL to specify the local computer. | ||
* @param szObjectName | ||
* String that specifies the name of the object whose counter and | ||
* instance names you want to enumerate. | ||
* @param dwDetailLevel | ||
* Detail level of the performance items to return. All items | ||
* that are of the specified detail level or less will be | ||
* returned. | ||
* @return Returns a List with two elements. Index 0 contains a List of | ||
* Strings of the counters for the object. Index 1 contains a List | ||
* of Strings of the instances of the object. | ||
*/ | ||
public static List<List<String>> PdhEnumObjectItems(String szDataSource, String szMachineName, String szObjectName, | ||
int dwDetailLevel) { | ||
int charToBytes = Boolean.getBoolean("w32.ascii") ? 1 : Native.WCHAR_SIZE; | ||
|
||
// Call once to get string lengths | ||
DWORDByReference pcchCounterListLength = new DWORDByReference(new DWORD(0)); | ||
DWORDByReference pcchInstanceListLength = new DWORDByReference(new DWORD(0)); | ||
Pdh.INSTANCE.PdhEnumObjectItems(szDataSource, szMachineName, szObjectName, null, pcchCounterListLength, null, | ||
pcchInstanceListLength, dwDetailLevel, 0); | ||
|
||
// Allocate memory and call again to populate strings | ||
Memory mszCounterList = new Memory(pcchCounterListLength.getValue().intValue() * charToBytes); | ||
Memory mszInstanceList = new Memory(pcchInstanceListLength.getValue().intValue() * charToBytes); | ||
Pdh.INSTANCE.PdhEnumObjectItems(szDataSource, szMachineName, szObjectName, mszCounterList, | ||
pcchCounterListLength, mszInstanceList, pcchInstanceListLength, dwDetailLevel, 0); | ||
|
||
// Fetch counters | ||
List<String> counters = new LinkedList<>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In another issue it was noted, that the LinkedList is not the best list to choose. I suggest to switch to ArrayList. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I'll switch. I'm curious the logic, though? I thought LinkedList was faster for adding (particularly when the final length is unknown) and ArrayList for getting. |
||
int offset = 0; | ||
while (offset < mszCounterList.size()) { | ||
String s = null; | ||
if (charToBytes == 1) { | ||
s = mszCounterList.getString(offset); | ||
} else { | ||
s = mszCounterList.getWideString(offset); | ||
} | ||
// list ends with double null | ||
if (s.isEmpty()) { | ||
break; | ||
} | ||
counters.add(s); | ||
offset += (s.length() + 1) * charToBytes; | ||
} | ||
|
||
List<String> instances = new LinkedList<>(); | ||
offset = 0; | ||
while (offset < mszInstanceList.size()) { | ||
String s = null; | ||
if (charToBytes == 1) { | ||
s = mszInstanceList.getString(offset); | ||
} else { | ||
s = mszInstanceList.getWideString(offset); | ||
} | ||
// list ends with double null | ||
if (s.isEmpty()) { | ||
break; | ||
} | ||
instances.add(s); | ||
// Increment for string + null terminator | ||
offset += (s.length() + 1) * charToBytes; | ||
} | ||
|
||
List<List<String>> objectItems = new ArrayList<>(2); | ||
objectItems.add(counters); | ||
objectItems.add(instances); | ||
return objectItems; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,10 +12,14 @@ | |
*/ | ||
package com.sun.jna.platform.win32; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
import java.io.PrintStream; | ||
import java.util.Collection; | ||
import java.util.HashMap; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.junit.Test; | ||
|
@@ -155,4 +159,44 @@ private static String makeCounterPath(Pdh pdh, PDH_COUNTER_PATH_ELEMENTS pathEle | |
|
||
return Native.toString(szFullPathBuffer); | ||
} | ||
|
||
@Test | ||
public void testLookupPerfIndex() { | ||
int processorIndex = 238; | ||
String processorStr = "Processor"; // English locale | ||
|
||
// Test index-to-name | ||
String testStr = PdhUtil.PdhLookupPerfNameByIndex(null, processorIndex); | ||
if (AbstractWin32TestSupport.isEnglishLocale) { | ||
assertEquals(processorStr, testStr); | ||
} else { | ||
assertTrue(testStr.length() > 0); | ||
} | ||
|
||
// Test name-to-index | ||
DWORDByReference pdwIndex = new DWORDByReference(); | ||
Pdh.INSTANCE.PdhLookupPerfIndexByName(null, testStr, pdwIndex); | ||
assertEquals(processorIndex, pdwIndex.getValue().intValue()); | ||
} | ||
|
||
@Test | ||
public void testEnumObjectItems() { | ||
if (AbstractWin32TestSupport.isEnglishLocale) { | ||
String processorStr = "Process"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this tested? I tested with a german locale and the translation of "Process" to german "Prozess" gave me performance counters concerning processes on the machine, not the processor. When I switched to "Prozessor" (I would translate it as "processor") (the CPU), I got the expected values for the single processor I expose via VirtualBox and "_Total" . This also does not match the original code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, it was tested before I fiddled with the String to debug something else and forgot to put it back. D'oh. |
||
String processorTimeStr = "% Processor Time"; | ||
|
||
// Fetch the counter and instance names | ||
List<List<String>> objectItems = PdhUtil.PdhEnumObjectItems(null, null, processorStr, 100); | ||
List<String> counters = objectItems.get(0); | ||
List<String> instances = objectItems.get(1); | ||
|
||
// Should have at least one processor and total instance | ||
assertTrue(instances.contains("0")); | ||
assertTrue(instances.contains("_Total")); | ||
// Should have a "% Processor Time" counter | ||
assertTrue(counters.contains(processorTimeStr)); | ||
} else { | ||
System.err.println("testEnumObjectItems test can only be run with english locale."); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would think about splitting this method - is it performance critical? If not, I think two method (one for the conters and one for the instances would be better), the List of List of String feels strange.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had that same thought. In my application I only am using the instance list anyway. The List of lists is a step up from my original implementation as an array of lists, though, so give me partial credit.