github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/runsc/boot/events.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package boot
    16  
    17  import (
    18  	"fmt"
    19  	"strconv"
    20  
    21  	"github.com/metacubex/gvisor/pkg/log"
    22  	"github.com/metacubex/gvisor/pkg/sentry/control"
    23  	"github.com/metacubex/gvisor/pkg/sentry/usage"
    24  )
    25  
    26  // EventOut is the return type of the Event command.
    27  type EventOut struct {
    28  	Event Event `json:"event"`
    29  
    30  	// ContainerUsage maps each container ID to its total CPU usage.
    31  	ContainerUsage map[string]uint64 `json:"containerUsage"`
    32  }
    33  
    34  // Event struct for encoding the event data to JSON. Corresponds to runc's
    35  // main.event struct.
    36  type Event struct {
    37  	Type string `json:"type"`
    38  	ID   string `json:"id"`
    39  	Data Stats  `json:"data"`
    40  }
    41  
    42  // Stats is the runc specific stats structure for stability when encoding and
    43  // decoding stats.
    44  type Stats struct {
    45  	CPU    CPU    `json:"cpu"`
    46  	Memory Memory `json:"memory"`
    47  	Pids   Pids   `json:"pids"`
    48  }
    49  
    50  // Pids contains stats on processes.
    51  type Pids struct {
    52  	Current uint64 `json:"current,omitempty"`
    53  	Limit   uint64 `json:"limit,omitempty"`
    54  }
    55  
    56  // MemoryEntry contains stats on a kind of memory.
    57  type MemoryEntry struct {
    58  	Limit   uint64 `json:"limit"`
    59  	Usage   uint64 `json:"usage,omitempty"`
    60  	Max     uint64 `json:"max,omitempty"`
    61  	Failcnt uint64 `json:"failcnt"`
    62  }
    63  
    64  // Memory contains stats on memory.
    65  type Memory struct {
    66  	Cache     uint64            `json:"cache,omitempty"`
    67  	Usage     MemoryEntry       `json:"usage,omitempty"`
    68  	Swap      MemoryEntry       `json:"swap,omitempty"`
    69  	Kernel    MemoryEntry       `json:"kernel,omitempty"`
    70  	KernelTCP MemoryEntry       `json:"kernelTCP,omitempty"`
    71  	Raw       map[string]uint64 `json:"raw,omitempty"`
    72  }
    73  
    74  // CPU contains stats on the CPU.
    75  type CPU struct {
    76  	Usage CPUUsage `json:"usage"`
    77  }
    78  
    79  // CPUUsage contains stats on CPU usage.
    80  type CPUUsage struct {
    81  	Kernel uint64   `json:"kernel,omitempty"`
    82  	User   uint64   `json:"user,omitempty"`
    83  	Total  uint64   `json:"total,omitempty"`
    84  	PerCPU []uint64 `json:"percpu,omitempty"`
    85  }
    86  
    87  func (cm *containerManager) getUsageFromCgroups(file control.CgroupControlFile) (uint64, error) {
    88  	var out control.CgroupsResults
    89  	args := control.CgroupsReadArgs{
    90  		Args: []control.CgroupsReadArg{
    91  			{
    92  				File: file,
    93  			},
    94  		},
    95  	}
    96  	cgroups := control.Cgroups{Kernel: cm.l.k}
    97  	if err := cgroups.ReadControlFiles(&args, &out); err != nil {
    98  		return 0, err
    99  	}
   100  	if len(out.Results) != 1 {
   101  		return 0, fmt.Errorf("expected 1 result, got %d, raw: %+v", len(out.Results), out)
   102  	}
   103  	val, err := out.Results[0].Unpack()
   104  	if err != nil {
   105  		return 0, err
   106  	}
   107  	usage, err := strconv.ParseUint(val, 10, 64)
   108  	if err != nil {
   109  		return 0, err
   110  	}
   111  	return usage, nil
   112  }
   113  
   114  // Event gets the events from the container.
   115  func (cm *containerManager) Event(cid *string, out *EventOut) error {
   116  	*out = EventOut{
   117  		Event: Event{
   118  			ID:   *cid,
   119  			Type: "stats",
   120  		},
   121  	}
   122  
   123  	// PIDs and check that container exists before going further.
   124  	pids, err := cm.l.pidsCount(*cid)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	out.Event.Data.Pids.Current = uint64(pids)
   129  
   130  	numContainers := cm.l.containerCount()
   131  	if numContainers == 0 {
   132  		return fmt.Errorf("no container was found")
   133  	}
   134  
   135  	// Memory usage.
   136  	memFile := control.CgroupControlFile{"memory", "/" + *cid, "memory.usage_in_bytes"}
   137  	memUsage, err := cm.getUsageFromCgroups(memFile)
   138  	if err != nil {
   139  		// Cgroups is not installed or there was an error to get usage
   140  		// from the cgroups. Fall back to the old method of getting the
   141  		// usage from the sentry.
   142  		log.Warningf("could not get container memory usage from cgroups, error:  %v", err)
   143  
   144  		mem := cm.l.k.MemoryFile()
   145  		_ = mem.UpdateUsage(nil) // best effort to update.
   146  		_, totalUsage := usage.MemoryAccounting.Copy()
   147  		if numContainers == 1 {
   148  			memUsage = totalUsage
   149  		} else {
   150  			// In the multi-container case, reports 0 for the root (pause)
   151  			// container, since it's small and idle. Then equally split the
   152  			// usage to the other containers. At least the sum of all
   153  			// containers will correctly account for the memory used by the
   154  			// sandbox.
   155  			if *cid == cm.l.sandboxID {
   156  				memUsage = 0
   157  			} else {
   158  				memUsage = totalUsage / uint64(numContainers-1)
   159  			}
   160  		}
   161  	}
   162  	out.Event.Data.Memory.Usage.Usage = memUsage
   163  
   164  	// CPU usage by container.
   165  	cpuacctFile := control.CgroupControlFile{"cpuacct", "/" + *cid, "cpuacct.usage"}
   166  	if cpuUsage, err := cm.getUsageFromCgroups(cpuacctFile); err != nil {
   167  		// Cgroups is not installed or there was an error to get usage
   168  		// from the cgroups. Fall back to the old method of getting the
   169  		// usage from the sentry and host cgroups.
   170  		log.Warningf("could not get container cpu usage from cgroups, error:  %v", err)
   171  
   172  		out.ContainerUsage = control.ContainerUsage(cm.l.k)
   173  	} else {
   174  		out.Event.Data.CPU.Usage.Total = cpuUsage
   175  	}
   176  	return nil
   177  }