Add systemd resource budgets with TOML profiles
This commit is contained in:
commit
be8749a384
1 changed files with 2591 additions and 0 deletions
142
internal/budget/units.go
Normal file
142
internal/budget/units.go
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
package budget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const serviceText = "[Unit]\nDescription=Terminal workload resource profiles\n\n[Service]\nExecStart=%h/.local/bin/budget watch\n\n[Install]\nWantedBy=default.target\n"
|
||||
|
||||
func escape(name string) string {
|
||||
var result strings.Builder
|
||||
for i := range len(name) {
|
||||
b := name[i]
|
||||
switch {
|
||||
case b == '/':
|
||||
result.WriteByte('-')
|
||||
case b >= 'a' && b <= 'z', b >= 'A' && b <= 'Z', b >= '0' && b <= '9', b == ':', b == '_', b == '.' && i != 0:
|
||||
result.WriteByte(b)
|
||||
default:
|
||||
fmt.Fprintf(&result, `\x%02x`, b)
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func appDirs(app string) []string {
|
||||
app = escape(app)
|
||||
return []string{"app-" + app + ".service.d", "app-" + app + "@.service.d", "app-" + app + "-.scope.d", "app-flatpak-" + app + "-.scope.d"}
|
||||
}
|
||||
|
||||
func appMatches(app, unit string) bool {
|
||||
app = escape(app)
|
||||
return unit == "app-"+app+".service" || strings.HasPrefix(unit, "app-"+app+"@") || strings.HasPrefix(unit, "app-"+app+"-") || strings.HasPrefix(unit, "app-flatpak-"+app+"-")
|
||||
}
|
||||
|
||||
func (c config) sharedSlice() string {
|
||||
apps := keys(c.Shared)
|
||||
if len(apps) == 0 {
|
||||
return ""
|
||||
}
|
||||
return "app-" + escape(apps[0]) + ".slice"
|
||||
}
|
||||
|
||||
func (c config) unitProfile(unit string) string {
|
||||
for _, name := range keys(c.Units) {
|
||||
if slices.Contains(c.Units[name], unit) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
for _, name := range keys(c.Apps) {
|
||||
for _, app := range c.Apps[name] {
|
||||
if appMatches(app, unit) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range keys(c.Prefixes) {
|
||||
for _, prefix := range c.Prefixes[name] {
|
||||
parts := strings.Split(prefix, ".")
|
||||
stem := strings.Join(parts[:len(parts)-2], ".")
|
||||
kind := "." + parts[len(parts)-2]
|
||||
if strings.HasPrefix(unit, stem) && strings.HasSuffix(unit, kind) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
if unit == "search.slice" {
|
||||
return "search"
|
||||
}
|
||||
if slice := c.sharedSlice(); slice != "" && unit == slice {
|
||||
return "default"
|
||||
}
|
||||
if strings.HasPrefix(unit, "app-") && (strings.HasSuffix(unit, ".scope") || strings.HasSuffix(unit, ".service")) {
|
||||
return "default"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c config) render() (map[string]string, error) {
|
||||
files := map[string]string{}
|
||||
add := func(directory, name, extra string) error {
|
||||
values, err := c.assignments(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
section := "Service"
|
||||
if strings.Contains(directory, ".scope.") {
|
||||
section = "Scope"
|
||||
}
|
||||
files[directory+"/50-budget.conf"] = "[" + section + "]\n" + strings.Join(values, "\n") + "\n" + extra
|
||||
return nil
|
||||
}
|
||||
for _, directory := range []string{"app-.service.d", "app-.scope.d"} {
|
||||
if err := add(directory, "default", ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, name := range keys(c.Apps) {
|
||||
for _, app := range c.Apps[name] {
|
||||
for _, directory := range appDirs(app) {
|
||||
if err := add(directory, name, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range keys(c.Units) {
|
||||
for _, unit := range c.Units[name] {
|
||||
if err := add(unit+".d", name, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range keys(c.Prefixes) {
|
||||
for _, directory := range c.Prefixes[name] {
|
||||
if err := add(directory, name, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, app := range keys(c.Shared) {
|
||||
for _, directory := range appDirs(app) {
|
||||
if err := add(directory, c.Shared[app], "Slice="+c.sharedSlice()+"\n"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
groups := map[string]string{"search.slice": "search"}
|
||||
if len(c.Shared) > 0 {
|
||||
groups[c.sharedSlice()] = "default"
|
||||
}
|
||||
for unit, name := range groups {
|
||||
values, err := c.assignments(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files[unit] = "[Slice]\n" + strings.Join(values, "\n") + "\n"
|
||||
}
|
||||
files["budget.service"] = serviceText
|
||||
return files, nil
|
||||
}
|
||||
Loading…
Reference in a new issue