master
go 147 lines 3.43 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package ethtool
4
5 import (
6 "bufio"
7 "bytes"
8 "errors"
9 "fmt"
10 "strconv"
11 "strings"
12 )
13
14 const precision = 1000
15
16 type (
17 moduleEeprom struct {
18 ddm *moduleDdm
19 }
20 moduleDdm struct {
21 laserBiasMA *float64
22 laserPowerMW *float64
23 laserPowerDBM *float64
24 rxSignalPowerMW *float64
25 rxSignalPowerDBM *float64
26 tempC *float64
27 tempF *float64
28 voltageV *float64
29 }
30 )
31
32 func (c *Collector) collectModuleEeprom(mx map[string]int64, iface string) error {
33 bs, err := c.exec.moduleEeprom(iface)
34 if err != nil {
35 return fmt.Errorf("failed to get eeprom: %w", err)
36 }
37
38 eeprom, err := parseEeprom(bs)
39 if err != nil {
40 return fmt.Errorf("failed to parse eeprom: %w", err)
41 }
42 if eeprom == nil || eeprom.ddm == nil {
43 return errors.New("module doesn't have ddm")
44 }
45
46 if !c.seenOpticIfaces[iface] {
47 c.seenOpticIfaces[iface] = true
48 c.addModuleEepromCharts(iface, eeprom)
49 }
50
51 px := fmt.Sprintf("iface_%s_", iface)
52
53 writeDdmValue(mx, px+"laser_bias_current_ma", eeprom.ddm.laserBiasMA)
54 writeDdmValue(mx, px+"laser_output_power_mw", eeprom.ddm.laserPowerMW)
55 writeDdmValue(mx, px+"laser_output_power_dbm", eeprom.ddm.laserPowerDBM)
56 writeDdmValue(mx, px+"receiver_signal_average_optical_power_mw", eeprom.ddm.rxSignalPowerMW)
57 writeDdmValue(mx, px+"receiver_signal_average_optical_power_dbm", eeprom.ddm.rxSignalPowerDBM)
58 writeDdmValue(mx, px+"module_temperature_c", eeprom.ddm.tempC)
59 writeDdmValue(mx, px+"module_temperature_f", eeprom.ddm.tempF)
60 writeDdmValue(mx, px+"module_voltage_v", eeprom.ddm.voltageV)
61
62 return nil
63 }
64
65 func parseEeprom(bs []byte) (*moduleEeprom, error) {
66 var ddm moduleDdm
67 var foundDdm bool
68
69 sc := bufio.NewScanner(bytes.NewReader(bs))
70
71 for sc.Scan() {
72 line := strings.TrimSpace(sc.Text())
73 if line == "" {
74 continue
75 }
76
77 metric, value, ok := cutTrimSpace(line, ":")
78 if !ok {
79 continue
80 }
81
82 var err error
83
84 switch metric {
85 case "Laser bias current":
86 err = parseDdmValue(&ddm.laserBiasMA, value)
87 case "Laser output power":
88 err = parseCompoundDdmValues(&ddm.laserPowerMW, &ddm.laserPowerDBM, value)
89 case "Receiver signal average optical power":
90 err = parseCompoundDdmValues(&ddm.rxSignalPowerMW, &ddm.rxSignalPowerDBM, value)
91 case "Module temperature":
92 err = parseCompoundDdmValues(&ddm.tempC, &ddm.tempF, value)
93 case "Module voltage":
94 err = parseDdmValue(&ddm.voltageV, value)
95 default:
96 continue
97 }
98 if err != nil {
99 return nil, fmt.Errorf("failed to parse '%s': %v", line, err)
100 }
101 foundDdm = true
102 }
103
104 if !foundDdm {
105 return nil, nil
106 }
107 return &moduleEeprom{ddm: &ddm}, nil
108 }
109
110 func writeDdmValue(mx map[string]int64, key string, v *float64) {
111 if v == nil {
112 return
113 }
114 mx[key] = int64(*v * precision)
115 }
116
117 func parseDdmValue(v **float64, s string) error {
118 val, _, ok := cutTrimSpace(s, " ")
119 if !ok {
120 return errors.New("missing value")
121 }
122 f, err := strconv.ParseFloat(val, 64)
123 if err != nil {
124 return fmt.Errorf("invalid number '%s': %v", val, err)
125 }
126 *v = &f
127 return nil
128 }
129
130 func parseCompoundDdmValues(v1, v2 **float64, s string) error {
131 val1, val2, ok := cutTrimSpace(s, "/")
132 if !ok {
133 return errors.New("missing compound values")
134 }
135 if err := parseDdmValue(v1, val1); err != nil {
136 return err
137 }
138 if err := parseDdmValue(v2, val2); err != nil {
139 return err
140 }
141 return nil
142 }
143
144 func cutTrimSpace(s string, sep string) (string, string, bool) {
145 b, a, ok := strings.Cut(s, sep)
146 return strings.TrimSpace(b), strings.TrimSpace(a), ok
147 }