Add systemd resource budgets with TOML profiles

This commit is contained in:
ms2k 2026-10-05 19:19:05 +09:00
commit be8749a384

119
internal/budget/app.go Normal file
View file

@ -0,0 +1,119 @@
package budget
import (
"fmt"
"io"
"strings"
)
type application struct {
config config
paths paths
ctl unitCtl
run func(...string) error
out io.Writer
}
func (a application) apply() error {
files, err := a.config.render()
if err != nil {
return err
}
if err := writeFiles(a.paths, files); err != nil {
return err
}
if err := a.run("daemon-reload"); err != nil {
return err
}
units, err := a.ctl.Units()
if err != nil {
return err
}
for _, unit := range units {
name := a.config.unitProfile(unit.Name)
if name == "" || unit.Active != "active" {
continue
}
values, err := a.config.properties(name)
if err != nil {
return err
}
if err := a.ctl.Set(unit.Name, values); err != nil {
return err
}
}
if err := a.run("enable", "budget.service"); err != nil {
return err
}
if err := a.run("restart", "budget.service"); err != nil {
return err
}
_, err = fmt.Fprintf(a.out, "Applied %d resource configuration files\n", len(files))
return err
}
func selectUnits(c config, units []unit, target string) []string {
selected := map[string]bool{}
if strings.HasSuffix(target, ".slice") || strings.HasSuffix(target, ".scope") || strings.HasSuffix(target, ".service") {
selected[target] = true
} else {
for _, unit := range units {
if unit.Active == "active" && appMatches(target, unit.Name) {
selected[unit.Name] = true
}
}
if _, shared := c.Shared[target]; shared {
selected[c.sharedSlice()] = true
}
}
return keys(selected)
}
func (a application) override(name, target string) error {
values, err := a.config.properties(name)
if err != nil {
return err
}
units, err := a.ctl.Units()
if err != nil {
return err
}
selected := selectUnits(a.config, units, target)
if len(selected) == 0 {
return fmt.Errorf("no running unit matches %s", target)
}
all := map[string]bool{}
for _, name := range selected {
all[name] = true
}
for _, unit := range selected {
if !strings.HasSuffix(unit, ".slice") {
continue
}
group, err := a.ctl.Group(unit)
if err != nil {
return err
}
for _, child := range units {
if child.Active != "active" || all[child.Name] || (!strings.HasSuffix(child.Name, ".scope") && !strings.HasSuffix(child.Name, ".service")) {
continue
}
childGroup, err := a.ctl.Group(child.Name)
if err != nil {
return err
}
if strings.HasPrefix(childGroup, group+"/") {
all[child.Name] = true
}
}
}
for _, unit := range keys(all) {
if err := a.ctl.Set(unit, values); err != nil {
return err
}
if _, err := fmt.Fprintf(a.out, "%s: %s\n", unit, name); err != nil {
return err
}
}
return nil
}