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" "go.uber.org/zap" ) const meminfoFileLocation = "/proc/meminfo" var meminfoRegex = regexp.MustCompile(`(?m)^(?P\S+):\s+(?P\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 l *zap.SugaredLogger } 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 } func (c *MeminfoCollector) Run(ctx context.Context, l *zap.SugaredLogger) { c.l = l c.l.Debug("Initializing collector") if err := c.update(); err != nil { c.l.Errorf("Failed to init collector: %s\n", err) } for { select { case <-ctx.Done(): c.l.Debug("Stopping collector") return case <-time.After(c.updateFreq): c.l.Debug("Updating collector") if err := c.update(); err != nil { c.l.Errorf("Failed to update collector: %s\n", err) } } } } 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()) }