Path: blob/main/components/ws-daemon/pkg/cgroup/plugin_process_priority_v2.go
2499 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package cgroup56import (7"context"8"errors"9"fmt"10"io/fs"11"io/ioutil"12"os"13"path/filepath"14"regexp"15"strconv"16"strings"17"syscall"18"time"1920"github.com/shirou/gopsutil/process"2122"github.com/gitpod-io/gitpod/common-go/log"23)2425// ProcessType referes to the kinds of prioritisable processes in the cgroup26type ProcessType string2728const (29ProcessWorkspaceKit ProcessType = "workspacekit"30// ProcessSupervisor referes to a supervisor process31ProcessSupervisor ProcessType = "supervisor"32// ProcessIDE refers to node.js IDE process33ProcessIDE ProcessType = "ide"34// ProcessWebIDEHelper refers to VS Code Browser process35ProcessWebIDEHelper ProcessType = "ide-helper"36// ProcessCodeServer refers to VS Code Desktop IDE process37ProcessCodeServer ProcessType = "vscode-server"38// ProcessCodeServerHelper refers to VS Code Desktop child process39ProcessCodeServerHelper ProcessType = "vscode-server-helper"40// ProcessJetBrainsIDE refers to JetBrains IDE process41ProcessJetBrainsIDE ProcessType = "jetbrains-ide"42// ProcessDefault referes to any process that is not one of the above43ProcessDefault ProcessType = "default"4445// Repeat applying until this number of processes is reached46NumberOfProcessesToStopApplying = 547)4849type OOMScoreAdjConfig struct {50Enabled bool `json:"enabled"`51Tier1 int `json:"tier1"`52Tier2 int `json:"tier2"`53}5455type ProcessPriorityV2 struct {56ProcessPriorities map[ProcessType]int57OOMScoreAdj map[ProcessType]int58EnableOOMScoreAdj bool59}6061func (c *ProcessPriorityV2) Name() string { return "process-priority-v2" }62func (c *ProcessPriorityV2) Type() Version { return Version2 }6364func (c *ProcessPriorityV2) Apply(ctx context.Context, opts *PluginOptions) error {65fullCgroupPath := filepath.Join(opts.BasePath, opts.CgroupPath)6667t := time.NewTicker(10 * time.Second)68defer t.Stop()6970for {71select {72case <-ctx.Done():73return nil74case <-t.C:75}7677data, err := ioutil.ReadFile(filepath.Join(fullCgroupPath, "workspace", "user", "cgroup.procs"))78if errors.Is(err, fs.ErrNotExist) {79// the target cgroup/workspace has gone80return nil81} else if err != nil {82log.WithFields(log.OWI("", "", opts.InstanceId)).WithField("path", fullCgroupPath).WithError(err).Errorf("cannot read cgroup.procs file")83return err84}8586var countRunningProcess int87lines := strings.Split(string(data), "\n")88for _, line := range lines {89line = strings.TrimSpace(line)90if len(line) == 0 {91continue92}9394pid, err := strconv.ParseInt(line, 10, 64)95if err != nil {96log.WithError(err).WithFields(log.OWI("", "", opts.InstanceId)).WithField("line", line).Warn("cannot parse pid")97continue98}99100proc, err := process.NewProcess(int32(pid))101if err != nil {102if errors.Is(err, process.ErrorProcessNotRunning) {103continue104}105106log.WithError(err).WithFields(log.OWI("", "", opts.InstanceId)).WithField("pid", pid).Warn("cannot get process")107continue108}109110procType := determineProcessType(proc)111if procType == ProcessDefault {112continue113}114115c.adaptProcessPriorites(procType, pid)116if c.EnableOOMScoreAdj {117c.adaptOOMScore(procType, pid)118}119}120121if countRunningProcess >= NumberOfProcessesToStopApplying {122return nil123}124}125}126127var (128vsCodeNodeRegex = regexp.MustCompile("/home/gitpod/.vscode-server/bin/.*/node")129jetBrainsRegex = regexp.MustCompile("/ide-desktop/.*/backend/plugins/remote-dev-server/selfcontained/lib")130)131132func determineProcessType(p *process.Process) ProcessType {133cmd := extractCommand(p)134if len(cmd) == 0 {135return ProcessDefault136}137138if strings.HasSuffix(cmd[0], "workspacekit") || (len(cmd) >= 2 && cmd[1] == "ring1") {139return ProcessWorkspaceKit140}141142if strings.HasSuffix(cmd[0], "supervisor") {143return ProcessSupervisor144}145146if strings.HasSuffix(cmd[0], "/bin/code-server") {147return ProcessCodeServer148}149150if vsCodeNodeRegex.MatchString(cmd[0]) {151return ProcessCodeServerHelper152}153154if strings.HasSuffix(cmd[0], "/ide/bin/gitpod-code") {155return ProcessIDE156}157158if strings.HasSuffix(cmd[0], "/ide/node") {159return ProcessWebIDEHelper160}161162if jetBrainsRegex.MatchString(strings.Join(cmd, " ")) {163return ProcessJetBrainsIDE164}165166return ProcessDefault167}168169func extractCommand(p *process.Process) []string {170if p == nil {171return []string{}172}173174cmdLine, err := p.CmdlineSlice()175if err != nil {176return []string{}177}178179if len(cmdLine) == 0 {180return []string{}181}182183cmd := cmdLine[0]184if cmd == "/bin/bash" || cmd == "sh" {185return cmdLine[1:]186}187188return cmdLine189}190191func (c *ProcessPriorityV2) adaptProcessPriorites(procType ProcessType, pid int64) {192priority, ok := c.ProcessPriorities[procType]193if !ok {194return195}196197err := syscall.Setpriority(syscall.PRIO_PROCESS, int(pid), priority)198if err != nil {199log.WithError(err).WithField("pid", pid).WithField("priority", priority).Warn("cannot set process priority")200}201}202203func (c *ProcessPriorityV2) adaptOOMScore(procType ProcessType, pid int64) {204oomScoreAdj, ok := c.OOMScoreAdj[procType]205if !ok {206return207}208209err := os.WriteFile(fmt.Sprintf("/proc/%v/oom_score_adj", pid), []byte(strconv.Itoa(oomScoreAdj)), 0644)210if err != nil {211log.WithError(err).WithField("pid", pid).WithField("oomScoreAdj", oomScoreAdj).Warn("cannot set oomScoreAdj")212}213}214215216