mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
208 lines
5.0 KiB
Go
208 lines
5.0 KiB
Go
// 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 cgroup
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/shirou/gopsutil/process"
|
|
|
|
"github.com/gitpod-io/gitpod/common-go/log"
|
|
)
|
|
|
|
// ProcessType referes to the kinds of prioritisable processes in the cgroup
|
|
type ProcessType string
|
|
|
|
const (
|
|
ProcessWorkspaceKit ProcessType = "workspacekit"
|
|
// ProcessSupervisor referes to a supervisor process
|
|
ProcessSupervisor ProcessType = "supervisor"
|
|
// ProcessIDE refers to node.js IDE process
|
|
ProcessIDE ProcessType = "ide"
|
|
// ProcessWebIDEHelper refers to VS Code Browser process
|
|
ProcessWebIDEHelper ProcessType = "ide-helper"
|
|
// ProcessCodeServer refers to VS Code Desktop IDE process
|
|
ProcessCodeServer ProcessType = "vscode-server"
|
|
// ProcessCodeServerHelper refers to VS Code Desktop child process
|
|
ProcessCodeServerHelper ProcessType = "vscode-server-helper"
|
|
// ProcessDefault referes to any process that is not one of the above
|
|
ProcessDefault ProcessType = "default"
|
|
|
|
// Repeat applying until this number of processes is reached
|
|
NumberOfProcessesToStopApplying = 5
|
|
)
|
|
|
|
type OOMScoreAdjConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Tier1 int `json:"tier1"`
|
|
Tier2 int `json:"tier2"`
|
|
}
|
|
|
|
type ProcessPriorityV2 struct {
|
|
ProcessPriorities map[ProcessType]int
|
|
OOMScoreAdj map[ProcessType]int
|
|
EnableOOMScoreAdj bool
|
|
}
|
|
|
|
func (c *ProcessPriorityV2) Name() string { return "process-priority-v2" }
|
|
func (c *ProcessPriorityV2) Type() Version { return Version2 }
|
|
|
|
func (c *ProcessPriorityV2) Apply(ctx context.Context, opts *PluginOptions) error {
|
|
fullCgroupPath := filepath.Join(opts.BasePath, opts.CgroupPath)
|
|
|
|
t := time.NewTicker(10 * time.Second)
|
|
defer t.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case <-t.C:
|
|
}
|
|
|
|
data, err := ioutil.ReadFile(filepath.Join(fullCgroupPath, "workspace", "user", "cgroup.procs"))
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
// the target cgroup/workspace has gone
|
|
return nil
|
|
} else if err != nil {
|
|
log.WithFields(log.OWI("", "", opts.InstanceId)).WithField("path", fullCgroupPath).WithError(err).Errorf("cannot read cgroup.procs file")
|
|
return err
|
|
}
|
|
|
|
var countRunningProcess int
|
|
lines := strings.Split(string(data), "\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if len(line) == 0 {
|
|
continue
|
|
}
|
|
|
|
pid, err := strconv.ParseInt(line, 10, 64)
|
|
if err != nil {
|
|
log.WithError(err).WithFields(log.OWI("", "", opts.InstanceId)).WithField("line", line).Warn("cannot parse pid")
|
|
continue
|
|
}
|
|
|
|
proc, err := process.NewProcess(int32(pid))
|
|
if err != nil {
|
|
if errors.Is(err, process.ErrorProcessNotRunning) {
|
|
continue
|
|
}
|
|
|
|
log.WithError(err).WithFields(log.OWI("", "", opts.InstanceId)).WithField("pid", pid).Warn("cannot get process")
|
|
continue
|
|
}
|
|
|
|
procType := determineProcessType(proc)
|
|
if procType == ProcessDefault {
|
|
continue
|
|
}
|
|
|
|
c.adaptProcessPriorites(procType, pid)
|
|
if c.EnableOOMScoreAdj {
|
|
c.adaptOOMScore(procType, pid)
|
|
}
|
|
}
|
|
|
|
if countRunningProcess >= NumberOfProcessesToStopApplying {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
var (
|
|
vsCodeNodeRegex = regexp.MustCompile("/home/gitpod/.vscode-server/bin/.*/node")
|
|
)
|
|
|
|
func determineProcessType(p *process.Process) ProcessType {
|
|
cmd := extractCommand(p)
|
|
if len(cmd) == 0 {
|
|
return ProcessDefault
|
|
}
|
|
|
|
if strings.HasSuffix(cmd[0], "workspacekit") || (len(cmd) >= 2 && cmd[1] == "ring1") {
|
|
return ProcessWorkspaceKit
|
|
}
|
|
|
|
if strings.HasSuffix(cmd[0], "supervisor") {
|
|
return ProcessSupervisor
|
|
}
|
|
|
|
if strings.HasSuffix(cmd[0], "/bin/code-server") {
|
|
return ProcessCodeServer
|
|
}
|
|
|
|
if vsCodeNodeRegex.MatchString(cmd[0]) {
|
|
return ProcessCodeServerHelper
|
|
}
|
|
|
|
if strings.HasSuffix(cmd[0], "/ide/bin/gitpod-code") {
|
|
return ProcessIDE
|
|
}
|
|
|
|
if strings.HasSuffix(cmd[0], "/ide/node") {
|
|
return ProcessWebIDEHelper
|
|
}
|
|
|
|
return ProcessDefault
|
|
}
|
|
|
|
func extractCommand(p *process.Process) []string {
|
|
if p == nil {
|
|
return []string{}
|
|
}
|
|
|
|
cmdLine, err := p.CmdlineSlice()
|
|
if err != nil {
|
|
return []string{}
|
|
}
|
|
|
|
if len(cmdLine) == 0 {
|
|
return []string{}
|
|
}
|
|
|
|
cmd := cmdLine[0]
|
|
if cmd == "/bin/bash" || cmd == "sh" {
|
|
return cmdLine[1:]
|
|
}
|
|
|
|
return cmdLine
|
|
}
|
|
|
|
func (c *ProcessPriorityV2) adaptProcessPriorites(procType ProcessType, pid int64) {
|
|
priority, ok := c.ProcessPriorities[procType]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
err := syscall.Setpriority(syscall.PRIO_PROCESS, int(pid), priority)
|
|
if err != nil {
|
|
log.WithError(err).WithField("pid", pid).WithField("priority", priority).Warn("cannot set process priority")
|
|
}
|
|
}
|
|
|
|
func (c *ProcessPriorityV2) adaptOOMScore(procType ProcessType, pid int64) {
|
|
oomScoreAdj, ok := c.OOMScoreAdj[procType]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
err := os.WriteFile(fmt.Sprintf("/proc/%v/oom_score_adj", pid), []byte(strconv.Itoa(oomScoreAdj)), 0644)
|
|
if err != nil {
|
|
log.WithError(err).WithField("pid", pid).WithField("oomScoreAdj", oomScoreAdj).Warn("cannot set oomScoreAdj")
|
|
}
|
|
}
|