2015-12-06 22:33:47 +01:00
// Copyright 2015 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 !nosystemd
package collector
import (
"fmt"
2016-08-16 08:39:49 +02:00
"regexp"
2018-01-31 16:06:58 +01:00
"strings"
2015-12-06 22:33:47 +01:00
"github.com/coreos/go-systemd/dbus"
"github.com/prometheus/client_golang/prometheus"
2016-08-16 08:39:49 +02:00
"github.com/prometheus/common/log"
2017-08-12 15:07:24 +02:00
"gopkg.in/alecthomas/kingpin.v2"
2018-11-14 10:50:39 +01:00
"math"
2016-08-16 08:39:49 +02:00
)
var (
2017-08-12 15:07:24 +02:00
unitWhitelist = kingpin . Flag ( "collector.systemd.unit-whitelist" , "Regexp of systemd units to whitelist. Units must both match whitelist and not match blacklist to be included." ) . Default ( ".+" ) . String ( )
unitBlacklist = kingpin . Flag ( "collector.systemd.unit-blacklist" , "Regexp of systemd units to blacklist. Units must both match whitelist and not match blacklist to be included." ) . Default ( ".+\\.scope" ) . String ( )
systemdPrivate = kingpin . Flag ( "collector.systemd.private" , "Establish a private, direct connection to systemd without dbus." ) . Bool ( )
2015-12-06 22:33:47 +01:00
)
type systemdCollector struct {
2018-07-05 16:26:48 +02:00
unitDesc * prometheus . Desc
2018-07-18 16:02:05 +02:00
unitStartTimeDesc * prometheus . Desc
2018-11-14 10:50:39 +01:00
unitTasksCurrentDesc * prometheus . Desc
unitTasksMaxDesc * prometheus . Desc
2018-07-05 16:26:48 +02:00
systemRunningDesc * prometheus . Desc
summaryDesc * prometheus . Desc
nRestartsDesc * prometheus . Desc
timerLastTriggerDesc * prometheus . Desc
socketAcceptedConnectionsDesc * prometheus . Desc
socketCurrentConnectionsDesc * prometheus . Desc
2018-07-16 16:01:42 +02:00
socketRefusedConnectionsDesc * prometheus . Desc
2018-07-05 16:26:48 +02:00
unitWhitelistPattern * regexp . Regexp
unitBlacklistPattern * regexp . Regexp
2015-12-06 22:33:47 +01:00
}
var unitStatesName = [ ] string { "active" , "activating" , "deactivating" , "inactive" , "failed" }
func init ( ) {
2017-09-28 15:06:26 +02:00
registerCollector ( "systemd" , defaultDisabled , NewSystemdCollector )
2015-12-06 22:33:47 +01:00
}
2017-02-28 17:44:53 +01:00
// NewSystemdCollector returns a new Collector exposing systemd statistics.
2015-12-06 22:33:47 +01:00
func NewSystemdCollector ( ) ( Collector , error ) {
2015-12-17 19:30:35 +01:00
const subsystem = "systemd"
2015-12-06 22:33:47 +01:00
unitDesc := prometheus . NewDesc (
2017-09-28 15:06:26 +02:00
prometheus . BuildFQName ( namespace , subsystem , "unit_state" ) ,
2015-12-06 22:33:47 +01:00
"Systemd unit" , [ ] string { "name" , "state" } , nil ,
)
2018-07-18 16:02:05 +02:00
unitStartTimeDesc := prometheus . NewDesc (
prometheus . BuildFQName ( namespace , subsystem , "unit_start_time_seconds" ) ,
"Start time of the unit since unix epoch in seconds." , [ ] string { "name" } , nil ,
)
2018-11-14 10:50:39 +01:00
unitTasksCurrentDesc := prometheus . NewDesc (
prometheus . BuildFQName ( namespace , subsystem , "unit_tasks_current" ) ,
"Current number of tasks per Systemd unit" , [ ] string { "name" } , nil ,
)
unitTasksMaxDesc := prometheus . NewDesc (
prometheus . BuildFQName ( namespace , subsystem , "unit_tasks_max" ) ,
"Maximum number of tasks per Systemd unit" , [ ] string { "name" } , nil ,
)
2015-12-17 19:30:35 +01:00
systemRunningDesc := prometheus . NewDesc (
2017-09-28 15:06:26 +02:00
prometheus . BuildFQName ( namespace , subsystem , "system_running" ) ,
2015-12-17 19:30:35 +01:00
"Whether the system is operational (see 'systemctl is-system-running')" ,
nil , nil ,
)
2018-01-04 11:49:36 +01:00
summaryDesc := prometheus . NewDesc (
prometheus . BuildFQName ( namespace , subsystem , "units" ) ,
"Summary of systemd unit states" , [ ] string { "state" } , nil )
2018-07-05 13:31:45 +02:00
nRestartsDesc := prometheus . NewDesc (
prometheus . BuildFQName ( namespace , subsystem , "service_restart_total" ) ,
"Service unit count of Restart triggers" , [ ] string { "state" } , nil )
2018-01-31 16:06:58 +01:00
timerLastTriggerDesc := prometheus . NewDesc (
prometheus . BuildFQName ( namespace , subsystem , "timer_last_trigger_seconds" ) ,
"Seconds since epoch of last trigger." , [ ] string { "name" } , nil )
2018-07-05 16:26:48 +02:00
socketAcceptedConnectionsDesc := prometheus . NewDesc (
prometheus . BuildFQName ( namespace , subsystem , "socket_accepted_connections_total" ) ,
"Total number of accepted socket connections" , [ ] string { "name" } , nil )
socketCurrentConnectionsDesc := prometheus . NewDesc (
prometheus . BuildFQName ( namespace , subsystem , "socket_current_connections" ) ,
"Current number of socket connections" , [ ] string { "name" } , nil )
2018-07-16 16:01:42 +02:00
socketRefusedConnectionsDesc := prometheus . NewDesc (
prometheus . BuildFQName ( namespace , subsystem , "socket_refused_connections_total" ) ,
"Total number of refused socket connections" , [ ] string { "name" } , nil )
2016-08-16 08:39:49 +02:00
unitWhitelistPattern := regexp . MustCompile ( fmt . Sprintf ( "^(?:%s)$" , * unitWhitelist ) )
unitBlacklistPattern := regexp . MustCompile ( fmt . Sprintf ( "^(?:%s)$" , * unitBlacklist ) )
2015-12-06 22:33:47 +01:00
return & systemdCollector {
2018-07-05 16:26:48 +02:00
unitDesc : unitDesc ,
2018-07-18 16:02:05 +02:00
unitStartTimeDesc : unitStartTimeDesc ,
2018-11-14 10:50:39 +01:00
unitTasksCurrentDesc : unitTasksCurrentDesc ,
unitTasksMaxDesc : unitTasksMaxDesc ,
2018-07-05 16:26:48 +02:00
systemRunningDesc : systemRunningDesc ,
summaryDesc : summaryDesc ,
nRestartsDesc : nRestartsDesc ,
timerLastTriggerDesc : timerLastTriggerDesc ,
socketAcceptedConnectionsDesc : socketAcceptedConnectionsDesc ,
socketCurrentConnectionsDesc : socketCurrentConnectionsDesc ,
2018-07-16 16:01:42 +02:00
socketRefusedConnectionsDesc : socketRefusedConnectionsDesc ,
2018-07-05 16:26:48 +02:00
unitWhitelistPattern : unitWhitelistPattern ,
unitBlacklistPattern : unitBlacklistPattern ,
2015-12-06 22:33:47 +01:00
} , nil
}
2017-02-28 19:47:20 +01:00
func ( c * systemdCollector ) Update ( ch chan <- prometheus . Metric ) error {
2018-01-04 11:49:36 +01:00
allUnits , err := c . getAllUnits ( )
2015-12-06 22:33:47 +01:00
if err != nil {
2018-01-04 11:49:36 +01:00
return fmt . Errorf ( "couldn't get units: %s" , err )
2015-12-06 22:33:47 +01:00
}
2018-01-04 11:49:36 +01:00
summary := summarizeUnits ( allUnits )
c . collectSummaryMetrics ( ch , summary )
units := filterUnits ( allUnits , c . unitWhitelistPattern , c . unitBlacklistPattern )
2015-12-17 19:30:35 +01:00
c . collectUnitStatusMetrics ( ch , units )
2018-07-18 16:02:05 +02:00
c . collectUnitStartTimeMetrics ( ch , units )
2018-11-14 10:50:39 +01:00
c . collectUnitTasksCurrentMetrics ( ch , units )
c . collectUnitTasksMaxMetrics ( ch , units )
2018-01-31 16:06:58 +01:00
c . collectTimers ( ch , units )
2018-07-05 16:26:48 +02:00
c . collectSockets ( ch , units )
2015-12-06 22:33:47 +01:00
2015-12-17 19:30:35 +01:00
systemState , err := c . getSystemState ( )
if err != nil {
return fmt . Errorf ( "couldn't get system state: %s" , err )
}
c . collectSystemState ( ch , systemState )
2015-12-06 22:33:47 +01:00
return nil
}
2018-01-31 16:19:18 +01:00
func ( c * systemdCollector ) collectUnitStatusMetrics ( ch chan <- prometheus . Metric , units [ ] unit ) {
2015-12-06 22:33:47 +01:00
for _ , unit := range units {
for _ , stateName := range unitStatesName {
isActive := 0.0
if stateName == unit . ActiveState {
isActive = 1.0
}
ch <- prometheus . MustNewConstMetric (
c . unitDesc , prometheus . GaugeValue , isActive ,
unit . Name , stateName )
}
2018-08-14 14:28:26 +02:00
if strings . HasSuffix ( unit . Name , ".service" ) && unit . nRestarts != nil {
2018-07-05 13:31:45 +02:00
ch <- prometheus . MustNewConstMetric (
c . nRestartsDesc , prometheus . CounterValue ,
2018-08-14 14:28:26 +02:00
float64 ( * unit . nRestarts ) , unit . Name )
2018-07-05 13:31:45 +02:00
}
2015-12-06 22:33:47 +01:00
}
}
2018-08-05 16:55:25 +02:00
func ( c * systemdCollector ) collectSockets ( ch chan <- prometheus . Metric , units [ ] unit ) {
2018-07-05 16:26:48 +02:00
for _ , unit := range units {
if ! strings . HasSuffix ( unit . Name , ".socket" ) {
continue
}
ch <- prometheus . MustNewConstMetric (
c . socketAcceptedConnectionsDesc , prometheus . CounterValue ,
float64 ( unit . acceptedConnections ) , unit . Name )
ch <- prometheus . MustNewConstMetric (
c . socketCurrentConnectionsDesc , prometheus . GaugeValue ,
float64 ( unit . currentConnections ) , unit . Name )
2018-08-14 14:28:26 +02:00
if unit . refusedConnections != nil {
ch <- prometheus . MustNewConstMetric (
c . socketRefusedConnectionsDesc , prometheus . GaugeValue ,
float64 ( * unit . refusedConnections ) , unit . Name )
}
2018-07-05 16:26:48 +02:00
}
}
2018-07-18 16:02:05 +02:00
func ( c * systemdCollector ) collectUnitStartTimeMetrics ( ch chan <- prometheus . Metric , units [ ] unit ) {
for _ , unit := range units {
ch <- prometheus . MustNewConstMetric (
c . unitStartTimeDesc , prometheus . GaugeValue ,
float64 ( unit . startTimeUsec ) / 1e6 , unit . Name )
}
}
2018-11-14 10:50:39 +01:00
func ( c * systemdCollector ) collectUnitTasksCurrentMetrics ( ch chan <- prometheus . Metric , units [ ] unit ) {
for _ , unit := range units {
if unit . tasksCurrent != nil {
ch <- prometheus . MustNewConstMetric (
c . unitTasksCurrentDesc , prometheus . GaugeValue ,
float64 ( * unit . tasksCurrent ) , unit . Name )
}
}
}
func ( c * systemdCollector ) collectUnitTasksMaxMetrics ( ch chan <- prometheus . Metric , units [ ] unit ) {
for _ , unit := range units {
if unit . tasksMax != nil {
ch <- prometheus . MustNewConstMetric (
c . unitTasksMaxDesc , prometheus . GaugeValue ,
float64 ( * unit . tasksMax ) , unit . Name )
}
}
}
2018-08-05 16:55:25 +02:00
func ( c * systemdCollector ) collectTimers ( ch chan <- prometheus . Metric , units [ ] unit ) {
2018-01-31 16:06:58 +01:00
for _ , unit := range units {
if ! strings . HasSuffix ( unit . Name , ".timer" ) {
continue
}
ch <- prometheus . MustNewConstMetric (
c . timerLastTriggerDesc , prometheus . GaugeValue ,
2018-01-31 16:19:18 +01:00
float64 ( unit . lastTriggerUsec ) / 1e6 , unit . Name )
2018-01-31 16:06:58 +01:00
}
}
2018-01-04 11:49:36 +01:00
func ( c * systemdCollector ) collectSummaryMetrics ( ch chan <- prometheus . Metric , summary map [ string ] float64 ) {
for stateName , count := range summary {
ch <- prometheus . MustNewConstMetric (
c . summaryDesc , prometheus . GaugeValue , count , stateName )
}
}
2015-12-17 19:30:35 +01:00
func ( c * systemdCollector ) collectSystemState ( ch chan <- prometheus . Metric , systemState string ) {
isSystemRunning := 0.0
if systemState == ` "running" ` {
isSystemRunning = 1.0
}
ch <- prometheus . MustNewConstMetric ( c . systemRunningDesc , prometheus . GaugeValue , isSystemRunning )
}
2016-03-29 16:19:47 +02:00
func ( c * systemdCollector ) newDbus ( ) ( * dbus . Conn , error ) {
if * systemdPrivate {
return dbus . NewSystemdConnection ( )
}
return dbus . New ( )
}
2018-01-31 16:19:18 +01:00
type unit struct {
dbus . UnitStatus
2018-07-05 16:26:48 +02:00
lastTriggerUsec uint64
2018-07-18 16:02:05 +02:00
startTimeUsec uint64
2018-11-14 10:50:39 +01:00
tasksCurrent * uint64
tasksMax * uint64
2018-08-14 14:28:26 +02:00
nRestarts * uint32
2018-07-05 16:26:48 +02:00
acceptedConnections uint32
currentConnections uint32
2018-08-14 14:28:26 +02:00
refusedConnections * uint32
2018-01-31 16:19:18 +01:00
}
func ( c * systemdCollector ) getAllUnits ( ) ( [ ] unit , error ) {
2016-03-29 16:19:47 +02:00
conn , err := c . newDbus ( )
2015-12-06 22:33:47 +01:00
if err != nil {
return nil , fmt . Errorf ( "couldn't get dbus connection: %s" , err )
}
2018-01-31 16:19:18 +01:00
defer conn . Close ( )
2016-08-16 08:39:49 +02:00
2018-07-22 09:20:03 +02:00
// Filter out any units that are not installed and are pulled in only as dependencies.
2018-09-24 15:04:55 +02:00
allUnits , err := conn . ListUnits ( )
2018-07-22 09:20:03 +02:00
2016-08-16 08:39:49 +02:00
if err != nil {
2018-01-31 16:19:18 +01:00
return nil , err
}
result := make ( [ ] unit , 0 , len ( allUnits ) )
for _ , status := range allUnits {
unit := unit {
UnitStatus : status ,
}
if strings . HasSuffix ( unit . Name , ".timer" ) {
lastTriggerValue , err := conn . GetUnitTypeProperty ( unit . Name , "Timer" , "LastTriggerUSec" )
if err != nil {
2018-09-24 15:04:55 +02:00
log . Debugf ( "couldn't get unit '%s' LastTriggerUSec: %s" , unit . Name , err )
2018-08-14 14:28:26 +02:00
continue
2018-01-31 16:19:18 +01:00
}
unit . lastTriggerUsec = lastTriggerValue . Value . Value ( ) . ( uint64 )
}
2018-07-05 13:31:45 +02:00
if strings . HasSuffix ( unit . Name , ".service" ) {
2018-08-14 14:28:26 +02:00
// NRestarts wasn't added until systemd 235.
restartsCount , err := conn . GetUnitTypeProperty ( unit . Name , "Service" , "NRestarts" )
2018-07-05 13:31:45 +02:00
if err != nil {
2018-09-24 15:04:55 +02:00
log . Debugf ( "couldn't get unit '%s' NRestarts: %s" , unit . Name , err )
2018-08-14 14:28:26 +02:00
} else {
nRestarts := restartsCount . Value . Value ( ) . ( uint32 )
unit . nRestarts = & nRestarts
2018-07-05 13:31:45 +02:00
}
2018-11-14 10:50:39 +01:00
tasksCurrentCount , err := conn . GetUnitTypeProperty ( unit . Name , "Service" , "TasksCurrent" )
if err != nil {
log . Debugf ( "couldn't get unit '%s' TasksCurrent: %s" , unit . Name , err )
} else {
val := tasksCurrentCount . Value . Value ( ) . ( uint64 )
// Don't set if tasksCurrent if dbus reports MaxUint64.
if val != math . MaxUint64 {
unit . tasksCurrent = & val
}
}
tasksMaxCount , err := conn . GetUnitTypeProperty ( unit . Name , "Service" , "TasksMax" )
if err != nil {
log . Debugf ( "couldn't get unit '%s' TasksMax: %s" , unit . Name , err )
} else {
val := tasksMaxCount . Value . Value ( ) . ( uint64 )
// Don't set if tasksMax if dbus reports MaxUint64.
if val != math . MaxUint64 {
unit . tasksMax = & val
}
}
2018-07-05 13:31:45 +02:00
}
2018-01-31 16:19:18 +01:00
2018-07-05 16:26:48 +02:00
if strings . HasSuffix ( unit . Name , ".socket" ) {
acceptedConnectionCount , err := conn . GetUnitTypeProperty ( unit . Name , "Socket" , "NAccepted" )
if err != nil {
2018-09-24 15:04:55 +02:00
log . Debugf ( "couldn't get unit '%s' NAccepted: %s" , unit . Name , err )
2018-08-14 14:28:26 +02:00
continue
2018-07-05 16:26:48 +02:00
}
unit . acceptedConnections = acceptedConnectionCount . Value . Value ( ) . ( uint32 )
currentConnectionCount , err := conn . GetUnitTypeProperty ( unit . Name , "Socket" , "NConnections" )
if err != nil {
2018-09-24 15:04:55 +02:00
log . Debugf ( "couldn't get unit '%s' NConnections: %s" , unit . Name , err )
2018-08-14 14:28:26 +02:00
continue
2018-07-05 16:26:48 +02:00
}
unit . currentConnections = currentConnectionCount . Value . Value ( ) . ( uint32 )
2018-08-14 14:28:26 +02:00
// NRefused wasn't added until systemd 239.
2018-07-16 16:01:42 +02:00
refusedConnectionCount , err := conn . GetUnitTypeProperty ( unit . Name , "Socket" , "NRefused" )
if err != nil {
2018-09-24 15:04:55 +02:00
log . Debugf ( "couldn't get unit '%s' NRefused: %s" , unit . Name , err )
2018-08-14 14:28:26 +02:00
} else {
nRefused := refusedConnectionCount . Value . Value ( ) . ( uint32 )
unit . refusedConnections = & nRefused
2018-07-16 16:01:42 +02:00
}
2018-07-05 16:26:48 +02:00
}
2018-07-18 16:02:05 +02:00
if unit . ActiveState != "active" {
unit . startTimeUsec = 0
} else {
timestampValue , err := conn . GetUnitProperty ( unit . Name , "ActiveEnterTimestamp" )
if err != nil {
2018-09-24 15:04:55 +02:00
log . Debugf ( "couldn't get unit '%s' StartTimeUsec: %s" , unit . Name , err )
2018-08-14 14:28:26 +02:00
continue
2018-07-18 16:02:05 +02:00
}
unit . startTimeUsec = timestampValue . Value . Value ( ) . ( uint64 )
}
2018-01-31 16:19:18 +01:00
result = append ( result , unit )
2016-08-16 08:39:49 +02:00
}
2018-01-31 16:19:18 +01:00
return result , nil
2018-01-04 11:49:36 +01:00
}
2018-01-31 16:19:18 +01:00
func summarizeUnits ( units [ ] unit ) map [ string ] float64 {
2018-01-04 11:49:36 +01:00
summarized := make ( map [ string ] float64 )
for _ , unitStateName := range unitStatesName {
summarized [ unitStateName ] = 0.0
}
for _ , unit := range units {
summarized [ unit . ActiveState ] += 1.0
}
return summarized
2016-08-16 08:39:49 +02:00
}
2018-01-31 16:19:18 +01:00
func filterUnits ( units [ ] unit , whitelistPattern , blacklistPattern * regexp . Regexp ) [ ] unit {
filtered := make ( [ ] unit , 0 , len ( units ) )
2016-08-16 08:39:49 +02:00
for _ , unit := range units {
2018-09-24 15:04:55 +02:00
if whitelistPattern . MatchString ( unit . Name ) && ! blacklistPattern . MatchString ( unit . Name ) && unit . LoadState == "loaded" {
log . Debugf ( "Adding unit: %s" , unit . Name )
2016-08-16 08:39:49 +02:00
filtered = append ( filtered , unit )
} else {
log . Debugf ( "Ignoring unit: %s" , unit . Name )
}
}
return filtered
2015-12-06 22:33:47 +01:00
}
2015-12-17 19:30:35 +01:00
func ( c * systemdCollector ) getSystemState ( ) ( state string , err error ) {
2016-03-29 16:19:47 +02:00
conn , err := c . newDbus ( )
2015-12-17 19:30:35 +01:00
if err != nil {
return "" , fmt . Errorf ( "couldn't get dbus connection: %s" , err )
}
state , err = conn . GetManagerProperty ( "SystemState" )
conn . Close ( )
return state , err
}