2024-12-01 17:12:19 +00:00
|
|
|
package meminfo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"git.faercol.me/monitoring/sys-exporter/registry"
|
|
|
|
"git.faercol.me/monitoring/sys-exporter/utils"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2024-12-02 17:37:19 +00:00
|
|
|
"go.uber.org/zap"
|
2024-12-01 17:12:19 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const meminfoFileLocation = "/proc/meminfo"
|
|
|
|
|
|
|
|
var meminfoRegex = regexp.MustCompile(`(?m)^(?P<key>\S+):\s+(?P<value>\d+(?:\s\S+$|$))`)
|
|
|
|
|
|
|
|
func valueInBytes(rawVal string) (int, error) {
|
|
|
|
split := strings.Split(rawVal, " ")
|
|
|
|
|
|
|
|
baseVal, err := strconv.ParseInt(split[0], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return 0, fmt.Errorf("invalid base value %q: %w", split[0], err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(split) == 1 { // no unit present in the value
|
|
|
|
return int(baseVal), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
switch split[1] {
|
|
|
|
case "B":
|
|
|
|
return int(baseVal), nil
|
|
|
|
case "kB":
|
|
|
|
return 1000 * int(baseVal), nil
|
|
|
|
case "MB":
|
|
|
|
return 1_000_000 * int(baseVal), nil
|
|
|
|
case "GB":
|
|
|
|
return 1_000_000_000 * int(baseVal), nil
|
|
|
|
case "TB":
|
|
|
|
return 1_000_000_000_000 * int(baseVal), nil
|
|
|
|
default:
|
|
|
|
return 0, fmt.Errorf("invalid unit %q", split[1])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type MeminfoCollector struct {
|
|
|
|
meminfoFileLocation string
|
|
|
|
|
|
|
|
promExporter *prometheus.SummaryVec
|
|
|
|
updateFreq time.Duration
|
2024-12-02 17:37:19 +00:00
|
|
|
|
|
|
|
l *zap.SugaredLogger
|
2024-12-01 17:12:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *MeminfoCollector) Collect() interface{} {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *MeminfoCollector) update() error {
|
|
|
|
fileContent, err := os.ReadFile(c.meminfoFileLocation)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to read meminfo file: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
rawMatches, err := utils.HandleRegexp(string(fileContent), *meminfoRegex)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to parse meminfo file: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
mappedVals := utils.HandleKeyValRegexRes(rawMatches)
|
|
|
|
for valueType, rawVal := range mappedVals {
|
|
|
|
val, err := valueInBytes(rawVal)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid value in meminfo file: %w", err)
|
|
|
|
}
|
|
|
|
c.promExporter.WithLabelValues(valueType).Observe(float64(val))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-12-02 17:37:19 +00:00
|
|
|
func (c *MeminfoCollector) Run(ctx context.Context, l *zap.SugaredLogger) {
|
|
|
|
c.l = l
|
|
|
|
|
|
|
|
c.l.Debug("Initializing collector")
|
2024-12-01 17:12:19 +00:00
|
|
|
if err := c.update(); err != nil {
|
2024-12-02 17:37:19 +00:00
|
|
|
c.l.Errorf("Failed to init collector: %s\n", err)
|
2024-12-01 17:12:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
2024-12-02 17:37:19 +00:00
|
|
|
c.l.Debug("Stopping collector")
|
2024-12-01 17:12:19 +00:00
|
|
|
return
|
|
|
|
case <-time.After(c.updateFreq):
|
2024-12-02 17:37:19 +00:00
|
|
|
c.l.Debug("Updating collector")
|
2024-12-01 17:12:19 +00:00
|
|
|
if err := c.update(); err != nil {
|
2024-12-02 17:37:19 +00:00
|
|
|
c.l.Errorf("Failed to update collector: %s\n", err)
|
2024-12-01 17:12:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *MeminfoCollector) PromCollector() prometheus.Collector {
|
|
|
|
return c.promExporter
|
|
|
|
}
|
|
|
|
|
|
|
|
func New() *MeminfoCollector {
|
|
|
|
return &MeminfoCollector{
|
|
|
|
updateFreq: 1 * time.Second,
|
|
|
|
meminfoFileLocation: meminfoFileLocation,
|
|
|
|
promExporter: prometheus.NewSummaryVec(prometheus.SummaryOpts{Namespace: "system", Name: "meminfo", Help: "System memory info"}, []string{"type"}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
registry.R.MustRegisterCollector("system.meminfo", New())
|
|
|
|
}
|