mirror of
https://github.com/prometheus/node_exporter
synced 2024-12-23 22:52:18 +00:00
211ddf33f1
Remove special tags necessary for gmond and runit collectors. All collectors get built. Selection of which collectors to use continues to happen via parameter.
225 lines
4.9 KiB
Go
225 lines
4.9 KiB
Go
// +build !nomegacli
|
|
|
|
package collector
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
const (
|
|
defaultMegaCli = "megacli"
|
|
adapterHeaderSep = "================"
|
|
)
|
|
|
|
type megaCliCollector struct {
|
|
config Config
|
|
cli string
|
|
|
|
driveTemperature *prometheus.GaugeVec
|
|
driveCounters *prometheus.CounterVec
|
|
drivePresence *prometheus.GaugeVec
|
|
}
|
|
|
|
func init() {
|
|
Factories["megacli"] = NewMegaCliCollector
|
|
}
|
|
|
|
// Takes a config struct and prometheus registry and returns a new Collector exposing
|
|
// RAID status through megacli.
|
|
func NewMegaCliCollector(config Config) (Collector, error) {
|
|
cli := defaultMegaCli
|
|
if config.Config["megacli_command"] != "" {
|
|
cli = config.Config["megacli_command"]
|
|
}
|
|
|
|
return &megaCliCollector{
|
|
config: config,
|
|
cli: cli,
|
|
driveTemperature: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Name: "megacli_drive_temperature_celsius",
|
|
Help: "megacli: drive temperature",
|
|
}, []string{"enclosure", "slot"}),
|
|
driveCounters: prometheus.NewCounterVec(prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Name: "megacli_drive_count",
|
|
Help: "megacli: drive error and event counters",
|
|
}, []string{"enclosure", "slot", "type"}),
|
|
drivePresence: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Name: "megacli_adapter_disk_presence",
|
|
Help: "megacli: disk presence per adapter",
|
|
}, []string{"type"}),
|
|
}, nil
|
|
}
|
|
|
|
func (c *megaCliCollector) Update(ch chan<- prometheus.Metric) (err error) {
|
|
err = c.updateAdapter()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = c.updateDisks()
|
|
c.driveTemperature.Collect(ch)
|
|
c.driveCounters.Collect(ch)
|
|
c.drivePresence.Collect(ch)
|
|
return err
|
|
}
|
|
|
|
func parseMegaCliDisks(r io.Reader) (map[int]map[int]map[string]string, error) {
|
|
var (
|
|
stats = map[int]map[int]map[string]string{}
|
|
scanner = bufio.NewScanner(r)
|
|
curEnc = -1
|
|
curSlot = -1
|
|
)
|
|
|
|
for scanner.Scan() {
|
|
var err error
|
|
text := strings.TrimSpace(scanner.Text())
|
|
parts := strings.SplitN(text, ":", 2)
|
|
if len(parts) != 2 { // Adapter #X
|
|
continue
|
|
}
|
|
key := strings.TrimSpace(parts[0])
|
|
value := strings.TrimSpace(parts[1])
|
|
switch {
|
|
case key == "Enclosure Device ID":
|
|
curEnc, err = strconv.Atoi(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case key == "Slot Number":
|
|
curSlot, err = strconv.Atoi(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case curSlot != -1 && curEnc != -1:
|
|
if _, ok := stats[curEnc]; !ok {
|
|
stats[curEnc] = map[int]map[string]string{}
|
|
}
|
|
if _, ok := stats[curEnc][curSlot]; !ok {
|
|
stats[curEnc][curSlot] = map[string]string{}
|
|
}
|
|
stats[curEnc][curSlot][key] = value
|
|
}
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
func parseMegaCliAdapter(r io.Reader) (map[string]map[string]string, error) {
|
|
var (
|
|
raidStats = map[string]map[string]string{}
|
|
scanner = bufio.NewScanner(r)
|
|
header = ""
|
|
last = ""
|
|
)
|
|
|
|
for scanner.Scan() {
|
|
text := strings.TrimSpace(scanner.Text())
|
|
if text == adapterHeaderSep {
|
|
header = last
|
|
raidStats[header] = map[string]string{}
|
|
continue
|
|
}
|
|
last = text
|
|
if header == "" { // skip Adapter #X and separator
|
|
continue
|
|
}
|
|
parts := strings.SplitN(text, ":", 2)
|
|
if len(parts) != 2 { // these section never include anything we are interested in
|
|
continue
|
|
}
|
|
key := strings.TrimSpace(parts[0])
|
|
value := strings.TrimSpace(parts[1])
|
|
|
|
raidStats[header][key] = value
|
|
|
|
}
|
|
|
|
return raidStats, nil
|
|
}
|
|
|
|
func (c *megaCliCollector) updateAdapter() error {
|
|
cmd := exec.Command(c.cli, "-AdpAllInfo", "-aALL")
|
|
pipe, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
stats, err := parseMegaCliAdapter(pipe)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Wait(); err != nil {
|
|
return err
|
|
}
|
|
|
|
for k, v := range stats["Device Present"] {
|
|
value, err := strconv.ParseFloat(v, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.drivePresence.WithLabelValues(k).Set(value)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *megaCliCollector) updateDisks() error {
|
|
var counters = []string{"Media Error Count", "Other Error Count", "Predictive Failure Count"}
|
|
|
|
cmd := exec.Command(c.cli, "-PDList", "-aALL")
|
|
pipe, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
stats, err := parseMegaCliDisks(pipe)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Wait(); err != nil {
|
|
return err
|
|
}
|
|
|
|
for enc, encStats := range stats {
|
|
for slot, slotStats := range encStats {
|
|
tStr := slotStats["Drive Temperature"]
|
|
tStr = tStr[:strings.Index(tStr, "C")]
|
|
t, err := strconv.ParseFloat(tStr, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
encStr := strconv.Itoa(enc)
|
|
slotStr := strconv.Itoa(slot)
|
|
|
|
c.driveTemperature.WithLabelValues(encStr, slotStr).Set(t)
|
|
|
|
for _, i := range counters {
|
|
counter, err := strconv.ParseFloat(slotStats[i], 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.driveCounters.WithLabelValues(encStr, slotStr, i).Set(counter)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|