Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

systemd.go: Added boot stage timestamp collector #110

Merged
merged 1 commit into from
Jan 11, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions systemd/systemd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"context"
"fmt"
"math"
"strconv"

// Register pprof-over-http handlers
_ "net/http/pprof"
Expand All @@ -42,6 +43,7 @@ var (
systemdUser = kingpin.Flag("systemd.collector.user", "Connect to the user systemd instance.").Bool()
enableRestartsMetrics = kingpin.Flag("systemd.collector.enable-restart-count", "Enables service restart count metrics. This feature only works with systemd 235 and above.").Bool()
enableIPAccountingMetrics = kingpin.Flag("systemd.collector.enable-ip-accounting", "Enables service ip accounting metrics. This feature only works with systemd 235 and above.").Bool()
bootTimeRE = regexp.MustCompile(`\d+`)
)

var unitStatesName = []string{"active", "activating", "deactivating", "inactive", "failed"}
Expand All @@ -58,6 +60,8 @@ var (
type Collector struct {
ctx context.Context
logger log.Logger
systemdBootMonotonic *prometheus.Desc
systemdBootTime *prometheus.Desc
unitCPUTotal *prometheus.Desc
unitState *prometheus.Desc
unitInfo *prometheus.Desc
Expand All @@ -84,6 +88,14 @@ type Collector struct {

// NewCollector returns a new Collector exposing systemd statistics.
func NewCollector(logger log.Logger) (*Collector, error) {
systemdBootMonotonic := prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "boot_monotonic_seconds"),
"Systemd boot stage monotonic timestamps", []string{"stage"}, nil,
)
systemdBootTime := prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "boot_time_seconds"),
"Systemd boot stage timestamps", []string{"stage"}, nil,
)
// Type is labeled twice e.g. name="foo.service" and type="service" to maintain compatibility
// with users before we started exporting type label
unitState := prometheus.NewDesc(
Expand Down Expand Up @@ -194,6 +206,8 @@ func NewCollector(logger log.Logger) (*Collector, error) {
return &Collector{
ctx: ctx,
logger: logger,
systemdBootMonotonic: systemdBootMonotonic,
systemdBootTime: systemdBootTime,
unitCPUTotal: unitCPUTotal,
unitState: unitState,
unitInfo: unitInfo,
Expand Down Expand Up @@ -228,6 +242,8 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) {

// Describe gathers descriptions of Metrics
func (c *Collector) Describe(desc chan<- *prometheus.Desc) {
desc <- c.systemdBootMonotonic
desc <- c.systemdBootTime
desc <- c.unitCPUTotal
desc <- c.unitState
desc <- c.unitInfo
Expand Down Expand Up @@ -259,6 +275,11 @@ func (c *Collector) collect(ch chan<- prometheus.Metric) error {
}
defer conn.Close()

err = c.collectBootStageTimestamps(conn, ch)
if err != nil {
level.Debug(c.logger).Log("msg", "Failed to collect boot stage timestamps", "err", err)
}

allUnits, err := conn.ListUnitsContext(c.ctx)
if err != nil {
return errors.Wrap(err, "could not get list of systemd units from dbus")
Expand All @@ -285,6 +306,53 @@ func (c *Collector) collect(ch chan<- prometheus.Metric) error {
return nil
}

func (c *Collector) collectBootStageTimestamps(conn *dbus.Conn, ch chan<- prometheus.Metric) error {
stages := []string{"Finish", "Firmware", "Loader", "Kernel", "InitRD",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, there's no way to get the stage list dynamically is there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not without embedding some of the C code - I doubt the list will change much going forward.

"InitRDGeneratorsStart", "InitRDGeneratorsFinish",
"InitRDSecurityStart", "InitRDSecurityFinish",
"InitRDUnitsLoadStart", "InitRDUnitsLoadFinish",
"GeneratorsStart", "GeneratorsFinish",
"SecurityStart", "SecurityFinish", "Userspace",
"UnitsLoadStart", "UnitsLoadFinish"}

for _, stage := range stages {
stageMonotonicValue, err := conn.GetManagerProperty(fmt.Sprintf("%sTimestampMonotonic", stage))
if err != nil {
return err
}

stageTimestampValue, err := conn.GetManagerProperty(fmt.Sprintf("%sTimestamp", stage))
if err != nil {
return err
}

stageMonotonic := strings.TrimPrefix(strings.TrimSuffix(stageMonotonicValue, `"`), `"`)
stageTimestamp := strings.TrimPrefix(strings.TrimSuffix(stageTimestampValue, `"`), `"`)

parsedStageMonotonic := bootTimeRE.FindString(stageMonotonic)
parsedStageTime := bootTimeRE.FindString(stageTimestamp)

vMonotonic, err := strconv.ParseFloat(parsedStageMonotonic, 64)
if err != nil {
return err
}

vTimestamp, err := strconv.ParseFloat(parsedStageTime, 64)
if err != nil {
return err
}

ch <- prometheus.MustNewConstMetric(
c.systemdBootMonotonic, prometheus.GaugeValue, float64(vMonotonic)/1e6,
stage)
ch <- prometheus.MustNewConstMetric(
c.systemdBootTime, prometheus.GaugeValue, float64(vTimestamp)/1e6,
stage)
}

return nil
}

func (c *Collector) collectUnit(conn *dbus.Conn, ch chan<- prometheus.Metric, unit dbus.UnitStatus) error {
logger := log.With(c.logger, "unit", unit.Name)

Expand Down