fetch_ml/internal/telemetry/telemetry.go
Jeremie Fraeys ea15af1833 Fix multi-user authentication and clean up debug code
- Fix YAML tags in auth config struct (json -> yaml)
- Update CLI configs to use pre-hashed API keys
- Remove double hashing in WebSocket client
- Fix port mapping (9102 -> 9103) in CLI commands
- Update permission keys to use jobs:read, jobs:create, etc.
- Clean up all debug logging from CLI and server
- All user roles now authenticate correctly:
  * Admin: Can queue jobs and see all jobs
  * Researcher: Can queue jobs and see own jobs
  * Analyst: Can see status (read-only access)

Multi-user authentication is now fully functional.
2025-12-06 12:35:32 -05:00

87 lines
1.9 KiB
Go

// 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
}