prometheusmetricshost-metricsmachine-metricsnode-metricsprocfsprometheus-exportersystem-informationsystem-metrics
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
237 lines
5.5 KiB
237 lines
5.5 KiB
// Copyright 2015 The Prometheus Authors |
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
|
// you may not use this file except in compliance with the License. |
|
// You may obtain a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, |
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
// See the License for the specific language governing permissions and |
|
// limitations under the License. |
|
|
|
// +build !nomegacli |
|
|
|
package collector |
|
|
|
import ( |
|
"bufio" |
|
"flag" |
|
"io" |
|
"os/exec" |
|
"strconv" |
|
"strings" |
|
|
|
"github.com/prometheus/client_golang/prometheus" |
|
) |
|
|
|
const ( |
|
defaultMegaCli = "megacli" |
|
adapterHeaderSep = "================" |
|
) |
|
|
|
var ( |
|
megacliCommand = flag.String("collector.megacli.command", defaultMegaCli, "Command to run megacli.") |
|
) |
|
|
|
type megaCliCollector struct { |
|
cli string |
|
|
|
driveTemperature *prometheus.GaugeVec |
|
driveCounters *prometheus.GaugeVec |
|
drivePresence *prometheus.GaugeVec |
|
} |
|
|
|
func init() { |
|
Factories["megacli"] = NewMegaCliCollector |
|
} |
|
|
|
// Takes a prometheus registry and returns a new Collector exposing |
|
// RAID status through megacli. |
|
func NewMegaCliCollector() (Collector, error) { |
|
warnDeprecated("megacli") |
|
return &megaCliCollector{ |
|
cli: *megacliCommand, |
|
driveTemperature: prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
|
Namespace: Namespace, |
|
Name: "megacli_drive_temperature_celsius", |
|
Help: "megacli: drive temperature", |
|
}, []string{"enclosure", "slot"}), |
|
driveCounters: prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
|
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 { |
|
encStr := strconv.Itoa(enc) |
|
slotStr := strconv.Itoa(slot) |
|
|
|
tStr := slotStats["Drive Temperature"] |
|
if strings.Index(tStr, "C") > 0 { |
|
tStr = tStr[:strings.Index(tStr, "C")] |
|
t, err := strconv.ParseFloat(tStr, 64) |
|
if err != nil { |
|
return err |
|
} |
|
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 |
|
}
|
|
|