Skip to content

Commit 5e71717

Browse files
authored
Add DBus node for swap size and swappiness config (#222)
* Add DBus node for swap size and swappiness config Add /io/hass/os/Config/Swap object that exposes interface with properties controlling the swap size and swappiness configuration added in [1] and [2]. There are no checks whether this supported on the target system (while swappiness would probably have effect also on Supervised installs, swap size can't be controlled there). These checks should be implemented on upper layer (i.e. Supervisor). [1] home-assistant/operating-system#3882 [2] home-assistant/operating-system#3884 * Read default/current swappiness from procfs
1 parent 812c1f3 commit 5e71717

File tree

3 files changed

+184
-3
lines changed

3 files changed

+184
-3
lines changed

config/swap/swap.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package swap
2+
3+
import (
4+
"fmt"
5+
"github.com/godbus/dbus/v5"
6+
"github.com/godbus/dbus/v5/introspect"
7+
"github.com/godbus/dbus/v5/prop"
8+
"github.com/home-assistant/os-agent/utils/lineinfile"
9+
logging "github.com/home-assistant/os-agent/utils/log"
10+
"os"
11+
"regexp"
12+
"strconv"
13+
"strings"
14+
)
15+
16+
const (
17+
objectPath = "/io/hass/os/Config/Swap"
18+
ifaceName = "io.hass.os.Config.Swap"
19+
swapPath = "/etc/default/haos-swapfile"
20+
swappinessPath = "/etc/sysctl.d/15-swappiness.conf"
21+
)
22+
23+
var (
24+
optSwapSize string
25+
optSwappiness int
26+
swapFileEditor = lineinfile.LineInFile{FilePath: swapPath}
27+
swappinessEditor = lineinfile.LineInFile{FilePath: swappinessPath}
28+
)
29+
30+
type swap struct {
31+
conn *dbus.Conn
32+
props *prop.Properties
33+
}
34+
35+
// Read swappiness from kernel procfs. If it fails, log errors and return 60
36+
// as it's usual kernel default.
37+
func readKernelSwappiness() int {
38+
content, err := os.ReadFile("/proc/sys/vm/swappiness")
39+
if err != nil {
40+
logging.Error.Printf("Failed to read kernel swappiness: %s", err)
41+
return 60
42+
}
43+
44+
swappiness, err := strconv.Atoi(strings.TrimSpace(string(content)))
45+
if err != nil {
46+
logging.Error.Printf("Failed to parse kernel swappiness: %s", err)
47+
return 60
48+
}
49+
50+
return swappiness
51+
}
52+
53+
func getSwapSize() string {
54+
found, err := swapFileEditor.Find(`^SWAPSIZE=`, "", true)
55+
if found == nil || err != nil {
56+
return ""
57+
}
58+
59+
matches := regexp.MustCompile(`^SWAPSIZE=(.*)`).FindStringSubmatch(*found)
60+
if len(matches) > 1 {
61+
return matches[1]
62+
}
63+
64+
return ""
65+
}
66+
67+
func getSwappiness() int {
68+
found, err := swappinessEditor.Find(`^vm.swappiness\s*=`, "", true)
69+
if found == nil || err != nil {
70+
return readKernelSwappiness()
71+
}
72+
73+
matches := regexp.MustCompile(`^vm.swappiness\s*=\s*(\d+)`).FindStringSubmatch(*found)
74+
if len(matches) > 1 {
75+
if swappiness, err := strconv.Atoi(matches[1]); err == nil {
76+
return swappiness
77+
}
78+
}
79+
80+
return readKernelSwappiness()
81+
}
82+
83+
func setSwapSize(c *prop.Change) *dbus.Error {
84+
swapSize, ok := c.Value.(string)
85+
if !ok {
86+
return dbus.MakeFailedError(fmt.Errorf("invalid type for swap size"))
87+
}
88+
89+
re := regexp.MustCompile(`^\d+([KMG]?(i?B)?)?$`)
90+
if !re.MatchString(swapSize) {
91+
return dbus.MakeFailedError(fmt.Errorf("invalid swap size format"))
92+
}
93+
94+
params := lineinfile.NewPresentParams(fmt.Sprintf("SWAPSIZE=%s", swapSize))
95+
params.Regexp, _ = regexp.Compile(`^[#\s]*SWAPSIZE=`)
96+
97+
if err := swapFileEditor.Present(params); err != nil {
98+
return dbus.MakeFailedError(fmt.Errorf("failed to set swap size: %w", err))
99+
}
100+
101+
return nil
102+
}
103+
104+
func setSwappiness(c *prop.Change) *dbus.Error {
105+
swappiness, ok := c.Value.(int32)
106+
if !ok {
107+
return dbus.MakeFailedError(fmt.Errorf("swappiness must be int32, got %T", c.Value))
108+
}
109+
110+
if swappiness < 0 || swappiness > 100 {
111+
return dbus.MakeFailedError(fmt.Errorf("swappiness must be between 0 and 100"))
112+
}
113+
114+
params := lineinfile.NewPresentParams(fmt.Sprintf("vm.swappiness=%d", swappiness))
115+
params.Regexp, _ = regexp.Compile(`^[#\s]*vm.swappiness\s*=`)
116+
117+
if err := swappinessEditor.Present(params); err != nil {
118+
return dbus.MakeFailedError(fmt.Errorf("failed to set swappiness: %w", err))
119+
}
120+
121+
return nil
122+
}
123+
124+
func InitializeDBus(conn *dbus.Conn) {
125+
d := swap{
126+
conn: conn,
127+
}
128+
129+
optSwapSize = getSwapSize()
130+
optSwappiness = getSwappiness()
131+
132+
propsSpec := map[string]map[string]*prop.Prop{
133+
ifaceName: {
134+
"SwapSize": {
135+
Value: optSwapSize,
136+
Writable: true,
137+
Emit: prop.EmitTrue,
138+
Callback: setSwapSize,
139+
},
140+
"Swappiness": {
141+
Value: optSwappiness,
142+
Writable: true,
143+
Emit: prop.EmitTrue,
144+
Callback: setSwappiness,
145+
},
146+
},
147+
}
148+
149+
props, err := prop.Export(conn, objectPath, propsSpec)
150+
if err != nil {
151+
logging.Critical.Panic(err)
152+
}
153+
d.props = props
154+
155+
err = conn.Export(d, objectPath, ifaceName)
156+
if err != nil {
157+
logging.Critical.Panic(err)
158+
}
159+
160+
node := &introspect.Node{
161+
Name: objectPath,
162+
Interfaces: []introspect.Interface{
163+
introspect.IntrospectData,
164+
prop.IntrospectData,
165+
{
166+
Name: ifaceName,
167+
Methods: introspect.Methods(d),
168+
Properties: props.Introspection(ifaceName),
169+
},
170+
},
171+
}
172+
173+
err = conn.Export(introspect.NewIntrospectable(node), objectPath, "org.freedesktop.DBus.Introspectable")
174+
if err != nil {
175+
logging.Critical.Panic(err)
176+
}
177+
178+
logging.Info.Printf("Exposing object %s with interface %s ...", objectPath, ifaceName)
179+
}

config/timesyncd.go renamed to config/timesyncd/timesyncd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package config
1+
package timesyncd
22

33
import (
44
"fmt"

main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import (
1212
"github.com/home-assistant/os-agent/apparmor"
1313
"github.com/home-assistant/os-agent/boards"
1414
"github.com/home-assistant/os-agent/cgroup"
15-
"github.com/home-assistant/os-agent/config"
15+
"github.com/home-assistant/os-agent/config/swap"
16+
"github.com/home-assistant/os-agent/config/timesyncd"
1617
"github.com/home-assistant/os-agent/datadisk"
1718
"github.com/home-assistant/os-agent/system"
1819
logging "github.com/home-assistant/os-agent/utils/log"
@@ -71,7 +72,8 @@ func main() {
7172
apparmor.InitializeDBus(conn)
7273
cgroup.InitializeDBus(conn)
7374
boards.InitializeDBus(conn, board)
74-
config.InitializeDBus(conn)
75+
swap.InitializeDBus(conn)
76+
timesyncd.InitializeDBus(conn)
7577

7678
_, err = daemon.SdNotify(false, daemon.SdNotifyReady)
7779
if err != nil {

0 commit comments

Comments
 (0)