github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/fsimpl/cgroupfs/cpuacct.go (about) 1 // Copyright 2021 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 cgroupfs 16 17 import ( 18 "bytes" 19 "fmt" 20 21 "github.com/ttpreport/gvisor-ligolo/pkg/abi/linux" 22 "github.com/ttpreport/gvisor-ligolo/pkg/context" 23 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/fsimpl/kernfs" 24 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel" 25 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel/auth" 26 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/usage" 27 "github.com/ttpreport/gvisor-ligolo/pkg/sync" 28 ) 29 30 // cpuacctController tracks CPU usage for tasks managed by the controller. The 31 // sentry already tracks CPU usage per task; the controller tries to avoid 32 // duplicate bookkeeping. When a task moves into a cpuacct cgroup, for currently 33 // running tasks we simple refer to the tasks themselves when asked to report 34 // usage. Things get more interesting when tasks leave the cgroup, since we need 35 // to attribute the usage across multiple cgroups. 36 // 37 // On migration, we attribute the task's usage up to the point of migration to 38 // the src cgroup, and keep track of how much of the overall usage to discount 39 // at the dst cgroup. 40 // 41 // On task exit, we attribute all unaccounted usage to the current cgroup and 42 // stop tracking the task. 43 // 44 // +stateify savable 45 type cpuacctController struct { 46 controllerCommon 47 controllerNoResource 48 49 mu sync.Mutex `state:"nosave"` 50 51 // taskCommittedCharges tracks charges for a task already attributed to this 52 // cgroup. This is used to avoid double counting usage for live 53 // tasks. Protected by mu. 54 taskCommittedCharges map[*kernel.Task]usage.CPUStats 55 56 // usage is the cumulative CPU time used by past tasks in this cgroup. Note 57 // that this doesn't include usage by live tasks currently in the 58 // cgroup. Protected by mu. 59 usage usage.CPUStats 60 } 61 62 var _ controller = (*cpuacctController)(nil) 63 64 func newCPUAcctController(fs *filesystem) *cpuacctController { 65 c := &cpuacctController{ 66 taskCommittedCharges: make(map[*kernel.Task]usage.CPUStats), 67 } 68 c.controllerCommon.init(kernel.CgroupControllerCPUAcct, fs) 69 return c 70 } 71 72 // Clone implements controller.Clone. 73 func (c *cpuacctController) Clone() controller { 74 new := &cpuacctController{ 75 taskCommittedCharges: make(map[*kernel.Task]usage.CPUStats), 76 } 77 new.controllerCommon.cloneFromParent(c) 78 return new 79 } 80 81 // AddControlFiles implements controller.AddControlFiles. 82 func (c *cpuacctController) AddControlFiles(ctx context.Context, creds *auth.Credentials, cg *cgroupInode, contents map[string]kernfs.Inode) { 83 cpuacctCG := &cpuacctCgroup{cg} 84 contents["cpuacct.stat"] = c.fs.newControllerFile(ctx, creds, &cpuacctStatData{cpuacctCG}, true) 85 contents["cpuacct.usage"] = c.fs.newControllerFile(ctx, creds, &cpuacctUsageData{cpuacctCG}, true) 86 contents["cpuacct.usage_user"] = c.fs.newControllerFile(ctx, creds, &cpuacctUsageUserData{cpuacctCG}, true) 87 contents["cpuacct.usage_sys"] = c.fs.newControllerFile(ctx, creds, &cpuacctUsageSysData{cpuacctCG}, true) 88 } 89 90 // Enter implements controller.Enter. 91 func (c *cpuacctController) Enter(t *kernel.Task) {} 92 93 // Leave implements controller.Leave. 94 func (c *cpuacctController) Leave(t *kernel.Task) { 95 charge := t.CPUStats() 96 c.mu.Lock() 97 outstandingCharge := charge.DifferenceSince(c.taskCommittedCharges[t]) 98 c.usage.Accumulate(outstandingCharge) 99 delete(c.taskCommittedCharges, t) 100 c.mu.Unlock() 101 } 102 103 // PrepareMigrate implements controller.PrepareMigrate. 104 func (c *cpuacctController) PrepareMigrate(t *kernel.Task, src controller) error { 105 return nil 106 } 107 108 // CommitMigrate implements controller.CommitMigrate. 109 func (c *cpuacctController) CommitMigrate(t *kernel.Task, src controller) { 110 charge := t.CPUStats() 111 112 // Commit current charge to src and stop tracking t at src. 113 srcCtl := src.(*cpuacctController) 114 srcCtl.mu.Lock() 115 srcTaskCharge := srcCtl.taskCommittedCharges[t] 116 outstandingCharge := charge.DifferenceSince(srcTaskCharge) 117 srcCtl.usage.Accumulate(outstandingCharge) 118 delete(srcCtl.taskCommittedCharges, t) 119 srcCtl.mu.Unlock() 120 121 // Start tracking charge at dst, excluding the charge at src. 122 c.mu.Lock() 123 c.taskCommittedCharges[t] = charge 124 c.mu.Unlock() 125 } 126 127 // AbortMigrate implements controller.AbortMigrate. 128 func (c *cpuacctController) AbortMigrate(t *kernel.Task, src controller) {} 129 130 // +stateify savable 131 type cpuacctCgroup struct { 132 *cgroupInode 133 } 134 135 func (c *cpuacctCgroup) cpuacctController() *cpuacctController { 136 return c.controllers[kernel.CgroupControllerCPUAcct].(*cpuacctController) 137 } 138 139 // checklocks:c.fs.tasksMu 140 func (c *cpuacctCgroup) collectCPUStatsLocked(acc *usage.CPUStats) { 141 ctl := c.cpuacctController() 142 for t := range c.ts { 143 charge := t.CPUStats() 144 ctl.mu.Lock() 145 outstandingCharge := charge.DifferenceSince(ctl.taskCommittedCharges[t]) 146 ctl.mu.Unlock() 147 acc.Accumulate(outstandingCharge) 148 } 149 ctl.mu.Lock() 150 acc.Accumulate(ctl.usage) 151 ctl.mu.Unlock() 152 153 c.forEachChildDir(func(d *dir) { 154 cg := cpuacctCgroup{d.cgi} 155 cg.collectCPUStatsLocked(acc) 156 }) 157 } 158 159 func (c *cpuacctCgroup) collectCPUStats() usage.CPUStats { 160 c.fs.tasksMu.RLock() 161 defer c.fs.tasksMu.RUnlock() 162 163 var cs usage.CPUStats 164 c.collectCPUStatsLocked(&cs) 165 return cs 166 } 167 168 // +stateify savable 169 type cpuacctStatData struct { 170 *cpuacctCgroup 171 } 172 173 // Generate implements vfs.DynamicBytesSource.Generate. 174 func (d *cpuacctStatData) Generate(ctx context.Context, buf *bytes.Buffer) error { 175 cs := d.collectCPUStats() 176 fmt.Fprintf(buf, "user %d\n", linux.ClockTFromDuration(cs.UserTime)) 177 fmt.Fprintf(buf, "system %d\n", linux.ClockTFromDuration(cs.SysTime)) 178 return nil 179 } 180 181 // +stateify savable 182 type cpuacctUsageData struct { 183 *cpuacctCgroup 184 } 185 186 // Generate implements vfs.DynamicBytesSource.Generate. 187 func (d *cpuacctUsageData) Generate(ctx context.Context, buf *bytes.Buffer) error { 188 cs := d.collectCPUStats() 189 fmt.Fprintf(buf, "%d\n", cs.UserTime.Nanoseconds()+cs.SysTime.Nanoseconds()) 190 return nil 191 } 192 193 // +stateify savable 194 type cpuacctUsageUserData struct { 195 *cpuacctCgroup 196 } 197 198 // Generate implements vfs.DynamicBytesSource.Generate. 199 func (d *cpuacctUsageUserData) Generate(ctx context.Context, buf *bytes.Buffer) error { 200 cs := d.collectCPUStats() 201 fmt.Fprintf(buf, "%d\n", cs.UserTime.Nanoseconds()) 202 return nil 203 } 204 205 // +stateify savable 206 type cpuacctUsageSysData struct { 207 *cpuacctCgroup 208 } 209 210 // Generate implements vfs.DynamicBytesSource.Generate. 211 func (d *cpuacctUsageSysData) Generate(ctx context.Context, buf *bytes.Buffer) error { 212 cs := d.collectCPUStats() 213 fmt.Fprintf(buf, "%d\n", cs.SysTime.Nanoseconds()) 214 return nil 215 }