2022-01-19 14:25:24 +01:00
|
|
|
package collectors
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"os/user"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
2022-01-28 09:42:19 +01:00
|
|
|
|
2022-01-25 15:37:43 +01:00
|
|
|
lp "github.com/ClusterCockpit/cc-metric-collector/internal/ccMetric"
|
2022-01-19 14:25:24 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type GpfsCollector struct {
|
2022-01-25 15:37:43 +01:00
|
|
|
metricCollector
|
|
|
|
tags map[string]string
|
2022-01-21 14:35:52 +01:00
|
|
|
config struct {
|
|
|
|
Mmpmon string `json:"mmpmon"`
|
|
|
|
}
|
2022-01-19 14:25:24 +01:00
|
|
|
}
|
|
|
|
|
2022-01-25 15:37:43 +01:00
|
|
|
func (m *GpfsCollector) Init(config json.RawMessage) error {
|
2022-01-19 14:25:24 +01:00
|
|
|
var err error
|
|
|
|
m.name = "GpfsCollector"
|
|
|
|
m.setup()
|
|
|
|
|
|
|
|
// Set default mmpmon binary
|
|
|
|
m.config.Mmpmon = "/usr/lpp/mmfs/bin/mmpmon"
|
|
|
|
|
|
|
|
// Read JSON configuration
|
|
|
|
if len(config) > 0 {
|
|
|
|
err = json.Unmarshal(config, &m.config)
|
|
|
|
if err != nil {
|
|
|
|
log.Print(err.Error())
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2022-01-25 15:37:43 +01:00
|
|
|
m.meta = map[string]string{
|
|
|
|
"source": m.name,
|
|
|
|
"group": "GPFS",
|
|
|
|
}
|
|
|
|
m.tags = map[string]string{
|
|
|
|
"type": "node",
|
|
|
|
"filesystem": "",
|
|
|
|
}
|
2022-01-19 14:25:24 +01:00
|
|
|
|
|
|
|
// GPFS / IBM Spectrum Scale file system statistics can only be queried by user root
|
|
|
|
user, err := user.Current()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("GpfsCollector.Init(): Failed to get current user: %v", err)
|
|
|
|
}
|
|
|
|
if user.Uid != "0" {
|
|
|
|
return fmt.Errorf("GpfsCollector.Init(): GPFS file system statistics can only be queried by user root")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if mmpmon is in executable search path
|
|
|
|
_, err = exec.LookPath(m.config.Mmpmon)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("GpfsCollector.Init(): Failed to find mmpmon binary '%s': %v", m.config.Mmpmon, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
m.init = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-25 15:37:43 +01:00
|
|
|
func (m *GpfsCollector) Read(interval time.Duration, output chan lp.CCMetric) {
|
2022-01-19 14:25:24 +01:00
|
|
|
if !m.init {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// mmpmon:
|
|
|
|
// -p: generate output that can be parsed
|
|
|
|
// -s: suppress the prompt on input
|
|
|
|
// fs_io_s: Displays I/O statistics per mounted file system
|
|
|
|
cmd := exec.Command(m.config.Mmpmon, "-p", "-s")
|
|
|
|
cmd.Stdin = strings.NewReader("once fs_io_s\n")
|
|
|
|
cmdStdout := new(bytes.Buffer)
|
|
|
|
cmdStderr := new(bytes.Buffer)
|
|
|
|
cmd.Stdout = cmdStdout
|
|
|
|
cmd.Stderr = cmdStderr
|
|
|
|
err := cmd.Run()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "GpfsCollector.Read(): Failed to execute command \"%s\": %s\n", cmd.String(), err.Error())
|
|
|
|
fmt.Fprintf(os.Stderr, "GpfsCollector.Read(): command exit code: \"%d\"\n", cmd.ProcessState.ExitCode())
|
|
|
|
data, _ := ioutil.ReadAll(cmdStderr)
|
|
|
|
fmt.Fprintf(os.Stderr, "GpfsCollector.Read(): command stderr: \"%s\"\n", string(data))
|
|
|
|
data, _ = ioutil.ReadAll(cmdStdout)
|
|
|
|
fmt.Fprintf(os.Stderr, "GpfsCollector.Read(): command stdout: \"%s\"\n", string(data))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read I/O statistics
|
|
|
|
scanner := bufio.NewScanner(cmdStdout)
|
|
|
|
for scanner.Scan() {
|
|
|
|
lineSplit := strings.Fields(scanner.Text())
|
|
|
|
if lineSplit[0] == "_fs_io_s_" {
|
|
|
|
key_value := make(map[string]string)
|
|
|
|
for i := 1; i < len(lineSplit); i += 2 {
|
|
|
|
key_value[lineSplit[i]] = lineSplit[i+1]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ignore keys:
|
|
|
|
// _n_: node IP address,
|
|
|
|
// _nn_: node name,
|
|
|
|
// _cl_: cluster name,
|
|
|
|
// _d_: number of disks
|
|
|
|
|
|
|
|
filesystem, ok := key_value["_fs_"]
|
|
|
|
if !ok {
|
|
|
|
fmt.Fprintf(os.Stderr, "GpfsCollector.Read(): Failed to get filesystem name.\n")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-01-25 15:37:43 +01:00
|
|
|
m.tags["filesystem"] = filesystem
|
|
|
|
|
2022-01-19 14:25:24 +01:00
|
|
|
// return code
|
|
|
|
rc, err := strconv.Atoi(key_value["_rc_"])
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "GpfsCollector.Read(): Failed to convert return code: %s\n", err.Error())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if rc != 0 {
|
|
|
|
fmt.Fprintf(os.Stderr, "GpfsCollector.Read(): Filesystem %s not ok.", filesystem)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
timestampInt, err := strconv.ParseInt(key_value["_t_"]+key_value["_tu_"], 10, 64)
|
|
|
|
timestamp := time.UnixMicro(timestampInt)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr,
|
|
|
|
"GpfsCollector.Read(): Failed to convert time stamp '%s': %s\n",
|
|
|
|
key_value["_t_"]+key_value["_tu_"], err.Error())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// bytes read
|
|
|
|
bytesRead, err := strconv.ParseInt(key_value["_br_"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr,
|
|
|
|
"GpfsCollector.Read(): Failed to convert bytes read '%s': %s\n",
|
|
|
|
key_value["_br_"], err.Error())
|
|
|
|
continue
|
|
|
|
}
|
2022-01-25 15:37:43 +01:00
|
|
|
|
|
|
|
y, err := lp.New("gpfs_bytes_read", m.tags, m.meta, map[string]interface{}{"value": bytesRead}, timestamp)
|
2022-01-19 14:25:24 +01:00
|
|
|
if err == nil {
|
2022-01-25 15:37:43 +01:00
|
|
|
output <- y
|
2022-01-19 14:25:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// bytes written
|
|
|
|
bytesWritten, err := strconv.ParseInt(key_value["_bw_"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr,
|
|
|
|
"GpfsCollector.Read(): Failed to convert bytes written '%s': %s\n",
|
|
|
|
key_value["_bw_"], err.Error())
|
|
|
|
continue
|
|
|
|
}
|
2022-01-25 15:37:43 +01:00
|
|
|
|
|
|
|
y, err = lp.New("gpfs_bytes_written", m.tags, m.meta, map[string]interface{}{"value": bytesWritten}, timestamp)
|
2022-01-19 14:25:24 +01:00
|
|
|
if err == nil {
|
2022-01-25 15:37:43 +01:00
|
|
|
output <- y
|
2022-01-19 14:25:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// number of opens
|
|
|
|
numOpens, err := strconv.ParseInt(key_value["_oc_"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr,
|
|
|
|
"GpfsCollector.Read(): Failed to convert number of opens '%s': %s\n",
|
|
|
|
key_value["_oc_"], err.Error())
|
|
|
|
continue
|
|
|
|
}
|
2022-01-25 15:37:43 +01:00
|
|
|
y, err = lp.New("gpfs_num_opens", m.tags, m.meta, map[string]interface{}{"value": numOpens}, timestamp)
|
2022-01-19 14:25:24 +01:00
|
|
|
if err == nil {
|
2022-01-25 15:37:43 +01:00
|
|
|
output <- y
|
2022-01-19 14:25:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// number of closes
|
|
|
|
numCloses, err := strconv.ParseInt(key_value["_cc_"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "GpfsCollector.Read(): Failed to convert number of closes: %s\n", err.Error())
|
|
|
|
continue
|
|
|
|
}
|
2022-01-25 15:37:43 +01:00
|
|
|
y, err = lp.New("gpfs_num_closes", m.tags, m.meta, map[string]interface{}{"value": numCloses}, timestamp)
|
2022-01-19 14:25:24 +01:00
|
|
|
if err == nil {
|
2022-01-25 15:37:43 +01:00
|
|
|
output <- y
|
2022-01-19 14:25:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// number of reads
|
|
|
|
numReads, err := strconv.ParseInt(key_value["_rdc_"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "GpfsCollector.Read(): Failed to convert number of reads: %s\n", err.Error())
|
|
|
|
continue
|
|
|
|
}
|
2022-01-25 15:37:43 +01:00
|
|
|
y, err = lp.New("gpfs_num_reads", m.tags, m.meta, map[string]interface{}{"value": numReads}, timestamp)
|
2022-01-19 14:25:24 +01:00
|
|
|
if err == nil {
|
2022-01-25 15:37:43 +01:00
|
|
|
output <- y
|
2022-01-19 14:25:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// number of writes
|
|
|
|
numWrites, err := strconv.ParseInt(key_value["_wc_"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "GpfsCollector.Read(): Failed to convert number of writes: %s\n", err.Error())
|
|
|
|
continue
|
|
|
|
}
|
2022-01-25 15:37:43 +01:00
|
|
|
y, err = lp.New("gpfs_num_writes", m.tags, m.meta, map[string]interface{}{"value": numWrites}, timestamp)
|
2022-01-19 14:25:24 +01:00
|
|
|
if err == nil {
|
2022-01-25 15:37:43 +01:00
|
|
|
output <- y
|
2022-01-19 14:25:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// number of read directories
|
|
|
|
numReaddirs, err := strconv.ParseInt(key_value["_dir_"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "GpfsCollector.Read(): Failed to convert number of read directories: %s\n", err.Error())
|
|
|
|
continue
|
|
|
|
}
|
2022-01-25 15:37:43 +01:00
|
|
|
y, err = lp.New("gpfs_num_readdirs", m.tags, m.meta, map[string]interface{}{"value": numReaddirs}, timestamp)
|
2022-01-19 14:25:24 +01:00
|
|
|
if err == nil {
|
2022-01-25 15:37:43 +01:00
|
|
|
output <- y
|
2022-01-19 14:25:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Number of inode updates
|
|
|
|
numInodeUpdates, err := strconv.ParseInt(key_value["_iu_"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "GpfsCollector.Read(): Failed to convert Number of inode updates: %s\n", err.Error())
|
|
|
|
continue
|
|
|
|
}
|
2022-01-25 15:37:43 +01:00
|
|
|
y, err = lp.New("gpfs_num_inode_updates", m.tags, m.meta, map[string]interface{}{"value": numInodeUpdates}, timestamp)
|
2022-01-19 14:25:24 +01:00
|
|
|
if err == nil {
|
2022-01-25 15:37:43 +01:00
|
|
|
output <- y
|
2022-01-19 14:25:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *GpfsCollector) Close() {
|
|
|
|
m.init = false
|
|
|
|
}
|