aboutsummaryrefslogtreecommitdiff
path: root/collector/logind_linux.go
diff options
context:
space:
mode:
authorMatthias Schiffer <mschiffer@universe-factory.net>2016-04-20 17:28:12 +0200
committerMatthias Schiffer <mschiffer@universe-factory.net>2016-04-20 17:28:12 +0200
commit91ddafdb33c72630e189b27d6076b186b0fdb9ca (patch)
treef15370eb6a9957888750338beb3205f2e160a41b /collector/logind_linux.go
parentd98335cbf0ffe904d568604d2aa06f7316fa2aac (diff)
downloadprometheus_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.go268
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
16package collector
17
18import (
19 "fmt"
20 "os"
21 "strconv"
22
23 "github.com/godbus/dbus"
24 "github.com/prometheus/client_golang/prometheus"
25)
26
27const (
28 logindSubsystem = "logind"
29 dbusObject = "org.freedesktop.login1"
30 dbusPath = "/org/freedesktop/login1"
31)
32
33var (
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
46type logindCollector struct{}
47
48type logindDbus struct {
49 conn *dbus.Conn
50 object dbus.BusObject
51}
52
53type logindInterface interface {
54 listSeats() ([]string, error)
55 listSessions() ([]logindSessionEntry, error)
56 getSession(logindSessionEntry) *logindSession
57}
58
59type 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.
67type logindSessionEntry struct {
68 SessionId string
69 UserId uint32
70 UserName string
71 SeatId string
72 SessionObjectPath dbus.ObjectPath
73}
74
75type logindSeatEntry struct {
76 SeatId string
77 SeatObjectPath dbus.ObjectPath
78}
79
80func init() {
81 Factories["logind"] = NewLogindCollector
82}
83
84// Takes a prometheus registry and returns a new Collector exposing
85// logind statistics.
86func NewLogindCollector() (Collector, error) {
87 return &logindCollector{}, nil
88}
89
90func (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
100func 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
137func 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
147func 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
175func (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
208func (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
234func (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}