...

Source file src/github.com/Microsoft/hcsshim/internal/vm/hcs/stats.go

Documentation: github.com/Microsoft/hcsshim/internal/vm/hcs

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

View as plain text