sigar_windows.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. // Copyright (c) 2012 VMware, Inc.
  2. package gosigar
  3. import (
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "runtime"
  8. "strings"
  9. "sync"
  10. "syscall"
  11. "time"
  12. "github.com/StackExchange/wmi"
  13. "github.com/elastic/gosigar/sys/windows"
  14. "github.com/pkg/errors"
  15. )
  16. // Win32_Process represents a process on the Windows operating system. If
  17. // additional fields are added here (that match the Windows struct) they will
  18. // automatically be populated when calling getWin32Process.
  19. // https://msdn.microsoft.com/en-us/library/windows/desktop/aa394372(v=vs.85).aspx
  20. type Win32_Process struct {
  21. CommandLine string
  22. }
  23. // Win32_OperatingSystem WMI class represents a Windows-based operating system
  24. // installed on a computer.
  25. // https://msdn.microsoft.com/en-us/library/windows/desktop/aa394239(v=vs.85).aspx
  26. type Win32_OperatingSystem struct {
  27. LastBootUpTime time.Time
  28. }
  29. var (
  30. // version is Windows version of the host OS.
  31. version = windows.GetWindowsVersion()
  32. // processQueryLimitedInfoAccess is set to PROCESS_QUERY_INFORMATION for Windows
  33. // 2003 and XP where PROCESS_QUERY_LIMITED_INFORMATION is unknown. For all newer
  34. // OS versions it is set to PROCESS_QUERY_LIMITED_INFORMATION.
  35. processQueryLimitedInfoAccess = windows.PROCESS_QUERY_LIMITED_INFORMATION
  36. // bootTime is the time when the OS was last booted. This value may be nil
  37. // on operating systems that do not support the WMI query used to obtain it.
  38. bootTime *time.Time
  39. bootTimeLock sync.Mutex
  40. )
  41. func init() {
  42. if !version.IsWindowsVistaOrGreater() {
  43. // PROCESS_QUERY_LIMITED_INFORMATION cannot be used on 2003 or XP.
  44. processQueryLimitedInfoAccess = syscall.PROCESS_QUERY_INFORMATION
  45. }
  46. }
  47. func (self *LoadAverage) Get() error {
  48. return ErrNotImplemented{runtime.GOOS}
  49. }
  50. func (self *FDUsage) Get() error {
  51. return ErrNotImplemented{runtime.GOOS}
  52. }
  53. func (self *ProcEnv) Get(pid int) error {
  54. return ErrNotImplemented{runtime.GOOS}
  55. }
  56. func (self *ProcExe) Get(pid int) error {
  57. return ErrNotImplemented{runtime.GOOS}
  58. }
  59. func (self *ProcFDUsage) Get(pid int) error {
  60. return ErrNotImplemented{runtime.GOOS}
  61. }
  62. func (self *Uptime) Get() error {
  63. // Minimum supported OS is Windows Vista.
  64. if !version.IsWindowsVistaOrGreater() {
  65. return ErrNotImplemented{runtime.GOOS}
  66. }
  67. bootTimeLock.Lock()
  68. defer bootTimeLock.Unlock()
  69. if bootTime == nil {
  70. os, err := getWin32OperatingSystem()
  71. if err != nil {
  72. return errors.Wrap(err, "failed to get boot time using WMI")
  73. }
  74. bootTime = &os.LastBootUpTime
  75. }
  76. self.Length = time.Since(*bootTime).Seconds()
  77. return nil
  78. }
  79. func (self *Mem) Get() error {
  80. memoryStatusEx, err := windows.GlobalMemoryStatusEx()
  81. if err != nil {
  82. return errors.Wrap(err, "GlobalMemoryStatusEx failed")
  83. }
  84. self.Total = memoryStatusEx.TotalPhys
  85. self.Free = memoryStatusEx.AvailPhys
  86. self.Used = self.Total - self.Free
  87. self.ActualFree = self.Free
  88. self.ActualUsed = self.Used
  89. return nil
  90. }
  91. func (self *Swap) Get() error {
  92. memoryStatusEx, err := windows.GlobalMemoryStatusEx()
  93. if err != nil {
  94. return errors.Wrap(err, "GlobalMemoryStatusEx failed")
  95. }
  96. self.Total = memoryStatusEx.TotalPageFile
  97. self.Free = memoryStatusEx.AvailPageFile
  98. self.Used = self.Total - self.Free
  99. return nil
  100. }
  101. func (self *HugeTLBPages) Get() error {
  102. return ErrNotImplemented{runtime.GOOS}
  103. }
  104. func (self *Cpu) Get() error {
  105. idle, kernel, user, err := windows.GetSystemTimes()
  106. if err != nil {
  107. return errors.Wrap(err, "GetSystemTimes failed")
  108. }
  109. // CPU times are reported in milliseconds by gosigar.
  110. self.Idle = uint64(idle / time.Millisecond)
  111. self.Sys = uint64(kernel / time.Millisecond)
  112. self.User = uint64(user / time.Millisecond)
  113. return nil
  114. }
  115. func (self *CpuList) Get() error {
  116. cpus, err := windows.NtQuerySystemProcessorPerformanceInformation()
  117. if err != nil {
  118. return errors.Wrap(err, "NtQuerySystemProcessorPerformanceInformation failed")
  119. }
  120. self.List = make([]Cpu, 0, len(cpus))
  121. for _, cpu := range cpus {
  122. self.List = append(self.List, Cpu{
  123. Idle: uint64(cpu.IdleTime / time.Millisecond),
  124. Sys: uint64(cpu.KernelTime / time.Millisecond),
  125. User: uint64(cpu.UserTime / time.Millisecond),
  126. })
  127. }
  128. return nil
  129. }
  130. func (self *FileSystemList) Get() error {
  131. drives, err := windows.GetLogicalDriveStrings()
  132. if err != nil {
  133. return errors.Wrap(err, "GetLogicalDriveStrings failed")
  134. }
  135. for _, drive := range drives {
  136. dt, err := windows.GetDriveType(drive)
  137. if err != nil {
  138. return errors.Wrapf(err, "GetDriveType failed")
  139. }
  140. self.List = append(self.List, FileSystem{
  141. DirName: drive,
  142. DevName: drive,
  143. TypeName: dt.String(),
  144. })
  145. }
  146. return nil
  147. }
  148. // Get retrieves a list of all process identifiers (PIDs) in the system.
  149. func (self *ProcList) Get() error {
  150. pids, err := windows.EnumProcesses()
  151. if err != nil {
  152. return errors.Wrap(err, "EnumProcesses failed")
  153. }
  154. // Convert uint32 PIDs to int.
  155. self.List = make([]int, 0, len(pids))
  156. for _, pid := range pids {
  157. self.List = append(self.List, int(pid))
  158. }
  159. return nil
  160. }
  161. func (self *ProcState) Get(pid int) error {
  162. var errs []error
  163. var err error
  164. self.Name, err = getProcName(pid)
  165. if err != nil {
  166. errs = append(errs, errors.Wrap(err, "getProcName failed"))
  167. }
  168. self.State, err = getProcStatus(pid)
  169. if err != nil {
  170. errs = append(errs, errors.Wrap(err, "getProcStatus failed"))
  171. }
  172. self.Ppid, err = getParentPid(pid)
  173. if err != nil {
  174. errs = append(errs, errors.Wrap(err, "getParentPid failed"))
  175. }
  176. self.Username, err = getProcCredName(pid)
  177. if err != nil {
  178. errs = append(errs, errors.Wrap(err, "getProcCredName failed"))
  179. }
  180. if len(errs) > 0 {
  181. errStrs := make([]string, 0, len(errs))
  182. for _, e := range errs {
  183. errStrs = append(errStrs, e.Error())
  184. }
  185. return errors.New(strings.Join(errStrs, "; "))
  186. }
  187. return nil
  188. }
  189. // getProcName returns the process name associated with the PID.
  190. func getProcName(pid int) (string, error) {
  191. handle, err := syscall.OpenProcess(processQueryLimitedInfoAccess, false, uint32(pid))
  192. if err != nil {
  193. return "", errors.Wrapf(err, "OpenProcess failed for pid=%v", pid)
  194. }
  195. defer syscall.CloseHandle(handle)
  196. filename, err := windows.GetProcessImageFileName(handle)
  197. if err != nil {
  198. return "", errors.Wrapf(err, "GetProcessImageFileName failed for pid=%v", pid)
  199. }
  200. return filepath.Base(filename), nil
  201. }
  202. // getProcStatus returns the status of a process.
  203. func getProcStatus(pid int) (RunState, error) {
  204. handle, err := syscall.OpenProcess(processQueryLimitedInfoAccess, false, uint32(pid))
  205. if err != nil {
  206. return RunStateUnknown, errors.Wrapf(err, "OpenProcess failed for pid=%v", pid)
  207. }
  208. defer syscall.CloseHandle(handle)
  209. var exitCode uint32
  210. err = syscall.GetExitCodeProcess(handle, &exitCode)
  211. if err != nil {
  212. return RunStateUnknown, errors.Wrapf(err, "GetExitCodeProcess failed for pid=%v")
  213. }
  214. if exitCode == 259 { //still active
  215. return RunStateRun, nil
  216. }
  217. return RunStateSleep, nil
  218. }
  219. // getParentPid returns the parent process ID of a process.
  220. func getParentPid(pid int) (int, error) {
  221. handle, err := syscall.OpenProcess(processQueryLimitedInfoAccess, false, uint32(pid))
  222. if err != nil {
  223. return RunStateUnknown, errors.Wrapf(err, "OpenProcess failed for pid=%v", pid)
  224. }
  225. defer syscall.CloseHandle(handle)
  226. procInfo, err := windows.NtQueryProcessBasicInformation(handle)
  227. if err != nil {
  228. return 0, errors.Wrapf(err, "NtQueryProcessBasicInformation failed for pid=%v", pid)
  229. }
  230. return int(procInfo.InheritedFromUniqueProcessID), nil
  231. }
  232. func getProcCredName(pid int) (string, error) {
  233. handle, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(pid))
  234. if err != nil {
  235. return "", errors.Wrapf(err, "OpenProcess failed for pid=%v", pid)
  236. }
  237. defer syscall.CloseHandle(handle)
  238. // Find process token via win32.
  239. var token syscall.Token
  240. err = syscall.OpenProcessToken(handle, syscall.TOKEN_QUERY, &token)
  241. if err != nil {
  242. return "", errors.Wrapf(err, "OpenProcessToken failed for pid=%v", pid)
  243. }
  244. // Find the token user.
  245. tokenUser, err := token.GetTokenUser()
  246. if err != nil {
  247. return "", errors.Wrapf(err, "GetTokenInformation failed for pid=%v", pid)
  248. }
  249. // Close token to prevent handle leaks.
  250. err = token.Close()
  251. if err != nil {
  252. return "", errors.Wrapf(err, "failed while closing process token handle for pid=%v", pid)
  253. }
  254. // Look up domain account by SID.
  255. account, domain, _, err := tokenUser.User.Sid.LookupAccount("")
  256. if err != nil {
  257. sid, sidErr := tokenUser.User.Sid.String()
  258. if sidErr != nil {
  259. return "", errors.Wrapf(err, "failed while looking up account name for pid=%v", pid)
  260. }
  261. return "", errors.Wrapf(err, "failed while looking up account name for SID=%v of pid=%v", sid, pid)
  262. }
  263. return fmt.Sprintf(`%s\%s`, domain, account), nil
  264. }
  265. func (self *ProcMem) Get(pid int) error {
  266. handle, err := syscall.OpenProcess(processQueryLimitedInfoAccess|windows.PROCESS_VM_READ, false, uint32(pid))
  267. if err != nil {
  268. return errors.Wrapf(err, "OpenProcess failed for pid=%v", pid)
  269. }
  270. defer syscall.CloseHandle(handle)
  271. counters, err := windows.GetProcessMemoryInfo(handle)
  272. if err != nil {
  273. return errors.Wrapf(err, "GetProcessMemoryInfo failed for pid=%v", pid)
  274. }
  275. self.Resident = uint64(counters.WorkingSetSize)
  276. self.Size = uint64(counters.PrivateUsage)
  277. return nil
  278. }
  279. func (self *ProcTime) Get(pid int) error {
  280. cpu, err := getProcTimes(pid)
  281. if err != nil {
  282. return err
  283. }
  284. // Windows epoch times are expressed as time elapsed since midnight on
  285. // January 1, 1601 at Greenwich, England. This converts the Filetime to
  286. // unix epoch in milliseconds.
  287. self.StartTime = uint64(cpu.CreationTime.Nanoseconds() / 1e6)
  288. // Convert to millis.
  289. self.User = uint64(windows.FiletimeToDuration(&cpu.UserTime).Nanoseconds() / 1e6)
  290. self.Sys = uint64(windows.FiletimeToDuration(&cpu.KernelTime).Nanoseconds() / 1e6)
  291. self.Total = self.User + self.Sys
  292. return nil
  293. }
  294. func getProcTimes(pid int) (*syscall.Rusage, error) {
  295. handle, err := syscall.OpenProcess(processQueryLimitedInfoAccess, false, uint32(pid))
  296. if err != nil {
  297. return nil, errors.Wrapf(err, "OpenProcess failed for pid=%v", pid)
  298. }
  299. defer syscall.CloseHandle(handle)
  300. var cpu syscall.Rusage
  301. if err := syscall.GetProcessTimes(handle, &cpu.CreationTime, &cpu.ExitTime, &cpu.KernelTime, &cpu.UserTime); err != nil {
  302. return nil, errors.Wrapf(err, "GetProcessTimes failed for pid=%v", pid)
  303. }
  304. return &cpu, nil
  305. }
  306. func (self *ProcArgs) Get(pid int) error {
  307. // The minimum supported client for Win32_Process is Windows Vista.
  308. if !version.IsWindowsVistaOrGreater() {
  309. return ErrNotImplemented{runtime.GOOS}
  310. }
  311. process, err := getWin32Process(int32(pid))
  312. if err != nil {
  313. return errors.Wrapf(err, "ProcArgs failed for pid=%v", pid)
  314. }
  315. self.List = []string{process.CommandLine}
  316. return nil
  317. }
  318. func (self *FileSystemUsage) Get(path string) error {
  319. freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes, err := windows.GetDiskFreeSpaceEx(path)
  320. if err != nil {
  321. return errors.Wrap(err, "GetDiskFreeSpaceEx failed")
  322. }
  323. self.Total = totalNumberOfBytes
  324. self.Free = totalNumberOfFreeBytes
  325. self.Used = self.Total - self.Free
  326. self.Avail = freeBytesAvailable
  327. return nil
  328. }
  329. // getWin32Process gets information about the process with the given process ID.
  330. // It uses a WMI query to get the information from the local system.
  331. func getWin32Process(pid int32) (Win32_Process, error) {
  332. var dst []Win32_Process
  333. query := fmt.Sprintf("WHERE ProcessId = %d", pid)
  334. q := wmi.CreateQuery(&dst, query)
  335. err := wmi.Query(q, &dst)
  336. if err != nil {
  337. return Win32_Process{}, fmt.Errorf("could not get Win32_Process %s: %v", query, err)
  338. }
  339. if len(dst) < 1 {
  340. return Win32_Process{}, fmt.Errorf("could not get Win32_Process %s: Process not found", query)
  341. }
  342. return dst[0], nil
  343. }
  344. func getWin32OperatingSystem() (Win32_OperatingSystem, error) {
  345. var dst []Win32_OperatingSystem
  346. q := wmi.CreateQuery(&dst, "")
  347. err := wmi.Query(q, &dst)
  348. if err != nil {
  349. return Win32_OperatingSystem{}, errors.Wrap(err, "wmi query for Win32_OperatingSystem failed")
  350. }
  351. if len(dst) != 1 {
  352. return Win32_OperatingSystem{}, errors.New("wmi query for Win32_OperatingSystem failed")
  353. }
  354. return dst[0], nil
  355. }
  356. func (self *Rusage) Get(who int) error {
  357. if who != 0 {
  358. return ErrNotImplemented{runtime.GOOS}
  359. }
  360. pid := os.Getpid()
  361. cpu, err := getProcTimes(pid)
  362. if err != nil {
  363. return err
  364. }
  365. self.Utime = windows.FiletimeToDuration(&cpu.UserTime)
  366. self.Stime = windows.FiletimeToDuration(&cpu.KernelTime)
  367. return nil
  368. }