Skip to content
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

Merged
merged 6 commits into from
Jun 28, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Features
* [#954](https://github.com/java-native-access/jna/pull/954): Add `c.s.j.Structure.FieldOrder` annotation to define the field order of a structures without implementing `Structure#getFieldOrder()` - [@idosu](https://github.com/idosu).
* [#959](https://github.com/java-native-access/jna/pull/959): Added `GetProcessTimes` and `GetProcessIoCounters` to `com.sun.jna.platform.win32.Kernel32` - [@dbwiddis](https://github.com/dbwiddis).
* [#952](https://github.com/java-native-access/jna/issues/952): Added `CreateMutex`, `OpenMutex` and `ReleaseMutex` to `com.sun.jna.platform.win32.Kernel32` - [@matthiasblaesing](https://github.com/matthiasblaesing).
* [#973](https://github.com/java-native-access/jna/issues/973): Added `PdhLookupPerfNameByIndex`, `PdhLookupPerfIndexByName`, and `PdhEnumObjectItems` to `com.sun.jna.platform.win32.Pdh` - [@dbwiddis](https://github.com/dbwiddis).

Bug Fixes
---------
Expand Down
123 changes: 123 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/Pdh.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.List;

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.Structure.FieldOrder;
import com.sun.jna.platform.win32.BaseTSD.DWORD_PTR;
Expand Down Expand Up @@ -277,4 +278,126 @@ public class PDH_TIME_INFO extends Structure {
* @see <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/aa372677(v=vs.85).aspx">PdhSetQueryTimeRange</A>
*/
int PdhSetQueryTimeRange(HANDLE hQuery, PDH_TIME_INFO pInfo);

/**
* Returns the specified object's counter and instance names that exist on
* the specified computer or in the specified log file.
*
* @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 mszCounterList
* Caller-allocated buffer that receives a list of
* null-terminated counter names provided by the specified
* object. The list contains unique counter names. The list is
* terminated by two NULL characters. Set to NULL if the
* pcchCounterListLengthparameter is zero.
* @param pcchCounterListLength
* Size of the mszCounterList buffer, in TCHARs. If zero on input
* and the object exists, the function returns PDH_MORE_DATA and
* sets this parameter to the required buffer size. If the buffer
* is larger than the required size, the function sets this
* parameter to the actual size of the buffer that was used. If
* the specified size on input is greater than zero but less than
* the required size, you should not rely on the returned size to
* reallocate the buffer.
* @param mszInstanceList
* Caller-allocated buffer that receives a list of
* null-terminated instance names provided by the specified
* object. The list contains unique instance names. The list is
* terminated by two NULL characters. Set to NULL if
* pcchInstanceListLength is zero.
* @param pcchInstanceListLength
* Size of the mszInstanceList buffer, in TCHARs. If zero on
* input and the object exists, the function returns
* PDH_MORE_DATA and sets this parameter to the required buffer
* size. If the buffer is larger than the required size, the
* function sets this parameter to the actual size of the buffer
* that was used. If the specified size on input is greater than
* zero but less than the required size, you should not rely on
* the returned size to reallocate the buffer. If the specified
* object does not support variable instances, then the returned
* value will be zero. If the specified object does support
* variable instances, but does not currently have any instances,
* then the value returned is 2, which is the size of an empty
* MULTI_SZ list string.
* @param dwDetailLevel
* Detail level of the performance items to return. All items
* that are of the specified detail level or less will be
* returned.
* @param dwFlags
* This parameter must be zero.
* @return If the function succeeds, it returns ERROR_SUCCESS. If the
* function fails, the return value is a system error code or a PDH
* error code.
* @see <A HREF=
* "https://msdn.microsoft.com/en-us/library/windows/desktop/aa372677(v=vs.85).aspx">PdhEnumObjectItems</A>
*/
int PdhEnumObjectItems(String szDataSource, String szMachineName, String szObjectName, Pointer mszCounterList,
DWORDByReference pcchCounterListLength, Pointer mszInstanceList, DWORDByReference pcchInstanceListLength,
int dwDetailLevel, int dwFlags);

/**
* Returns the counter index corresponding to the specified counter name.
*
* @param szMachineName
* Null-terminated string that specifies the name of the computer
* where the specified 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 szNameBuffer
* Null-terminated string that contains the counter name.
* @param pdwIndex
* Index of the counter.
* @return If the function succeeds, it returns ERROR_SUCCESS. If the
* function fails, the return value is a system error code or a PDH
* error code.
* @see <A HREF=
* "https://msdn.microsoft.com/en-us/library/windows/desktop/aa372647(v=vs.85).aspx">PdhLookupPerfIndexByName</A>
*/
int PdhLookupPerfIndexByName(String szMachineName, String szNameBuffer, DWORDByReference pdwIndex);

/**
* Returns the performance object name or counter name corresponding to the
* specified index.
*
* @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.
* @param szNameBuffer
* Caller-allocated buffer that receives the null-terminated name
* of the performance object or counter. Set to NULL if
* pcchNameBufferSize is zero.
* @param pcchNameBufferSize
* Size of the szNameBuffer buffer, in TCHARs. If zero on input,
* the function returns PDH_MORE_DATA and sets this parameter to
* the required buffer size. If the buffer is larger than the
* required size, the function sets this parameter to the actual
* size of the buffer that was used. If the specified size on
* input is greater than zero but less than the required size,
* you should not rely on the returned size to reallocate the
* buffer.
* @return If the function succeeds, it returns ERROR_SUCCESS. If the
* function fails, the return value is a system error code or a PDH
* error code.
* @see <A HREF=
* "https://msdn.microsoft.com/en-us/library/windows/desktop/aa372648(v=vs.85).aspx">PdhLookupPerfNameByIndex</A>
*/
int PdhLookupPerfNameByIndex(String szMachineName, int dwNameIndex, Pointer szNameBuffer,
DWORDByReference pcchNameBufferSize);
}
160 changes: 160 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/PdhUtil.java
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,
Copy link
Member

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.

Copy link
Contributor Author

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.

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<>();
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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;
}
}
44 changes: 44 additions & 0 deletions contrib/platform/test/com/sun/jna/platform/win32/PdhTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.");
}
}
}