firehol/netdata

View on GitHub
src/go/plugin/go.d/modules/systemdunits/collect_units.go

Summary

Maintainability
A
3 hrs
Test Coverage
// SPDX-License-Identifier: GPL-3.0-or-later

//go:build linux
// +build linux

package systemdunits

import (
    "context"
    "fmt"
    "strconv"
    "strings"

    "github.com/coreos/go-systemd/v22/dbus"
)

const transientProperty = "Transient"

const (
    // https://www.freedesktop.org/software/systemd/man/systemd.html
    unitStateActive       = "active"
    unitStateInactive     = "inactive"
    unitStateActivating   = "activating"
    unitStateDeactivating = "deactivating"
    unitStateFailed       = "failed"
)

var unitStates = []string{
    unitStateActive,
    unitStateActivating,
    unitStateFailed,
    unitStateInactive,
    unitStateDeactivating,
}

func (s *SystemdUnits) collectUnits(mx map[string]int64, conn systemdConnection) error {
    var units []dbus.UnitStatus
    var err error

    if s.systemdVersion >= 230 {
        // https://github.com/systemd/systemd/pull/3142
        units, err = s.getLoadedUnitsByPatterns(conn)
    } else {
        units, err = s.getLoadedUnits(conn)
    }
    if err != nil {
        return err
    }

    seen := make(map[string]bool)

    for _, unit := range units {
        name, typ, ok := extractUnitNameType(unit.Name)
        if !ok {
            continue
        }

        seen[unit.Name] = true

        if s.SkipTransient {
            if _, ok := s.unitTransient[unit.Name]; !ok {
                prop, err := s.getUnitTransientProperty(conn, unit.Name)
                if err != nil {
                    return err
                }
                prop = strings.Trim(prop, "\"")
                s.unitTransient[unit.Name] = prop == "true"
            }
            if s.unitTransient[unit.Name] {
                continue
            }
        }

        if !s.seenUnits[unit.Name] {
            s.seenUnits[unit.Name] = true
            s.addUnitCharts(name, typ)
        }

        for _, s := range unitStates {
            mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, s)] = 0
        }
        mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, unit.ActiveState)] = 1
    }

    for k := range s.seenUnits {
        if !seen[k] {
            delete(s.seenUnits, k)
            if name, typ, ok := extractUnitNameType(k); ok {
                s.removeUnitCharts(name, typ)
            }
        }
    }

    for k := range s.unitTransient {
        if !seen[k] {
            delete(s.unitTransient, k)
        }
    }

    return nil
}

func (s *SystemdUnits) getLoadedUnits(conn systemdConnection) ([]dbus.UnitStatus, error) {
    ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration())
    defer cancel()

    s.Debugf("calling function 'ListUnits'")
    units, err := conn.ListUnitsContext(ctx)
    if err != nil {
        return nil, fmt.Errorf("error on ListUnits: %v", err)
    }

    for i := range units {
        units[i].Name = cleanUnitName(units[i].Name)
    }

    loaded := units[:0]
    for _, unit := range units {
        if unit.LoadState == "loaded" && s.unitSr.MatchString(unit.Name) {
            loaded = append(loaded, unit)
        }
    }

    s.Debugf("got total/loaded %d/%d units", len(units), len(loaded))

    return loaded, nil
}

func (s *SystemdUnits) getLoadedUnitsByPatterns(conn systemdConnection) ([]dbus.UnitStatus, error) {
    ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration())
    defer cancel()

    s.Debugf("calling function 'ListUnitsByPatterns'")

    units, err := conn.ListUnitsByPatternsContext(ctx, unitStates, s.Include)
    if err != nil {
        return nil, fmt.Errorf("error on ListUnitsByPatterns: %v", err)
    }

    for i := range units {
        units[i].Name = cleanUnitName(units[i].Name)
    }

    loaded := units[:0]
    for _, unit := range units {
        if unit.LoadState == "loaded" {
            loaded = append(loaded, unit)
        }
    }
    s.Debugf("got total/loaded %d/%d units", len(units), len(loaded))

    return loaded, nil
}

func (s *SystemdUnits) getUnitTransientProperty(conn systemdConnection, unit string) (string, error) {
    ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration())
    defer cancel()

    s.Debugf("calling function 'GetUnitProperty' for unit '%s'", unit)

    prop, err := conn.GetUnitPropertyContext(ctx, unit, transientProperty)
    if err != nil {
        return "", fmt.Errorf("error on GetUnitProperty: %v", err)
    }

    return prop.Value.String(), nil
}

func extractUnitNameType(name string) (string, string, bool) {
    idx := strings.LastIndexByte(name, '.')
    if idx <= 0 {
        return "", "", false
    }
    return name[:idx], name[idx+1:], true
}

func cleanUnitName(name string) string {
    // dev-disk-by\x2duuid-DE44\x2dCEE0.device => dev-disk-by-uuid-DE44-CEE0.device
    if strings.IndexByte(name, '\\') == -1 {
        return name
    }
    v, err := strconv.Unquote("\"" + name + "\"")
    if err != nil {
        return name
    }
    return v
}