1package uvm 2 3import ( 4 "context" 5 "strings" 6 7 "github.com/Microsoft/go-winio/pkg/guid" 8 "github.com/Microsoft/go-winio/pkg/process" 9 "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats" 10 "github.com/Microsoft/hcsshim/internal/log" 11 hcsschema "github.com/Microsoft/hcsshim/internal/schema2" 12 "github.com/pkg/errors" 13 "github.com/sirupsen/logrus" 14 "golang.org/x/sys/windows" 15) 16 17// checkProcess checks if the process identified by the given pid has a name 18// matching `desiredProcessName`, and is running as a user with domain 19// `desiredDomain` and user name `desiredUser`. If the process matches, it 20// returns a handle to the process. If the process does not match, it returns 21// 0. 22func checkProcess(ctx context.Context, pid uint32, desiredProcessName string, desiredDomain string, desiredUser string) (p windows.Handle, err error) { 23 desiredProcessName = strings.ToUpper(desiredProcessName) 24 desiredDomain = strings.ToUpper(desiredDomain) 25 desiredUser = strings.ToUpper(desiredUser) 26 27 p, err = windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION|windows.PROCESS_VM_READ, false, pid) 28 if err != nil { 29 return 0, err 30 } 31 defer func(openedProcess windows.Handle) { 32 // If we don't return this process handle, close it so it doesn't leak. 33 if p == 0 { 34 windows.Close(openedProcess) 35 } 36 }(p) 37 // Querying vmmem's image name as a win32 path returns ERROR_GEN_FAILURE 38 // for some reason, so we query it as an NT path instead. 39 name, err := process.QueryFullProcessImageName(p, process.ImageNameFormatNTPath) 40 if err != nil { 41 return 0, err 42 } 43 if strings.ToUpper(name) == desiredProcessName { 44 var t windows.Token 45 if err := windows.OpenProcessToken(p, windows.TOKEN_QUERY, &t); err != nil { 46 return 0, err 47 } 48 defer t.Close() 49 tUser, err := t.GetTokenUser() 50 if err != nil { 51 return 0, err 52 } 53 user, domain, _, err := tUser.User.Sid.LookupAccount("") 54 if err != nil { 55 return 0, err 56 } 57 log.G(ctx).WithFields(logrus.Fields{ 58 "name": name, 59 "domain": domain, 60 "user": user, 61 }).Debug("checking vmmem process identity") 62 if strings.ToUpper(domain) == desiredDomain && strings.ToUpper(user) == desiredUser { 63 return p, nil 64 } 65 } 66 return 0, nil 67} 68 69// lookupVMMEM locates the vmmem process for a VM given the VM ID. It returns 70// a handle to the vmmem process. The lookup is implemented by enumerating all 71// processes on the system, and finding a process with full name "vmmem", 72// running as "NT VIRTUAL MACHINE\<VM ID>". 73func lookupVMMEM(ctx context.Context, vmID guid.GUID) (proc windows.Handle, err error) { 74 vmIDStr := strings.ToUpper(vmID.String()) 75 log.G(ctx).WithField("vmID", vmIDStr).Debug("looking up vmmem") 76 77 pids, err := process.EnumProcesses() 78 if err != nil { 79 return 0, errors.Wrap(err, "failed to enumerate processes") 80 } 81 for _, pid := range pids { 82 p, err := checkProcess(ctx, pid, "vmmem", "NT VIRTUAL MACHINE", vmIDStr) 83 if err != nil { 84 // Checking the process could fail for a variety of reasons, such as 85 // the process exiting since we called EnumProcesses, or not having 86 // access to open the process (even as SYSTEM). In the case of an 87 // error, we just log and continue looking at the other processes. 88 log.G(ctx).WithField("pid", pid).Debug("failed to check process") 89 continue 90 } 91 if p != 0 { 92 log.G(ctx).WithField("pid", pid).Debug("found vmmem match") 93 return p, nil 94 } 95 } 96 return 0, errors.New("failed to find matching vmmem process") 97} 98 99// getVMMEMProcess returns a handle to the vmmem process associated with this 100// UVM. It only does the actual process lookup once, after which it caches the 101// process handle in the UVM object. 102func (uvm *UtilityVM) getVMMEMProcess(ctx context.Context) (windows.Handle, error) { 103 uvm.vmmemOnce.Do(func() { 104 uvm.vmmemProcess, uvm.vmmemErr = lookupVMMEM(ctx, uvm.runtimeID) 105 }) 106 return uvm.vmmemProcess, uvm.vmmemErr 107} 108 109// Stats returns various UVM statistics. 110func (uvm *UtilityVM) Stats(ctx context.Context) (*stats.VirtualMachineStatistics, error) { 111 s := &stats.VirtualMachineStatistics{} 112 props, err := uvm.hcsSystem.PropertiesV2(ctx, hcsschema.PTStatistics, hcsschema.PTMemory) 113 if err != nil { 114 return nil, err 115 } 116 s.Processor = &stats.VirtualMachineProcessorStatistics{} 117 s.Processor.TotalRuntimeNS = uint64(props.Statistics.Processor.TotalRuntime100ns * 100) 118 119 s.Memory = &stats.VirtualMachineMemoryStatistics{} 120 if uvm.physicallyBacked { 121 // If the uvm is physically backed we set the working set to the total amount allocated 122 // to the UVM. AssignedMemory returns the number of 4KB pages. Will always be 4KB 123 // regardless of what the UVMs actual page size is so we don't need that information. 124 if props.Memory != nil { 125 s.Memory.WorkingSetBytes = props.Memory.VirtualMachineMemory.AssignedMemory * 4096 126 } 127 } else { 128 // The HCS properties does not return sufficient information to calculate 129 // working set size for a VA-backed UVM. To work around this, we instead 130 // locate the vmmem process for the VM, and query that process's working set 131 // instead, which will be the working set for the VM. 132 vmmemProc, err := uvm.getVMMEMProcess(ctx) 133 if err != nil { 134 return nil, err 135 } 136 memCounters, err := process.GetProcessMemoryInfo(vmmemProc) 137 if err != nil { 138 return nil, err 139 } 140 s.Memory.WorkingSetBytes = uint64(memCounters.WorkingSetSize) 141 } 142 143 if props.Memory != nil { 144 s.Memory.VirtualNodeCount = props.Memory.VirtualNodeCount 145 s.Memory.VmMemory = &stats.VirtualMachineMemory{} 146 s.Memory.VmMemory.AvailableMemory = props.Memory.VirtualMachineMemory.AvailableMemory 147 s.Memory.VmMemory.AvailableMemoryBuffer = props.Memory.VirtualMachineMemory.AvailableMemoryBuffer 148 s.Memory.VmMemory.ReservedMemory = props.Memory.VirtualMachineMemory.ReservedMemory 149 s.Memory.VmMemory.AssignedMemory = props.Memory.VirtualMachineMemory.AssignedMemory 150 s.Memory.VmMemory.SlpActive = props.Memory.VirtualMachineMemory.SlpActive 151 s.Memory.VmMemory.BalancingEnabled = props.Memory.VirtualMachineMemory.BalancingEnabled 152 s.Memory.VmMemory.DmOperationInProgress = props.Memory.VirtualMachineMemory.DmOperationInProgress 153 } 154 return s, nil 155} 156