github.com/netdata/go.d.plugin@v0.58.1/modules/systemdunits/collect.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  //go:build linux
     4  // +build linux
     5  
     6  package systemdunits
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/coreos/go-systemd/v22/dbus"
    16  )
    17  
    18  const (
    19  	// https://www.freedesktop.org/software/systemd/man/systemd.html
    20  	unitStateActive       = "active"
    21  	unitStateInactive     = "inactive"
    22  	unitStateActivating   = "activating"
    23  	unitStateDeactivating = "deactivating"
    24  	unitStateFailed       = "failed"
    25  
    26  	// https://www.freedesktop.org/software/systemd/man/systemd.html
    27  	unitTypeService   = "service"
    28  	unitTypeSocket    = "socket"
    29  	unitTypeTarget    = "target"
    30  	unitTypePath      = "path"
    31  	unitTypeDevice    = "device"
    32  	unitTypeMount     = "mount"
    33  	unitTypeAutomount = "automount"
    34  	unitTypeSwap      = "swap"
    35  	unitTypeTimer     = "timer"
    36  	unitTypeScope     = "scope"
    37  	unitTypeSlice     = "slice"
    38  )
    39  
    40  var (
    41  	unitStates = []string{
    42  		unitStateActive,
    43  		unitStateActivating,
    44  		unitStateFailed,
    45  		unitStateInactive,
    46  		unitStateDeactivating,
    47  	}
    48  )
    49  
    50  func (s *SystemdUnits) collect() (map[string]int64, error) {
    51  	conn, err := s.getConnection()
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	if s.systemdVersion == 0 {
    57  		ver, err := s.getSystemdVersion(conn)
    58  		if err != nil {
    59  			s.closeConnection()
    60  			return nil, err
    61  		}
    62  		s.systemdVersion = ver
    63  	}
    64  
    65  	var units []dbus.UnitStatus
    66  	if s.systemdVersion >= 230 {
    67  		// https://github.com/systemd/systemd/pull/3142
    68  		units, err = s.getLoadedUnitsByPatterns(conn)
    69  	} else {
    70  		units, err = s.getLoadedUnits(conn)
    71  	}
    72  	if err != nil {
    73  		s.closeConnection()
    74  		return nil, err
    75  	}
    76  
    77  	if len(units) == 0 {
    78  		return nil, nil
    79  	}
    80  
    81  	mx := make(map[string]int64)
    82  	s.collectUnitsStates(mx, units)
    83  
    84  	return mx, nil
    85  }
    86  
    87  func (s *SystemdUnits) collectUnitsStates(mx map[string]int64, units []dbus.UnitStatus) {
    88  	for _, unit := range units {
    89  		name, typ := extractUnitNameType(cleanUnitName(unit.Name))
    90  		if name == "" || typ == "" {
    91  			continue
    92  		}
    93  
    94  		if !s.units[unit.Name] {
    95  			s.units[unit.Name] = true
    96  			s.addUnitToCharts(name, typ)
    97  		}
    98  
    99  		for _, s := range unitStates {
   100  			mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, s)] = 0
   101  		}
   102  		mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, unit.ActiveState)] = 1
   103  	}
   104  }
   105  
   106  func (s *SystemdUnits) getConnection() (systemdConnection, error) {
   107  	if s.conn == nil {
   108  		conn, err := s.client.connect()
   109  		if err != nil {
   110  			return nil, fmt.Errorf("error on creating a connection: %v", err)
   111  		}
   112  		s.conn = conn
   113  	}
   114  	return s.conn, nil
   115  }
   116  
   117  func (s *SystemdUnits) closeConnection() {
   118  	if s.conn != nil {
   119  		s.conn.Close()
   120  		s.conn = nil
   121  	}
   122  }
   123  
   124  var reVersion = regexp.MustCompile(`[0-9][0-9][0-9]`)
   125  
   126  const versionProperty = "Version"
   127  
   128  func (s *SystemdUnits) getSystemdVersion(conn systemdConnection) (int, error) {
   129  	s.Debugf("calling function 'GetManagerProperty'")
   130  	version, err := conn.GetManagerProperty(versionProperty)
   131  	if err != nil {
   132  		return 0, fmt.Errorf("error on getting '%s' manager property: %v", versionProperty, err)
   133  	}
   134  
   135  	s.Debugf("systemd version: %s", version)
   136  
   137  	major := reVersion.FindString(version)
   138  	if major == "" {
   139  		return 0, fmt.Errorf("couldn't parse systemd version string '%s'", version)
   140  	}
   141  
   142  	ver, err := strconv.Atoi(major)
   143  	if err != nil {
   144  		return 0, fmt.Errorf("couldn't parse systemd version string '%s': %v", version, err)
   145  	}
   146  
   147  	return ver, nil
   148  }
   149  
   150  func (s *SystemdUnits) getLoadedUnits(conn systemdConnection) ([]dbus.UnitStatus, error) {
   151  	ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration)
   152  	defer cancel()
   153  
   154  	s.Debugf("calling function 'ListUnits'")
   155  	units, err := conn.ListUnitsContext(ctx)
   156  	if err != nil {
   157  		return nil, fmt.Errorf("error on ListUnits: %v", err)
   158  	}
   159  
   160  	loaded := units[:0]
   161  	for _, unit := range units {
   162  		if unit.LoadState == "loaded" && s.sr.MatchString(unit.Name) {
   163  			loaded = append(loaded, unit)
   164  		}
   165  	}
   166  	s.Debugf("got total/loaded %d/%d units", len(units), len(loaded))
   167  
   168  	return loaded, nil
   169  }
   170  
   171  func (s *SystemdUnits) getLoadedUnitsByPatterns(conn systemdConnection) ([]dbus.UnitStatus, error) {
   172  	ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration)
   173  	defer cancel()
   174  
   175  	s.Debugf("calling function 'ListUnitsByPatterns'")
   176  
   177  	units, err := conn.ListUnitsByPatternsContext(ctx, unitStates, s.Include)
   178  	if err != nil {
   179  		return nil, fmt.Errorf("error on ListUnitsByPatterns: %v", err)
   180  	}
   181  
   182  	loaded := units[:0]
   183  	for _, unit := range units {
   184  		if unit.LoadState == "loaded" {
   185  			loaded = append(loaded, unit)
   186  		}
   187  	}
   188  	s.Debugf("got total/loaded %d/%d units", len(units), len(loaded))
   189  
   190  	return loaded, nil
   191  }
   192  
   193  func extractUnitNameType(name string) (string, string) {
   194  	idx := strings.LastIndexByte(name, '.')
   195  	if idx <= 0 {
   196  		return "", ""
   197  	}
   198  	return name[:idx], name[idx+1:]
   199  }
   200  
   201  func cleanUnitName(name string) string {
   202  	// dev-disk-by\x2duuid-DE44\x2dCEE0.device => dev-disk-by-uuid-DE44-CEE0.device
   203  	if strings.IndexByte(name, '\\') == -1 {
   204  		return name
   205  	}
   206  	v, err := strconv.Unquote("\"" + name + "\"")
   207  	if err != nil {
   208  		return name
   209  	}
   210  	return v
   211  }