diff --git a/collectors/README.md b/collectors/README.md index 1c3784e..1f4e0d9 100644 --- a/collectors/README.md +++ b/collectors/README.md @@ -29,6 +29,8 @@ In contrast to the configuration files for sinks and receivers, the collectors c * [`customcmd`](./customCmdMetric.md) * [`ipmistat`](./ipmiMetric.md) * [`topprocs`](./topprocsMetric.md) +* [`nfs3stat`](./nfsMetric.md#nfs3stat-collector) +* [`nfs4stat`](./nfsMetric.md#nfs4stat-collector) ## Todos diff --git a/collectors/collectorManager.go b/collectors/collectorManager.go index f91db20..b1de852 100644 --- a/collectors/collectorManager.go +++ b/collectors/collectorManager.go @@ -31,7 +31,8 @@ var AvailableCollectors = map[string]MetricCollector{ "gpfs": new(GpfsCollector), "cpufreq": new(CPUFreqCollector), "cpufreq_cpuinfo": new(CPUFreqCpuInfoCollector), - "nfsstat": new(NfsCollector), + "nfs3stat": new(Nfs3Collector), + "nfs4stat": new(Nfs4Collector), } // Metric collector manager data structure diff --git a/collectors/nfsMetric.go b/collectors/nfsMetric.go index 16a6d23..9cf5a30 100644 --- a/collectors/nfsMetric.go +++ b/collectors/nfsMetric.go @@ -19,18 +19,30 @@ type NfsCollectorData struct { last int64 } -type NfsCollector struct { +type Nfs3Collector struct { metricCollector - tags map[string]string - config struct { + tags map[string]string + version string + config struct { Nfsutils string `json:"nfsutils"` ExcludeMetrics []string `json:"exclude_metrics,omitempty"` } - data map[string]map[string]NfsCollectorData + data map[string]NfsCollectorData } -func (m *NfsCollector) initStats() error { - cmd := exec.Command(m.config.Nfsutils, "-l") +type Nfs4Collector struct { + metricCollector + tags map[string]string + version string + config struct { + Nfsutils string `json:"nfsutils"` + ExcludeMetrics []string `json:"exclude_metrics,omitempty"` + } + data map[string]NfsCollectorData +} + +func (m *Nfs3Collector) initStats() error { + cmd := exec.Command(m.config.Nfsutils, `-l`, `-3`) cmd.Wait() buffer, err := cmd.Output() if err == nil { @@ -39,17 +51,16 @@ func (m *NfsCollector) initStats() error { if len(lf) != 5 { continue } - if _, exist := m.data[lf[1]]; !exist { - m.data[lf[1]] = make(map[string]NfsCollectorData) - } - name := strings.Trim(lf[3], ":") - if _, exist := m.data[lf[1]][name]; !exist { - value, err := strconv.ParseInt(lf[4], 0, 64) - if err == nil { - x := m.data[lf[1]][name] - x.current = value - x.last = 0 - m.data[lf[1]][name] = x + if lf[1] == m.version { // `v3` + name := strings.Trim(lf[3], ":") + if _, exist := m.data[name]; !exist { + value, err := strconv.ParseInt(lf[4], 0, 64) + if err == nil { + x := m.data[name] + x.current = value + x.last = 0 + m.data[name] = x + } } } } @@ -57,8 +68,8 @@ func (m *NfsCollector) initStats() error { return err } -func (m *NfsCollector) updateStats() error { - cmd := exec.Command(m.config.Nfsutils, "-l") +func (m *Nfs4Collector) initStats() error { + cmd := exec.Command(m.config.Nfsutils, `-l`, `-4`) cmd.Wait() buffer, err := cmd.Output() if err == nil { @@ -67,17 +78,16 @@ func (m *NfsCollector) updateStats() error { if len(lf) != 5 { continue } - if _, exist := m.data[lf[1]]; !exist { - m.data[lf[1]] = make(map[string]NfsCollectorData) - } - name := strings.Trim(lf[3], ":") - if _, exist := m.data[lf[1]][name]; exist { - value, err := strconv.ParseInt(lf[4], 0, 64) - if err == nil { - x := m.data[lf[1]][name] - x.last = x.current - x.current = value - m.data[lf[1]][name] = x + if lf[1] == m.version { // `v4` + name := strings.Trim(lf[3], ":") + if _, exist := m.data[name]; !exist { + value, err := strconv.ParseInt(lf[4], 0, 64) + if err == nil { + x := m.data[name] + x.current = value + x.last = 0 + m.data[name] = x + } } } } @@ -85,9 +95,64 @@ func (m *NfsCollector) updateStats() error { return err } -func (m *NfsCollector) Init(config json.RawMessage) error { +func (m *Nfs3Collector) updateStats() error { + cmd := exec.Command(m.config.Nfsutils, `-l`, `-3`) + cmd.Wait() + buffer, err := cmd.Output() + if err == nil { + for _, line := range strings.Split(string(buffer), "\n") { + lf := strings.Fields(line) + if len(lf) != 5 { + continue + } + if lf[1] == m.version { // `v3` + name := strings.Trim(lf[3], ":") + if _, exist := m.data[name]; exist { + value, err := strconv.ParseInt(lf[4], 0, 64) + if err == nil { + x := m.data[name] + x.last = x.current + x.current = value + m.data[name] = x + } + } + } + } + } + return err +} + +func (m *Nfs4Collector) updateStats() error { + cmd := exec.Command(m.config.Nfsutils, `-l`, `-4`) + cmd.Wait() + buffer, err := cmd.Output() + if err == nil { + for _, line := range strings.Split(string(buffer), "\n") { + lf := strings.Fields(line) + if len(lf) != 5 { + continue + } + if lf[1] == m.version { // `v4` + name := strings.Trim(lf[3], ":") + if _, exist := m.data[name]; exist { + value, err := strconv.ParseInt(lf[4], 0, 64) + if err == nil { + x := m.data[name] + x.last = x.current + x.current = value + m.data[name] = x + } + } + } + } + } + return err +} + +func (m *Nfs3Collector) Init(config json.RawMessage) error { var err error - m.name = "NfsCollector" + m.name = "Nfs3Collector" + m.version = `v3` m.setup() // Set default mmpmon binary @@ -113,13 +178,48 @@ func (m *NfsCollector) Init(config json.RawMessage) error { if err != nil { return fmt.Errorf("NfsCollector.Init(): Failed to find nfsstat binary '%s': %v", m.config.Nfsutils, err) } - m.data = make(map[string]map[string]NfsCollectorData) + m.data = make(map[string]NfsCollectorData) m.initStats() m.init = true return nil } -func (m *NfsCollector) Read(interval time.Duration, output chan lp.CCMetric) { +func (m *Nfs4Collector) Init(config json.RawMessage) error { + var err error + m.name = "Nfs4Collector" + m.version = `v4` + m.setup() + + // Set default mmpmon binary + m.config.Nfsutils = "/usr/sbin/nfsstat" + + // Read JSON configuration + if len(config) > 0 { + err = json.Unmarshal(config, &m.config) + if err != nil { + log.Print(err.Error()) + return err + } + } + m.meta = map[string]string{ + "source": m.name, + "group": "NFS", + } + m.tags = map[string]string{ + "type": "node", + } + // Check if mmpmon is in executable search path + _, err = exec.LookPath(m.config.Nfsutils) + if err != nil { + return fmt.Errorf("NfsCollector.Init(): Failed to find nfsstat binary '%s': %v", m.config.Nfsutils, err) + } + m.data = make(map[string]NfsCollectorData) + m.initStats() + m.init = true + return nil +} + +func (m *Nfs3Collector) Read(interval time.Duration, output chan lp.CCMetric) { if !m.init { return } @@ -127,21 +227,44 @@ func (m *NfsCollector) Read(interval time.Duration, output chan lp.CCMetric) { m.updateStats() - for version, metrics := range m.data { - for name, data := range metrics { - if _, skip := stringArrayContains(m.config.ExcludeMetrics, name); skip { - continue - } - value := data.current - data.last - y, err := lp.New(fmt.Sprintf("nfs_%s", name), m.tags, m.meta, map[string]interface{}{"value": value}, timestamp) - if err == nil { - y.AddMeta("version", version) - output <- y - } + for name, data := range m.data { + if _, skip := stringArrayContains(m.config.ExcludeMetrics, name); skip { + continue + } + value := data.current - data.last + y, err := lp.New(fmt.Sprintf("nfs3_%s", name), m.tags, m.meta, map[string]interface{}{"value": value}, timestamp) + if err == nil { + y.AddMeta("version", m.version) + output <- y } } } -func (m *NfsCollector) Close() { +func (m *Nfs4Collector) Read(interval time.Duration, output chan lp.CCMetric) { + if !m.init { + return + } + timestamp := time.Now() + + m.updateStats() + + for name, data := range m.data { + if _, skip := stringArrayContains(m.config.ExcludeMetrics, name); skip { + continue + } + value := data.current - data.last + y, err := lp.New(fmt.Sprintf("nfs4_%s", name), m.tags, m.meta, map[string]interface{}{"value": value}, timestamp) + if err == nil { + y.AddMeta("version", m.version) + output <- y + } + } +} + +func (m *Nfs3Collector) Close() { + m.init = false +} + +func (m *Nfs4Collector) Close() { m.init = false } diff --git a/collectors/nfsMetric.md b/collectors/nfsMetric.md new file mode 100644 index 0000000..bd2a785 --- /dev/null +++ b/collectors/nfsMetric.md @@ -0,0 +1,96 @@ +## `nfs3stat` collector + +```json + "nfs3stat": { + "nfsutils" : "/usr/bin/nfsstat", + "exclude_metrics": [ + "nfs3_total" + ] + } +``` + +The `nfs3stat` collector reads data from `nfsstat` and outputs a handful **node** metrics. If a metric is not required, it can be excluded from forwarding it to the sink. It is not possible to get data per NFSv3 mount point at the moment. + +Metrics: +-`nfs3_total` +-`nfs3_null` +-`nfs3_getattr` +-`nfs3_setattr` +-`nfs3_lookup` +-`nfs3_access` +-`nfs3_readlink` +-`nfs3_read` +-`nfs3_write` +-`nfs3_create` +-`nfs3_mkdir` +-`nfs3_symlink` +-`nfs3_remove` +-`nfs3_rmdir` +-`nfs3_rename` +-`nfs3_link` +-`nfs3_readdir` +-`nfs3_readdirplus` +-`nfs3_fsstat` +-`nfs3_fsinfo` +-`nfs3_pathconf` +-`nfs3_commit` + + +## `nfs4stat` collector + +```json + "nfs4stat": { + "nfsutils" : "/usr/bin/nfsstat", + "exclude_metrics": [ + "nfs4_total" + ] + } +``` + +The `nfs4stat` collector reads data from `nfsstat` and outputs a handful **node** metrics. If a metric is not required, it can be excluded from forwarding it to the sink. It is not possible to get data per NFSv4 mount point at the moment. + +Metrics: +-`nfs4_total` +-`nfs4_null` +-`nfs4_read` +-`nfs4_write` +-`nfs4_commit` +-`nfs4_open` +-`nfs4_open_conf` +-`nfs4_open_noat` +-`nfs4_open_dgrd` +-`nfs4_close` +-`nfs4_setattr` +-`nfs4_fsinfo` +-`nfs4_renew` +-`nfs4_setclntid` +-`nfs4_confirm` +-`nfs4_lock` +-`nfs4_lockt` +-`nfs4_locku` +-`nfs4_access` +-`nfs4_getattr` +-`nfs4_lookup` +-`nfs4_lookup_root` +-`nfs4_remove` +-`nfs4_rename` +-`nfs4_link` +-`nfs4_symlink` +-`nfs4_create` +-`nfs4_pathconf` +-`nfs4_statfs` +-`nfs4_readlink` +-`nfs4_readdir` +-`nfs4_server_caps` +-`nfs4_delegreturn` +-`nfs4_getacl` +-`nfs4_setacl` +-`nfs4_rel_lkowner` +-`nfs4_exchange_id` +-`nfs4_create_session` +-`nfs4_destroy_session` +-`nfs4_sequence` +-`nfs4_get_lease_time` +-`nfs4_reclaim_comp` +-`nfs4_secinfo_no` +-`nfs4_bind_conn_to_ses`