system_collector_windows.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. //go:build windows
  2. package diagnostic
  3. import (
  4. "context"
  5. "fmt"
  6. "os/exec"
  7. "runtime"
  8. "strconv"
  9. )
  10. const kiloBytesScale = 1.0 / 1024
  11. type SystemCollectorImpl struct {
  12. version string
  13. }
  14. func NewSystemCollectorImpl(
  15. version string,
  16. ) *SystemCollectorImpl {
  17. return &SystemCollectorImpl{
  18. version,
  19. }
  20. }
  21. func (collector *SystemCollectorImpl) Collect(ctx context.Context) (*SystemInformation, error) {
  22. memoryInfo, memoryInfoRaw, memoryInfoErr := collectMemoryInformation(ctx)
  23. disks, disksRaw, diskErr := collectDiskVolumeInformation(ctx)
  24. osInfo, osInfoRaw, osInfoErr := collectOSInformation(ctx)
  25. var memoryMaximum, memoryCurrent, fileDescriptorMaximum, fileDescriptorCurrent uint64
  26. var osSystem, name, osVersion, osRelease, architecture string
  27. err := SystemInformationGeneralError{
  28. OperatingSystemInformationError: nil,
  29. MemoryInformationError: nil,
  30. FileDescriptorsInformationError: nil,
  31. DiskVolumeInformationError: nil,
  32. }
  33. if memoryInfoErr != nil {
  34. err.MemoryInformationError = SystemInformationError{
  35. Err: memoryInfoErr,
  36. RawInfo: memoryInfoRaw,
  37. }
  38. } else {
  39. memoryMaximum = memoryInfo.MemoryMaximum
  40. memoryCurrent = memoryInfo.MemoryCurrent
  41. }
  42. if diskErr != nil {
  43. err.DiskVolumeInformationError = SystemInformationError{
  44. Err: diskErr,
  45. RawInfo: disksRaw,
  46. }
  47. }
  48. if osInfoErr != nil {
  49. err.OperatingSystemInformationError = SystemInformationError{
  50. Err: osInfoErr,
  51. RawInfo: osInfoRaw,
  52. }
  53. } else {
  54. osSystem = osInfo.OsSystem
  55. name = osInfo.Name
  56. osVersion = osInfo.OsVersion
  57. osRelease = osInfo.OsRelease
  58. architecture = osInfo.Architecture
  59. }
  60. cloudflaredVersion := collector.version
  61. info := NewSystemInformation(
  62. memoryMaximum,
  63. memoryCurrent,
  64. fileDescriptorMaximum,
  65. fileDescriptorCurrent,
  66. osSystem,
  67. name,
  68. osVersion,
  69. osRelease,
  70. architecture,
  71. cloudflaredVersion,
  72. runtime.Version(),
  73. runtime.GOARCH,
  74. disks,
  75. )
  76. return info, err
  77. }
  78. func collectMemoryInformation(ctx context.Context) (*MemoryInformation, string, error) {
  79. const (
  80. memoryTotalPrefix = "TotalVirtualMemorySize"
  81. memoryAvailablePrefix = "FreeVirtualMemory"
  82. )
  83. command := exec.CommandContext(
  84. ctx,
  85. "powershell",
  86. "-Command",
  87. "Get-CimInstance -Class Win32_OperatingSystem | Select-Object FreeVirtualMemory, TotalVirtualMemorySize | Format-List",
  88. )
  89. stdout, err := command.Output()
  90. if err != nil {
  91. return nil, "", fmt.Errorf("error retrieving output from command '%s': %w", command.String(), err)
  92. }
  93. output := string(stdout)
  94. // the result of the command above will return values in bytes hence
  95. // they need to be converted to kilobytes
  96. mapper := func(field string) (uint64, error) {
  97. value, err := strconv.ParseUint(field, 10, 64)
  98. return uint64(float64(value) * kiloBytesScale), err
  99. }
  100. memoryInfo, err := ParseMemoryInformationFromKV(output, memoryTotalPrefix, memoryAvailablePrefix, mapper)
  101. if err != nil {
  102. return nil, output, err
  103. }
  104. // returning raw output in case other collected information
  105. // resulted in errors
  106. return memoryInfo, output, nil
  107. }
  108. func collectDiskVolumeInformation(ctx context.Context) ([]*DiskVolumeInformation, string, error) {
  109. command := exec.CommandContext(
  110. ctx,
  111. "powershell", "-Command", "Get-CimInstance -Class Win32_LogicalDisk | Select-Object DeviceID, Size, FreeSpace")
  112. stdout, err := command.Output()
  113. if err != nil {
  114. return nil, "", fmt.Errorf("error retrieving output from command '%s': %w", command.String(), err)
  115. }
  116. output := string(stdout)
  117. disks, err := ParseDiskVolumeInformationOutput(output, 2, kiloBytesScale)
  118. if err != nil {
  119. return nil, output, err
  120. }
  121. // returning raw output in case other collected information
  122. // resulted in errors
  123. return disks, output, nil
  124. }
  125. func collectOSInformation(ctx context.Context) (*OsInfo, string, error) {
  126. const (
  127. architecturePrefix = "OSArchitecture"
  128. osSystemPrefix = "Caption"
  129. osVersionPrefix = "Version"
  130. osReleasePrefix = "BuildNumber"
  131. namePrefix = "CSName"
  132. )
  133. command := exec.CommandContext(
  134. ctx,
  135. "powershell",
  136. "-Command",
  137. "Get-CimInstance -Class Win32_OperatingSystem | Select-Object OSArchitecture, Caption, Version, BuildNumber, CSName | Format-List",
  138. )
  139. stdout, err := command.Output()
  140. if err != nil {
  141. return nil, "", fmt.Errorf("error retrieving output from command '%s': %w", command.String(), err)
  142. }
  143. output := string(stdout)
  144. osInfo, err := ParseWinOperatingSystemInfo(output, architecturePrefix, osSystemPrefix, osVersionPrefix, osReleasePrefix, namePrefix)
  145. if err != nil {
  146. return nil, output, err
  147. }
  148. // returning raw output in case other collected information
  149. // resulted in errors
  150. return osInfo, output, nil
  151. }