| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "windows_plugin.h" |
| 4 | #include "windows-internals.h" |
| 5 | |
| 6 | #define _COMMON_PLUGIN_NAME PLUGIN_WINDOWS_NAME |
| 7 | #define _COMMON_PLUGIN_MODULE_NAME "PerflibStorage" |
| 8 | #include "../common-contexts/common-contexts.h" |
| 9 | #include "libnetdata/os/windows-wmi/windows-wmi.h" |
| 10 | |
| 11 | struct logical_disk { |
| 12 | usec_t last_collected; |
| 13 | bool collected_metadata; |
| 14 | |
| 15 | UINT DriveType; |
| 16 | DWORD SerialNumber; |
| 17 | ULONG divisor; |
| 18 | bool readonly; |
| 19 | |
| 20 | STRING *filesystem; |
| 21 | |
| 22 | RRDSET *st_disk_space; |
| 23 | RRDDIM *rd_disk_space_used; |
| 24 | RRDDIM *rd_disk_space_free; |
| 25 | |
| 26 | COUNTER_DATA percentDiskFree; |
| 27 | // COUNTER_DATA freeMegabytes; |
| 28 | }; |
| 29 | |
| 30 | struct physical_disk { |
| 31 | usec_t last_collected; |
| 32 | bool collected_metadata; |
| 33 | |
| 34 | STRING *device; |
| 35 | STRING *mount_point; |
| 36 | STRING *manufacturer; |
| 37 | STRING *model; |
| 38 | STRING *media_type; |
| 39 | STRING *name; |
| 40 | STRING *device_id; |
| 41 | |
| 42 | ND_DISK_IO disk_io; |
| 43 | // COUNTER_DATA diskBytesPerSec; |
| 44 | COUNTER_DATA diskReadBytesPerSec; |
| 45 | COUNTER_DATA diskWriteBytesPerSec; |
| 46 | |
| 47 | ND_DISK_OPS disk_ops; |
| 48 | // COUNTER_DATA diskTransfersPerSec; |
| 49 | COUNTER_DATA diskReadsPerSec; |
| 50 | COUNTER_DATA diskWritesPerSec; |
| 51 | |
| 52 | ND_DISK_UTIL disk_util; |
| 53 | COUNTER_DATA percentIdleTime; |
| 54 | |
| 55 | ND_DISK_BUSY disk_busy; |
| 56 | COUNTER_DATA percentDiskTime; |
| 57 | |
| 58 | ND_DISK_IOTIME disk_iotime; |
| 59 | COUNTER_DATA percentDiskReadTime; |
| 60 | COUNTER_DATA percentDiskWriteTime; |
| 61 | |
| 62 | ND_DISK_QOPS disk_qops; |
| 63 | COUNTER_DATA currentDiskQueueLength; |
| 64 | // COUNTER_DATA averageDiskQueueLength; |
| 65 | // COUNTER_DATA averageDiskReadQueueLength; |
| 66 | // COUNTER_DATA averageDiskWriteQueueLength; |
| 67 | |
| 68 | ND_DISK_AWAIT disk_await; |
| 69 | COUNTER_DATA averageDiskSecondsPerRead; |
| 70 | COUNTER_DATA averageDiskSecondsPerWrite; |
| 71 | |
| 72 | ND_DISK_SVCTM disk_svctm; |
| 73 | COUNTER_DATA averageDiskSecondsPerTransfer; |
| 74 | |
| 75 | ND_DISK_AVGSZ disk_avgsz; |
| 76 | //COUNTER_DATA averageDiskBytesPerTransfer; |
| 77 | COUNTER_DATA averageDiskBytesPerRead; |
| 78 | COUNTER_DATA averageDiskBytesPerWrite; |
| 79 | |
| 80 | COUNTER_DATA splitIoPerSec; |
| 81 | RRDSET *st_split; |
| 82 | RRDDIM *rd_split; |
| 83 | }; |
| 84 | |
| 85 | struct physical_disk system_physical_total = { |
| 86 | .collected_metadata = true, |
| 87 | }; |
| 88 | |
| 89 | static void |
| 90 | dict_logical_disk_insert_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) |
| 91 | { |
| 92 | struct logical_disk *d = value; |
| 93 | |
| 94 | d->percentDiskFree.key = "% Free Space"; |
| 95 | // d->freeMegabytes.key = "Free Megabytes"; |
| 96 | } |
| 97 | |
| 98 | static void logical_disk_cleanup(struct logical_disk *d) |
| 99 | { |
| 100 | string_freez(d->filesystem); |
| 101 | d->filesystem = NULL; |
| 102 | |
| 103 | d->collected_metadata = false; |
| 104 | |
| 105 | rrdset_is_obsolete___safe_from_collector_thread(d->st_disk_space); |
| 106 | d->st_disk_space = NULL; |
| 107 | } |
| 108 | |
| 109 | static void physical_disk_initialize(struct physical_disk *d) |
| 110 | { |
| 111 | d->percentIdleTime.key = "% Idle Time"; |
| 112 | d->percentDiskTime.key = "% Disk Time"; |
| 113 | d->percentDiskReadTime.key = "% Disk Read Time"; |
| 114 | d->percentDiskWriteTime.key = "% Disk Write Time"; |
| 115 | d->currentDiskQueueLength.key = "Current Disk Queue Length"; |
| 116 | // d->averageDiskQueueLength.key = "Avg. Disk Queue Length"; |
| 117 | // d->averageDiskReadQueueLength.key = "Avg. Disk Read Queue Length"; |
| 118 | // d->averageDiskWriteQueueLength.key = "Avg. Disk Write Queue Length"; |
| 119 | d->averageDiskSecondsPerTransfer.key = "Avg. Disk sec/Transfer"; |
| 120 | d->averageDiskSecondsPerRead.key = "Avg. Disk sec/Read"; |
| 121 | d->averageDiskSecondsPerWrite.key = "Avg. Disk sec/Write"; |
| 122 | // d->diskTransfersPerSec.key = "Disk Transfers/sec"; |
| 123 | d->diskReadsPerSec.key = "Disk Reads/sec"; |
| 124 | d->diskWritesPerSec.key = "Disk Writes/sec"; |
| 125 | // d->diskBytesPerSec.key = "Disk Bytes/sec"; |
| 126 | d->diskReadBytesPerSec.key = "Disk Read Bytes/sec"; |
| 127 | d->diskWriteBytesPerSec.key = "Disk Write Bytes/sec"; |
| 128 | // d->averageDiskBytesPerTransfer.key = "Avg. Disk Bytes/Transfer"; |
| 129 | d->averageDiskBytesPerRead.key = "Avg. Disk Bytes/Read"; |
| 130 | d->averageDiskBytesPerWrite.key = "Avg. Disk Bytes/Write"; |
| 131 | d->splitIoPerSec.key = "Split IO/Sec"; |
| 132 | } |
| 133 | |
| 134 | static void physical_disk_cleanup(struct physical_disk *d) |
| 135 | { |
| 136 | string_freez(d->device); |
| 137 | d->device = NULL; |
| 138 | string_freez(d->mount_point); |
| 139 | d->mount_point = NULL; |
| 140 | string_freez(d->manufacturer); |
| 141 | d->manufacturer = NULL; |
| 142 | string_freez(d->model); |
| 143 | d->model = NULL; |
| 144 | string_freez(d->media_type); |
| 145 | d->media_type = NULL; |
| 146 | string_freez(d->name); |
| 147 | d->name = NULL; |
| 148 | string_freez(d->device_id); |
| 149 | d->device_id = NULL; |
| 150 | |
| 151 | d->collected_metadata = false; |
| 152 | |
| 153 | rrdset_is_obsolete___safe_from_collector_thread(d->disk_io.st_io); |
| 154 | rrdset_is_obsolete___safe_from_collector_thread(d->disk_ops.st_ops); |
| 155 | rrdset_is_obsolete___safe_from_collector_thread(d->disk_util.st_util); |
| 156 | rrdset_is_obsolete___safe_from_collector_thread(d->disk_busy.st_busy); |
| 157 | rrdset_is_obsolete___safe_from_collector_thread(d->disk_iotime.st_iotime); |
| 158 | rrdset_is_obsolete___safe_from_collector_thread(d->disk_qops.st_qops); |
| 159 | rrdset_is_obsolete___safe_from_collector_thread(d->disk_await.st_await); |
| 160 | rrdset_is_obsolete___safe_from_collector_thread(d->disk_svctm.st_svctm); |
| 161 | rrdset_is_obsolete___safe_from_collector_thread(d->disk_avgsz.st_avgsz); |
| 162 | rrdset_is_obsolete___safe_from_collector_thread(d->st_split); |
| 163 | } |
| 164 | |
| 165 | void dict_physical_disk_insert_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) |
| 166 | { |
| 167 | struct physical_disk *pd = value; |
| 168 | physical_disk_initialize(pd); |
| 169 | } |
| 170 | |
| 171 | static void dict_logical_disk_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) |
| 172 | { |
| 173 | struct logical_disk *d = value; |
| 174 | logical_disk_cleanup(d); |
| 175 | } |
| 176 | |
| 177 | static void dict_physical_disk_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) |
| 178 | { |
| 179 | struct physical_disk *d = value; |
| 180 | physical_disk_cleanup(d); |
| 181 | } |
| 182 | |
| 183 | static DICTIONARY *logicalDisks = NULL, *physicalDisks = NULL; |
| 184 | static void initialize(void) |
| 185 | { |
| 186 | physical_disk_initialize(&system_physical_total); |
| 187 | |
| 188 | logicalDisks = dictionary_create_advanced( |
| 189 | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(struct logical_disk)); |
| 190 | |
| 191 | dictionary_register_insert_callback(logicalDisks, dict_logical_disk_insert_cb, NULL); |
| 192 | dictionary_register_delete_callback(logicalDisks, dict_logical_disk_delete_cb, NULL); |
| 193 | |
| 194 | physicalDisks = dictionary_create_advanced( |
| 195 | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(struct physical_disk)); |
| 196 | |
| 197 | dictionary_register_insert_callback(physicalDisks, dict_physical_disk_insert_cb, NULL); |
| 198 | dictionary_register_delete_callback(physicalDisks, dict_physical_disk_delete_cb, NULL); |
| 199 | } |
| 200 | |
| 201 | static STRING *getFileSystemType(struct logical_disk *d, const char *diskName) |
| 202 | { |
| 203 | if (!diskName || !*diskName) |
| 204 | return NULL; |
| 205 | |
| 206 | char fileSystemNameBuffer[128] = {0}; // Buffer for file system name |
| 207 | char pathBuffer[260] = {0}; // Path buffer to accommodate different formats |
| 208 | char volumeName[260 + 1] = {0}; |
| 209 | DWORD serialNumber = 0; |
| 210 | DWORD maxComponentLength = 0; |
| 211 | DWORD fileSystemFlags = 0; |
| 212 | BOOL success; |
| 213 | |
| 214 | // Check if the input is likely a drive letter (e.g., "C:") |
| 215 | if (isalpha((uint8_t)diskName[0]) && diskName[1] == ':' && diskName[2] == '\0') |
| 216 | snprintfz(pathBuffer, sizeof(pathBuffer) - 1, "%s\\", diskName); // Format as "C:\" |
| 217 | else |
| 218 | // Assume it's a Volume GUID path or a device path |
| 219 | snprintfz(pathBuffer, sizeof(pathBuffer) - 1, "\\\\.\\%s\\", diskName); // Format as "\\.\HarddiskVolume1\" |
| 220 | |
| 221 | d->DriveType = GetDriveTypeA(pathBuffer); |
| 222 | |
| 223 | // Attempt to get the volume information |
| 224 | success = GetVolumeInformationA( |
| 225 | pathBuffer, // Path to the disk |
| 226 | volumeName, // Volume name buffer |
| 227 | 260, // Size of volume name buffer |
| 228 | &serialNumber, // Volume serial number |
| 229 | &maxComponentLength, // Maximum component length |
| 230 | &fileSystemFlags, // File system flags |
| 231 | fileSystemNameBuffer, // File system name buffer |
| 232 | sizeof(fileSystemNameBuffer) // Size of file system name buffer |
| 233 | ); |
| 234 | |
| 235 | if (success) { |
| 236 | d->readonly = fileSystemFlags & FILE_READ_ONLY_VOLUME; |
| 237 | d->SerialNumber = serialNumber; |
| 238 | |
| 239 | if (fileSystemNameBuffer[0]) { |
| 240 | char *s = fileSystemNameBuffer; |
| 241 | while (*s) { |
| 242 | *s = tolower((uint8_t)*s); |
| 243 | s++; |
| 244 | } |
| 245 | return string_strdupz(fileSystemNameBuffer); // Duplicate the file system name |
| 246 | } |
| 247 | } |
| 248 | return NULL; |
| 249 | } |
| 250 | |
| 251 | static const char *drive_type_to_str(UINT type) |
| 252 | { |
| 253 | switch (type) { |
| 254 | default: |
| 255 | case 0: |
| 256 | return "unknown"; |
| 257 | case 1: |
| 258 | return "norootdir"; |
| 259 | case 2: |
| 260 | return "removable"; |
| 261 | case 3: |
| 262 | return "fixed"; |
| 263 | case 4: |
| 264 | return "remote"; |
| 265 | case 5: |
| 266 | return "cdrom"; |
| 267 | case 6: |
| 268 | return "ramdisk"; |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | static inline void netdata_set_hd_usage(PERF_DATA_BLOCK *pDataBlock, |
| 273 | PERF_OBJECT_TYPE *pObjectType, |
| 274 | PERF_INSTANCE_DEFINITION *pi, |
| 275 | struct logical_disk *d) |
| 276 | { |
| 277 | ULARGE_INTEGER totalNumberOfBytes; |
| 278 | ULARGE_INTEGER totalNumberOfFreeBytes; |
| 279 | ULARGE_INTEGER totalAvailableToCaller; |
| 280 | |
| 281 | // https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry |
| 282 | #define MAX_DRIVE_LENGTH 255 |
| 283 | char path[MAX_DRIVE_LENGTH + 1]; |
| 284 | snprintfz(path, MAX_DRIVE_LENGTH, "%s\\", windows_shared_buffer); |
| 285 | |
| 286 | // Description of incompatibilities present in both methods we are using |
| 287 | // https://devblogs.microsoft.com/oldnewthing/20071101-00/?p=24613 |
| 288 | // We are using the variable that should not be affected by qyota () |
| 289 | if ((GetDriveTypeA(path) == DRIVE_UNKNOWN) || !GetDiskFreeSpaceExA(path, |
| 290 | &totalAvailableToCaller, |
| 291 | &totalNumberOfBytes, |
| 292 | &totalNumberOfFreeBytes)) { |
| 293 | perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->percentDiskFree); |
| 294 | d->divisor = 1024; |
| 295 | return; |
| 296 | } |
| 297 | |
| 298 | d->divisor = GIGA_FACTOR; |
| 299 | d->percentDiskFree.current.Data = totalNumberOfFreeBytes.QuadPart; |
| 300 | d->percentDiskFree.current.Time = totalNumberOfBytes.QuadPart; |
| 301 | } |
| 302 | |
| 303 | static bool do_logical_disk(PERF_DATA_BLOCK *pDataBlock, int update_every, usec_t now_ut) |
| 304 | { |
| 305 | DICTIONARY *dict = logicalDisks; |
| 306 | |
| 307 | PERF_OBJECT_TYPE *pObjectType = perflibFindObjectTypeByName(pDataBlock, "LogicalDisk"); |
| 308 | if (!pObjectType) |
| 309 | return false; |
| 310 | |
| 311 | PERF_INSTANCE_DEFINITION *pi = NULL; |
| 312 | for (LONG i = 0; i < pObjectType->NumInstances; i++) { |
| 313 | pi = perflibForEachInstance(pDataBlock, pObjectType, pi); |
| 314 | if (!pi) |
| 315 | break; |
| 316 | |
| 317 | if (!getInstanceName(pDataBlock, pObjectType, pi, windows_shared_buffer, sizeof(windows_shared_buffer))) |
| 318 | strncpyz(windows_shared_buffer, "[unknown]", sizeof(windows_shared_buffer) - 1); |
| 319 | |
| 320 | if (strcasecmp(windows_shared_buffer, "_Total") == 0) |
| 321 | continue; |
| 322 | |
| 323 | struct logical_disk *d = dictionary_set(dict, windows_shared_buffer, NULL, sizeof(*d)); |
| 324 | d->last_collected = now_ut; |
| 325 | |
| 326 | if (!d->collected_metadata) { |
| 327 | d->filesystem = getFileSystemType(d, windows_shared_buffer); |
| 328 | d->collected_metadata = true; |
| 329 | } |
| 330 | |
| 331 | netdata_set_hd_usage(pDataBlock, pObjectType, pi, d); |
| 332 | // perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->freeMegabytes); |
| 333 | |
| 334 | if (!d->st_disk_space) { |
| 335 | d->st_disk_space = rrdset_create_localhost( |
| 336 | "disk_space", |
| 337 | windows_shared_buffer, |
| 338 | NULL, |
| 339 | windows_shared_buffer, |
| 340 | "disk.space", |
| 341 | "Disk Space Usage", |
| 342 | "GiB", |
| 343 | PLUGIN_WINDOWS_NAME, |
| 344 | "PerflibStorage", |
| 345 | NETDATA_CHART_PRIO_DISKSPACE_SPACE, |
| 346 | update_every, |
| 347 | RRDSET_TYPE_STACKED); |
| 348 | |
| 349 | rrdlabels_add(d->st_disk_space->rrdlabels, "mount_point", windows_shared_buffer, RRDLABEL_SRC_AUTO); |
| 350 | rrdlabels_add( |
| 351 | d->st_disk_space->rrdlabels, "drive_type", drive_type_to_str(d->DriveType), RRDLABEL_SRC_AUTO); |
| 352 | rrdlabels_add( |
| 353 | d->st_disk_space->rrdlabels, |
| 354 | "filesystem", |
| 355 | d->filesystem ? string2str(d->filesystem) : "unknown", |
| 356 | RRDLABEL_SRC_AUTO); |
| 357 | rrdlabels_add(d->st_disk_space->rrdlabels, "rw_mode", d->readonly ? "ro" : "rw", RRDLABEL_SRC_AUTO); |
| 358 | |
| 359 | { |
| 360 | char buf[UINT64_HEX_MAX_LENGTH]; |
| 361 | print_uint64_hex(buf, d->SerialNumber); |
| 362 | rrdlabels_add(d->st_disk_space->rrdlabels, "serial_number", buf, RRDLABEL_SRC_AUTO); |
| 363 | } |
| 364 | |
| 365 | d->rd_disk_space_free = rrddim_add(d->st_disk_space, "avail", NULL, 1, d->divisor, RRD_ALGORITHM_ABSOLUTE); |
| 366 | d->rd_disk_space_used = rrddim_add(d->st_disk_space, "used", NULL, 1, d->divisor, RRD_ALGORITHM_ABSOLUTE); |
| 367 | } |
| 368 | |
| 369 | // percentDiskFree has the free space in Data and the size of the disk in Time, in MiB. |
| 370 | rrddim_set_by_pointer( |
| 371 | d->st_disk_space, d->rd_disk_space_free, (collected_number)d->percentDiskFree.current.Data); |
| 372 | rrddim_set_by_pointer( |
| 373 | d->st_disk_space, |
| 374 | d->rd_disk_space_used, |
| 375 | (collected_number)(d->percentDiskFree.current.Time - d->percentDiskFree.current.Data)); |
| 376 | rrdset_done(d->st_disk_space); |
| 377 | } |
| 378 | |
| 379 | // cleanup - delete callback will handle resource cleanup |
| 380 | { |
| 381 | struct logical_disk *d; |
| 382 | dfe_start_write(dict, d) |
| 383 | { |
| 384 | if (d->last_collected < now_ut) |
| 385 | dictionary_del(dict, d_dfe.name); |
| 386 | } |
| 387 | dfe_done(d); |
| 388 | dictionary_garbage_collect(dict); |
| 389 | } |
| 390 | |
| 391 | return true; |
| 392 | } |
| 393 | |
| 394 | static void physical_disk_labels(RRDSET *st, void *data) |
| 395 | { |
| 396 | struct physical_disk *d = data; |
| 397 | |
| 398 | if (d->device) |
| 399 | rrdlabels_add(st->rrdlabels, "device", string2str(d->device), RRDLABEL_SRC_AUTO); |
| 400 | |
| 401 | if (d->mount_point) |
| 402 | rrdlabels_add(st->rrdlabels, "mount_point", string2str(d->mount_point), RRDLABEL_SRC_AUTO); |
| 403 | |
| 404 | // if (d->manufacturer) |
| 405 | // rrdlabels_add(st->rrdlabels, "manufacturer", string2str(d->manufacturer), RRDLABEL_SRC_AUTO); |
| 406 | |
| 407 | if (d->model) |
| 408 | rrdlabels_add(st->rrdlabels, "model", string2str(d->model), RRDLABEL_SRC_AUTO); |
| 409 | |
| 410 | // if (d->media_type) |
| 411 | // rrdlabels_add(st->rrdlabels, "media_type", string2str(d->media_type), RRDLABEL_SRC_AUTO); |
| 412 | |
| 413 | // if (d->name) |
| 414 | // rrdlabels_add(st->rrdlabels, "name", string2str(d->name), RRDLABEL_SRC_AUTO); |
| 415 | |
| 416 | if (d->device_id) |
| 417 | rrdlabels_add(st->rrdlabels, "device_id", string2str(d->device_id), RRDLABEL_SRC_AUTO); |
| 418 | } |
| 419 | |
| 420 | static bool str_is_numeric(const char *s) |
| 421 | { |
| 422 | while (*s) |
| 423 | if (!isdigit((uint8_t)*s++)) |
| 424 | return false; |
| 425 | return true; |
| 426 | } |
| 427 | |
| 428 | static inline double perflib_average_timer_ms(COUNTER_DATA *d) |
| 429 | { |
| 430 | if (!d->updated) |
| 431 | return 0.0; |
| 432 | |
| 433 | ULONGLONG data1 = d->current.Data; |
| 434 | ULONGLONG data0 = d->previous.Data; |
| 435 | LONGLONG time1 = d->current.Time; |
| 436 | LONGLONG time0 = d->previous.Time; |
| 437 | LONGLONG freq1 = d->current.Frequency; |
| 438 | |
| 439 | if (data1 >= data0 && time1 > time0 && time0 && freq1) |
| 440 | return ((double)(data1 - data0) * (double)MSEC_PER_SEC) / ((double)freq1 * (double)(time1 - time0)); |
| 441 | |
| 442 | return 0; |
| 443 | } |
| 444 | |
| 445 | static inline uint64_t perflib_average_bulk(COUNTER_DATA *d) |
| 446 | { |
| 447 | if (!d->updated) |
| 448 | return 0; |
| 449 | |
| 450 | ULONGLONG data1 = d->current.Data; |
| 451 | ULONGLONG data0 = d->previous.Data; |
| 452 | LONGLONG time1 = d->current.Time; |
| 453 | LONGLONG time0 = d->previous.Time; |
| 454 | |
| 455 | if (data1 >= data0 && time1 > time0 && time0) |
| 456 | return (data1 - data0) / (time1 - time0); |
| 457 | |
| 458 | return 0; |
| 459 | } |
| 460 | |
| 461 | static inline uint64_t perflib_idle_time_percent(COUNTER_DATA *d) |
| 462 | { |
| 463 | if (!d->updated) |
| 464 | return 0.0; |
| 465 | |
| 466 | ULONGLONG data1 = d->current.Data; |
| 467 | ULONGLONG data0 = d->previous.Data; |
| 468 | LONGLONG time1 = d->current.Time; |
| 469 | LONGLONG time0 = d->previous.Time; |
| 470 | |
| 471 | if (data1 >= data0 && time1 > time0 && time0) { |
| 472 | uint64_t pcent = 100 * (data1 - data0) / (time1 - time0); |
| 473 | return pcent > 100 ? 100 : pcent; |
| 474 | } |
| 475 | |
| 476 | return 0; |
| 477 | } |
| 478 | |
| 479 | #define MAX_WMI_DRIVES 100 |
| 480 | static DiskDriveInfoWMI infos[MAX_WMI_DRIVES]; |
| 481 | |
| 482 | static bool do_physical_disk(PERF_DATA_BLOCK *pDataBlock, int update_every, usec_t now_ut) |
| 483 | { |
| 484 | DICTIONARY *dict = physicalDisks; |
| 485 | size_t infoCount = 0; |
| 486 | bool infos_loaded = false; |
| 487 | |
| 488 | PERF_OBJECT_TYPE *pObjectType = perflibFindObjectTypeByName(pDataBlock, "PhysicalDisk"); |
| 489 | if (!pObjectType) |
| 490 | return false; |
| 491 | |
| 492 | PERF_INSTANCE_DEFINITION *pi = NULL; |
| 493 | for (LONG i = 0; i < pObjectType->NumInstances; i++) { |
| 494 | pi = perflibForEachInstance(pDataBlock, pObjectType, pi); |
| 495 | if (!pi) |
| 496 | break; |
| 497 | |
| 498 | if (!getInstanceName(pDataBlock, pObjectType, pi, windows_shared_buffer, sizeof(windows_shared_buffer))) |
| 499 | strncpyz(windows_shared_buffer, "[unknown]", sizeof(windows_shared_buffer) - 1); |
| 500 | |
| 501 | int device_index = -1; |
| 502 | char *device = windows_shared_buffer; |
| 503 | char mount_point[128]; |
| 504 | mount_point[0] = '\0'; |
| 505 | |
| 506 | struct physical_disk *d; |
| 507 | bool is_system; |
| 508 | if (strcasecmp(windows_shared_buffer, "_Total") == 0) { |
| 509 | d = &system_physical_total; |
| 510 | is_system = true; |
| 511 | } else { |
| 512 | char *space; |
| 513 | if ((space = strchr(windows_shared_buffer, ' '))) { |
| 514 | *space++ = '\0'; |
| 515 | strncpyz(mount_point, space, sizeof(mount_point) - 1); |
| 516 | } |
| 517 | |
| 518 | if (str_is_numeric(windows_shared_buffer)) { |
| 519 | device_index = str2ull(device, NULL); |
| 520 | snprintfz(windows_shared_buffer, sizeof(windows_shared_buffer), "Disk %d", device_index); |
| 521 | device = windows_shared_buffer; |
| 522 | } |
| 523 | |
| 524 | d = dictionary_set(dict, device, NULL, sizeof(*d)); |
| 525 | is_system = false; |
| 526 | } |
| 527 | d->last_collected = now_ut; |
| 528 | |
| 529 | if (!d->collected_metadata) { |
| 530 | if (!is_system && device_index != -1) { |
| 531 | if (!infos_loaded) { |
| 532 | infoCount = GetDiskDriveInfo(infos, _countof(infos)); |
| 533 | infos_loaded = true; |
| 534 | } |
| 535 | |
| 536 | for (size_t k = 0; k < infoCount; k++) { |
| 537 | if (infos[k].Index != device_index) |
| 538 | continue; |
| 539 | |
| 540 | d->manufacturer = string_strdupz(infos[k].Manufacturer); |
| 541 | d->model = string_strdupz(infos[k].Model); |
| 542 | d->media_type = string_strdupz(infos[k].MediaType); |
| 543 | d->name = string_strdupz(infos[k].Name); |
| 544 | d->device_id = string_strdupz(infos[k].DeviceID); |
| 545 | |
| 546 | break; |
| 547 | } |
| 548 | } |
| 549 | |
| 550 | d->device = string_strdupz(device); |
| 551 | d->mount_point = string_strdupz(mount_point); |
| 552 | d->collected_metadata = true; |
| 553 | } |
| 554 | |
| 555 | if (perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->diskReadBytesPerSec) && |
| 556 | perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->diskWriteBytesPerSec)) { |
| 557 | if (is_system) |
| 558 | common_system_io( |
| 559 | d->diskReadBytesPerSec.current.Data, d->diskWriteBytesPerSec.current.Data, update_every); |
| 560 | else |
| 561 | common_disk_io( |
| 562 | &d->disk_io, |
| 563 | device, |
| 564 | NULL, |
| 565 | d->diskReadBytesPerSec.current.Data, |
| 566 | d->diskWriteBytesPerSec.current.Data, |
| 567 | update_every, |
| 568 | physical_disk_labels, |
| 569 | d); |
| 570 | } |
| 571 | |
| 572 | if (is_system) |
| 573 | continue; |
| 574 | |
| 575 | if (perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->diskReadsPerSec) && |
| 576 | perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->diskWritesPerSec)) { |
| 577 | common_disk_ops( |
| 578 | &d->disk_ops, |
| 579 | device, |
| 580 | NULL, |
| 581 | d->diskReadsPerSec.current.Data, |
| 582 | d->diskWritesPerSec.current.Data, |
| 583 | update_every, |
| 584 | physical_disk_labels, |
| 585 | d); |
| 586 | } |
| 587 | |
| 588 | if (perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->percentIdleTime)) { |
| 589 | common_disk_util( |
| 590 | &d->disk_util, |
| 591 | device, |
| 592 | NULL, |
| 593 | 100 - perflib_idle_time_percent(&d->percentIdleTime), |
| 594 | update_every, |
| 595 | physical_disk_labels, |
| 596 | d); |
| 597 | } |
| 598 | |
| 599 | if (perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->percentDiskTime)) { |
| 600 | common_disk_busy( |
| 601 | &d->disk_busy, |
| 602 | device, |
| 603 | NULL, |
| 604 | d->percentDiskTime.current.Data / NS100_PER_MS, |
| 605 | update_every, |
| 606 | physical_disk_labels, |
| 607 | d); |
| 608 | } |
| 609 | |
| 610 | if (perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->percentDiskReadTime) && |
| 611 | perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->percentDiskWriteTime)) { |
| 612 | common_disk_iotime( |
| 613 | &d->disk_iotime, |
| 614 | device, |
| 615 | NULL, |
| 616 | d->percentDiskReadTime.current.Data / NS100_PER_MS, |
| 617 | d->percentDiskWriteTime.current.Data / NS100_PER_MS, |
| 618 | update_every, |
| 619 | physical_disk_labels, |
| 620 | d); |
| 621 | } |
| 622 | |
| 623 | if (perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->currentDiskQueueLength)) { |
| 624 | common_disk_qops( |
| 625 | &d->disk_qops, |
| 626 | device, |
| 627 | NULL, |
| 628 | d->currentDiskQueueLength.current.Data, |
| 629 | update_every, |
| 630 | physical_disk_labels, |
| 631 | d); |
| 632 | } |
| 633 | |
| 634 | if (perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->averageDiskSecondsPerRead) && |
| 635 | perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->averageDiskSecondsPerWrite)) { |
| 636 | common_disk_await( |
| 637 | &d->disk_await, |
| 638 | device, |
| 639 | NULL, |
| 640 | perflib_average_timer_ms(&d->averageDiskSecondsPerRead), |
| 641 | perflib_average_timer_ms(&d->averageDiskSecondsPerWrite), |
| 642 | update_every, |
| 643 | physical_disk_labels, |
| 644 | d); |
| 645 | } |
| 646 | |
| 647 | if (perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->averageDiskSecondsPerTransfer)) { |
| 648 | common_disk_svctm( |
| 649 | &d->disk_svctm, |
| 650 | device, |
| 651 | NULL, |
| 652 | perflib_average_timer_ms(&d->averageDiskSecondsPerTransfer), |
| 653 | update_every, |
| 654 | physical_disk_labels, |
| 655 | d); |
| 656 | } |
| 657 | |
| 658 | if (perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->averageDiskBytesPerRead) && |
| 659 | perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->averageDiskBytesPerWrite)) { |
| 660 | common_disk_avgsz( |
| 661 | &d->disk_avgsz, |
| 662 | device, |
| 663 | NULL, |
| 664 | perflib_average_bulk(&d->averageDiskBytesPerRead), |
| 665 | perflib_average_bulk(&d->averageDiskBytesPerWrite), |
| 666 | update_every, |
| 667 | physical_disk_labels, |
| 668 | d); |
| 669 | } |
| 670 | |
| 671 | if (perflibGetInstanceCounter(pDataBlock, pObjectType, pi, &d->splitIoPerSec)) { |
| 672 | if (!d->st_split) { |
| 673 | d->st_split = rrdset_create_localhost( |
| 674 | "disk_split", |
| 675 | device, |
| 676 | NULL, |
| 677 | "iops", |
| 678 | "disk.split", |
| 679 | "Split I/O Operations", |
| 680 | "operations/s", |
| 681 | _COMMON_PLUGIN_NAME, |
| 682 | _COMMON_PLUGIN_MODULE_NAME, |
| 683 | NETDATA_CHART_PRIO_DISK_SPLIT, |
| 684 | update_every, |
| 685 | RRDSET_TYPE_LINE); |
| 686 | |
| 687 | d->rd_split = rrddim_add(d->st_split, "discards", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); |
| 688 | |
| 689 | physical_disk_labels(d->st_split, d); |
| 690 | } |
| 691 | |
| 692 | rrddim_set_by_pointer(d->st_split, d->rd_split, d->splitIoPerSec.current.Data); |
| 693 | rrdset_done(d->st_split); |
| 694 | } |
| 695 | } |
| 696 | |
| 697 | // cleanup - delete callback will handle resource cleanup |
| 698 | { |
| 699 | struct physical_disk *d; |
| 700 | dfe_start_write(dict, d) |
| 701 | { |
| 702 | if (d->last_collected < now_ut) |
| 703 | dictionary_del(dict, d_dfe.name); |
| 704 | } |
| 705 | dfe_done(d); |
| 706 | dictionary_garbage_collect(dict); |
| 707 | } |
| 708 | |
| 709 | return true; |
| 710 | } |
| 711 | |
| 712 | int do_PerflibStorage(int update_every, usec_t dt __maybe_unused) |
| 713 | { |
| 714 | static bool initialized = false; |
| 715 | DWORD logical_id, physical_id; |
| 716 | |
| 717 | if (unlikely(!initialized)) { |
| 718 | initialize(); |
| 719 | initialized = true; |
| 720 | } |
| 721 | |
| 722 | logical_id = RegistryFindIDByName("LogicalDisk"); |
| 723 | physical_id = RegistryFindIDByName("PhysicalDisk"); |
| 724 | |
| 725 | if (logical_id == PERFLIB_REGISTRY_NAME_NOT_FOUND && physical_id == PERFLIB_REGISTRY_NAME_NOT_FOUND) |
| 726 | return -1; |
| 727 | |
| 728 | // Each perflibGetPerformanceData() call reuses the same internal buffer, so we must |
| 729 | // query and consume each block before issuing the next call to avoid pointer aliasing. |
| 730 | usec_t now_ut = now_monotonic_usec(); |
| 731 | bool processed_any = false; |
| 732 | |
| 733 | if (logical_id != PERFLIB_REGISTRY_NAME_NOT_FOUND) { |
| 734 | PERF_DATA_BLOCK *pDataBlock = perflibGetPerformanceData(logical_id); |
| 735 | if (pDataBlock) { |
| 736 | do_logical_disk(pDataBlock, update_every, now_ut); |
| 737 | processed_any = true; |
| 738 | } |
| 739 | } |
| 740 | |
| 741 | if (physical_id != PERFLIB_REGISTRY_NAME_NOT_FOUND) { |
| 742 | PERF_DATA_BLOCK *pDataBlock = perflibGetPerformanceData(physical_id); |
| 743 | if (pDataBlock) { |
| 744 | do_physical_disk(pDataBlock, update_every, now_ut); |
| 745 | processed_any = true; |
| 746 | } |
| 747 | } |
| 748 | |
| 749 | return processed_any ? 0 : -1; |
| 750 | } |