123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- // Copyright (c) 2012 VMware, Inc.
- package gosigar
- import (
- "fmt"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "sync"
- "syscall"
- "time"
- "github.com/StackExchange/wmi"
- "github.com/elastic/gosigar/sys/windows"
- "github.com/pkg/errors"
- )
- // Win32_Process represents a process on the Windows operating system. If
- // additional fields are added here (that match the Windows struct) they will
- // automatically be populated when calling getWin32Process.
- // https://msdn.microsoft.com/en-us/library/windows/desktop/aa394372(v=vs.85).aspx
- type Win32_Process struct {
- CommandLine string
- }
- // Win32_OperatingSystem WMI class represents a Windows-based operating system
- // installed on a computer.
- // https://msdn.microsoft.com/en-us/library/windows/desktop/aa394239(v=vs.85).aspx
- type Win32_OperatingSystem struct {
- LastBootUpTime time.Time
- }
- var (
- // version is Windows version of the host OS.
- version = windows.GetWindowsVersion()
- // processQueryLimitedInfoAccess is set to PROCESS_QUERY_INFORMATION for Windows
- // 2003 and XP where PROCESS_QUERY_LIMITED_INFORMATION is unknown. For all newer
- // OS versions it is set to PROCESS_QUERY_LIMITED_INFORMATION.
- processQueryLimitedInfoAccess = windows.PROCESS_QUERY_LIMITED_INFORMATION
- // bootTime is the time when the OS was last booted. This value may be nil
- // on operating systems that do not support the WMI query used to obtain it.
- bootTime *time.Time
- bootTimeLock sync.Mutex
- )
- func init() {
- if !version.IsWindowsVistaOrGreater() {
- // PROCESS_QUERY_LIMITED_INFORMATION cannot be used on 2003 or XP.
- processQueryLimitedInfoAccess = syscall.PROCESS_QUERY_INFORMATION
- }
- }
- func (self *LoadAverage) Get() error {
- return ErrNotImplemented{runtime.GOOS}
- }
- func (self *FDUsage) Get() error {
- return ErrNotImplemented{runtime.GOOS}
- }
- func (self *ProcEnv) Get(pid int) error {
- return ErrNotImplemented{runtime.GOOS}
- }
- func (self *ProcExe) Get(pid int) error {
- return ErrNotImplemented{runtime.GOOS}
- }
- func (self *ProcFDUsage) Get(pid int) error {
- return ErrNotImplemented{runtime.GOOS}
- }
- func (self *Uptime) Get() error {
- // Minimum supported OS is Windows Vista.
- if !version.IsWindowsVistaOrGreater() {
- return ErrNotImplemented{runtime.GOOS}
- }
- bootTimeLock.Lock()
- defer bootTimeLock.Unlock()
- if bootTime == nil {
- os, err := getWin32OperatingSystem()
- if err != nil {
- return errors.Wrap(err, "failed to get boot time using WMI")
- }
- bootTime = &os.LastBootUpTime
- }
- self.Length = time.Since(*bootTime).Seconds()
- return nil
- }
- func (self *Mem) Get() error {
- memoryStatusEx, err := windows.GlobalMemoryStatusEx()
- if err != nil {
- return errors.Wrap(err, "GlobalMemoryStatusEx failed")
- }
- self.Total = memoryStatusEx.TotalPhys
- self.Free = memoryStatusEx.AvailPhys
- self.Used = self.Total - self.Free
- self.ActualFree = self.Free
- self.ActualUsed = self.Used
- return nil
- }
- func (self *Swap) Get() error {
- memoryStatusEx, err := windows.GlobalMemoryStatusEx()
- if err != nil {
- return errors.Wrap(err, "GlobalMemoryStatusEx failed")
- }
- self.Total = memoryStatusEx.TotalPageFile
- self.Free = memoryStatusEx.AvailPageFile
- self.Used = self.Total - self.Free
- return nil
- }
- func (self *HugeTLBPages) Get() error {
- return ErrNotImplemented{runtime.GOOS}
- }
- func (self *Cpu) Get() error {
- idle, kernel, user, err := windows.GetSystemTimes()
- if err != nil {
- return errors.Wrap(err, "GetSystemTimes failed")
- }
- // CPU times are reported in milliseconds by gosigar.
- self.Idle = uint64(idle / time.Millisecond)
- self.Sys = uint64(kernel / time.Millisecond)
- self.User = uint64(user / time.Millisecond)
- return nil
- }
- func (self *CpuList) Get() error {
- cpus, err := windows.NtQuerySystemProcessorPerformanceInformation()
- if err != nil {
- return errors.Wrap(err, "NtQuerySystemProcessorPerformanceInformation failed")
- }
- self.List = make([]Cpu, 0, len(cpus))
- for _, cpu := range cpus {
- self.List = append(self.List, Cpu{
- Idle: uint64(cpu.IdleTime / time.Millisecond),
- Sys: uint64(cpu.KernelTime / time.Millisecond),
- User: uint64(cpu.UserTime / time.Millisecond),
- })
- }
- return nil
- }
- func (self *FileSystemList) Get() error {
- drives, err := windows.GetLogicalDriveStrings()
- if err != nil {
- return errors.Wrap(err, "GetLogicalDriveStrings failed")
- }
- for _, drive := range drives {
- dt, err := windows.GetDriveType(drive)
- if err != nil {
- return errors.Wrapf(err, "GetDriveType failed")
- }
- self.List = append(self.List, FileSystem{
- DirName: drive,
- DevName: drive,
- TypeName: dt.String(),
- })
- }
- return nil
- }
- // Get retrieves a list of all process identifiers (PIDs) in the system.
- func (self *ProcList) Get() error {
- pids, err := windows.EnumProcesses()
- if err != nil {
- return errors.Wrap(err, "EnumProcesses failed")
- }
- // Convert uint32 PIDs to int.
- self.List = make([]int, 0, len(pids))
- for _, pid := range pids {
- self.List = append(self.List, int(pid))
- }
- return nil
- }
- func (self *ProcState) Get(pid int) error {
- var errs []error
- var err error
- self.Name, err = getProcName(pid)
- if err != nil {
- errs = append(errs, errors.Wrap(err, "getProcName failed"))
- }
- self.State, err = getProcStatus(pid)
- if err != nil {
- errs = append(errs, errors.Wrap(err, "getProcStatus failed"))
- }
- self.Ppid, err = getParentPid(pid)
- if err != nil {
- errs = append(errs, errors.Wrap(err, "getParentPid failed"))
- }
- self.Username, err = getProcCredName(pid)
- if err != nil {
- errs = append(errs, errors.Wrap(err, "getProcCredName failed"))
- }
- if len(errs) > 0 {
- errStrs := make([]string, 0, len(errs))
- for _, e := range errs {
- errStrs = append(errStrs, e.Error())
- }
- return errors.New(strings.Join(errStrs, "; "))
- }
- return nil
- }
- // getProcName returns the process name associated with the PID.
- func getProcName(pid int) (string, error) {
- handle, err := syscall.OpenProcess(processQueryLimitedInfoAccess, false, uint32(pid))
- if err != nil {
- return "", errors.Wrapf(err, "OpenProcess failed for pid=%v", pid)
- }
- defer syscall.CloseHandle(handle)
- filename, err := windows.GetProcessImageFileName(handle)
- if err != nil {
- return "", errors.Wrapf(err, "GetProcessImageFileName failed for pid=%v", pid)
- }
- return filepath.Base(filename), nil
- }
- // getProcStatus returns the status of a process.
- func getProcStatus(pid int) (RunState, error) {
- handle, err := syscall.OpenProcess(processQueryLimitedInfoAccess, false, uint32(pid))
- if err != nil {
- return RunStateUnknown, errors.Wrapf(err, "OpenProcess failed for pid=%v", pid)
- }
- defer syscall.CloseHandle(handle)
- var exitCode uint32
- err = syscall.GetExitCodeProcess(handle, &exitCode)
- if err != nil {
- return RunStateUnknown, errors.Wrapf(err, "GetExitCodeProcess failed for pid=%v")
- }
- if exitCode == 259 { //still active
- return RunStateRun, nil
- }
- return RunStateSleep, nil
- }
- // getParentPid returns the parent process ID of a process.
- func getParentPid(pid int) (int, error) {
- handle, err := syscall.OpenProcess(processQueryLimitedInfoAccess, false, uint32(pid))
- if err != nil {
- return RunStateUnknown, errors.Wrapf(err, "OpenProcess failed for pid=%v", pid)
- }
- defer syscall.CloseHandle(handle)
- procInfo, err := windows.NtQueryProcessBasicInformation(handle)
- if err != nil {
- return 0, errors.Wrapf(err, "NtQueryProcessBasicInformation failed for pid=%v", pid)
- }
- return int(procInfo.InheritedFromUniqueProcessID), nil
- }
- func getProcCredName(pid int) (string, error) {
- handle, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(pid))
- if err != nil {
- return "", errors.Wrapf(err, "OpenProcess failed for pid=%v", pid)
- }
- defer syscall.CloseHandle(handle)
- // Find process token via win32.
- var token syscall.Token
- err = syscall.OpenProcessToken(handle, syscall.TOKEN_QUERY, &token)
- if err != nil {
- return "", errors.Wrapf(err, "OpenProcessToken failed for pid=%v", pid)
- }
- // Find the token user.
- tokenUser, err := token.GetTokenUser()
- if err != nil {
- return "", errors.Wrapf(err, "GetTokenInformation failed for pid=%v", pid)
- }
- // Close token to prevent handle leaks.
- err = token.Close()
- if err != nil {
- return "", errors.Wrapf(err, "failed while closing process token handle for pid=%v", pid)
- }
- // Look up domain account by SID.
- account, domain, _, err := tokenUser.User.Sid.LookupAccount("")
- if err != nil {
- sid, sidErr := tokenUser.User.Sid.String()
- if sidErr != nil {
- return "", errors.Wrapf(err, "failed while looking up account name for pid=%v", pid)
- }
- return "", errors.Wrapf(err, "failed while looking up account name for SID=%v of pid=%v", sid, pid)
- }
- return fmt.Sprintf(`%s\%s`, domain, account), nil
- }
- func (self *ProcMem) Get(pid int) error {
- handle, err := syscall.OpenProcess(processQueryLimitedInfoAccess|windows.PROCESS_VM_READ, false, uint32(pid))
- if err != nil {
- return errors.Wrapf(err, "OpenProcess failed for pid=%v", pid)
- }
- defer syscall.CloseHandle(handle)
- counters, err := windows.GetProcessMemoryInfo(handle)
- if err != nil {
- return errors.Wrapf(err, "GetProcessMemoryInfo failed for pid=%v", pid)
- }
- self.Resident = uint64(counters.WorkingSetSize)
- self.Size = uint64(counters.PrivateUsage)
- return nil
- }
- func (self *ProcTime) Get(pid int) error {
- cpu, err := getProcTimes(pid)
- if err != nil {
- return err
- }
- // Windows epoch times are expressed as time elapsed since midnight on
- // January 1, 1601 at Greenwich, England. This converts the Filetime to
- // unix epoch in milliseconds.
- self.StartTime = uint64(cpu.CreationTime.Nanoseconds() / 1e6)
- // Convert to millis.
- self.User = uint64(windows.FiletimeToDuration(&cpu.UserTime).Nanoseconds() / 1e6)
- self.Sys = uint64(windows.FiletimeToDuration(&cpu.KernelTime).Nanoseconds() / 1e6)
- self.Total = self.User + self.Sys
- return nil
- }
- func getProcTimes(pid int) (*syscall.Rusage, error) {
- handle, err := syscall.OpenProcess(processQueryLimitedInfoAccess, false, uint32(pid))
- if err != nil {
- return nil, errors.Wrapf(err, "OpenProcess failed for pid=%v", pid)
- }
- defer syscall.CloseHandle(handle)
- var cpu syscall.Rusage
- if err := syscall.GetProcessTimes(handle, &cpu.CreationTime, &cpu.ExitTime, &cpu.KernelTime, &cpu.UserTime); err != nil {
- return nil, errors.Wrapf(err, "GetProcessTimes failed for pid=%v", pid)
- }
- return &cpu, nil
- }
- func (self *ProcArgs) Get(pid int) error {
- // The minimum supported client for Win32_Process is Windows Vista.
- if !version.IsWindowsVistaOrGreater() {
- return ErrNotImplemented{runtime.GOOS}
- }
- process, err := getWin32Process(int32(pid))
- if err != nil {
- return errors.Wrapf(err, "ProcArgs failed for pid=%v", pid)
- }
- self.List = []string{process.CommandLine}
- return nil
- }
- func (self *FileSystemUsage) Get(path string) error {
- freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes, err := windows.GetDiskFreeSpaceEx(path)
- if err != nil {
- return errors.Wrap(err, "GetDiskFreeSpaceEx failed")
- }
- self.Total = totalNumberOfBytes
- self.Free = totalNumberOfFreeBytes
- self.Used = self.Total - self.Free
- self.Avail = freeBytesAvailable
- return nil
- }
- // getWin32Process gets information about the process with the given process ID.
- // It uses a WMI query to get the information from the local system.
- func getWin32Process(pid int32) (Win32_Process, error) {
- var dst []Win32_Process
- query := fmt.Sprintf("WHERE ProcessId = %d", pid)
- q := wmi.CreateQuery(&dst, query)
- err := wmi.Query(q, &dst)
- if err != nil {
- return Win32_Process{}, fmt.Errorf("could not get Win32_Process %s: %v", query, err)
- }
- if len(dst) < 1 {
- return Win32_Process{}, fmt.Errorf("could not get Win32_Process %s: Process not found", query)
- }
- return dst[0], nil
- }
- func getWin32OperatingSystem() (Win32_OperatingSystem, error) {
- var dst []Win32_OperatingSystem
- q := wmi.CreateQuery(&dst, "")
- err := wmi.Query(q, &dst)
- if err != nil {
- return Win32_OperatingSystem{}, errors.Wrap(err, "wmi query for Win32_OperatingSystem failed")
- }
- if len(dst) != 1 {
- return Win32_OperatingSystem{}, errors.New("wmi query for Win32_OperatingSystem failed")
- }
- return dst[0], nil
- }
- func (self *Rusage) Get(who int) error {
- if who != 0 {
- return ErrNotImplemented{runtime.GOOS}
- }
- pid := os.Getpid()
- cpu, err := getProcTimes(pid)
- if err != nil {
- return err
- }
- self.Utime = windows.FiletimeToDuration(&cpu.UserTime)
- self.Stime = windows.FiletimeToDuration(&cpu.KernelTime)
- return nil
- }
|