148 lines
4.1 KiB
Go
148 lines
4.1 KiB
Go
//go:generate ../../../tools/readme_config_includer/generator
|
|
package systemd_units
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
)
|
|
|
|
type valueDef struct {
|
|
valueName string
|
|
valueMap *map[string]int
|
|
}
|
|
|
|
// The following two maps configure the mapping of systemd properties to
|
|
// tags or values. The properties are automatically requested from
|
|
// `systemctl show`.
|
|
// If a vlaue has no valueMap, `atoi` is called on the value to convert it to
|
|
// an integer.
|
|
var tagMap = map[string]string{
|
|
"Id": "name",
|
|
"LoadState": "load",
|
|
"ActiveState": "active",
|
|
"SubState": "sub",
|
|
"UnitFileState": "uf_state",
|
|
"UnitFilePreset": "uf_preset",
|
|
}
|
|
|
|
var valueMap = map[string]valueDef{
|
|
"LoadState": {valueName: "load_code", valueMap: &loadMap},
|
|
"ActiveState": {valueName: "active_code", valueMap: &activeMap},
|
|
"SubState": {valueName: "sub_code", valueMap: &subMap},
|
|
"StatusErrno": {valueName: "status_errno", valueMap: nil},
|
|
"NRestarts": {valueName: "restarts", valueMap: nil},
|
|
"MemoryCurrent": {valueName: "mem_current", valueMap: nil},
|
|
"MemoryPeak": {valueName: "mem_peak", valueMap: nil},
|
|
"MemorySwapCurrent": {valueName: "swap_current", valueMap: nil},
|
|
"MemorySwapPeak": {valueName: "swap_peak", valueMap: nil},
|
|
"MemoryAvailable": {valueName: "mem_avail", valueMap: nil},
|
|
"MainPID": {valueName: "pid", valueMap: nil},
|
|
}
|
|
|
|
// Gather parses systemctl outputs and adds counters to the Accumulator
|
|
func parseShow(acc telegraf.Accumulator, buffer *bytes.Buffer) {
|
|
scanner := bufio.NewScanner(buffer)
|
|
|
|
tags := make(map[string]string)
|
|
fields := make(map[string]interface{})
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
// An empty line signals the start of the next unit
|
|
if len(line) == 0 {
|
|
// We need at least a "name" field. This prevents values from the
|
|
// global information block (enabled by the --all switch) to be
|
|
// shown as a unit.
|
|
if _, ok := tags["name"]; ok {
|
|
acc.AddFields(measurement, fields, tags)
|
|
}
|
|
|
|
tags = make(map[string]string)
|
|
fields = make(map[string]interface{})
|
|
|
|
continue
|
|
}
|
|
|
|
key, value, ok := strings.Cut(line, "=")
|
|
if !ok {
|
|
acc.AddError(fmt.Errorf("error parsing line (expected key=value): %s", line))
|
|
continue
|
|
}
|
|
|
|
// Map the tags
|
|
if tagName, isTag := tagMap[key]; isTag {
|
|
tags[tagName] = value
|
|
}
|
|
|
|
// Map the values
|
|
if valueDef, isValue := valueMap[key]; isValue {
|
|
// If a value map is set use it. If not, just try to convert the
|
|
// value into an integer.
|
|
if valueDef.valueMap != nil {
|
|
code, ok := (*valueDef.valueMap)[value]
|
|
if !ok {
|
|
acc.AddError(fmt.Errorf("error parsing field '%s', value '%s' not in map", key, value))
|
|
continue
|
|
}
|
|
|
|
fields[valueDef.valueName] = code
|
|
} else {
|
|
if value != "[not set]" {
|
|
intVal, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
acc.AddError(fmt.Errorf("error '%w' parsing field '%s'. Not an integer value", err, key))
|
|
continue
|
|
}
|
|
fields[valueDef.valueName] = intVal
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the last unit because the output does not contain a newline for this
|
|
if _, ok := tags["name"]; ok {
|
|
acc.AddFields(measurement, fields, tags)
|
|
}
|
|
}
|
|
|
|
func getShowParameters(s *SystemdUnits) *[]string {
|
|
// build parameters for systemctl call
|
|
params := []string{"show"}
|
|
// create patterns parameters if provided in config
|
|
if s.Pattern == "" {
|
|
params = append(params, "*")
|
|
} else {
|
|
psplit := strings.SplitN(s.Pattern, " ", -1)
|
|
params = append(params, psplit...)
|
|
}
|
|
|
|
params = append(params, "--all", "--type="+s.UnitType)
|
|
|
|
// add the fields we're interested in to the command line
|
|
for property := range tagMap {
|
|
params = append(params, "--property="+property)
|
|
}
|
|
for property := range valueMap {
|
|
// If a property exists within the tagMap it was already added. Do not add it again to
|
|
// keep the command line short.
|
|
if _, exists := tagMap[property]; !exists {
|
|
params = append(params, "--property="+property)
|
|
}
|
|
}
|
|
|
|
return ¶ms
|
|
}
|
|
|
|
func initSubcommandShow() *subCommandInfo {
|
|
return &subCommandInfo{
|
|
getParameters: getShowParameters,
|
|
parseResult: parseShow,
|
|
}
|
|
}
|