master
go 186 lines 4.18 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 //go:build linux
4
5 package systemdunits
6
7 import (
8 "context"
9 "fmt"
10 "strconv"
11 "strings"
12
13 "github.com/coreos/go-systemd/v22/dbus"
14 )
15
16 const transientProperty = "Transient"
17
18 const (
19 // https://www.freedesktop.org/software/systemd/man/systemd.html
20 unitStateActive = "active"
21 unitStateInactive = "inactive"
22 unitStateActivating = "activating"
23 unitStateDeactivating = "deactivating"
24 unitStateFailed = "failed"
25 )
26
27 var unitStates = []string{
28 unitStateActive,
29 unitStateActivating,
30 unitStateFailed,
31 unitStateInactive,
32 unitStateDeactivating,
33 }
34
35 func (c *Collector) collectUnits(mx map[string]int64, conn systemdConnection) error {
36 var units []dbus.UnitStatus
37 var err error
38
39 if c.systemdVersion >= 230 {
40 // https://github.com/systemd/systemd/pull/3142
41 units, err = c.getLoadedUnitsByPatterns(conn)
42 } else {
43 units, err = c.getLoadedUnits(conn)
44 }
45 if err != nil {
46 return err
47 }
48
49 seen := make(map[string]bool)
50
51 for _, unit := range units {
52 name, typ, ok := extractUnitNameType(unit.Name)
53 if !ok {
54 continue
55 }
56
57 seen[unit.Name] = true
58
59 if c.SkipTransient {
60 if _, ok := c.unitTransient[unit.Name]; !ok {
61 prop, err := c.getUnitTransientProperty(conn, unit.Name)
62 if err != nil {
63 return err
64 }
65 prop = strings.Trim(prop, "\"")
66 c.unitTransient[unit.Name] = prop == "true"
67 }
68 if c.unitTransient[unit.Name] {
69 continue
70 }
71 }
72
73 if !c.seenUnits[unit.Name] {
74 c.seenUnits[unit.Name] = true
75 c.addUnitCharts(name, typ)
76 }
77
78 for _, s := range unitStates {
79 mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, s)] = 0
80 }
81 mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, unit.ActiveState)] = 1
82 }
83
84 for k := range c.seenUnits {
85 if !seen[k] {
86 delete(c.seenUnits, k)
87 if name, typ, ok := extractUnitNameType(k); ok {
88 c.removeUnitCharts(name, typ)
89 }
90 }
91 }
92
93 for k := range c.unitTransient {
94 if !seen[k] {
95 delete(c.unitTransient, k)
96 }
97 }
98
99 return nil
100 }
101
102 func (c *Collector) getLoadedUnits(conn systemdConnection) ([]dbus.UnitStatus, error) {
103 ctx, cancel := context.WithTimeout(context.Background(), c.Timeout.Duration())
104 defer cancel()
105
106 c.Debugf("calling function 'ListUnits'")
107 units, err := conn.ListUnitsContext(ctx)
108 if err != nil {
109 return nil, fmt.Errorf("error on ListUnits: %v", err)
110 }
111
112 for i := range units {
113 units[i].Name = cleanUnitName(units[i].Name)
114 }
115
116 loaded := units[:0]
117 for _, unit := range units {
118 if unit.LoadState == "loaded" && c.unitSr.MatchString(unit.Name) {
119 loaded = append(loaded, unit)
120 }
121 }
122
123 c.Debugf("got total/loaded %d/%d units", len(units), len(loaded))
124
125 return loaded, nil
126 }
127
128 func (c *Collector) getLoadedUnitsByPatterns(conn systemdConnection) ([]dbus.UnitStatus, error) {
129 ctx, cancel := context.WithTimeout(context.Background(), c.Timeout.Duration())
130 defer cancel()
131
132 c.Debugf("calling function 'ListUnitsByPatterns'")
133
134 units, err := conn.ListUnitsByPatternsContext(ctx, unitStates, c.Include)
135 if err != nil {
136 return nil, fmt.Errorf("error on ListUnitsByPatterns: %v", err)
137 }
138
139 for i := range units {
140 units[i].Name = cleanUnitName(units[i].Name)
141 }
142
143 loaded := units[:0]
144 for _, unit := range units {
145 if unit.LoadState == "loaded" {
146 loaded = append(loaded, unit)
147 }
148 }
149 c.Debugf("got total/loaded %d/%d units", len(units), len(loaded))
150
151 return loaded, nil
152 }
153
154 func (c *Collector) getUnitTransientProperty(conn systemdConnection, unit string) (string, error) {
155 ctx, cancel := context.WithTimeout(context.Background(), c.Timeout.Duration())
156 defer cancel()
157
158 c.Debugf("calling function 'GetUnitProperty' for unit '%s'", unit)
159
160 prop, err := conn.GetUnitPropertyContext(ctx, unit, transientProperty)
161 if err != nil {
162 return "", fmt.Errorf("error on GetUnitProperty: %v", err)
163 }
164
165 return prop.Value.String(), nil
166 }
167
168 func extractUnitNameType(name string) (string, string, bool) {
169 idx := strings.LastIndexByte(name, '.')
170 if idx <= 0 {
171 return "", "", false
172 }
173 return name[:idx], name[idx+1:], true
174 }
175
176 func cleanUnitName(name string) string {
177 // dev-disk-by\x2duuid-DE44\x2dCEE0.device => dev-disk-by-uuid-DE44-CEE0.device
178 if strings.IndexByte(name, '\\') == -1 {
179 return name
180 }
181 v, err := strconv.Unquote("\"" + name + "\"")
182 if err != nil {
183 return name
184 }
185 return v
186 }