github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/statsutil/stats_linux.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package statsutil 18 19 import ( 20 "time" 21 22 v1 "github.com/containerd/cgroups/v3/cgroup1/stats" 23 v2 "github.com/containerd/cgroups/v3/cgroup2/stats" 24 "github.com/vishvananda/netlink" 25 ) 26 27 func SetCgroupStatsFields(previousStats *ContainerStats, data *v1.Metrics, links []netlink.Link) (StatsEntry, error) { 28 29 cpuPercent := calculateCgroupCPUPercent(previousStats, data) 30 blkRead, blkWrite := calculateCgroupBlockIO(data) 31 mem := calculateCgroupMemUsage(data) 32 memLimit := float64(data.Memory.Usage.Limit) 33 memPercent := calculateMemPercent(memLimit, mem) 34 pidsStatsCurrent := data.Pids.Current 35 netRx, netTx := calculateCgroupNetwork(links) 36 37 return StatsEntry{ 38 CPUPercentage: cpuPercent, 39 Memory: mem, 40 MemoryPercentage: memPercent, 41 MemoryLimit: memLimit, 42 NetworkRx: netRx, 43 NetworkTx: netTx, 44 BlockRead: float64(blkRead), 45 BlockWrite: float64(blkWrite), 46 PidsCurrent: pidsStatsCurrent, 47 }, nil 48 49 } 50 51 func SetCgroup2StatsFields(previousStats *ContainerStats, metrics *v2.Metrics, links []netlink.Link) (StatsEntry, error) { 52 53 cpuPercent := calculateCgroup2CPUPercent(previousStats, metrics) 54 blkRead, blkWrite := calculateCgroup2IO(metrics) 55 mem := calculateCgroup2MemUsage(metrics) 56 memLimit := float64(metrics.Memory.UsageLimit) 57 memPercent := calculateMemPercent(memLimit, mem) 58 pidsStatsCurrent := metrics.Pids.Current 59 netRx, netTx := calculateCgroupNetwork(links) 60 61 return StatsEntry{ 62 CPUPercentage: cpuPercent, 63 Memory: mem, 64 MemoryPercentage: memPercent, 65 MemoryLimit: memLimit, 66 NetworkRx: netRx, 67 NetworkTx: netTx, 68 BlockRead: float64(blkRead), 69 BlockWrite: float64(blkWrite), 70 PidsCurrent: pidsStatsCurrent, 71 }, nil 72 73 } 74 75 func calculateCgroupCPUPercent(previousStats *ContainerStats, metrics *v1.Metrics) float64 { 76 var ( 77 cpuPercent = 0.0 78 // calculate the change for the cpu usage of the container in between readings 79 cpuDelta = float64(metrics.CPU.Usage.Total) - float64(previousStats.CgroupCPU) 80 // calculate the change for the entire system between readings 81 systemDelta = float64(metrics.CPU.Usage.Kernel) - float64(previousStats.CgroupSystem) 82 onlineCPUs = float64(len(metrics.CPU.Usage.PerCPU)) 83 ) 84 85 if systemDelta > 0.0 && cpuDelta > 0.0 { 86 cpuPercent = (cpuDelta / systemDelta) * onlineCPUs * 100.0 87 } 88 return cpuPercent 89 } 90 91 // PercpuUsage is not supported in CgroupV2 92 func calculateCgroup2CPUPercent(previousStats *ContainerStats, metrics *v2.Metrics) float64 { 93 var ( 94 cpuPercent = 0.0 95 // calculate the change for the cpu usage of the container in between readings 96 cpuDelta = float64(metrics.CPU.UsageUsec*1000) - float64(previousStats.Cgroup2CPU) 97 // calculate the change for the entire system between readings 98 _ = float64(metrics.CPU.SystemUsec*1000) - float64(previousStats.Cgroup2System) 99 // time duration 100 timeDelta = time.Since(previousStats.Time) 101 ) 102 if cpuDelta > 0.0 { 103 cpuPercent = cpuDelta / float64(timeDelta.Nanoseconds()) * 100.0 104 } 105 return cpuPercent 106 } 107 108 func calculateCgroupMemUsage(metrics *v1.Metrics) float64 { 109 if v := metrics.Memory.TotalInactiveFile; v < metrics.Memory.Usage.Usage { 110 return float64(metrics.Memory.Usage.Usage - v) 111 } 112 return float64(metrics.Memory.Usage.Usage) 113 } 114 115 func calculateCgroup2MemUsage(metrics *v2.Metrics) float64 { 116 if v := metrics.Memory.InactiveFile; v < metrics.Memory.Usage { 117 return float64(metrics.Memory.Usage - v) 118 } 119 return float64(metrics.Memory.Usage) 120 } 121 122 func calculateCgroupBlockIO(metrics *v1.Metrics) (uint64, uint64) { 123 var blkRead, blkWrite uint64 124 for _, bioEntry := range metrics.Blkio.IoServiceBytesRecursive { 125 if len(bioEntry.Op) == 0 { 126 continue 127 } 128 switch bioEntry.Op[0] { 129 case 'r', 'R': 130 blkRead = blkRead + bioEntry.Value 131 case 'w', 'W': 132 blkWrite = blkWrite + bioEntry.Value 133 } 134 } 135 return blkRead, blkWrite 136 } 137 138 func calculateCgroup2IO(metrics *v2.Metrics) (uint64, uint64) { 139 var ioRead, ioWrite uint64 140 141 for _, iOEntry := range metrics.Io.Usage { 142 if iOEntry.Rios == 0 && iOEntry.Wios == 0 { 143 continue 144 } 145 146 if iOEntry.Rios != 0 { 147 ioRead = ioRead + iOEntry.Rbytes 148 } 149 150 if iOEntry.Wios != 0 { 151 ioWrite = ioWrite + iOEntry.Wbytes 152 } 153 } 154 155 return ioRead, ioWrite 156 } 157 158 func calculateCgroupNetwork(links []netlink.Link) (float64, float64) { 159 var rx, tx float64 160 161 for _, l := range links { 162 stats := l.Attrs().Statistics 163 if stats != nil { 164 rx += float64(stats.RxBytes) 165 tx += float64(stats.TxBytes) 166 } 167 } 168 return rx, tx 169 }