1// +build windows
2
3/*
4** Zabbix
5** Copyright (C) 2001-2021 Zabbix SIA
6**
7** This program is free software; you can redistribute it and/or modify
8** it under the terms of the GNU General Public License as published by
9** the Free Software Foundation; either version 2 of the License, or
10** (at your option) any later version.
11**
12** This program is distributed in the hope that it will be useful,
13** but WITHOUT ANY WARRANTY; without even the implied warranty of
14** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15** GNU General Public License for more details.
16**
17** You should have received a copy of the GNU General Public License
18** along with this program; if not, write to the Free Software
19** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20**/
21
22package win32
23
24import (
25	"errors"
26	"fmt"
27	"syscall"
28	"unsafe"
29
30	"golang.org/x/sys/windows"
31)
32
33var (
34	hPdh Hlib
35
36	pdhOpenQuery                uintptr
37	pdhCloseQuery               uintptr
38	pdhAddCounter               uintptr
39	pdhAddEnglishCounter        uintptr
40	pdhCollectQueryData         uintptr
41	pdhGetFormattedCounterValue uintptr
42	pdhParseCounterPath         uintptr
43	pdhMakeCounterPath          uintptr
44	pdhLookupPerfNameByIndex    uintptr
45	pdhLookupPerfIndexByName    uintptr
46	pdhRemoveCounter            uintptr
47	pdhEnumObjectItem           uintptr
48	pdhEnumObjectItems          uintptr
49	pdhEnumObjects              uintptr
50)
51
52const (
53	PDH_CSTATUS_VALID_DATA   = 0x00000000
54	PDH_CSTATUS_NEW_DATA     = 0x00000001
55	PDH_CSTATUS_INVALID_DATA = 0xC0000BBA
56
57	PDH_MORE_DATA    = 0x800007D2
58	PDH_NO_DATA      = 0x800007D5
59	PDH_INVALID_DATA = 0xc0000bc6
60
61	PDH_FMT_DOUBLE   = 0x00000200
62	PDH_FMT_LARGE    = 0x00000400
63	PDH_FMT_NOCAP100 = 0x00008000
64
65	PDH_MAX_COUNTER_NAME = 1024
66
67	PERF_DETAIL_WIZARD = 400
68)
69
70type Instance struct {
71	Name string `json:"{#INSTANCE}"`
72}
73
74func newPdhError(ret uintptr) (err error) {
75	flags := uint32(windows.FORMAT_MESSAGE_FROM_HMODULE | windows.FORMAT_MESSAGE_IGNORE_INSERTS)
76	var len uint32
77	buf := make([]uint16, 1024)
78	len, err = windows.FormatMessage(flags, uintptr(hPdh), uint32(ret), 0, buf, nil)
79	if len == 0 {
80		return
81	}
82	return errors.New(windows.UTF16ToString(buf))
83}
84
85func PdhOpenQuery(dataSource *string, userData uintptr) (query PDH_HQUERY, err error) {
86	var source uintptr
87	if dataSource != nil {
88		wcharSource := windows.StringToUTF16(*dataSource)
89		source = uintptr(unsafe.Pointer(&wcharSource[0]))
90	}
91	ret, _, _ := syscall.Syscall(pdhOpenQuery, 3, source, userData, uintptr(unsafe.Pointer(&query)))
92
93	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
94		return 0, newPdhError(ret)
95	}
96	return
97}
98
99func PdhCloseQuery(query PDH_HQUERY) (err error) {
100	ret, _, _ := syscall.Syscall(pdhCloseQuery, 1, uintptr(query), 0, 0)
101	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
102		return newPdhError(ret)
103	}
104	return nil
105}
106
107func PdhAddCounter(query PDH_HQUERY, path string, userData uintptr) (counter PDH_HCOUNTER, err error) {
108	wcharPath, _ := syscall.UTF16PtrFromString(path)
109	ret, _, _ := syscall.Syscall6(pdhAddCounter, 4, uintptr(query), uintptr(unsafe.Pointer(wcharPath)), userData,
110		uintptr(unsafe.Pointer(&counter)), 0, 0)
111
112	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
113		return 0, newPdhError(ret)
114	}
115	return
116}
117
118func PdhAddEnglishCounter(query PDH_HQUERY, path string, userData uintptr) (counter PDH_HCOUNTER, err error) {
119	wcharPath, _ := syscall.UTF16PtrFromString(path)
120	ret, _, _ := syscall.Syscall6(pdhAddEnglishCounter, 4, uintptr(query), uintptr(unsafe.Pointer(wcharPath)), userData,
121		uintptr(unsafe.Pointer(&counter)), 0, 0)
122
123	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
124		return 0, newPdhError(ret)
125	}
126	return
127}
128
129func PdhCollectQueryData(query PDH_HQUERY) (err error) {
130	ret, _, _ := syscall.Syscall(pdhCollectQueryData, 1, uintptr(query), 0, 0)
131	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
132		return newPdhError(ret)
133	}
134	return nil
135}
136
137func PdhGetFormattedCounterValueDouble(counter PDH_HCOUNTER) (value *float64, err error) {
138	var pdhValue PDH_FMT_COUNTERVALUE_DOUBLE
139	ret, _, _ := syscall.Syscall6(pdhGetFormattedCounterValue, 4, uintptr(counter),
140		uintptr(PDH_FMT_DOUBLE|PDH_FMT_NOCAP100), 0, uintptr(unsafe.Pointer(&pdhValue)), 0, 0)
141	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
142		if ret == PDH_INVALID_DATA || ret == PDH_CSTATUS_INVALID_DATA {
143			return nil, nil
144		}
145		return nil, newPdhError(ret)
146	}
147	return &pdhValue.Value, nil
148}
149
150func PdhGetFormattedCounterValueInt64(counter PDH_HCOUNTER) (value *int64, err error) {
151	var pdhValue PDH_FMT_COUNTERVALUE_LARGE
152	ret, _, _ := syscall.Syscall6(pdhGetFormattedCounterValue, 4, uintptr(counter), uintptr(PDH_FMT_LARGE), 0,
153		uintptr(unsafe.Pointer(&pdhValue)), 0, 0)
154	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
155		if ret == PDH_INVALID_DATA || ret == PDH_CSTATUS_INVALID_DATA {
156			return nil, nil
157		}
158		return nil, newPdhError(ret)
159	}
160	return &pdhValue.Value, nil
161}
162
163func PdhRemoveCounter(counter PDH_HCOUNTER) (err error) {
164	ret, _, _ := syscall.Syscall(pdhRemoveCounter, 1, uintptr(counter), 0, 0)
165	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
166		return newPdhError(ret)
167	}
168	return nil
169}
170
171func PdhParseCounterPath(path string) (elements *PDH_COUNTER_PATH_ELEMENTS, err error) {
172	wPath := windows.StringToUTF16(path)
173	ptrPath := uintptr(unsafe.Pointer(&wPath[0]))
174	var size uint32
175	ret, _, _ := syscall.Syscall6(pdhParseCounterPath, 4, ptrPath, 0, uintptr(unsafe.Pointer(&size)), 0, 0, 0)
176	if ret != PDH_MORE_DATA && syscall.Errno(ret) != windows.ERROR_SUCCESS {
177		return nil, newPdhError(ret)
178	}
179
180	buf := make([]uint16, size/2)
181	ret, _, _ = syscall.Syscall6(pdhParseCounterPath, 4, ptrPath, uintptr(unsafe.Pointer(&buf[0])),
182		uintptr(unsafe.Pointer(&size)), 0, 0, 0)
183	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
184		return nil, newPdhError(ret)
185	}
186	return LP_PDH_COUNTER_PATH_ELEMENTS(unsafe.Pointer(&buf[0])), nil
187}
188
189func PdhMakeCounterPath(elements *PDH_COUNTER_PATH_ELEMENTS) (path string, err error) {
190	size := uint32(PDH_MAX_COUNTER_NAME)
191	buf := make([]uint16, size)
192	ret, _, _ := syscall.Syscall6(pdhMakeCounterPath, 4, uintptr(unsafe.Pointer(elements)),
193		uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&size)), 0, 0, 0)
194	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
195		return "", newPdhError(ret)
196	}
197	return windows.UTF16ToString(buf), nil
198}
199
200func PdhLookupPerfNameByIndex(index int) (path string, err error) {
201	size := uint32(PDH_MAX_COUNTER_NAME)
202	buf := make([]uint16, size)
203	ret, _, _ := syscall.Syscall6(pdhLookupPerfNameByIndex, 4, 0, uintptr(index), uintptr(unsafe.Pointer(&buf[0])),
204		uintptr(unsafe.Pointer(&size)), 0, 0)
205	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
206		return "", newPdhError(ret)
207	}
208	return windows.UTF16ToString(buf), nil
209}
210
211func PdhLookupPerfIndexByName(name string) (idx int, err error) {
212	nameUTF16, err := syscall.UTF16PtrFromString(name)
213	if err != nil {
214		return 0, err
215	}
216
217	ret, _, _ := syscall.Syscall(pdhLookupPerfIndexByName, 3, 0, uintptr(unsafe.Pointer(nameUTF16)), uintptr(unsafe.Pointer(&idx)))
218	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
219		return 0, newPdhError(ret)
220	}
221
222	return idx, nil
223}
224
225func PdhEnumObjectItems(objectName string) (instances []Instance, err error) {
226	var counterListSize, instanceListSize uint32
227	nameUTF16, err := syscall.UTF16FromString(objectName)
228	if err != nil {
229		return nil, err
230	}
231	ptrNameUTF16 := uintptr(unsafe.Pointer(&nameUTF16[0]))
232
233	ret, _, _ := syscall.Syscall9(pdhEnumObjectItems, 9, 0, 0,
234		ptrNameUTF16, 0, uintptr(unsafe.Pointer(&counterListSize)), 0,
235		uintptr(unsafe.Pointer(&instanceListSize)), uintptr(PERF_DETAIL_WIZARD), 0)
236	if ret != PDH_MORE_DATA {
237		if syscall.Errno(ret) == windows.ERROR_SUCCESS {
238			return
239		}
240		return nil, newPdhError(ret)
241	}
242	var instptr uintptr
243	var instbuf []uint16
244
245	if counterListSize < 1 {
246		return nil, fmt.Errorf("No counters found for given object.")
247	}
248
249	counterbuf := make([]uint16, counterListSize)
250
251	for {
252		if instanceListSize == 0 {
253			return nil, fmt.Errorf("Object does not support variable instances.")
254		}
255
256		instbuf = make([]uint16, instanceListSize)
257		instptr = uintptr(unsafe.Pointer(&instbuf[0]))
258
259		ret, _, _ = syscall.Syscall9(pdhEnumObjectItems, 9, 0, 0, ptrNameUTF16, uintptr(unsafe.Pointer(&counterbuf[0])),
260			uintptr(unsafe.Pointer(&counterListSize)), instptr, uintptr(unsafe.Pointer(&instanceListSize)),
261			uintptr(PERF_DETAIL_WIZARD), 0)
262		if ret == PDH_MORE_DATA {
263			continue
264		}
265		if syscall.Errno(ret) != windows.ERROR_SUCCESS {
266			return nil, newPdhError(ret)
267		}
268
269		break
270	}
271
272	var singleName []uint16
273	m := make(map[string]bool)
274	for len(instbuf) != 0 {
275		singleName, instbuf = NextField(instbuf)
276		if len(singleName) == 0 {
277			break
278		}
279
280		strName := windows.UTF16ToString(singleName)
281		if _, ok := m[strName]; !ok {
282			m[strName] = true
283			instances = append(instances, Instance{strName})
284		}
285	}
286
287	return instances, nil
288}
289
290func PdhEnumObject() (objects []string, err error) {
291	var objectListSize uint32
292	ret, _, _ := syscall.Syscall6(pdhEnumObjects, 6, 0, 0, 0, uintptr(unsafe.Pointer(&objectListSize)),
293		uintptr(PERF_DETAIL_WIZARD), bool2uintptr(true))
294	if ret != PDH_MORE_DATA {
295		return nil, newPdhError(ret)
296	}
297
298	if objectListSize < 1 {
299		return nil, fmt.Errorf("No objects found.")
300	}
301
302	objectBuf := make([]uint16, objectListSize)
303	ret, _, _ = syscall.Syscall6(pdhEnumObjects, 6, 0, 0, uintptr(unsafe.Pointer(&objectBuf[0])),
304		uintptr(unsafe.Pointer(&objectListSize)), uintptr(PERF_DETAIL_WIZARD),
305		bool2uintptr(false))
306	if syscall.Errno(ret) != windows.ERROR_SUCCESS {
307		return nil, newPdhError(ret)
308	}
309
310	var singleName []uint16
311
312	for len(objectBuf) != 0 {
313		singleName, objectBuf = NextField(objectBuf)
314		if len(singleName) == 0 {
315			break
316		}
317		objects = append(objects, windows.UTF16ToString(singleName))
318	}
319
320	return objects, nil
321}
322
323func init() {
324	hPdh = mustLoadLibrary("pdh.dll")
325
326	pdhOpenQuery = hPdh.mustGetProcAddress("PdhOpenQuery")
327	pdhCloseQuery = hPdh.mustGetProcAddress("PdhCloseQuery")
328	pdhAddCounter = hPdh.mustGetProcAddress("PdhAddCounterW")
329	pdhAddEnglishCounter = hPdh.mustGetProcAddress("PdhAddEnglishCounterW")
330	pdhCollectQueryData = hPdh.mustGetProcAddress("PdhCollectQueryData")
331	pdhGetFormattedCounterValue = hPdh.mustGetProcAddress("PdhGetFormattedCounterValue")
332	pdhParseCounterPath = hPdh.mustGetProcAddress("PdhParseCounterPathW")
333	pdhMakeCounterPath = hPdh.mustGetProcAddress("PdhMakeCounterPathW")
334	pdhLookupPerfNameByIndex = hPdh.mustGetProcAddress("PdhLookupPerfNameByIndexW")
335	pdhLookupPerfIndexByName = hPdh.mustGetProcAddress("PdhLookupPerfIndexByNameW")
336	pdhRemoveCounter = hPdh.mustGetProcAddress("PdhRemoveCounter")
337	pdhEnumObjectItems = hPdh.mustGetProcAddress("PdhEnumObjectItemsW")
338	pdhEnumObjects = hPdh.mustGetProcAddress("PdhEnumObjectsW")
339}
340