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  }