From 52458ce5a1b51e35efb8d85cd6c7cd777af53318 Mon Sep 17 00:00:00 2001 From: Thomas Roehl Date: Mon, 7 Feb 2022 13:27:35 +0100 Subject: [PATCH] Fix for LustreCollector. Check for root user --- collectors/lustreMetric.go | 139 ++++++++++++++++++++++++++----------- 1 file changed, 97 insertions(+), 42 deletions(-) diff --git a/collectors/lustreMetric.go b/collectors/lustreMetric.go index 99b371c..6d6fe26 100644 --- a/collectors/lustreMetric.go +++ b/collectors/lustreMetric.go @@ -5,10 +5,12 @@ import ( "errors" "fmt" "os/exec" + "os/user" "strconv" "strings" "time" + cclog "github.com/ClusterCockpit/cc-metric-collector/internal/ccLogger" lp "github.com/ClusterCockpit/cc-metric-collector/internal/ccMetric" ) @@ -19,17 +21,26 @@ const LCTL_OPTION = `get_param` type LustreCollectorConfig struct { LCtlCommand string `json:"lctl_command"` ExcludeMetrics []string `json:"exclude_metrics"` + SendAllMetrics bool `json:"send_all_metrics"` } type LustreCollector struct { metricCollector tags map[string]string matches map[string]map[string]int - devices []string + stats map[string]map[string]int64 config LustreCollectorConfig lctl string } +func (m *LustreCollector) getDeviceDataCommand(device string) []string { + statsfile := fmt.Sprintf("llite.%s.stats", device) + command := exec.Command(m.lctl, LCTL_OPTION, statsfile) + command.Wait() + stdout, _ := command.Output() + return strings.Split(string(stdout), "\n") +} + func (m *LustreCollector) getDevices() []string { devices := make([]string, 0) @@ -44,13 +55,9 @@ func (m *LustreCollector) getDevices() []string { // devices = append(devices, pathlist[4]) // } - command := exec.Command(m.lctl, LCTL_OPTION, "llite.*.stats") - command.Wait() - stdout, err := command.Output() - if err != nil { - return devices - } - for _, line := range strings.Split(string(stdout), "\n") { + data := m.getDeviceDataCommand("*") + + for _, line := range data { if strings.HasPrefix(line, "llite") { linefields := strings.Split(line, ".") if len(linefields) > 2 { @@ -73,14 +80,6 @@ func (m *LustreCollector) getDevices() []string { // return strings.Split(string(buffer), "\n") // } -func (m *LustreCollector) getDeviceDataCommand(device string) []string { - statsfile := fmt.Sprintf("llite.%s.stats", device) - command := exec.Command(m.lctl, LCTL_OPTION, statsfile) - command.Wait() - stdout, _ := command.Output() - return strings.Split(string(stdout), "\n") -} - func (m *LustreCollector) Init(config json.RawMessage) error { var err error m.name = "LustreCollector" @@ -93,14 +92,42 @@ func (m *LustreCollector) Init(config json.RawMessage) error { m.setup() m.tags = map[string]string{"type": "node"} m.meta = map[string]string{"source": m.name, "group": "Lustre"} - m.matches = map[string]map[string]int{"read_bytes": {"read_bytes": 6, "read_requests": 1}, - "write_bytes": {"write_bytes": 6, "write_requests": 1}, - "open": {"open": 1}, - "close": {"close": 1}, - "setattr": {"setattr": 1}, - "getattr": {"getattr": 1}, - "statfs": {"statfs": 1}, - "inode_permission": {"inode_permission": 1}} + defmatches := map[string]map[string]int{ + "read_bytes": {"lustre_read_bytes": 6, "lustre_read_requests": 1}, + "write_bytes": {"lustre_write_bytes": 6, "lustre_write_requests": 1}, + "open": {"lustre_open": 1}, + "close": {"lustre_close": 1}, + "setattr": {"lustre_setattr": 1}, + "getattr": {"lustre_getattr": 1}, + "statfs": {"lustre_statfs": 1}, + "inode_permission": {"lustre_inode_permission": 1}} + + // Lustre file system statistics can only be queried by user root + user, err := user.Current() + if err != nil { + cclog.ComponentError(m.name, "Failed to get current user:", err.Error()) + return err + } + if user.Uid != "0" { + cclog.ComponentError(m.name, "Lustre file system statistics can only be queried by user root:", err.Error()) + return err + } + + m.matches = make(map[string]map[string]int) + for lineprefix, names := range defmatches { + for metricname, offset := range names { + _, skip := stringArrayContains(m.config.ExcludeMetrics, metricname) + if skip { + continue + } + if _, prefixExist := m.matches[lineprefix]; !prefixExist { + m.matches[lineprefix] = make(map[string]int) + } + if _, metricExist := m.matches[lineprefix][metricname]; !metricExist { + m.matches[lineprefix][metricname] = offset + } + } + } p, err := exec.LookPath(m.config.LCtlCommand) if err != nil { p, err = exec.LookPath(LCTL_CMD) @@ -110,10 +137,19 @@ func (m *LustreCollector) Init(config json.RawMessage) error { } m.lctl = p - m.devices = m.getDevices() - if len(m.devices) == 0 { + devices := m.getDevices() + if len(devices) == 0 { return errors.New("no metrics to collect") } + m.stats = make(map[string]map[string]int64) + for _, d := range devices { + m.stats[d] = make(map[string]int64) + for _, names := range m.matches { + for metricname := range names { + m.stats[d][metricname] = 0 + } + } + } m.init = true return nil } @@ -122,34 +158,53 @@ func (m *LustreCollector) Read(interval time.Duration, output chan lp.CCMetric) if !m.init { return } - for _, device := range m.devices { + for device, devData := range m.stats { stats := m.getDeviceDataCommand(device) + processed := []string{} for _, line := range stats { lf := strings.Fields(line) if len(lf) > 1 { - for match, fields := range m.matches { - if lf[0] == match { - for name, idx := range fields { - _, skip := stringArrayContains(m.config.ExcludeMetrics, name) - if skip { - continue + if fields, ok := m.matches[lf[0]]; ok { + for name, idx := range fields { + x, err := strconv.ParseInt(lf[idx], 0, 64) + if err != nil { + continue + } + value := x - devData[name] + devData[name] = x + if value < 0 { + value = 0 + } + y, err := lp.New(name, m.tags, m.meta, map[string]interface{}{"value": value}, time.Now()) + if err == nil { + y.AddTag("device", device) + if strings.Contains(name, "byte") { + y.AddMeta("unit", "Byte") } - x, err := strconv.ParseInt(lf[idx], 0, 64) - if err == nil { - y, err := lp.New(name, m.tags, m.meta, map[string]interface{}{"value": x}, time.Now()) - if err == nil { - if strings.Contains(name, "byte") { - y.AddMeta("unit", "Byte") - } - output <- y - } + output <- y + if m.config.SendAllMetrics { + processed = append(processed, name) } } } } } } + if m.config.SendAllMetrics { + for name := range devData { + if _, done := stringArrayContains(processed, name); !done { + y, err := lp.New(name, m.tags, m.meta, map[string]interface{}{"value": 0}, time.Now()) + if err == nil { + y.AddTag("device", device) + if strings.Contains(name, "byte") { + y.AddMeta("unit", "Byte") + } + output <- y + } + } + } + } } }