diff options
author | Matthias Schiffer <mschiffer@universe-factory.net> | 2016-04-20 17:28:12 +0200 |
---|---|---|
committer | Matthias Schiffer <mschiffer@universe-factory.net> | 2016-04-20 17:28:12 +0200 |
commit | 91ddafdb33c72630e189b27d6076b186b0fdb9ca (patch) | |
tree | f15370eb6a9957888750338beb3205f2e160a41b /collector/logind_linux.go | |
parent | d98335cbf0ffe904d568604d2aa06f7316fa2aac (diff) | |
download | prometheus_node_collector-91ddafdb33c72630e189b27d6076b186b0fdb9ca.tar.bz2 prometheus_node_collector-91ddafdb33c72630e189b27d6076b186b0fdb9ca.tar.xz prometheus_node_collector-91ddafdb33c72630e189b27d6076b186b0fdb9ca.zip |
Add 'logind' exporter
logind provides a nice interface to find out about the numbers of sessions
on a system; it is used on most Linux distributions, even those which
aren't using systemd.
The exporter exposes the total number of sessions indexed by the following
attributes:
* seat
* type ("tty", "x11", ...)
* class ("user", "greeter", ...)
* remote ("true"/"false")
Diffstat (limited to 'collector/logind_linux.go')
-rw-r--r-- | collector/logind_linux.go | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/collector/logind_linux.go b/collector/logind_linux.go new file mode 100644 index 0000000..e7172f1 --- /dev/null +++ b/collector/logind_linux.go | |||
@@ -0,0 +1,268 @@ | |||
1 | // Copyright 2016 The Prometheus Authors | ||
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | ||
3 | // you may not use this file except in compliance with the License. | ||
4 | // You may obtain a copy of the License at | ||
5 | // | ||
6 | // http://www.apache.org/licenses/LICENSE-2.0 | ||
7 | // | ||
8 | // Unless required by applicable law or agreed to in writing, software | ||
9 | // distributed under the License is distributed on an "AS IS" BASIS, | ||
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
11 | // See the License for the specific language governing permissions and | ||
12 | // limitations under the License. | ||
13 | |||
14 | // +build !nologind | ||
15 | |||
16 | package collector | ||
17 | |||
18 | import ( | ||
19 | "fmt" | ||
20 | "os" | ||
21 | "strconv" | ||
22 | |||
23 | "github.com/godbus/dbus" | ||
24 | "github.com/prometheus/client_golang/prometheus" | ||
25 | ) | ||
26 | |||
27 | const ( | ||
28 | logindSubsystem = "logind" | ||
29 | dbusObject = "org.freedesktop.login1" | ||
30 | dbusPath = "/org/freedesktop/login1" | ||
31 | ) | ||
32 | |||
33 | var ( | ||
34 | // Taken from logind as of systemd v229. | ||
35 | // "other" is the fallback value for unknown values (in case logind gets extended in the future). | ||
36 | attrRemoteValues = []string{"true", "false"} | ||
37 | attrTypeValues = []string{"other", "unspecified", "tty", "x11", "wayland", "mir", "web"} | ||
38 | attrClassValues = []string{"other", "user", "greeter", "lock-screen", "background"} | ||
39 | |||
40 | sessionsDesc = prometheus.NewDesc( | ||
41 | prometheus.BuildFQName(Namespace, logindSubsystem, "sessions"), | ||
42 | "Number of sessions registered in logind.", []string{"seat", "remote", "type", "class"}, nil, | ||
43 | ) | ||
44 | ) | ||
45 | |||
46 | type logindCollector struct{} | ||
47 | |||
48 | type logindDbus struct { | ||
49 | conn *dbus.Conn | ||
50 | object dbus.BusObject | ||
51 | } | ||
52 | |||
53 | type logindInterface interface { | ||
54 | listSeats() ([]string, error) | ||
55 | listSessions() ([]logindSessionEntry, error) | ||
56 | getSession(logindSessionEntry) *logindSession | ||
57 | } | ||
58 | |||
59 | type logindSession struct { | ||
60 | seat string | ||
61 | remote string | ||
62 | sessionType string | ||
63 | class string | ||
64 | } | ||
65 | |||
66 | // Struct elements must be public for the reflection magic of godbus to work. | ||
67 | type logindSessionEntry struct { | ||
68 | SessionId string | ||
69 | UserId uint32 | ||
70 | UserName string | ||
71 | SeatId string | ||
72 | SessionObjectPath dbus.ObjectPath | ||
73 | } | ||
74 | |||
75 | type logindSeatEntry struct { | ||
76 | SeatId string | ||
77 | SeatObjectPath dbus.ObjectPath | ||
78 | } | ||
79 | |||
80 | func init() { | ||
81 | Factories["logind"] = NewLogindCollector | ||
82 | } | ||
83 | |||
84 | // Takes a prometheus registry and returns a new Collector exposing | ||
85 | // logind statistics. | ||
86 | func NewLogindCollector() (Collector, error) { | ||
87 | return &logindCollector{}, nil | ||
88 | } | ||
89 | |||
90 | func (lc *logindCollector) Update(ch chan<- prometheus.Metric) error { | ||
91 | c, err := newDbus() | ||
92 | if err != nil { | ||
93 | return fmt.Errorf("unable to connect to dbus: %s", err) | ||
94 | } | ||
95 | defer c.conn.Close() | ||
96 | |||
97 | return collectMetrics(ch, c) | ||
98 | } | ||
99 | |||
100 | func collectMetrics(ch chan<- prometheus.Metric, c logindInterface) error { | ||
101 | seats, err := c.listSeats() | ||
102 | if err != nil { | ||
103 | return fmt.Errorf("unable to get seats: %s", err) | ||
104 | } | ||
105 | |||
106 | sessionList, err := c.listSessions() | ||
107 | if err != nil { | ||
108 | return fmt.Errorf("unable to get sessions: %s", err) | ||
109 | } | ||
110 | |||
111 | sessions := make(map[logindSession]float64) | ||
112 | |||
113 | for _, s := range sessionList { | ||
114 | session := c.getSession(s) | ||
115 | if session != nil { | ||
116 | sessions[*session]++ | ||
117 | } | ||
118 | } | ||
119 | |||
120 | for _, remote := range attrRemoteValues { | ||
121 | for _, sessionType := range attrTypeValues { | ||
122 | for _, class := range attrClassValues { | ||
123 | for _, seat := range seats { | ||
124 | count := sessions[logindSession{seat, remote, sessionType, class}] | ||
125 | |||
126 | ch <- prometheus.MustNewConstMetric( | ||
127 | sessionsDesc, prometheus.GaugeValue, count, | ||
128 | seat, remote, sessionType, class) | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | |||
134 | return nil | ||
135 | } | ||
136 | |||
137 | func knownStringOrOther(value string, known []string) string { | ||
138 | for i := range known { | ||
139 | if value == known[i] { | ||
140 | return value | ||
141 | } | ||
142 | } | ||
143 | |||
144 | return "other" | ||
145 | } | ||
146 | |||
147 | func newDbus() (*logindDbus, error) { | ||
148 | conn, err := dbus.SystemBusPrivate() | ||
149 | if err != nil { | ||
150 | return nil, err | ||
151 | } | ||
152 | |||
153 | methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} | ||
154 | |||
155 | err = conn.Auth(methods) | ||
156 | if err != nil { | ||
157 | conn.Close() | ||
158 | return nil, err | ||
159 | } | ||
160 | |||
161 | err = conn.Hello() | ||
162 | if err != nil { | ||
163 | conn.Close() | ||
164 | return nil, err | ||
165 | } | ||
166 | |||
167 | object := conn.Object(dbusObject, dbus.ObjectPath(dbusPath)) | ||
168 | |||
169 | return &logindDbus{ | ||
170 | conn: conn, | ||
171 | object: object, | ||
172 | }, nil | ||
173 | } | ||
174 | |||
175 | func (c *logindDbus) listSeats() ([]string, error) { | ||
176 | var result [][]interface{} | ||
177 | err := c.object.Call(dbusObject+".Manager.ListSeats", 0).Store(&result) | ||
178 | if err != nil { | ||
179 | return nil, err | ||
180 | } | ||
181 | |||
182 | resultInterface := make([]interface{}, len(result)) | ||
183 | for i := range result { | ||
184 | resultInterface[i] = result[i] | ||
185 | } | ||
186 | |||
187 | seats := make([]logindSeatEntry, len(result)) | ||
188 | seatsInterface := make([]interface{}, len(seats)) | ||
189 | for i := range seats { | ||
190 | seatsInterface[i] = &seats[i] | ||
191 | } | ||
192 | |||
193 | err = dbus.Store(resultInterface, seatsInterface...) | ||
194 | if err != nil { | ||
195 | return nil, err | ||
196 | } | ||
197 | |||
198 | ret := make([]string, len(seats)+1) | ||
199 | for i := range seats { | ||
200 | ret[i] = seats[i].SeatId | ||
201 | } | ||
202 | // Always add the empty seat, which is used for remote sessions like SSH | ||
203 | ret[len(seats)] = "" | ||
204 | |||
205 | return ret, nil | ||
206 | } | ||
207 | |||
208 | func (c *logindDbus) listSessions() ([]logindSessionEntry, error) { | ||
209 | var result [][]interface{} | ||
210 | err := c.object.Call(dbusObject+".Manager.ListSessions", 0).Store(&result) | ||
211 | if err != nil { | ||
212 | return nil, err | ||
213 | } | ||
214 | |||
215 | resultInterface := make([]interface{}, len(result)) | ||
216 | for i := range result { | ||
217 | resultInterface[i] = result[i] | ||
218 | } | ||
219 | |||
220 | sessions := make([]logindSessionEntry, len(result)) | ||
221 | sessionsInterface := make([]interface{}, len(sessions)) | ||
222 | for i := range sessions { | ||
223 | sessionsInterface[i] = &sessions[i] | ||
224 | } | ||
225 | |||
226 | err = dbus.Store(resultInterface, sessionsInterface...) | ||
227 | if err != nil { | ||
228 | return nil, err | ||
229 | } | ||
230 | |||
231 | return sessions, nil | ||
232 | } | ||
233 | |||
234 | func (c *logindDbus) getSession(session logindSessionEntry) *logindSession { | ||
235 | object := c.conn.Object(dbusObject, session.SessionObjectPath) | ||
236 | |||
237 | remote, err := object.GetProperty(dbusObject + ".Session.Remote") | ||
238 | if err != nil { | ||
239 | return nil | ||
240 | } | ||
241 | |||
242 | sessionType, err := object.GetProperty(dbusObject + ".Session.Type") | ||
243 | if err != nil { | ||
244 | return nil | ||
245 | } | ||
246 | |||
247 | sessionTypeStr, ok := sessionType.Value().(string) | ||
248 | if !ok { | ||
249 | return nil | ||
250 | } | ||
251 | |||
252 | class, err := object.GetProperty(dbusObject + ".Session.Class") | ||
253 | if err != nil { | ||
254 | return nil | ||
255 | } | ||
256 | |||
257 | classStr, ok := class.Value().(string) | ||
258 | if !ok { | ||
259 | return nil | ||
260 | } | ||
261 | |||
262 | return &logindSession{ | ||
263 | seat: session.SeatId, | ||
264 | remote: remote.String(), | ||
265 | sessionType: knownStringOrOther(sessionTypeStr, attrTypeValues), | ||
266 | class: knownStringOrOther(classStr, attrClassValues), | ||
267 | } | ||
268 | } | ||