mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
the state is the settle path's structural hash of the recorded hierarchy, the same function the drivers wait on, so a state boundary here is the one the harness itself uses. --reference reports the observation at which two runs' hierarchies first differ. trace only: no device, no replay.
134 lines
4.2 KiB
Go
134 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"sort"
|
|
|
|
"github.com/priyanshujain/sanderling/internal/driver/ioscompanion"
|
|
"github.com/priyanshujain/sanderling/internal/tracecorpus"
|
|
)
|
|
|
|
// observation is one hierarchy-bearing step: the index the run gave it and the
|
|
// state it observed.
|
|
type observation struct {
|
|
Step int
|
|
State string
|
|
}
|
|
|
|
// Reach is what one run explored. Distinct counts the structural states its
|
|
// observations visited, which is the measure; Observations is how many looks
|
|
// it took to visit them.
|
|
type Reach struct {
|
|
Directory string `json:"directory"`
|
|
Seed int64 `json:"seed"`
|
|
Platform string `json:"platform"`
|
|
Arm string `json:"arm,omitempty"`
|
|
Observations int `json:"observations"`
|
|
Distinct int `json:"distinct_states"`
|
|
// Unobserved counts steps carrying no hierarchy, which the run-end
|
|
// finalize record is, so an observation count cannot be read as a step
|
|
// count by accident.
|
|
Unobserved int `json:"steps_without_hierarchy"`
|
|
observations []observation
|
|
}
|
|
|
|
// measure keys each observation by a digest of its structural hash. The
|
|
// measure is equality between hashes and nothing else, and a corpus holds
|
|
// thousands of trees whose hashes run to tens of kilobytes each.
|
|
func measure(run tracecorpus.Run) Reach {
|
|
reach := Reach{
|
|
Directory: run.Directory,
|
|
Seed: run.Meta.Seed,
|
|
Platform: run.Meta.Platform,
|
|
Arm: run.Meta.Arm,
|
|
}
|
|
distinct := map[string]bool{}
|
|
for _, step := range run.Steps {
|
|
if step.Hierarchy == nil {
|
|
reach.Unobserved++
|
|
continue
|
|
}
|
|
digest := sha256.Sum256([]byte(ioscompanion.StructuralHash(step.Hierarchy)))
|
|
key := hex.EncodeToString(digest[:])
|
|
reach.observations = append(
|
|
reach.observations,
|
|
observation{Step: step.Index, State: key},
|
|
)
|
|
distinct[key] = true
|
|
}
|
|
reach.Observations = len(reach.observations)
|
|
reach.Distinct = len(distinct)
|
|
return reach
|
|
}
|
|
|
|
// corpusDistinct counts the structural states the whole corpus reached, which
|
|
// is not the sum of the per-run counts: runs of one application revisit the
|
|
// same screens.
|
|
func corpusDistinct(reaches []Reach) int {
|
|
distinct := map[string]bool{}
|
|
for _, reach := range reaches {
|
|
for _, seen := range reach.observations {
|
|
distinct[seen.State] = true
|
|
}
|
|
}
|
|
return len(distinct)
|
|
}
|
|
|
|
// Divergence is where a replay stopped observing what the reference observed.
|
|
// Step is the index of the first observation whose structural state differs;
|
|
// Diverged is false when the replay matched the reference for every
|
|
// observation the two share, in which case Step is that shared length and the
|
|
// observation is right-censored.
|
|
type Divergence struct {
|
|
Directory string `json:"directory"`
|
|
Seed int64 `json:"seed"`
|
|
Step int `json:"step"`
|
|
Diverged bool `json:"diverged"`
|
|
Compared int `json:"observations_compared"`
|
|
}
|
|
|
|
func diverge(reference, replay Reach) Divergence {
|
|
result := Divergence{Directory: replay.Directory, Seed: replay.Seed}
|
|
shared := len(reference.observations)
|
|
if len(replay.observations) < shared {
|
|
shared = len(replay.observations)
|
|
}
|
|
result.Compared = shared
|
|
for position := 0; position < shared; position++ {
|
|
if reference.observations[position].State != replay.observations[position].State {
|
|
result.Step = reference.observations[position].Step
|
|
result.Diverged = true
|
|
return result
|
|
}
|
|
}
|
|
if shared > 0 {
|
|
result.Step = reference.observations[shared-1].Step
|
|
}
|
|
return result
|
|
}
|
|
|
|
// medianDivergence is E6's number: the median observation index at which a
|
|
// replay first diverges from the reference. A replay that never diverged
|
|
// enters at the last index the two share, which is where the observation is
|
|
// censored rather than where it broke, so the count of such replays is
|
|
// reported beside the median rather than folded into it.
|
|
func medianDivergence(divergences []Divergence) (median float64, censored int) {
|
|
if len(divergences) == 0 {
|
|
return 0, 0
|
|
}
|
|
steps := make([]int, 0, len(divergences))
|
|
for _, divergence := range divergences {
|
|
steps = append(steps, divergence.Step)
|
|
if !divergence.Diverged {
|
|
censored++
|
|
}
|
|
}
|
|
sort.Ints(steps)
|
|
middle := len(steps) / 2
|
|
if len(steps)%2 == 1 {
|
|
return float64(steps[middle]), censored
|
|
}
|
|
return float64(steps[middle-1]+steps[middle]) / 2, censored
|
|
}
|