mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
reads a run directory's meta and every step, and refuses a step whose trace_version is not the current one: an older step stores no element depths, so its hierarchy decodes with a nil root and a structural hash over it is the empty string for every screen. Discover walks a tree for the directories holding both meta.json and trace.jsonl.
114 lines
3.3 KiB
Go
114 lines
3.3 KiB
Go
// Package tracecorpus loads recorded runs for measures that read stored
|
|
// traces with no device attached.
|
|
package tracecorpus
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
|
|
"github.com/priyanshujain/sanderling/internal/trace"
|
|
)
|
|
|
|
const maxStepBytes = 64 * 1024 * 1024
|
|
|
|
// Run is one run directory: its meta and every step in file order, including
|
|
// the synthetic end-of-run record a finalised liveness obligation lands on.
|
|
type Run struct {
|
|
Directory string
|
|
Meta trace.Meta
|
|
Steps []trace.Step
|
|
}
|
|
|
|
// Load reads a run directory and refuses anything an offline measure cannot
|
|
// read. A step written before the format change stores no element depths, so
|
|
// its hierarchy decodes with a nil root: a structural hash over it is the
|
|
// empty string for every screen, and every run would look identical to every
|
|
// other. The refusal has to name the version rather than report that number.
|
|
func Load(directory string) (Run, error) {
|
|
metaBody, err := os.ReadFile(filepath.Join(directory, "meta.json"))
|
|
if err != nil {
|
|
return Run{}, fmt.Errorf("read meta: %w", err)
|
|
}
|
|
var meta trace.Meta
|
|
if err := json.Unmarshal(metaBody, &meta); err != nil {
|
|
return Run{}, fmt.Errorf("decode meta: %w", err)
|
|
}
|
|
|
|
file, err := os.Open(filepath.Join(directory, "trace.jsonl"))
|
|
if err != nil {
|
|
return Run{}, fmt.Errorf("open trace: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
run := Run{Directory: directory, Meta: meta}
|
|
scanner := bufio.NewScanner(file)
|
|
scanner.Buffer(make([]byte, 0, 1024*1024), maxStepBytes)
|
|
line := 0
|
|
for scanner.Scan() {
|
|
line++
|
|
if len(scanner.Bytes()) == 0 {
|
|
continue
|
|
}
|
|
var step trace.Step
|
|
if err := json.Unmarshal(scanner.Bytes(), &step); err != nil {
|
|
return Run{}, fmt.Errorf("decode step on line %d: %w", line, err)
|
|
}
|
|
if step.TraceVersion != trace.TraceVersion {
|
|
return Run{}, fmt.Errorf(
|
|
"step %d is trace_version %d and an offline measure reads version %d only: "+
|
|
"an older step stores no element depths, no logs and no exceptions, so "+
|
|
"its hierarchy decodes with a nil root and nothing retrofits it",
|
|
step.Index, step.TraceVersion, trace.TraceVersion)
|
|
}
|
|
if step.Hierarchy != nil && len(step.Hierarchy.Elements) > 0 &&
|
|
step.Hierarchy.Root == nil {
|
|
return Run{}, fmt.Errorf(
|
|
"step %d stores %d elements and no tree shape, so its hierarchy "+
|
|
"cannot be rebuilt",
|
|
step.Index, len(step.Hierarchy.Elements))
|
|
}
|
|
run.Steps = append(run.Steps, step)
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return Run{}, fmt.Errorf("read trace: %w", err)
|
|
}
|
|
if len(run.Steps) == 0 {
|
|
return Run{}, fmt.Errorf("trace has no steps")
|
|
}
|
|
return run, nil
|
|
}
|
|
|
|
// Discover finds every run directory at or below root, a run directory being
|
|
// one holding both meta.json and trace.jsonl.
|
|
func Discover(root string) ([]string, error) {
|
|
var directories []string
|
|
err := filepath.Walk(
|
|
root,
|
|
func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.IsDir() {
|
|
return nil
|
|
}
|
|
if _, statErr := os.Stat(filepath.Join(path, "trace.jsonl")); statErr != nil {
|
|
return nil
|
|
}
|
|
if _, statErr := os.Stat(filepath.Join(path, "meta.json")); statErr != nil {
|
|
return nil
|
|
}
|
|
directories = append(directories, path)
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sort.Strings(directories)
|
|
return directories, nil
|
|
}
|