Skip to content

Commit 1dc97f6

Browse files
committed
Add GetPerformanceInfo syscall
1 parent ee94ac3 commit 1dc97f6

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed

psapi.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
//sys _GetProcessMemoryInfo(handle syscall.Handle, psmemCounters *ProcessMemoryCountersEx, cb uint32) (err error) = psapi.GetProcessMemoryInfo
3131
//sys _GetProcessImageFileNameA(handle syscall.Handle, imageFileName *byte, nSize uint32) (len uint32, err error) = psapi.GetProcessImageFileNameA
3232
//sys _EnumProcesses(lpidProcess *uint32, cb uint32, lpcbNeeded *uint32) (err error) = psapi.EnumProcesses
33+
//sys _GetPerformanceInfo(pi *PerformanceInformation, cb uint32) (err error) = psapi.GetPerformanceInfo
3334

3435
var (
3536
sizeofProcessMemoryCountersEx = uint32(unsafe.Sizeof(ProcessMemoryCountersEx{}))
@@ -98,3 +99,33 @@ func EnumProcesses() (pids []uint32, err error) {
9899
}
99100
}
100101
}
102+
103+
// PerformanceInformation represents the PERFORMANCE_INFORMATION structure
104+
type PerformanceInformation struct {
105+
CB uint32
106+
CommitTotal uintptr
107+
CommitLimit uintptr
108+
CommitPeak uintptr
109+
PhysicalTotal uintptr
110+
PhysicalAvailable uintptr
111+
SystemCache uintptr
112+
KernelTotal uintptr
113+
KernelPaged uintptr
114+
KernelNonpaged uintptr
115+
PageSize uintptr
116+
HandleCount uint32
117+
ProcessCount uint32
118+
ThreadCount uint32
119+
}
120+
121+
// GetPerformanceInfo retrieves performance information for the system
122+
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getperformanceinfo
123+
func GetPerformanceInfo() (PerformanceInformation, error) {
124+
var pi PerformanceInformation
125+
pi.CB = uint32(unsafe.Sizeof(pi))
126+
127+
if err := _GetPerformanceInfo(&pi, pi.CB); err != nil {
128+
return PerformanceInformation{}, fmt.Errorf("GetPerformanceInfo failed: %w", err)
129+
}
130+
return pi, nil
131+
}

psapi_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//go:build windows
19+
// +build windows
20+
21+
package windows
22+
23+
import (
24+
"syscall"
25+
"testing"
26+
"unsafe"
27+
28+
"github.com/stretchr/testify/assert"
29+
)
30+
31+
func TestGetPerformanceInfo(t *testing.T) {
32+
pi, err := GetPerformanceInfo()
33+
if err != nil {
34+
t.Fatal(err)
35+
}
36+
37+
// Verify the structure was populated correctly
38+
assert.Equal(t, uint32(unsafe.Sizeof(pi)), pi.CB, "CB field should contain the size of the structure")
39+
40+
// Verify that physical memory values are reasonable
41+
assert.True(t, pi.PhysicalTotal > 0, "PhysicalTotal should be greater than 0")
42+
assert.True(t, pi.PhysicalAvailable <= pi.PhysicalTotal, "PhysicalAvailable should not exceed PhysicalTotal")
43+
44+
// Verify that commit values are reasonable
45+
assert.True(t, pi.CommitTotal > 0, "CommitTotal should be greater than 0")
46+
assert.True(t, pi.CommitLimit > 0, "CommitLimit should be greater than 0")
47+
assert.True(t, pi.CommitTotal <= pi.CommitLimit, "CommitTotal should not exceed CommitLimit")
48+
49+
// Verify that page size is reasonable (typically at least 4KB on x86/x64)
50+
assert.True(t, pi.PageSize >= 4096, "PageSize should be at least 4KB")
51+
52+
// Verify that process and thread counts are reasonable
53+
assert.True(t, pi.ProcessCount > 0, "ProcessCount should be greater than 0")
54+
assert.True(t, pi.ThreadCount > 0, "ThreadCount should be greater than 0")
55+
assert.True(t, pi.ThreadCount >= pi.ProcessCount, "ThreadCount should be at least equal to ProcessCount")
56+
57+
// Verify that handle count is reasonable
58+
assert.True(t, pi.HandleCount > 0, "HandleCount should be greater than 0")
59+
60+
// Verify that kernel memory values are reasonable
61+
assert.True(t, pi.KernelTotal > 0, "KernelTotal should be greater than 0")
62+
assert.True(t, pi.KernelPaged <= pi.KernelTotal, "KernelPaged should not exceed KernelTotal")
63+
assert.True(t, pi.KernelNonpaged <= pi.KernelTotal, "KernelNonpaged should not exceed KernelTotal")
64+
65+
// Verify that system cache is reasonable
66+
assert.True(t, pi.SystemCache >= 0, "SystemCache should be non-negative")
67+
}
68+
69+
func TestGetProcessMemoryInfo(t *testing.T) {
70+
h, err := syscall.GetCurrentProcess()
71+
if err != nil {
72+
t.Fatal(err)
73+
}
74+
75+
info, err := GetProcessMemoryInfo(h)
76+
if err != nil {
77+
t.Fatal(err)
78+
}
79+
80+
// Verify the structure was populated correctly
81+
assert.Equal(t, uint32(unsafe.Sizeof(info)), info.cb, "cb field should contain the size of the structure")
82+
83+
// Verify that memory values are reasonable
84+
assert.True(t, info.WorkingSetSize > 0, "WorkingSetSize should be greater than 0")
85+
assert.True(t, info.PeakWorkingSetSize >= info.WorkingSetSize, "PeakWorkingSetSize should be at least equal to WorkingSetSize")
86+
assert.True(t, info.PrivateUsage > 0, "PrivateUsage should be greater than 0")
87+
88+
// Verify that page fault count is reasonable
89+
assert.True(t, info.PageFaultCount >= 0, "PageFaultCount should be non-negative")
90+
}
91+
92+
func TestGetProcessImageFileName(t *testing.T) {
93+
h, err := syscall.GetCurrentProcess()
94+
if err != nil {
95+
t.Fatal(err)
96+
}
97+
98+
filename, err := GetProcessImageFileName(h)
99+
if err != nil {
100+
t.Fatal(err)
101+
}
102+
103+
// Verify that we got a valid device path
104+
assert.True(t, len(filename) > 0, "Filename should not be empty")
105+
assert.Contains(t, filename, "\\Device\\", "Filename should be a device path starting with \\Device\\")
106+
107+
// The path should contain the executable name
108+
assert.Contains(t, filename, ".exe", "Filename should contain .exe extension")
109+
}
110+
111+
func TestEnumProcesses(t *testing.T) {
112+
pids, err := EnumProcesses()
113+
if err != nil {
114+
t.Fatal(err)
115+
}
116+
117+
// Verify that we got a reasonable number of processes
118+
assert.True(t, len(pids) > 0, "Should return at least one process")
119+
assert.True(t, len(pids) < 10000, "Should not return an unreasonable number of processes")
120+
121+
// Verify that the current process is in the list
122+
currentPID := uint32(syscall.Getpid())
123+
found := false
124+
for _, pid := range pids {
125+
if pid == currentPID {
126+
found = true
127+
break
128+
}
129+
}
130+
assert.True(t, found, "Current process PID should be in the returned list")
131+
}

zsyscall_windows.go

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)