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