...

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

Documentation: github.com/Microsoft/hcsshim/internal/uvm

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

View as plain text