mirror of
				https://github.com/prometheus/node_exporter.git
				synced 2025-08-20 18:33:52 -07:00 
			
		
		
		
	Switch to Update using the Collecter Collect interface, due to not knowing all metricnames in all modules beforehand we can't use Describe and thus the full Collecter interface. Remove 'updates', it's meaning varies by module and doesn't add much.
		
			
				
	
	
		
			222 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // +build megacli
 | |
| 
 | |
| package collector
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"io"
 | |
| 	"os/exec"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/prometheus/client_golang/prometheus"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	defaultMegaCli   = "megacli"
 | |
| 	adapterHeaderSep = "================"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	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"})
 | |
| 
 | |
| 	counters = []string{"Media Error Count", "Other Error Count", "Predictive Failure Count"}
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	Factories["megacli"] = NewMegaCliCollector
 | |
| }
 | |
| 
 | |
| func parseMegaCliDisks(r io.ReadCloser) (map[int]map[int]map[string]string, error) {
 | |
| 	defer r.Close()
 | |
| 	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.ReadCloser) (map[string]map[string]string, error) {
 | |
| 	defer r.Close()
 | |
| 	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
 | |
| }
 | |
| 
 | |
| type megaCliCollector struct {
 | |
| 	config Config
 | |
| 	cli    string
 | |
| }
 | |
| 
 | |
| // 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"]
 | |
| 	}
 | |
| 
 | |
| 	c := megaCliCollector{
 | |
| 		config: config,
 | |
| 		cli:    cli,
 | |
| 	}
 | |
| 	return &c, nil
 | |
| }
 | |
| 
 | |
| func (c *megaCliCollector) Update(ch chan<- prometheus.Metric) (err error) {
 | |
| 	err = c.updateAdapter()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	err = c.updateDisks()
 | |
| 	driveTemperature.Collect(ch)
 | |
| 	driveCounters.Collect(ch)
 | |
| 	drivePresence.Collect(ch)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| 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
 | |
| 		}
 | |
| 		drivePresence.WithLabelValues(k).Set(value)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *megaCliCollector) updateDisks() error {
 | |
| 	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)
 | |
| 
 | |
| 			driveTemperature.WithLabelValues(encStr, slotStr).Set(t)
 | |
| 
 | |
| 			for _, c := range counters {
 | |
| 				counter, err := strconv.ParseFloat(slotStats[c], 64)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 
 | |
| 				driveCounters.WithLabelValues(encStr, slotStr, c).Set(counter)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 |