diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-28 09:33:53 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-28 09:33:53 +0200 |
| commit | a4b234587cdeed5e1a71c3ee42d5fe35bb7b8a32 (patch) | |
| tree | 466639685de73ad479f87c60d13e7b47e61cfe40 /internal | |
| parent | 1900184807155c611b5ccd91fc57a57880d7908f (diff) | |
refactor: extract shared parseRecordLine to eliminate duplication
Extract record line parsing into a shared parseRecordLine function in
types.go, replacing duplicated parsing logic in aggregate.go and db.go.
Also simplifies lastKernelFromFile to use the shared function.
Task: a6e5c545-a37d-41a8-8a98-f3edb28ab662
Amp-Thread-ID: https://ampcode.com/threads/T-019ca323-dde1-73ac-97f0-cebfae5922a5
Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/goprecords/aggregate.go | 49 | ||||
| -rw-r--r-- | internal/goprecords/db.go | 28 | ||||
| -rw-r--r-- | internal/goprecords/parse_test.go | 78 | ||||
| -rw-r--r-- | internal/goprecords/types.go | 44 |
4 files changed, 136 insertions, 63 deletions
diff --git a/internal/goprecords/aggregate.go b/internal/goprecords/aggregate.go index ba446cb..e75d486 100644 --- a/internal/goprecords/aggregate.go +++ b/internal/goprecords/aggregate.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "path/filepath" - "strconv" "strings" ) @@ -82,35 +81,14 @@ func processRecordsFile(ctx context.Context, path, host string, out *Aggregates) return ctx.Err() default: } - line := strings.TrimSpace(sc.Text()) - if line == "" { + rec, ok := parseRecordLine(sc.Text()) + if !ok { continue } - parts := strings.SplitN(line, ":", 3) - if len(parts) != 3 { - continue - } - uptime, _ := strconv.ParseUint(parts[0], 10, 64) - bootTime, _ := strconv.ParseUint(parts[1], 10, 64) - osStr := parts[2] - uname := osStr - if i := strings.Index(osStr, " "); i > 0 { - uname = osStr[:i] - } - osMajor := uname + " " - rest := osStr - if i := strings.Index(osStr, " "); i >= 0 { - rest = osStr[i+1:] - } - if j := strings.Index(rest, "."); j >= 0 { - osMajor += rest[:j] + "..." - } else { - osMajor += rest + "..." - } - out.Host[host].AddRecord(uptime, bootTime) - getOrNewAggregate(out.Kernel, osStr).AddRecord(uptime, bootTime) - getOrNewAggregate(out.KernelName, uname).AddRecord(uptime, bootTime) - getOrNewAggregate(out.KernelMajor, osMajor).AddRecord(uptime, bootTime) + out.Host[host].AddRecord(rec.Uptime, rec.BootTime) + getOrNewAggregate(out.Kernel, rec.OS).AddRecord(rec.Uptime, rec.BootTime) + getOrNewAggregate(out.KernelName, rec.KernelName).AddRecord(rec.Uptime, rec.BootTime) + getOrNewAggregate(out.KernelMajor, rec.KernelMajor).AddRecord(rec.Uptime, rec.BootTime) } if err := sc.Err(); err != nil { return fmt.Errorf("scan %s: %w", path, err) @@ -137,18 +115,13 @@ func lastKernelFromFile(path string) (string, error) { var lastOS string sc := bufio.NewScanner(f) for sc.Scan() { - line := strings.TrimSpace(sc.Text()) - if line == "" { - continue - } - parts := strings.SplitN(line, ":", 3) - if len(parts) != 3 { + rec, ok := parseRecordLine(sc.Text()) + if !ok { continue } - bootTime, _ := strconv.ParseUint(parts[1], 10, 64) - if bootTime >= maxBoot { - maxBoot = bootTime - lastOS = parts[2] + if rec.BootTime >= maxBoot { + maxBoot = rec.BootTime + lastOS = rec.OS } } return lastOS, sc.Err() diff --git a/internal/goprecords/db.go b/internal/goprecords/db.go index 18fadf5..c11cdb0 100644 --- a/internal/goprecords/db.go +++ b/internal/goprecords/db.go @@ -7,7 +7,6 @@ import ( "fmt" "os" "path/filepath" - "strconv" "strings" _ "modernc.org/sqlite" @@ -96,32 +95,11 @@ func ImportFromDir(ctx context.Context, db *sql.DB, statsDir string) error { } sc := bufio.NewScanner(f) for sc.Scan() { - line := strings.TrimSpace(sc.Text()) - if line == "" { + rec, ok := parseRecordLine(sc.Text()) + if !ok { continue } - parts := strings.SplitN(line, ":", 3) - if len(parts) != 3 { - continue - } - uptimeSec, _ := strconv.ParseInt(parts[0], 10, 64) - bootTime, _ := strconv.ParseInt(parts[1], 10, 64) - osStr := parts[2] - osKernelName := osStr - if i := strings.Index(osStr, " "); i > 0 { - osKernelName = osStr[:i] - } - osMajor := osKernelName + " " - rest := osStr - if i := strings.Index(osStr, " "); i >= 0 { - rest = osStr[i+1:] - } - if j := strings.Index(rest, "."); j >= 0 { - osMajor += rest[:j] + "..." - } else { - osMajor += rest + "..." - } - _, err := insert.ExecContext(ctx, host, uptimeSec, bootTime, osStr, osKernelName, osMajor) + _, err := insert.ExecContext(ctx, host, rec.Uptime, rec.BootTime, rec.OS, rec.KernelName, rec.KernelMajor) if err != nil { f.Close() return fmt.Errorf("insert: %w", err) diff --git a/internal/goprecords/parse_test.go b/internal/goprecords/parse_test.go index 304b06e..700440d 100644 --- a/internal/goprecords/parse_test.go +++ b/internal/goprecords/parse_test.go @@ -51,6 +51,84 @@ func TestParseMetric(t *testing.T) { } } +func TestParseRecordLine(t *testing.T) { + tests := []struct { + in string + want recordLine + ok bool + }{ + { + "12345:1700000000:Linux 6.5.0-generic", + recordLine{ + Uptime: 12345, + BootTime: 1700000000, + OS: "Linux 6.5.0-generic", + KernelName: "Linux", + KernelMajor: "Linux 6...", + }, + true, + }, + { + " 99:100:FreeBSD 14.0-RELEASE ", + recordLine{ + Uptime: 99, + BootTime: 100, + OS: "FreeBSD 14.0-RELEASE", + KernelName: "FreeBSD", + KernelMajor: "FreeBSD 14...", + }, + true, + }, + { + "500:200:SingleToken", + recordLine{ + Uptime: 500, + BootTime: 200, + OS: "SingleToken", + KernelName: "SingleToken", + KernelMajor: "SingleToken SingleToken...", + }, + true, + }, + { + "100:200:Linux 6.5.0:extra", + recordLine{ + Uptime: 100, + BootTime: 200, + OS: "Linux 6.5.0:extra", + KernelName: "Linux", + KernelMajor: "Linux 6...", + }, + true, + }, + { + "abc:def:Linux 6.5.0", + recordLine{ + Uptime: 0, + BootTime: 0, + OS: "Linux 6.5.0", + KernelName: "Linux", + KernelMajor: "Linux 6...", + }, + true, + }, + {"", recordLine{}, false}, + {" ", recordLine{}, false}, + {"only:two", recordLine{}, false}, + {"no-colons-at-all", recordLine{}, false}, + } + for _, tt := range tests { + got, ok := parseRecordLine(tt.in) + if ok != tt.ok { + t.Errorf("parseRecordLine(%q) ok=%v, want %v", tt.in, ok, tt.ok) + continue + } + if tt.ok && got != tt.want { + t.Errorf("parseRecordLine(%q) = %+v, want %+v", tt.in, got, tt.want) + } + } +} + func TestParseOutputFormat(t *testing.T) { tests := []struct { in string diff --git a/internal/goprecords/types.go b/internal/goprecords/types.go index 05a68f8..ae8a059 100644 --- a/internal/goprecords/types.go +++ b/internal/goprecords/types.go @@ -75,6 +75,15 @@ func NewHostAggregate(name, lastKernel string) *HostAggregate { } } +// recordLine holds the parsed fields from a single uptimed record line. +type recordLine struct { + Uptime uint64 + BootTime uint64 + OS string + KernelName string + KernelMajor string +} + // tableRow is one row in the report table. type tableRow struct { Pos string @@ -250,6 +259,41 @@ func ParseOutputFormat(s string) (OutputFormat, error) { } } +func parseRecordLine(line string) (recordLine, bool) { + line = strings.TrimSpace(line) + if line == "" { + return recordLine{}, false + } + parts := strings.SplitN(line, ":", 3) + if len(parts) != 3 { + return recordLine{}, false + } + uptime, _ := strconv.ParseUint(parts[0], 10, 64) + bootTime, _ := strconv.ParseUint(parts[1], 10, 64) + osStr := parts[2] + kernelName := osStr + if i := strings.Index(osStr, " "); i > 0 { + kernelName = osStr[:i] + } + kernelMajor := kernelName + " " + rest := osStr + if i := strings.Index(osStr, " "); i >= 0 { + rest = osStr[i+1:] + } + if j := strings.Index(rest, "."); j >= 0 { + kernelMajor += rest[:j] + "..." + } else { + kernelMajor += rest + "..." + } + return recordLine{ + Uptime: uptime, + BootTime: bootTime, + OS: osStr, + KernelName: kernelName, + KernelMajor: kernelMajor, + }, true +} + func wordWrap(s string, lineLimit int) string { if lineLimit <= 0 || len(s) <= lineLimit { return s |
