// Package telemetry provides application telemetry package telemetry import ( "bufio" "os" "strconv" "strings" "time" "github.com/jfraeys/fetch_ml/internal/logging" ) // IOStats represents process I/O statistics. type IOStats struct { ReadBytes uint64 WriteBytes uint64 } // ReadProcessIO reads I/O statistics from /proc/self/io. func ReadProcessIO() (IOStats, error) { f, err := os.Open("/proc/self/io") if err != nil { return IOStats{}, err } defer func() { _ = f.Close() }() var stats IOStats scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "read_bytes:") { stats.ReadBytes = parseUintField(line) } if strings.HasPrefix(line, "write_bytes:") { stats.WriteBytes = parseUintField(line) } } if err := scanner.Err(); err != nil { return IOStats{}, err } return stats, nil } // DiffIO calculates the difference between two IOStats snapshots. func DiffIO(before, after IOStats) IOStats { var delta IOStats if after.ReadBytes >= before.ReadBytes { delta.ReadBytes = after.ReadBytes - before.ReadBytes } if after.WriteBytes >= before.WriteBytes { delta.WriteBytes = after.WriteBytes - before.WriteBytes } return delta } func parseUintField(line string) uint64 { parts := strings.Split(line, ":") if len(parts) != 2 { return 0 } value, err := strconv.ParseUint(strings.TrimSpace(parts[1]), 10, 64) if err != nil { return 0 } return value } // ExecWithMetrics executes a function with timing and logging. func ExecWithMetrics( logger *logging.Logger, description string, threshold time.Duration, fn func() (string, error), ) (string, error) { start := time.Now() out, err := fn() duration := time.Since(start) if duration > threshold { fields := []any{"latency_ms", duration.Milliseconds(), "command", description} if err != nil { fields = append(fields, "error", err) } logger.Debug("ssh exec", fields...) } return out, err }