mirror of
https://github.com/prometheus/node_exporter.git
synced 2024-12-31 08:27:42 -08:00
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")
This commit is contained in:
parent
d98335cbf0
commit
91ddafdb33
|
@ -45,6 +45,7 @@ interrupts | Exposes detailed interrupts statistics. | Linux, OpenBSD
|
||||||
ipvs | Exposes IPVS status from `/proc/net/ip_vs` and stats from `/proc/net/ip_vs_stats`. | Linux
|
ipvs | Exposes IPVS status from `/proc/net/ip_vs` and stats from `/proc/net/ip_vs_stats`. | Linux
|
||||||
ksmd | Exposes kernel and system statistics from `/sys/kernel/mm/ksm`. | Linux
|
ksmd | Exposes kernel and system statistics from `/sys/kernel/mm/ksm`. | Linux
|
||||||
lastlogin | Exposes the last time there was a login. | _any_
|
lastlogin | Exposes the last time there was a login. | _any_
|
||||||
|
logind | Exposes session counts from [logind](http://www.freedesktop.org/wiki/Software/systemd/logind/). | Linux
|
||||||
megacli | Exposes RAID statistics from MegaCLI. | Linux
|
megacli | Exposes RAID statistics from MegaCLI. | Linux
|
||||||
meminfo_numa | Exposes memory statistics from `/proc/meminfo_numa`. | Linux
|
meminfo_numa | Exposes memory statistics from `/proc/meminfo_numa`. | Linux
|
||||||
ntp | Exposes time drift from an NTP server. | _any_
|
ntp | Exposes time drift from an NTP server. | _any_
|
||||||
|
|
268
collector/logind_linux.go
Normal file
268
collector/logind_linux.go
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
// Copyright 2016 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build !nologind
|
||||||
|
|
||||||
|
package collector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
logindSubsystem = "logind"
|
||||||
|
dbusObject = "org.freedesktop.login1"
|
||||||
|
dbusPath = "/org/freedesktop/login1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Taken from logind as of systemd v229.
|
||||||
|
// "other" is the fallback value for unknown values (in case logind gets extended in the future).
|
||||||
|
attrRemoteValues = []string{"true", "false"}
|
||||||
|
attrTypeValues = []string{"other", "unspecified", "tty", "x11", "wayland", "mir", "web"}
|
||||||
|
attrClassValues = []string{"other", "user", "greeter", "lock-screen", "background"}
|
||||||
|
|
||||||
|
sessionsDesc = prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(Namespace, logindSubsystem, "sessions"),
|
||||||
|
"Number of sessions registered in logind.", []string{"seat", "remote", "type", "class"}, nil,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
type logindCollector struct{}
|
||||||
|
|
||||||
|
type logindDbus struct {
|
||||||
|
conn *dbus.Conn
|
||||||
|
object dbus.BusObject
|
||||||
|
}
|
||||||
|
|
||||||
|
type logindInterface interface {
|
||||||
|
listSeats() ([]string, error)
|
||||||
|
listSessions() ([]logindSessionEntry, error)
|
||||||
|
getSession(logindSessionEntry) *logindSession
|
||||||
|
}
|
||||||
|
|
||||||
|
type logindSession struct {
|
||||||
|
seat string
|
||||||
|
remote string
|
||||||
|
sessionType string
|
||||||
|
class string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Struct elements must be public for the reflection magic of godbus to work.
|
||||||
|
type logindSessionEntry struct {
|
||||||
|
SessionId string
|
||||||
|
UserId uint32
|
||||||
|
UserName string
|
||||||
|
SeatId string
|
||||||
|
SessionObjectPath dbus.ObjectPath
|
||||||
|
}
|
||||||
|
|
||||||
|
type logindSeatEntry struct {
|
||||||
|
SeatId string
|
||||||
|
SeatObjectPath dbus.ObjectPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Factories["logind"] = NewLogindCollector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a prometheus registry and returns a new Collector exposing
|
||||||
|
// logind statistics.
|
||||||
|
func NewLogindCollector() (Collector, error) {
|
||||||
|
return &logindCollector{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lc *logindCollector) Update(ch chan<- prometheus.Metric) error {
|
||||||
|
c, err := newDbus()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to connect to dbus: %s", err)
|
||||||
|
}
|
||||||
|
defer c.conn.Close()
|
||||||
|
|
||||||
|
return collectMetrics(ch, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectMetrics(ch chan<- prometheus.Metric, c logindInterface) error {
|
||||||
|
seats, err := c.listSeats()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get seats: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionList, err := c.listSessions()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get sessions: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessions := make(map[logindSession]float64)
|
||||||
|
|
||||||
|
for _, s := range sessionList {
|
||||||
|
session := c.getSession(s)
|
||||||
|
if session != nil {
|
||||||
|
sessions[*session]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, remote := range attrRemoteValues {
|
||||||
|
for _, sessionType := range attrTypeValues {
|
||||||
|
for _, class := range attrClassValues {
|
||||||
|
for _, seat := range seats {
|
||||||
|
count := sessions[logindSession{seat, remote, sessionType, class}]
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
sessionsDesc, prometheus.GaugeValue, count,
|
||||||
|
seat, remote, sessionType, class)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func knownStringOrOther(value string, known []string) string {
|
||||||
|
for i := range known {
|
||||||
|
if value == known[i] {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "other"
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDbus() (*logindDbus, error) {
|
||||||
|
conn, err := dbus.SystemBusPrivate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
|
||||||
|
|
||||||
|
err = conn.Auth(methods)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conn.Hello()
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
object := conn.Object(dbusObject, dbus.ObjectPath(dbusPath))
|
||||||
|
|
||||||
|
return &logindDbus{
|
||||||
|
conn: conn,
|
||||||
|
object: object,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *logindDbus) listSeats() ([]string, error) {
|
||||||
|
var result [][]interface{}
|
||||||
|
err := c.object.Call(dbusObject+".Manager.ListSeats", 0).Store(&result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resultInterface := make([]interface{}, len(result))
|
||||||
|
for i := range result {
|
||||||
|
resultInterface[i] = result[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
seats := make([]logindSeatEntry, len(result))
|
||||||
|
seatsInterface := make([]interface{}, len(seats))
|
||||||
|
for i := range seats {
|
||||||
|
seatsInterface[i] = &seats[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbus.Store(resultInterface, seatsInterface...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make([]string, len(seats)+1)
|
||||||
|
for i := range seats {
|
||||||
|
ret[i] = seats[i].SeatId
|
||||||
|
}
|
||||||
|
// Always add the empty seat, which is used for remote sessions like SSH
|
||||||
|
ret[len(seats)] = ""
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *logindDbus) listSessions() ([]logindSessionEntry, error) {
|
||||||
|
var result [][]interface{}
|
||||||
|
err := c.object.Call(dbusObject+".Manager.ListSessions", 0).Store(&result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resultInterface := make([]interface{}, len(result))
|
||||||
|
for i := range result {
|
||||||
|
resultInterface[i] = result[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
sessions := make([]logindSessionEntry, len(result))
|
||||||
|
sessionsInterface := make([]interface{}, len(sessions))
|
||||||
|
for i := range sessions {
|
||||||
|
sessionsInterface[i] = &sessions[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbus.Store(resultInterface, sessionsInterface...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *logindDbus) getSession(session logindSessionEntry) *logindSession {
|
||||||
|
object := c.conn.Object(dbusObject, session.SessionObjectPath)
|
||||||
|
|
||||||
|
remote, err := object.GetProperty(dbusObject + ".Session.Remote")
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionType, err := object.GetProperty(dbusObject + ".Session.Type")
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionTypeStr, ok := sessionType.Value().(string)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
class, err := object.GetProperty(dbusObject + ".Session.Class")
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
classStr, ok := class.Value().(string)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &logindSession{
|
||||||
|
seat: session.SeatId,
|
||||||
|
remote: remote.String(),
|
||||||
|
sessionType: knownStringOrOther(sessionTypeStr, attrTypeValues),
|
||||||
|
class: knownStringOrOther(classStr, attrClassValues),
|
||||||
|
}
|
||||||
|
}
|
102
collector/logind_linux_test.go
Normal file
102
collector/logind_linux_test.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2016 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package collector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testLogindInterface struct{}
|
||||||
|
|
||||||
|
var testSeats = []string{"seat0", ""}
|
||||||
|
|
||||||
|
func (c *testLogindInterface) listSeats() ([]string, error) {
|
||||||
|
return testSeats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testLogindInterface) listSessions() ([]logindSessionEntry, error) {
|
||||||
|
return []logindSessionEntry{
|
||||||
|
{
|
||||||
|
SessionId: "1",
|
||||||
|
UserId: 0,
|
||||||
|
UserName: "",
|
||||||
|
SeatId: "",
|
||||||
|
SessionObjectPath: dbus.ObjectPath("/org/freedesktop/login1/session/1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SessionId: "2",
|
||||||
|
UserId: 0,
|
||||||
|
UserName: "",
|
||||||
|
SeatId: "seat0",
|
||||||
|
SessionObjectPath: dbus.ObjectPath("/org/freedesktop/login1/session/2"),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testLogindInterface) getSession(session logindSessionEntry) *logindSession {
|
||||||
|
sessions := map[dbus.ObjectPath]*logindSession{
|
||||||
|
dbus.ObjectPath("/org/freedesktop/login1/session/1"): {
|
||||||
|
seat: session.SeatId,
|
||||||
|
remote: "true",
|
||||||
|
sessionType: knownStringOrOther("tty", attrTypeValues),
|
||||||
|
class: knownStringOrOther("user", attrClassValues),
|
||||||
|
},
|
||||||
|
dbus.ObjectPath("/org/freedesktop/login1/session/2"): {
|
||||||
|
seat: session.SeatId,
|
||||||
|
remote: "false",
|
||||||
|
sessionType: knownStringOrOther("x11", attrTypeValues),
|
||||||
|
class: knownStringOrOther("greeter", attrClassValues),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessions[session.SessionObjectPath]
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogindCollectorKnownStringOrOther(t *testing.T) {
|
||||||
|
known := []string{"foo", "bar"}
|
||||||
|
|
||||||
|
actual := knownStringOrOther("foo", known)
|
||||||
|
expected := "foo"
|
||||||
|
if actual != expected {
|
||||||
|
t.Errorf("knownStringOrOther failed: got %q, expected %q.", actual, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = knownStringOrOther("baz", known)
|
||||||
|
expected = "other"
|
||||||
|
if actual != expected {
|
||||||
|
t.Errorf("knownStringOrOther failed: got %q, expected %q.", actual, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogindCollectorCollectMetrics(t *testing.T) {
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
collectMetrics(ch, &testLogindInterface{})
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
for range ch {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := len(testSeats) * len(attrRemoteValues) * len(attrTypeValues) * len(attrClassValues)
|
||||||
|
if count != expected {
|
||||||
|
t.Errorf("collectMetrics did not generate the expected number of metrics: got %d, expected %d.", count, expected)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue