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