github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/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/metacubex/gvisor/pkg/abi/linux"
    22  	"github.com/metacubex/gvisor/pkg/context"
    23  	"github.com/metacubex/gvisor/pkg/sentry/fsimpl/kernfs"
    24  	"github.com/metacubex/gvisor/pkg/sentry/kernel"
    25  	"github.com/metacubex/gvisor/pkg/sentry/kernel/auth"
    26  	"github.com/metacubex/gvisor/pkg/sentry/usage"
    27  	"github.com/metacubex/gvisor/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  }