node_exporter/collector/logind_linux.go
Calle Pettersson 859a825bb8 Replace --collectors.enabled with per-collector flags (#640)
* Move NodeCollector into package collector

* Refactor collector enabling

* Update README with new collector enabled flags

* Fix out-of-date inline flag reference syntax

* Use new flags in end-to-end tests

* Add flag to disable all default collectors

* Track if a flag has been set explicitly

* Add --collectors.disable-defaults to README

* Revert disable-defaults flag

* Shorten flags

* Fixup timex collector registration

* Fix end-to-end tests

* Change procfs and sysfs path flags

* Fix review comments
2017-09-28 15:06:26 +02:00

268 lines
6.2 KiB
Go

// 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() {
registerCollector("logind", defaultDisabled, NewLogindCollector)
}
// NewLogindCollector 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),
}
}