-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
172 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// Copyright (c) 2022 Gitpod GmbH. All rights reserved. | ||
// Licensed under the GNU Affero General Public License (AGPL). | ||
// See License-AGPL.txt in the project root for license information. | ||
|
||
package cpulimit | ||
|
||
import ( | ||
"bufio" | ||
"math" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"golang.org/x/xerrors" | ||
) | ||
|
||
type CgroupV2CFSController string | ||
|
||
func (basePath CgroupV2CFSController) Usage() (CPUTime, error) { | ||
usage, err := basePath.getFlatKeyedValue("usage_usec") | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
return CPUTime(time.Duration(usage) * time.Microsecond), nil | ||
} | ||
|
||
func (basePath CgroupV2CFSController) SetLimit(limit Bandwidth) (changed bool, err error) { | ||
quota, period, err := basePath.readCpuMax() | ||
if err != nil { | ||
return false, xerrors.Errorf("failed to read cpu max from %s: %w", basePath, err) | ||
} | ||
|
||
target := limit.Quota(period) | ||
if quota == target { | ||
return false, nil | ||
} | ||
|
||
err = basePath.writeQuota(target) | ||
if err != nil { | ||
return false, xerrors.Errorf("cannot set CFS quota of %d (period is %d, parent quota is %d): %w", | ||
target.Microseconds(), period.Microseconds(), basePath.readParentQuota().Microseconds(), err) | ||
} | ||
|
||
return true, nil | ||
} | ||
|
||
func (basePath CgroupV2CFSController) NrThrottled() (uint64, error) { | ||
throttled, err := basePath.getFlatKeyedValue("nr_throttled") | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
return uint64(throttled), nil | ||
} | ||
|
||
func (basePath CgroupV2CFSController) readCpuMax() (time.Duration, time.Duration, error) { | ||
cpuMaxPath := filepath.Join(string(basePath), "cpu.max") | ||
cpuMax, err := os.ReadFile(cpuMaxPath) | ||
if err != nil { | ||
return 0, 0, xerrors.Errorf("unable to read cpu.max: %w", err) | ||
} | ||
|
||
parts := strings.Fields(string(cpuMax)) | ||
if len(parts) < 2 { | ||
return 0, 0, xerrors.Errorf("cpu.max did not have expected number of fields: %s", parts) | ||
} | ||
|
||
var quota int64 | ||
if parts[0] == "max" { | ||
quota = math.MaxInt64 | ||
} else { | ||
quota, err = strconv.ParseInt(parts[0], 10, 64) | ||
if err != nil { | ||
return 0, 0, xerrors.Errorf("could not parse quota of %s: %w", parts[0], err) | ||
} | ||
} | ||
|
||
period, err := strconv.ParseInt(parts[1], 10, 64) | ||
if err != nil { | ||
return 0, 0, xerrors.Errorf("could not parse period of %s: %w", parts[1], err) | ||
} | ||
|
||
return time.Duration(quota) * time.Microsecond, time.Duration(period) * time.Microsecond, nil | ||
} | ||
|
||
func (basePath CgroupV2CFSController) writeQuota(quota time.Duration) error { | ||
cpuMaxPath := filepath.Join(string(basePath), "cpu.max") | ||
return os.WriteFile(cpuMaxPath, []byte(strconv.FormatInt(quota.Microseconds(), 10)), 0644) | ||
} | ||
|
||
func (basePath CgroupV2CFSController) readParentQuota() time.Duration { | ||
parent := CgroupV2CFSController(filepath.Dir(string(basePath))) | ||
quota, _, err := parent.readCpuMax() | ||
if err != nil { | ||
return time.Duration(0) | ||
} | ||
|
||
return time.Duration(quota) | ||
} | ||
|
||
func (basePath CgroupV2CFSController) getFlatKeyedValue(key string) (int64, error) { | ||
stats, err := os.Open(filepath.Join(string(basePath), "cpu.stat")) | ||
if err != nil { | ||
return 0, xerrors.Errorf("cannot read cpu.stat: %w", err) | ||
} | ||
defer stats.Close() | ||
|
||
scanner := bufio.NewScanner(stats) | ||
for scanner.Scan() { | ||
entry := scanner.Text() | ||
if !strings.HasPrefix(entry, key) { | ||
continue | ||
} | ||
|
||
r, err := strconv.ParseInt(strings.TrimSpace(strings.TrimPrefix(entry, key)), 10, 64) | ||
if err != nil { | ||
return 0, xerrors.Errorf("cannot parse cpu.stat: %s: %w", entry, err) | ||
} | ||
return int64(r), nil | ||
} | ||
return 0, xerrors.Errorf("cpu.stat did not contain nr_throttled") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters