master
c 1,080 lines 45.4 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "apps_plugin.h"
4 /*
5 {
6 "SystemName": "WIN11",
7 "NumObjectTypes": 1,
8 "LittleEndian": 1,
9 "Version": 1,
10 "Revision": 1,
11 "DefaultObject": 238,
12 "PerfFreq": 10000000,
13 "PerfTime": 9242655165203,
14 "PerfTime100nSec": 133716612800215149,
15 "SystemTime": {
16 "Year": 2024,
17 "Month": 9,
18 "DayOfWeek": 2,
19 "Day": 24,
20 "Hour": 14,
21 "Minute": 21,
22 "Second": 20,
23 "Milliseconds": 21
24 },
25 "Objects": [
26 {
27 "NameId": 230,
28 "Name": "Process",
29 "HelpId": 231,
30 "Help": "The Process performance object consists of counters that monitor running application program and system processes. All the threads in a process share the same address space and have access to the same data.",
31 "NumInstances": 274,
32 "NumCounters": 28,
33 "PerfTime": 133716612800215149,
34 "PerfFreq": 10000000,
35 "CodePage": 0,
36 "DefaultCounter": 0,
37 "DetailLevel": "Novice (100)",
38 "Instances": [
39 {
40 "Instance": "Idle",
41 "UniqueID": -1,
42 "Labels": [
43 {
44 "key": "Process",
45 "value": "Idle"
46 }
47 ],
48 "Counters": [
49 {
50 "Counter": "% Processor Time",
51 "Value": {
52 "data": 106881107812500,
53 "time": 133716612800215149,
54 "type": 542180608,
55 "multi": 0,
56 "frequency": 0
57 },
58 "Help": "% Processor Time is the percentage of elapsed time that all of process threads used the processor to execution instructions. An instruction is the basic unit of execution in a computer, a thread is the object that executes instructions, and a process is the object created when a program is run. Code executed to handle some hardware interrupts and trap conditions are included in this count.",
59 "Type": "PERF_100NSEC_TIMER",
60 "Algorithm": "100 * (data1 - data0) / (time1 - time0)",
61 "Description": "64-bit Timer in 100 nsec units. Display delta divided by delta time. Display suffix: \"%\""
62 },
63 {
64 "Counter": "% User Time",
65 "Value": {
66 "data": 0,
67 "time": 133716612800215149,
68 "type": 542180608,
69 "multi": 0,
70 "frequency": 0
71 },
72 "Help": "% User Time is the percentage of elapsed time that the process threads spent executing code in user mode. Applications, environment subsystems, and integral subsystems execute in user mode. Code executing in user mode cannot damage the integrity of the Windows executive, kernel, and device drivers. Unlike some early operating systems, Windows uses process boundaries for subsystem protection in addition to the traditional protection of user and privileged modes. Some work done by Windows on behalf of the application might appear in other subsystem processes in addition to the privileged time in the process.",
73 "Type": "PERF_100NSEC_TIMER",
74 "Algorithm": "100 * (data1 - data0) / (time1 - time0)",
75 "Description": "64-bit Timer in 100 nsec units. Display delta divided by delta time. Display suffix: \"%\""
76 },
77 {
78 "Counter": "% Privileged Time",
79 "Value": {
80 "data": 106881107812500,
81 "time": 133716612800215149,
82 "type": 542180608,
83 "multi": 0,
84 "frequency": 0
85 },
86 "Help": "% Privileged Time is the percentage of elapsed time that the process threads spent executing code in privileged mode. When a Windows system service is called, the service will often run in privileged mode to gain access to system-private data. Such data is protected from access by threads executing in user mode. Calls to the system can be explicit or implicit, such as page faults or interrupts. Unlike some early operating systems, Windows uses process boundaries for subsystem protection in addition to the traditional protection of user and privileged modes. Some work done by Windows on behalf of the application might appear in other subsystem processes in addition to the privileged time in the process.",
87 "Type": "PERF_100NSEC_TIMER",
88 "Algorithm": "100 * (data1 - data0) / (time1 - time0)",
89 "Description": "64-bit Timer in 100 nsec units. Display delta divided by delta time. Display suffix: \"%\""
90 },
91 {
92 "Counter": "Virtual Bytes Peak",
93 "Value": {
94 "data": 8192,
95 "time": 0,
96 "type": 65792,
97 "multi": 0,
98 "frequency": 0
99 },
100 "Help": "Virtual Bytes Peak is the maximum size, in bytes, of virtual address space the process has used at any one time. Use of virtual address space does not necessarily imply corresponding use of either disk or main memory pages. However, virtual space is finite, and the process might limit its ability to load libraries.",
101 "Type": "PERF_COUNTER_LARGE_RAWCOUNT",
102 "Algorithm": "data0",
103 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
104 },
105 {
106 "Counter": "Virtual Bytes",
107 "Value": {
108 "data": 8192,
109 "time": 0,
110 "type": 65792,
111 "multi": 0,
112 "frequency": 0
113 },
114 "Help": "Virtual Bytes is the current size, in bytes, of the virtual address space the process is using. Use of virtual address space does not necessarily imply corresponding use of either disk or main memory pages. Virtual space is finite, and the process can limit its ability to load libraries.",
115 "Type": "PERF_COUNTER_LARGE_RAWCOUNT",
116 "Algorithm": "data0",
117 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
118 },
119 {
120 "Counter": "Page Faults/sec",
121 "Value": {
122 "data": 9,
123 "time": 9242655165203,
124 "type": 272696320,
125 "multi": 0,
126 "frequency": 10000000
127 },
128 "Help": "Page Faults/sec is the rate at which page faults by the threads executing in this process are occurring. A page fault occurs when a thread refers to a virtual memory page that is not in its working set in main memory. This may not cause the page to be fetched from disk if it is on the standby list and hence already in main memory, or if it is in use by another process with whom the page is shared.",
129 "Type": "PERF_COUNTER_COUNTER",
130 "Algorithm": "(data1 - data0) / ((time1 - time0) / frequency)",
131 "Description": "32-bit Counter. Divide delta by delta time. Display suffix: \"/sec\""
132 },
133 {
134 "Counter": "Working Set Peak",
135 "Value": {
136 "data": 8192,
137 "time": 0,
138 "type": 65792,
139 "multi": 0,
140 "frequency": 0
141 },
142 "Help": "Working Set Peak is the maximum size, in bytes, of the Working Set of this process at any point in time. The Working Set is the set of memory pages touched recently by the threads in the process. If free memory in the computer is above a threshold, pages are left in the Working Set of a process even if they are not in use. When free memory falls below a threshold, pages are trimmed from Working Sets. If they are needed they will then be soft-faulted back into the Working Set before they leave main memory.",
143 "Type": "PERF_COUNTER_LARGE_RAWCOUNT",
144 "Algorithm": "data0",
145 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
146 },
147 {
148 "Counter": "Working Set",
149 "Value": {
150 "data": 8192,
151 "time": 0,
152 "type": 65792,
153 "multi": 0,
154 "frequency": 0
155 },
156 "Help": "Working Set is the current size, in bytes, of the Working Set of this process. The Working Set is the set of memory pages touched recently by the threads in the process. If free memory in the computer is above a threshold, pages are left in the Working Set of a process even if they are not in use. When free memory falls below a threshold, pages are trimmed from Working Sets. If they are needed they will then be soft-faulted back into the Working Set before leaving main memory.",
157 "Type": "PERF_COUNTER_LARGE_RAWCOUNT",
158 "Algorithm": "data0",
159 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
160 },
161 {
162 "Counter": "Page File Bytes Peak",
163 "Value": {
164 "data": 61440,
165 "time": 0,
166 "type": 65792,
167 "multi": 0,
168 "frequency": 0
169 },
170 "Help": "Page File Bytes Peak is the maximum amount of virtual memory, in bytes, that this process has reserved for use in the paging file(s). Paging files are used to store pages of memory used by the process that are not contained in other files. Paging files are shared by all processes, and the lack of space in paging files can prevent other processes from allocating memory. If there is no paging file, this counter reflects the maximum amount of virtual memory that the process has reserved for use in physical memory.",
171 "Type": "PERF_COUNTER_LARGE_RAWCOUNT",
172 "Algorithm": "data0",
173 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
174 },
175 {
176 "Counter": "Page File Bytes",
177 "Value": {
178 "data": 61440,
179 "time": 0,
180 "type": 65792,
181 "multi": 0,
182 "frequency": 0
183 },
184 "Help": "Page File Bytes is the current amount of virtual memory, in bytes, that this process has reserved for use in the paging file(s). Paging files are used to store pages of memory used by the process that are not contained in other files. Paging files are shared by all processes, and the lack of space in paging files can prevent other processes from allocating memory. If there is no paging file, this counter reflects the current amount of virtual memory that the process has reserved for use in physical memory.",
185 "Type": "PERF_COUNTER_LARGE_RAWCOUNT",
186 "Algorithm": "data0",
187 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
188 },
189 {
190 "Counter": "Private Bytes",
191 "Value": {
192 "data": 61440,
193 "time": 0,
194 "type": 65792,
195 "multi": 0,
196 "frequency": 0
197 },
198 "Help": "Private Bytes is the current size, in bytes, of memory that this process has allocated that cannot be shared with other processes.",
199 "Type": "PERF_COUNTER_LARGE_RAWCOUNT",
200 "Algorithm": "data0",
201 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
202 },
203 {
204 "Counter": "Thread Count",
205 "Value": {
206 "data": 24,
207 "time": 0,
208 "type": 65536,
209 "multi": 0,
210 "frequency": 0
211 },
212 "Help": "The number of threads currently active in this process. An instruction is the basic unit of execution in a processor, and a thread is the object that executes instructions. Every running process has at least one thread.",
213 "Type": "PERF_COUNTER_RAWCOUNT",
214 "Algorithm": "data0",
215 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
216 },
217 {
218 "Counter": "Priority Base",
219 "Value": {
220 "data": 0,
221 "time": 0,
222 "type": 65536,
223 "multi": 0,
224 "frequency": 0
225 },
226 "Help": "The current base priority of this process. Threads within a process can raise and lower their own base priority relative to the process' base priority.",
227 "Type": "PERF_COUNTER_RAWCOUNT",
228 "Algorithm": "data0",
229 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
230 },
231 {
232 "Counter": "Elapsed Time",
233 "Value": {
234 "data": 133707369666486855,
235 "time": 133716612800215149,
236 "type": 807666944,
237 "multi": 0,
238 "frequency": 10000000
239 },
240 "Help": "The total elapsed time, in seconds, that this process has been running.",
241 "Type": "PERF_ELAPSED_TIME",
242 "Algorithm": "(time0 - data0) / frequency0",
243 "Description": "The data collected in this counter is actually the start time of the item being measured. For display, this data is subtracted from the sample time to yield the elapsed time as the difference between the two. In the definition below, the PerfTime field of the Object contains the sample time as indicated by the PERF_OBJECT_TIMER bit and the difference is scaled by the PerfFreq of the Object to convert the time units into seconds."
244 },
245 {
246 "Counter": "ID Process",
247 "Value": {
248 "data": 0,
249 "time": 0,
250 "type": 65536,
251 "multi": 0,
252 "frequency": 0
253 },
254 "Help": "ID Process is the unique identifier of this process. ID Process numbers are reused, so they only identify a process for the lifetime of that process.",
255 "Type": "PERF_COUNTER_RAWCOUNT",
256 "Algorithm": "data0",
257 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
258 },
259 {
260 "Counter": "Creating Process ID",
261 "Value": {
262 "data": 0,
263 "time": 0,
264 "type": 65536,
265 "multi": 0,
266 "frequency": 0
267 },
268 "Help": "The Creating Process ID value is the Process ID of the process that created the process. The creating process may have terminated, so this value may no longer identify a running process.",
269 "Type": "PERF_COUNTER_RAWCOUNT",
270 "Algorithm": "data0",
271 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
272 },
273 {
274 "Counter": "Pool Paged Bytes",
275 "Value": {
276 "data": 0,
277 "time": 0,
278 "type": 65536,
279 "multi": 0,
280 "frequency": 0
281 },
282 "Help": "Pool Paged Bytes is the size, in bytes, of the paged pool, an area of the system virtual memory that is used for objects that can be written to disk when they are not being used. Memory\\\\Pool Paged Bytes is calculated differently than Process\\\\Pool Paged Bytes, so it might not equal Process(_Total)\\\\Pool Paged Bytes. This counter displays the last observed value only; it is not an average.",
283 "Type": "PERF_COUNTER_RAWCOUNT",
284 "Algorithm": "data0",
285 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
286 },
287 {
288 "Counter": "Pool Nonpaged Bytes",
289 "Value": {
290 "data": 272,
291 "time": 0,
292 "type": 65536,
293 "multi": 0,
294 "frequency": 0
295 },
296 "Help": "Pool Nonpaged Bytes is the size, in bytes, of the nonpaged pool, an area of the system virtual memory that is used for objects that cannot be written to disk, but must remain in physical memory as long as they are allocated. Memory\\\\Pool Nonpaged Bytes is calculated differently than Process\\\\Pool Nonpaged Bytes, so it might not equal Process(_Total)\\\\Pool Nonpaged Bytes. This counter displays the last observed value only; it is not an average.",
297 "Type": "PERF_COUNTER_RAWCOUNT",
298 "Algorithm": "data0",
299 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
300 },
301 {
302 "Counter": "Handle Count",
303 "Value": {
304 "data": 0,
305 "time": 0,
306 "type": 65536,
307 "multi": 0,
308 "frequency": 0
309 },
310 "Help": "The total number of handles currently open by this process. This number is equal to the sum of the handles currently open by each thread in this process.",
311 "Type": "PERF_COUNTER_RAWCOUNT",
312 "Algorithm": "data0",
313 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
314 },
315 {
316 "Counter": "IO Read Operations/sec",
317 "Value": {
318 "data": 0,
319 "time": 9242655165203,
320 "type": 272696576,
321 "multi": 0,
322 "frequency": 10000000
323 },
324 "Help": "The rate at which the process is issuing read I/O operations. This counter counts all I/O activity generated by the process to include file, network and device I/Os.",
325 "Type": "PERF_COUNTER_BULK_COUNT",
326 "Algorithm": "(data1 - data0) / ((time1 - time0) / frequency)",
327 "Description": "64-bit Counter. Divide delta by delta time. Display Suffix: \"/sec\""
328 },
329 {
330 "Counter": "IO Write Operations/sec",
331 "Value": {
332 "data": 0,
333 "time": 9242655165203,
334 "type": 272696576,
335 "multi": 0,
336 "frequency": 10000000
337 },
338 "Help": "The rate at which the process is issuing write I/O operations. This counter counts all I/O activity generated by the process to include file, network and device I/Os.",
339 "Type": "PERF_COUNTER_BULK_COUNT",
340 "Algorithm": "(data1 - data0) / ((time1 - time0) / frequency)",
341 "Description": "64-bit Counter. Divide delta by delta time. Display Suffix: \"/sec\""
342 },
343 {
344 "Counter": "IO Data Operations/sec",
345 "Value": {
346 "data": 0,
347 "time": 9242655165203,
348 "type": 272696576,
349 "multi": 0,
350 "frequency": 10000000
351 },
352 "Help": "The rate at which the process is issuing read and write I/O operations. This counter counts all I/O activity generated by the process to include file, network and device I/Os.",
353 "Type": "PERF_COUNTER_BULK_COUNT",
354 "Algorithm": "(data1 - data0) / ((time1 - time0) / frequency)",
355 "Description": "64-bit Counter. Divide delta by delta time. Display Suffix: \"/sec\""
356 },
357 {
358 "Counter": "IO Other Operations/sec",
359 "Value": {
360 "data": 0,
361 "time": 9242655165203,
362 "type": 272696576,
363 "multi": 0,
364 "frequency": 10000000
365 },
366 "Help": "The rate at which the process is issuing I/O operations that are neither read nor write operations (for example, a control function). This counter counts all I/O activity generated by the process to include file, network and device I/Os.",
367 "Type": "PERF_COUNTER_BULK_COUNT",
368 "Algorithm": "(data1 - data0) / ((time1 - time0) / frequency)",
369 "Description": "64-bit Counter. Divide delta by delta time. Display Suffix: \"/sec\""
370 },
371 {
372 "Counter": "IO Read Bytes/sec",
373 "Value": {
374 "data": 0,
375 "time": 9242655165203,
376 "type": 272696576,
377 "multi": 0,
378 "frequency": 10000000
379 },
380 "Help": "The rate at which the process is reading bytes from I/O operations. This counter counts all I/O activity generated by the process to include file, network and device I/Os.",
381 "Type": "PERF_COUNTER_BULK_COUNT",
382 "Algorithm": "(data1 - data0) / ((time1 - time0) / frequency)",
383 "Description": "64-bit Counter. Divide delta by delta time. Display Suffix: \"/sec\""
384 },
385 {
386 "Counter": "IO Write Bytes/sec",
387 "Value": {
388 "data": 0,
389 "time": 9242655165203,
390 "type": 272696576,
391 "multi": 0,
392 "frequency": 10000000
393 },
394 "Help": "The rate at which the process is writing bytes to I/O operations. This counter counts all I/O activity generated by the process to include file, network and device I/Os.",
395 "Type": "PERF_COUNTER_BULK_COUNT",
396 "Algorithm": "(data1 - data0) / ((time1 - time0) / frequency)",
397 "Description": "64-bit Counter. Divide delta by delta time. Display Suffix: \"/sec\""
398 },
399 {
400 "Counter": "IO Data Bytes/sec",
401 "Value": {
402 "data": 0,
403 "time": 9242655165203,
404 "type": 272696576,
405 "multi": 0,
406 "frequency": 10000000
407 },
408 "Help": "The rate at which the process is reading and writing bytes in I/O operations. This counter counts all I/O activity generated by the process to include file, network and device I/Os.",
409 "Type": "PERF_COUNTER_BULK_COUNT",
410 "Algorithm": "(data1 - data0) / ((time1 - time0) / frequency)",
411 "Description": "64-bit Counter. Divide delta by delta time. Display Suffix: \"/sec\""
412 },
413 {
414 "Counter": "IO Other Bytes/sec",
415 "Value": {
416 "data": 0,
417 "time": 9242655165203,
418 "type": 272696576,
419 "multi": 0,
420 "frequency": 10000000
421 },
422 "Help": "The rate at which the process is issuing bytes to I/O operations that do not involve data such as control operations. This counter counts all I/O activity generated by the process to include file, network and device I/Os.",
423 "Type": "PERF_COUNTER_BULK_COUNT",
424 "Algorithm": "(data1 - data0) / ((time1 - time0) / frequency)",
425 "Description": "64-bit Counter. Divide delta by delta time. Display Suffix: \"/sec\""
426 },
427 {
428 "Counter": "Working Set - Private",
429 "Value": {
430 "data": 8192,
431 "time": 0,
432 "type": 65792,
433 "multi": 0,
434 "frequency": 0
435 },
436 "Help": "Working Set - Private displays the size of the working set, in bytes, that is use for this process only and not shared nor sharable by other processes.",
437 "Type": "PERF_COUNTER_LARGE_RAWCOUNT",
438 "Algorithm": "data0",
439 "Description": "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."
440 }
441 ]
442 },
443 */
444
445
446 #if defined(OS_WINDOWS)
447
448 #include <tlhelp32.h>
449 #include <wchar.h>
450 #include <psapi.h>
451 #include <tchar.h>
452
453 WCHAR* GetProcessCommandLine(HANDLE hProcess);
454
455 struct perflib_data {
456 PERF_DATA_BLOCK *pDataBlock;
457 PERF_OBJECT_TYPE *pObjectType;
458 PERF_INSTANCE_DEFINITION *pi;
459 DWORD pid;
460 };
461
462 void apps_os_init_windows(void) {
463 PerflibNamesRegistryInitialize();
464
465 if(!EnableWindowsPrivilege(SE_DEBUG_NAME))
466 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_DEBUG_NAME);
467
468 if(!EnableWindowsPrivilege(SE_SYSTEM_PROFILE_NAME))
469 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_SYSTEM_PROFILE_NAME);
470
471 if(!EnableWindowsPrivilege(SE_PROF_SINGLE_PROCESS_NAME))
472 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_PROF_SINGLE_PROCESS_NAME);
473 }
474
475 uint64_t apps_os_get_total_memory_windows(void) {
476 MEMORYSTATUSEX memStat = { 0 };
477 memStat.dwLength = sizeof(memStat);
478
479 if (!GlobalMemoryStatusEx(&memStat)) {
480 netdata_log_error("GlobalMemoryStatusEx() failed.");
481 return 0;
482 }
483
484 return memStat.ullTotalPhys;
485 }
486
487 // remove the PID suffix and .exe suffix, if any
488 static void fix_windows_comm(struct pid_stat *p, char *comm) {
489 char pid[UINT64_MAX_LENGTH + 1]; // +1 for the underscore
490 pid[0] = '_';
491 print_uint64(&pid[1], p->pid);
492 size_t pid_len = strlen(pid);
493 size_t comm_len = strlen(comm);
494 if (pid_len < comm_len) {
495 char *compare = &comm[comm_len - pid_len];
496 if (strcmp(pid, compare) == 0)
497 *compare = '\0';
498 }
499
500 // remove the .exe suffix, if any
501 comm_len = strlen(comm);
502 size_t exe_len = strlen(".exe");
503 if(exe_len < comm_len) {
504 char *compare = &comm[comm_len - exe_len];
505 if (strcmp(".exe", compare) == 0)
506 *compare = '\0';
507 }
508 }
509
510 // Convert wide string to UTF-8
511 static char *wchar_to_utf8(WCHAR *s) {
512 static __thread char utf8[PATH_MAX];
513 static __thread int utf8_size = sizeof(utf8);
514
515 int len = WideCharToMultiByte(CP_UTF8, 0, s, -1, NULL, 0, NULL, NULL);
516 if (len <= 0 || len >= utf8_size)
517 return NULL;
518
519 WideCharToMultiByte(CP_UTF8, 0, s, -1, utf8, utf8_size, NULL, NULL);
520 return utf8;
521 }
522
523 static char *ansi_to_utf8(LPCSTR str) {
524 static __thread WCHAR unicode[PATH_MAX];
525
526 // Step 1: Convert ANSI string (LPSTR) to wide string (UTF-16)
527 size_t count = any_to_utf16(CP_ACP, unicode, _countof(unicode), str, -1, NULL);
528 if (!count) return NULL;
529
530 return wchar_to_utf8(unicode);
531 }
532
533 // --------------------------------------------------------------------------------------------------------------------
534
535 // return a sanitized name for the process
536 STRING *GetProcessFriendlyNameFromPathSanitized(WCHAR *path) {
537 static __thread uint8_t void_buf[1024 * 1024];
538 static __thread DWORD void_buf_size = sizeof(void_buf);
539 static __thread wchar_t unicode[PATH_MAX];
540 static __thread DWORD unicode_size = sizeof(unicode) / sizeof(*unicode);
541
542 DWORD handle;
543 DWORD size = GetFileVersionInfoSizeW(path, &handle);
544 if (size == 0 || size > void_buf_size)
545 return FALSE;
546
547 if (GetFileVersionInfoW(path, handle, size, void_buf)) {
548 LPWSTR value = NULL;
549 UINT len = 0;
550 if (VerQueryValueW(void_buf, L"\\StringFileInfo\\040904B0\\FileDescription", (LPVOID*)&value, &len) &&
551 len > 0 && len < unicode_size) {
552 wcsncpy(unicode, value, unicode_size - 1);
553 unicode[unicode_size - 1] = L'\0';
554 char *name = wchar_to_utf8(unicode);
555 sanitize_apps_plugin_chart_meta(name);
556 return string_strdupz(name);
557 }
558 }
559
560 return NULL;
561 }
562
563 #define SERVICE_PREFIX "Service "
564 // return a sanitized name for the process
565 static STRING *GetNameFromCmdlineSanitized(struct pid_stat *p) {
566 if(!p->cmdline) return NULL;
567
568 char *buf = strdupz(string2str(p->cmdline));
569 char *words[100];
570 size_t num_words = quoted_strings_splitter(buf, words, 100, isspace_map_pluginsd);
571
572 if(string_strcmp(p->comm, "svchost") == 0) {
573 // find -s SERVICE in the command line
574 for(size_t i = 0; i < num_words ;i++) {
575 if(strcmp(words[i], "-s") == 0 && i + 1 < num_words) {
576 char *service = mallocz(strlen(words[i + 1]) + sizeof(SERVICE_PREFIX)); // sizeof() includes a null
577 strcpy(service, SERVICE_PREFIX);
578 strcpy(&service[sizeof(SERVICE_PREFIX) - 1], words[i + 1]);
579 sanitize_apps_plugin_chart_meta(service);
580 STRING *sanitized = string_strdupz(service);
581 freez(service);
582 freez(buf);
583 return sanitized;
584 }
585 }
586 }
587
588 freez(buf);
589 return NULL;
590 }
591
592 static void GetServiceNames(void) {
593 SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
594 if (hSCManager == NULL) return;
595
596 DWORD dwBytesNeeded = 0, dwServicesReturned = 0, dwResumeHandle = 0;
597 ENUM_SERVICE_STATUS_PROCESS *pServiceStatus = NULL;
598
599 // First, query the required buffer size
600 EnumServicesStatusEx(
601 hSCManager, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL,
602 NULL, 0, &dwBytesNeeded, &dwServicesReturned, &dwResumeHandle, NULL);
603
604 if (dwBytesNeeded == 0) {
605 CloseServiceHandle(hSCManager);
606 return;
607 }
608
609 // Allocate memory to hold the services
610 pServiceStatus = mallocz(dwBytesNeeded);
611
612 // Now, retrieve the list of services
613 if (!EnumServicesStatusEx(
614 hSCManager, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL,
615 (LPBYTE)pServiceStatus, dwBytesNeeded, &dwBytesNeeded, &dwServicesReturned,
616 &dwResumeHandle, NULL)) {
617 freez(pServiceStatus);
618 CloseServiceHandle(hSCManager);
619 return;
620 }
621
622 // Loop through the services
623 for (DWORD i = 0; i < dwServicesReturned; i++) {
624 if(!pServiceStatus[i].lpDisplayName || !*pServiceStatus[i].lpDisplayName)
625 continue;
626
627 struct pid_stat *p = find_pid_entry((pid_t)pServiceStatus[i].ServiceStatusProcess.dwProcessId);
628 if(p && !p->got_service) {
629 p->got_service = true;
630
631 char *name = ansi_to_utf8(pServiceStatus[i].lpDisplayName);
632 if(name) {
633 sanitize_apps_plugin_chart_meta(name);
634 string_freez(p->name);
635 p->name = string_strdupz(name);
636 #if (PROCESSES_HAVE_SERVICE == 1)
637 string_freez(p->service_name);
638 p->service_name = string_strdupz(name);
639 #endif
640 }
641 }
642 }
643
644 free(pServiceStatus);
645 CloseServiceHandle(hSCManager);
646 }
647
648 static WCHAR *executable_path_from_cmdline(WCHAR *cmdline) {
649 if (!cmdline || !*cmdline) return NULL;
650
651 WCHAR *exe_path_start = cmdline;
652 WCHAR *exe_path_end = NULL;
653
654 if (cmdline[0] == L'"') {
655 // Command line starts with a double quote
656 exe_path_start++; // Move past the first double quote
657 exe_path_end = wcschr(exe_path_start, L'"'); // Find the next quote
658 }
659 else {
660 // Command line does not start with a double quote
661 exe_path_end = wcschr(exe_path_start, L' '); // Find the first space
662 }
663
664 if (exe_path_end) {
665 // Null-terminate the string at the end of the executable path
666 *exe_path_end = L'\0';
667 return exe_path_start;
668 }
669
670 return NULL;
671 }
672
673 static BOOL GetProcessUserSID(HANDLE hProcess, PSID *ppSid) {
674 HANDLE hToken;
675 BOOL result = FALSE;
676 DWORD dwSize = 0;
677 PTOKEN_USER pTokenUser = NULL;
678
679 if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
680 return FALSE;
681
682 GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize);
683 if (dwSize == 0) {
684 CloseHandle(hToken);
685 return FALSE;
686 }
687
688 pTokenUser = (PTOKEN_USER)LocalAlloc(LPTR, dwSize);
689 if (pTokenUser == NULL) {
690 CloseHandle(hToken);
691 return FALSE;
692 }
693
694 if (GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize)) {
695 DWORD sidSize = GetLengthSid(pTokenUser->User.Sid);
696 *ppSid = (PSID)LocalAlloc(LPTR, sidSize);
697 if (*ppSid) {
698 if (CopySid(sidSize, *ppSid, pTokenUser->User.Sid)) {
699 result = TRUE;
700 } else {
701 LocalFree(*ppSid);
702 *ppSid = NULL;
703 }
704 }
705 }
706
707 LocalFree(pTokenUser);
708 CloseHandle(hToken);
709 return result;
710 }
711
712 void GetAllProcessesInfo(void) {
713 static __thread wchar_t unicode[PATH_MAX];
714 static __thread DWORD unicode_size = sizeof(unicode) / sizeof(*unicode);
715
716 calls_counter++;
717
718 HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
719 if (hSnapshot == INVALID_HANDLE_VALUE) return;
720
721 PROCESSENTRY32W pe32;
722 pe32.dwSize = sizeof(PROCESSENTRY32W);
723
724 if (!Process32FirstW(hSnapshot, &pe32)) {
725 CloseHandle(hSnapshot);
726 return;
727 }
728
729 bool need_service_names = false;
730
731 do {
732 if(!pe32.th32ProcessID) continue;
733
734 struct pid_stat *p = get_or_allocate_pid_entry((pid_t)pe32.th32ProcessID);
735 p->ppid = (pid_t)pe32.th32ParentProcessID;
736 if(p->got_info) continue;
737 p->got_info = true;
738
739 HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, p->pid);
740 if (hProcess == NULL)
741 continue;
742
743 // Get the full command line, if possible
744 {
745 WCHAR *cmdline = GetProcessCommandLine(hProcess); // returns malloc'd buffer
746 if (cmdline) {
747 update_pid_cmdline(p, wchar_to_utf8(cmdline));
748
749 // extract the process full path from the command line
750 WCHAR *path = executable_path_from_cmdline(cmdline);
751 if(path) {
752 string_freez(p->name);
753 p->name = GetProcessFriendlyNameFromPathSanitized(path);
754 }
755
756 free(cmdline); // free(), not freez()
757 }
758 }
759
760 if(!p->cmdline || !p->name) {
761 if (QueryFullProcessImageNameW(hProcess, 0, unicode, &unicode_size)) {
762 // put the full path name to the command into cmdline
763 if(!p->cmdline)
764 update_pid_cmdline(p, wchar_to_utf8(unicode));
765
766 if(!p->name)
767 p->name = GetProcessFriendlyNameFromPathSanitized(unicode);
768 }
769 }
770
771 if(!p->sid_name) {
772 PSID pSid = NULL;
773 if (GetProcessUserSID(hProcess, &pSid))
774 p->sid_name = cached_sid_fullname_or_sid_str(pSid);
775 else
776 p->sid_name = string_strdupz("Unknown");
777 }
778
779 CloseHandle(hProcess);
780
781 char *comm = wchar_to_utf8(pe32.szExeFile);
782 fix_windows_comm(p, comm);
783 update_pid_comm(p, comm); // will sanitize p->comm
784
785 if(!need_service_names && string_strcmp(p->comm, "svchost") == 0)
786 need_service_names = true;
787
788 STRING *better_name = GetNameFromCmdlineSanitized(p);
789 if(better_name) {
790 string_freez(p->name);
791 p->name = better_name;
792 }
793
794 } while (Process32NextW(hSnapshot, &pe32));
795
796 CloseHandle(hSnapshot);
797
798 if(need_service_names)
799 GetServiceNames();
800 }
801
802 static inline kernel_uint_t perflib_cpu_utilization(COUNTER_DATA *d) {
803 internal_fatal(d->current.CounterType != PERF_100NSEC_TIMER,
804 "Wrong timer type");
805
806 ULONGLONG data1 = d->current.Data;
807 ULONGLONG data0 = d->previous.Data;
808 LONGLONG time1 = d->current.Time;
809 LONGLONG time0 = d->previous.Time;
810
811 /*
812 * The Windows documentation provides the formula for percentage:
813 *
814 * 100 * (data1 - data0) / (time1 - time0)
815 *
816 * To get a fraction (0.0 to 1.0) instead of a percentage, we
817 * simply remove the 100 multiplier:
818 *
819 * (data1 - data0) / (time1 - time0)
820 *
821 * This fraction represents the portion of a single CPU core used
822 * over the time period. Multiplying this fraction by NSEC_PER_SEC
823 * converts it to nanosecond-cores:
824 *
825 * NSEC_PER_SEC * (data1 - data0) / (time1 - time0)
826 */
827
828 LONGLONG dt = time1 - time0;
829 if(dt > 0 && data1 >= data0)
830 return NSEC_PER_SEC * (data1 - data0) / dt;
831 else
832 return 0;
833 }
834
835 static inline kernel_uint_t perflib_rate(COUNTER_DATA *d) {
836 ULONGLONG data1 = d->current.Data;
837 ULONGLONG data0 = d->previous.Data;
838 LONGLONG time1 = d->current.Time;
839 LONGLONG time0 = d->previous.Time;
840
841 LONGLONG dt = (time1 - time0);
842 if(dt > 0 && data1 >= data0)
843 return (RATES_DETAIL * (data1 - data0)) / dt;
844 else
845 return 0;
846 }
847
848 static inline kernel_uint_t perflib_value(COUNTER_DATA *d) {
849 internal_fatal(d->current.CounterType != PERF_COUNTER_LARGE_RAWCOUNT &&
850 d->current.CounterType != PERF_COUNTER_RAWCOUNT,
851 "Wrong gauge type");
852
853 return d->current.Data;
854 }
855
856 static inline kernel_uint_t perflib_elapsed(COUNTER_DATA *d) {
857 ULONGLONG data1 = d->current.Data;
858 LONGLONG time1 = d->current.Time;
859 LONGLONG freq1 = d->current.Frequency;
860
861 internal_fatal(d->current.CounterType != PERF_ELAPSED_TIME || !freq1,
862 "Wrong gauge type");
863
864 if(!data1 || !time1 || !freq1 || data1 > (ULONGLONG)time1)
865 return 0;
866
867 return (time1 - data1) / freq1;
868 }
869
870 bool apps_os_collect_all_pids_windows(void) {
871 calls_counter++;
872
873 struct perflib_data d = { 0 };
874 d.pDataBlock = perflibGetPerformanceData(RegistryFindIDByName("Process"));
875 if(!d.pDataBlock) return false;
876
877 d.pObjectType = perflibFindObjectTypeByName(d.pDataBlock, "Process");
878 if(!d.pObjectType) {
879 perflibFreePerformanceData();
880 return false;
881 }
882
883 // we need these outside the loop to avoid searching by name all the time
884 // (our perflib library caches the id inside the COUNTER_DATA).
885 COUNTER_DATA processId = {.key = "ID Process"};
886
887 d.pi = NULL;
888 size_t added = 0;
889 for(LONG i = 0; i < d.pObjectType->NumInstances; i++) {
890 d.pi = perflibForEachInstance(d.pDataBlock, d.pObjectType, d.pi);
891 if (!d.pi) break;
892
893 perflibGetInstanceCounter(d.pDataBlock, d.pObjectType, d.pi, &processId);
894 d.pid = (DWORD) processId.current.Data;
895 if (d.pid <= 0) continue; // 0 = Idle (this takes all the spare resources)
896
897 // Get or create pid_stat structure
898 struct pid_stat *p = get_or_allocate_pid_entry((pid_t) d.pid);
899
900 if (unlikely(!p->initialized)) {
901 // a new pid
902 p->initialized = true;
903
904 static __thread char comm[MAX_PATH];
905
906 if (getInstanceName(d.pDataBlock, d.pObjectType, d.pi, comm, sizeof(comm)))
907 fix_windows_comm(p, comm);
908 else
909 strncpyz(comm, "unknown", sizeof(comm) - 1);
910
911 if(strcmp(comm, "wininit") == 0)
912 INIT_PID = p->pid;
913
914 update_pid_comm(p, comm); // will sanitize p->comm
915 added++;
916
917 COUNTER_DATA ppid = {.key = "Creating Process ID"};
918 perflibGetInstanceCounter(d.pDataBlock, d.pObjectType, d.pi, &ppid);
919 p->ppid = (pid_t) ppid.current.Data;
920
921 p->perflib[PDF_UTIME].key = "% User Time";
922 p->perflib[PDF_STIME].key = "% Privileged Time";
923 p->perflib[PDF_VMSIZE].key = "Virtual Bytes";
924 p->perflib[PDF_VMRSS].key = "Working Set - Private";
925 p->perflib[PDF_VMSWAP].key = "Page File Bytes";
926 p->perflib[PDF_LREAD].key = "IO Read Bytes/sec";
927 p->perflib[PDF_LWRITE].key = "IO Write Bytes/sec";
928 p->perflib[PDF_OREAD].key = "IO Read Operations/sec";
929 p->perflib[PDF_OWRITE].key = "IO Write Operations/sec";
930 p->perflib[PDF_THREADS].key = "Thread Count";
931 p->perflib[PDF_HANDLES].key = "Handle Count";
932 p->perflib[PDF_MINFLT].key = "Page Faults/sec";
933 p->perflib[PDF_UPTIME].key = "Elapsed Time";
934 }
935
936 pid_collection_started(p);
937
938 // get all data from perflib
939 size_t ok = 0, failed = 0, invalid = 0;
940 for (PID_FIELD f = 0; f < PDF_MAX; f++) {
941 if (p->perflib[f].key) {
942 if (!perflibGetInstanceCounter(d.pDataBlock, d.pObjectType, d.pi, &p->perflib[f])) {
943 failed++;
944 nd_log(NDLS_COLLECTORS, NDLP_ERR,
945 "Cannot find field '%s' in processes data", p->perflib[f].key);
946 } else
947 ok++;
948 } else
949 invalid++;
950 }
951
952 if(failed) {
953 pid_collection_failed(p);
954 continue;
955 }
956
957 // Detect PID reuse: if the process creation time changed, the PID was recycled
958 // by a different process. Without this check, the unsigned subtraction in
959 // perflib_cpu_utilization() and perflib_rate() would underflow, producing
960 // massive bogus values (e.g., 184725% CPU).
961 if(p->perflib[PDF_UPTIME].previous.Data != 0 &&
962 p->perflib[PDF_UPTIME].current.Data != p->perflib[PDF_UPTIME].previous.Data) {
963
964 nd_log(NDLS_COLLECTORS, NDLP_WARNING,
965 "APPS: PID %d (%s) creation time changed "
966 "(0x%" PRIx64 " -> 0x%" PRIx64 "), "
967 "PID reuse detected, resetting counters",
968 (int)p->pid, pid_stat_comm(p),
969 (uint64_t)p->perflib[PDF_UPTIME].previous.Data,
970 (uint64_t)p->perflib[PDF_UPTIME].current.Data);
971
972 // Reset all counter history to prevent unsigned underflow in rate calculations
973 for(PID_FIELD f = 0; f < PDF_MAX; f++) {
974 if(p->perflib[f].key)
975 p->perflib[f].previous = RAW_DATA_EMPTY;
976 }
977
978 // Reset process identity so GetAllProcessesInfo() re-reads everything
979 p->got_info = false;
980 p->got_service = false;
981
982 string_freez(p->sid_name);
983 p->sid_name = NULL;
984
985 string_freez(p->service_name);
986 p->service_name = NULL;
987
988 string_freez(p->name);
989 p->name = NULL;
990
991 string_freez(p->cmdline);
992 p->cmdline = NULL;
993
994 // Re-read comm name from the perflib instance
995 {
996 char reuse_comm[MAX_PATH];
997 if(getInstanceName(d.pDataBlock, d.pObjectType, d.pi, reuse_comm, sizeof(reuse_comm)))
998 fix_windows_comm(p, reuse_comm);
999 else
1000 strncpyz(reuse_comm, "unknown", sizeof(reuse_comm) - 1);
1001
1002 update_pid_comm(p, reuse_comm);
1003 }
1004
1005 // Update parent PID
1006 {
1007 COUNTER_DATA ppid = {.key = "Creating Process ID"};
1008 perflibGetInstanceCounter(d.pDataBlock, d.pObjectType, d.pi, &ppid);
1009 p->ppid = (pid_t)ppid.current.Data;
1010 }
1011
1012 // Reset target assignment — the new process may belong to a different group
1013 p->target = NULL;
1014 p->matched_by_config = false;
1015
1016 // Trigger GetAllProcessesInfo() to re-read cmdline, name, SID, service
1017 added++;
1018 }
1019
1020 // CPU time
1021 p->values[PDF_UTIME] = perflib_cpu_utilization(&p->perflib[PDF_UTIME]);
1022 p->values[PDF_STIME] = perflib_cpu_utilization(&p->perflib[PDF_STIME]);
1023
1024 // Memory
1025 p->values[PDF_VMRSS] = perflib_value(&p->perflib[PDF_VMRSS]);
1026 p->values[PDF_VMSIZE] = perflib_value(&p->perflib[PDF_VMSIZE]);
1027 p->values[PDF_VMSWAP] = perflib_value(&p->perflib[PDF_VMSWAP]);
1028
1029 // I/O
1030 p->values[PDF_LREAD] = perflib_rate(&p->perflib[PDF_LREAD]);
1031 p->values[PDF_LWRITE] = perflib_rate(&p->perflib[PDF_LWRITE]);
1032 p->values[PDF_OREAD] = perflib_rate(&p->perflib[PDF_OREAD]);
1033 p->values[PDF_OWRITE] = perflib_rate(&p->perflib[PDF_OWRITE]);
1034
1035 // Threads
1036 p->values[PDF_THREADS] = perflib_value(&p->perflib[PDF_THREADS]);
1037
1038 // Handle count
1039 p->values[PDF_HANDLES] = perflib_value(&p->perflib[PDF_HANDLES]);
1040
1041 // Page faults
1042 // Windows doesn't distinguish between minor and major page faults
1043 p->values[PDF_MINFLT] = perflib_rate(&p->perflib[PDF_MINFLT]);
1044
1045 // Process uptime
1046 // Convert 100-nanosecond units to seconds
1047 p->values[PDF_UPTIME] = perflib_elapsed(&p->perflib[PDF_UPTIME]);
1048
1049 pid_collection_completed(p);
1050
1051 // if(p->perflib[PDF_UTIME].current.Data != p->perflib[PDF_UTIME].previous.Data &&
1052 // p->perflib[PDF_UTIME].current.Data && p->perflib[PDF_UTIME].previous.Data &&
1053 // p->pid == 61812) {
1054 // const char *cmd = string2str(p->comm);
1055 // uint64_t cpu_divisor = NSEC_PER_SEC / 100ULL;
1056 // uint64_t cpus = os_get_system_cpus();
1057 // double u = (double)p->values[PDF_UTIME] / cpu_divisor;
1058 // double s = (double)p->values[PDF_STIME] / cpu_divisor;
1059 // int x = 0;
1060 // x++;
1061 // }
1062 }
1063
1064 perflibFreePerformanceData();
1065
1066 if(added) {
1067 GetAllProcessesInfo();
1068
1069 #if (USE_APPS_GROUPS_CONF == 1)
1070 for(struct pid_stat *p = root_of_pids(); p ;p = p->next) {
1071 if(!p->assigned_to_target)
1072 assign_app_group_target_to_pid(p);
1073 }
1074 #endif
1075 }
1076
1077 return true;
1078 }
1079
1080 #endif