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 }