271 lines
6.2 KiB
Go
271 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/go-kit/kit/log"
|
|
"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 {
|
|
logger log.Logger
|
|
}
|
|
|
|
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(logger log.Logger) (Collector, error) {
|
|
return &logindCollector{logger}, 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),
|
|
}
|
|
}
|