github.com/netdata/go.d.plugin@v0.58.1/modules/systemdunits/collect.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 //go:build linux 4 // +build linux 5 6 package systemdunits 7 8 import ( 9 "context" 10 "fmt" 11 "regexp" 12 "strconv" 13 "strings" 14 15 "github.com/coreos/go-systemd/v22/dbus" 16 ) 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 // https://www.freedesktop.org/software/systemd/man/systemd.html 27 unitTypeService = "service" 28 unitTypeSocket = "socket" 29 unitTypeTarget = "target" 30 unitTypePath = "path" 31 unitTypeDevice = "device" 32 unitTypeMount = "mount" 33 unitTypeAutomount = "automount" 34 unitTypeSwap = "swap" 35 unitTypeTimer = "timer" 36 unitTypeScope = "scope" 37 unitTypeSlice = "slice" 38 ) 39 40 var ( 41 unitStates = []string{ 42 unitStateActive, 43 unitStateActivating, 44 unitStateFailed, 45 unitStateInactive, 46 unitStateDeactivating, 47 } 48 ) 49 50 func (s *SystemdUnits) collect() (map[string]int64, error) { 51 conn, err := s.getConnection() 52 if err != nil { 53 return nil, err 54 } 55 56 if s.systemdVersion == 0 { 57 ver, err := s.getSystemdVersion(conn) 58 if err != nil { 59 s.closeConnection() 60 return nil, err 61 } 62 s.systemdVersion = ver 63 } 64 65 var units []dbus.UnitStatus 66 if s.systemdVersion >= 230 { 67 // https://github.com/systemd/systemd/pull/3142 68 units, err = s.getLoadedUnitsByPatterns(conn) 69 } else { 70 units, err = s.getLoadedUnits(conn) 71 } 72 if err != nil { 73 s.closeConnection() 74 return nil, err 75 } 76 77 if len(units) == 0 { 78 return nil, nil 79 } 80 81 mx := make(map[string]int64) 82 s.collectUnitsStates(mx, units) 83 84 return mx, nil 85 } 86 87 func (s *SystemdUnits) collectUnitsStates(mx map[string]int64, units []dbus.UnitStatus) { 88 for _, unit := range units { 89 name, typ := extractUnitNameType(cleanUnitName(unit.Name)) 90 if name == "" || typ == "" { 91 continue 92 } 93 94 if !s.units[unit.Name] { 95 s.units[unit.Name] = true 96 s.addUnitToCharts(name, typ) 97 } 98 99 for _, s := range unitStates { 100 mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, s)] = 0 101 } 102 mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, unit.ActiveState)] = 1 103 } 104 } 105 106 func (s *SystemdUnits) getConnection() (systemdConnection, error) { 107 if s.conn == nil { 108 conn, err := s.client.connect() 109 if err != nil { 110 return nil, fmt.Errorf("error on creating a connection: %v", err) 111 } 112 s.conn = conn 113 } 114 return s.conn, nil 115 } 116 117 func (s *SystemdUnits) closeConnection() { 118 if s.conn != nil { 119 s.conn.Close() 120 s.conn = nil 121 } 122 } 123 124 var reVersion = regexp.MustCompile(`[0-9][0-9][0-9]`) 125 126 const versionProperty = "Version" 127 128 func (s *SystemdUnits) getSystemdVersion(conn systemdConnection) (int, error) { 129 s.Debugf("calling function 'GetManagerProperty'") 130 version, err := conn.GetManagerProperty(versionProperty) 131 if err != nil { 132 return 0, fmt.Errorf("error on getting '%s' manager property: %v", versionProperty, err) 133 } 134 135 s.Debugf("systemd version: %s", version) 136 137 major := reVersion.FindString(version) 138 if major == "" { 139 return 0, fmt.Errorf("couldn't parse systemd version string '%s'", version) 140 } 141 142 ver, err := strconv.Atoi(major) 143 if err != nil { 144 return 0, fmt.Errorf("couldn't parse systemd version string '%s': %v", version, err) 145 } 146 147 return ver, nil 148 } 149 150 func (s *SystemdUnits) getLoadedUnits(conn systemdConnection) ([]dbus.UnitStatus, error) { 151 ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration) 152 defer cancel() 153 154 s.Debugf("calling function 'ListUnits'") 155 units, err := conn.ListUnitsContext(ctx) 156 if err != nil { 157 return nil, fmt.Errorf("error on ListUnits: %v", err) 158 } 159 160 loaded := units[:0] 161 for _, unit := range units { 162 if unit.LoadState == "loaded" && s.sr.MatchString(unit.Name) { 163 loaded = append(loaded, unit) 164 } 165 } 166 s.Debugf("got total/loaded %d/%d units", len(units), len(loaded)) 167 168 return loaded, nil 169 } 170 171 func (s *SystemdUnits) getLoadedUnitsByPatterns(conn systemdConnection) ([]dbus.UnitStatus, error) { 172 ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration) 173 defer cancel() 174 175 s.Debugf("calling function 'ListUnitsByPatterns'") 176 177 units, err := conn.ListUnitsByPatternsContext(ctx, unitStates, s.Include) 178 if err != nil { 179 return nil, fmt.Errorf("error on ListUnitsByPatterns: %v", err) 180 } 181 182 loaded := units[:0] 183 for _, unit := range units { 184 if unit.LoadState == "loaded" { 185 loaded = append(loaded, unit) 186 } 187 } 188 s.Debugf("got total/loaded %d/%d units", len(units), len(loaded)) 189 190 return loaded, nil 191 } 192 193 func extractUnitNameType(name string) (string, string) { 194 idx := strings.LastIndexByte(name, '.') 195 if idx <= 0 { 196 return "", "" 197 } 198 return name[:idx], name[idx+1:] 199 } 200 201 func cleanUnitName(name string) string { 202 // dev-disk-by\x2duuid-DE44\x2dCEE0.device => dev-disk-by-uuid-DE44-CEE0.device 203 if strings.IndexByte(name, '\\') == -1 { 204 return name 205 } 206 v, err := strconv.Unquote("\"" + name + "\"") 207 if err != nil { 208 return name 209 } 210 return v 211 }