- 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.
87 lines
1.9 KiB
Go
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
|
|
}
|