feat(inputs.systemd_units): Add active_enter_timestamp_us field (#16287)

This commit is contained in:
Sven Rebhan 2024-12-11 22:32:52 +01:00 committed by GitHub
parent 2ccc79ce27
commit e3ce01abf0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 114 additions and 93 deletions

2
go.mod
View File

@ -89,7 +89,6 @@ require (
github.com/go-sql-driver/mysql v1.8.1 github.com/go-sql-driver/mysql v1.8.1
github.com/go-stomp/stomp v2.1.4+incompatible github.com/go-stomp/stomp v2.1.4+incompatible
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/godbus/dbus/v5 v5.1.0
github.com/gofrs/uuid/v5 v5.3.0 github.com/gofrs/uuid/v5 v5.3.0
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec github.com/golang/geo v0.0.0-20190916061304-5b978397cfec
@ -348,6 +347,7 @@ require (
github.com/goburrow/serial v0.1.1-0.20211022031912-bfb69110f8dd // indirect github.com/goburrow/serial v0.1.1-0.20211022031912-bfb69110f8dd // indirect
github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-json v0.10.3 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect

View File

@ -94,6 +94,7 @@ The following *additional* metrics are available with `details = true`:
- swap_current (uint, current swap usage) - swap_current (uint, current swap usage)
- swap_peak (uint, peak swap usage) - swap_peak (uint, peak swap usage)
- mem_avail (uint, available memory for this unit) - mem_avail (uint, available memory for this unit)
- active_enter_timestamp_us (uint, timestamp in us when entered the state)
### Load ### Load

View File

@ -123,17 +123,18 @@ type client interface {
ListUnitFilesByPatternsContext(ctx context.Context, states, pattern []string) ([]dbus.UnitFile, error) ListUnitFilesByPatternsContext(ctx context.Context, states, pattern []string) ([]dbus.UnitFile, error)
ListUnitsByNamesContext(ctx context.Context, units []string) ([]dbus.UnitStatus, error) ListUnitsByNamesContext(ctx context.Context, units []string) ([]dbus.UnitStatus, error)
GetUnitTypePropertiesContext(ctx context.Context, unit, unitType string) (map[string]interface{}, error) GetUnitTypePropertiesContext(ctx context.Context, unit, unitType string) (map[string]interface{}, error)
GetUnitPropertyContext(ctx context.Context, unit, propertyName string) (*dbus.Property, error) GetUnitPropertiesContext(ctx context.Context, unit string) (map[string]interface{}, error)
ListUnitsContext(ctx context.Context) ([]dbus.UnitStatus, error) ListUnitsContext(ctx context.Context) ([]dbus.UnitStatus, error)
} }
type archParams struct { type archParams struct {
client client client client
pattern []string pattern []string
filter filter.Filter filter filter.Filter
unitTypeDBus string unitTypeDBus string
scope string scope string
user string user string
warnUnitProps map[string]bool
} }
func (s *SystemdUnits) Init() error { func (s *SystemdUnits) Init() error {
@ -176,6 +177,8 @@ func (s *SystemdUnits) Init() error {
return fmt.Errorf("invalid 'scope' %q", s.Scope) return fmt.Errorf("invalid 'scope' %q", s.Scope)
} }
s.warnUnitProps = make(map[string]bool)
return nil return nil
} }
@ -374,26 +377,35 @@ func (s *SystemdUnits) Gather(acc telegraf.Accumulator) error {
} }
// Get required unit file properties // Get required unit file properties
var unitFileState string unitProperties, err := s.client.GetUnitPropertiesContext(ctx, state.Name)
if v, err := s.client.GetUnitPropertyContext(ctx, state.Name, "UnitFileState"); err == nil { if err != nil && !s.warnUnitProps[state.Name] {
unitFileState = strings.Trim(v.Value.String(), `'"`) s.Log.Warnf("Cannot read unit properties for %q: %v", state.Name, err)
} s.warnUnitProps[state.Name] = true
var unitFilePreset string
if v, err := s.client.GetUnitPropertyContext(ctx, state.Name, "UnitFilePreset"); err == nil {
unitFilePreset = strings.Trim(v.Value.String(), `'"`)
} }
tags["state"] = unitFileState // Set tags
tags["preset"] = unitFilePreset if v, found := unitProperties["UnitFileState"]; found {
tags["state"] = v.(string)
}
if v, found := unitProperties["UnitFilePreset"]; found {
tags["preset"] = v.(string)
}
// Set fields
if v, found := unitProperties["ActiveEnterTimestamp"]; found {
fields["active_enter_timestamp_us"] = v
}
fields["status_errno"] = properties["StatusErrno"] fields["status_errno"] = properties["StatusErrno"]
fields["restarts"] = properties["NRestarts"] fields["restarts"] = properties["NRestarts"]
fields["pid"] = properties["MainPID"] fields["pid"] = properties["MainPID"]
fields["mem_current"] = properties["MemoryCurrent"] fields["mem_current"] = properties["MemoryCurrent"]
fields["mem_peak"] = properties["MemoryPeak"] fields["mem_peak"] = properties["MemoryPeak"]
fields["mem_avail"] = properties["MemoryAvailable"]
fields["swap_current"] = properties["MemorySwapCurrent"] fields["swap_current"] = properties["MemorySwapCurrent"]
fields["swap_peak"] = properties["MemorySwapPeak"] fields["swap_peak"] = properties["MemorySwapPeak"]
fields["mem_avail"] = properties["MemoryAvailable"]
// Sanitize unset memory fields // Sanitize unset memory fields
for k, value := range fields { for k, value := range fields {

View File

@ -4,7 +4,6 @@ package systemd_units
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"math" "math"
"os/user" "os/user"
@ -13,7 +12,6 @@ import (
"time" "time"
sdbus "github.com/coreos/go-systemd/v22/dbus" sdbus "github.com/coreos/go-systemd/v22/dbus"
"github.com/godbus/dbus/v5"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
@ -25,12 +23,13 @@ import (
) )
type properties struct { type properties struct {
uf *sdbus.UnitFile uf *sdbus.UnitFile
utype string utype string
state *sdbus.UnitStatus state *sdbus.UnitStatus
ufPreset string ufPreset string
ufState string ufState string
properties map[string]interface{} ufActiveEnter uint64
properties map[string]interface{}
} }
func TestDefaultPattern(t *testing.T) { func TestDefaultPattern(t *testing.T) {
@ -284,6 +283,7 @@ func TestListFiles(t *testing.T) {
} }
func TestShow(t *testing.T) { func TestShow(t *testing.T) {
enter := time.Now().UnixMicro()
tests := []struct { tests := []struct {
name string name string
properties map[string]properties properties map[string]properties
@ -301,8 +301,9 @@ func TestShow(t *testing.T) {
ActiveState: "active", ActiveState: "active",
SubState: "running", SubState: "running",
}, },
ufPreset: "disabled", ufPreset: "disabled",
ufState: "enabled", ufState: "enabled",
ufActiveEnter: uint64(enter),
properties: map[string]interface{}{ properties: map[string]interface{}{
"Id": "example.service", "Id": "example.service",
"StatusErrno": 0, "StatusErrno": 0,
@ -328,17 +329,18 @@ func TestShow(t *testing.T) {
"preset": "disabled", "preset": "disabled",
}, },
map[string]interface{}{ map[string]interface{}{
"load_code": 0, "load_code": 0,
"active_code": 0, "active_code": 0,
"sub_code": 0, "sub_code": 0,
"status_errno": 0, "status_errno": 0,
"restarts": 1, "restarts": 1,
"mem_current": uint64(1000), "mem_current": uint64(1000),
"mem_peak": uint64(2000), "mem_peak": uint64(2000),
"swap_current": uint64(3000), "swap_current": uint64(3000),
"swap_peak": uint64(4000), "swap_peak": uint64(4000),
"mem_avail": uint64(5000), "mem_avail": uint64(5000),
"pid": 9999, "pid": 9999,
"active_enter_timestamp_us": uint64(enter),
}, },
time.Unix(0, 0), time.Unix(0, 0),
), ),
@ -355,8 +357,9 @@ func TestShow(t *testing.T) {
ActiveState: "active", ActiveState: "active",
SubState: "exited", SubState: "exited",
}, },
ufPreset: "disabled", ufPreset: "disabled",
ufState: "enabled", ufState: "enabled",
ufActiveEnter: 0,
properties: map[string]interface{}{ properties: map[string]interface{}{
"Id": "example.service", "Id": "example.service",
"StatusErrno": 0, "StatusErrno": 0,
@ -376,16 +379,17 @@ func TestShow(t *testing.T) {
"preset": "disabled", "preset": "disabled",
}, },
map[string]interface{}{ map[string]interface{}{
"load_code": 0, "load_code": 0,
"active_code": 0, "active_code": 0,
"sub_code": 4, "sub_code": 4,
"status_errno": 0, "status_errno": 0,
"restarts": 0, "restarts": 0,
"mem_current": uint64(0), "mem_current": uint64(0),
"mem_peak": uint64(0), "mem_peak": uint64(0),
"swap_current": uint64(0), "swap_current": uint64(0),
"swap_peak": uint64(0), "swap_peak": uint64(0),
"mem_avail": uint64(0), "mem_avail": uint64(0),
"active_enter_timestamp_us": uint64(0),
}, },
time.Unix(0, 0), time.Unix(0, 0),
), ),
@ -402,8 +406,9 @@ func TestShow(t *testing.T) {
ActiveState: "failed", ActiveState: "failed",
SubState: "failed", SubState: "failed",
}, },
ufPreset: "disabled", ufPreset: "disabled",
ufState: "enabled", ufState: "enabled",
ufActiveEnter: uint64(enter),
properties: map[string]interface{}{ properties: map[string]interface{}{
"Id": "example.service", "Id": "example.service",
"StatusErrno": 10, "StatusErrno": 10,
@ -428,16 +433,17 @@ func TestShow(t *testing.T) {
"preset": "disabled", "preset": "disabled",
}, },
map[string]interface{}{ map[string]interface{}{
"load_code": 0, "load_code": 0,
"active_code": 3, "active_code": 3,
"sub_code": 12, "sub_code": 12,
"status_errno": 10, "status_errno": 10,
"restarts": 1, "restarts": 1,
"mem_current": uint64(1000), "mem_current": uint64(1000),
"mem_peak": uint64(2000), "mem_peak": uint64(2000),
"swap_current": uint64(3000), "swap_current": uint64(3000),
"swap_peak": uint64(4000), "swap_peak": uint64(4000),
"mem_avail": uint64(5000), "mem_avail": uint64(5000),
"active_enter_timestamp_us": uint64(enter),
}, },
time.Unix(0, 0), time.Unix(0, 0),
), ),
@ -454,8 +460,9 @@ func TestShow(t *testing.T) {
ActiveState: "inactive", ActiveState: "inactive",
SubState: "dead", SubState: "dead",
}, },
ufPreset: "disabled", ufPreset: "disabled",
ufState: "enabled", ufState: "enabled",
ufActiveEnter: uint64(0),
properties: map[string]interface{}{ properties: map[string]interface{}{
"Id": "example.service", "Id": "example.service",
}, },
@ -473,14 +480,15 @@ func TestShow(t *testing.T) {
"preset": "disabled", "preset": "disabled",
}, },
map[string]interface{}{ map[string]interface{}{
"load_code": 2, "load_code": 2,
"active_code": 2, "active_code": 2,
"sub_code": 1, "sub_code": 1,
"mem_current": uint64(0), "mem_current": uint64(0),
"mem_peak": uint64(0), "mem_peak": uint64(0),
"swap_current": uint64(0), "swap_current": uint64(0),
"swap_peak": uint64(0), "swap_peak": uint64(0),
"mem_avail": uint64(0), "mem_avail": uint64(0),
"active_enter_timestamp_us": uint64(0),
}, },
time.Unix(0, 0), time.Unix(0, 0),
), ),
@ -517,8 +525,9 @@ func TestShow(t *testing.T) {
ActiveState: "inactive", ActiveState: "inactive",
SubState: "dead", SubState: "dead",
}, },
ufPreset: "disabled", ufPreset: "disabled",
ufState: "disabled", ufState: "disabled",
ufActiveEnter: uint64(0),
properties: map[string]interface{}{ properties: map[string]interface{}{
"Id": "example.service", "Id": "example.service",
"StatusErrno": 0, "StatusErrno": 0,
@ -543,16 +552,17 @@ func TestShow(t *testing.T) {
"preset": "disabled", "preset": "disabled",
}, },
map[string]interface{}{ map[string]interface{}{
"load_code": 0, "load_code": 0,
"active_code": int64(2), "active_code": int64(2),
"sub_code": 1, "sub_code": 1,
"status_errno": 0, "status_errno": 0,
"restarts": 0, "restarts": 0,
"mem_current": uint64(0), "mem_current": uint64(0),
"mem_peak": uint64(0), "mem_peak": uint64(0),
"swap_current": uint64(0), "swap_current": uint64(0),
"swap_peak": uint64(0), "swap_peak": uint64(0),
"mem_avail": uint64(0), "mem_avail": uint64(0),
"active_enter_timestamp_us": uint64(0),
}, },
time.Unix(0, 0), time.Unix(0, 0),
), ),
@ -974,19 +984,17 @@ func (c *fakeClient) GetUnitTypePropertiesContext(_ context.Context, unit, unitT
return u.properties, nil return u.properties, nil
} }
func (c *fakeClient) GetUnitPropertyContext(_ context.Context, unit, propertyName string) (*sdbus.Property, error) { func (c *fakeClient) GetUnitPropertiesContext(_ context.Context, unit string) (map[string]interface{}, error) {
u, found := c.units[unit] u, found := c.units[unit]
if !found { if !found {
return nil, nil return nil, nil
} }
switch propertyName { return map[string]interface{}{
case "UnitFileState": "UnitFileState": u.ufState,
return &sdbus.Property{Name: propertyName, Value: dbus.MakeVariant(u.ufState)}, nil "UnitFilePreset": u.ufPreset,
case "UnitFilePreset": "ActiveEnterTimestamp": u.ufActiveEnter,
return &sdbus.Property{Name: propertyName, Value: dbus.MakeVariant(u.ufPreset)}, nil }, nil
}
return nil, errors.New("unknown property")
} }
func (c *fakeClient) ListUnitsContext(_ context.Context) ([]sdbus.UnitStatus, error) { func (c *fakeClient) ListUnitsContext(_ context.Context) ([]sdbus.UnitStatus, error) {