1//go:build linux
2
3package main
4
5import (
6 "encoding/json"
7 "errors"
8 "flag"
9 "fmt"
10 "io"
11 "os"
12 "path/filepath"
13)
14
15var (
16 canonicalTopologyIDs = []string{"1x4", "4x1", "4x4", "8x1"}
17 canonicalTopologySet = stringSet(canonicalTopologyIDs)
18 canonicalWorkloadIDs = []string{
19 "idle", "active_output", "all_output", "inactive_output", "interactive_flood",
20 "copy_search", "resize_sweep", "snapshot_output_resize", "attach_restore_tab_switch",
21 }
22 canonicalWorkloadSet = stringSet(canonicalWorkloadIDs)
23)
24
25func stringSet(ids []string) map[string]bool {
26 set := make(map[string]bool, len(ids))
27 for _, id := range ids {
28 set[id] = true
29 }
30 return set
31}
32
33func parseOptions(args []string) (options, error) {
34 var o options
35 fs := flag.NewFlagSet("vev-perf-harness", flag.ContinueOnError)
36 fs.SetOutput(io.Discard)
37 fs.StringVar(&o.vevBin, "vev-bin", "", "public vev binary")
38 fs.StringVar(&o.manifest, "manifest", "", "manifest")
39 fs.StringVar(&o.out, "out", "", "output directory")
40 fs.StringVar(&o.scenario, "scenario", "", "one canonical scenario ID (after full manifest validation)")
41 fs.DurationVar(&o.warmup, "warmup", 0, "warmup")
42 fs.DurationVar(&o.duration, "duration", 0, "measurement duration")
43 fs.IntVar(&o.repetitions, "repetitions", 0, "repetitions")
44 if err := fs.Parse(args); err != nil {
45 return o, err
46 }
47 seen := map[string]bool{}
48 fs.Visit(func(f *flag.Flag) { seen[f.Name] = true })
49 for _, name := range []string{"vev-bin", "manifest", "out", "warmup", "duration", "repetitions"} {
50 if !seen[name] {
51 return o, fmt.Errorf("--%s is required", name)
52 }
53 }
54 if o.vevBin == "" || o.manifest == "" || o.out == "" {
55 return o, errors.New("--vev-bin, --manifest, and --out are required")
56 }
57 if o.warmup < 0 {
58 return o, errors.New("--warmup must not be negative")
59 }
60 if o.duration < minimumDuration {
61 return o, fmt.Errorf("--duration must be at least %s", minimumDuration)
62 }
63 if o.repetitions < minimumRepetitions {
64 return o, fmt.Errorf("--repetitions must be at least %d", minimumRepetitions)
65 }
66 return o, nil
67}
68
69// resolvePathOptions captures the harness cwd once, before role launchers
70// assign their isolated working directories. All downstream filesystem access,
71// manifests, and process environments consequently refer to the same paths.
72func resolvePathOptions(o options) (options, error) {
73 cwd, err := os.Getwd()
74 if err != nil {
75 return o, fmt.Errorf("resolve harness working directory: %w", err)
76 }
77 absolute := func(path string) string {
78 if filepath.IsAbs(path) {
79 return filepath.Clean(path)
80 }
81 return filepath.Clean(filepath.Join(cwd, path))
82 }
83 o.vevBin = absolute(o.vevBin)
84 o.manifest = absolute(o.manifest)
85 o.out = absolute(o.out)
86 return o, nil
87}
88
89func readManifest(path string) (manifest, error) {
90 f, err := os.Open(path)
91 if err != nil {
92 return manifest{}, err
93 }
94 var m manifest
95 if err := json.NewDecoder(f).Decode(&m); err != nil {
96 return manifest{}, closeFile(f, err)
97 }
98 if err := f.Close(); err != nil {
99 return manifest{}, err
100 }
101 return m, nil
102}
103
104func validateManifest(m manifest) error {
105 if m.Schema != 1 {
106 return errors.New("unsupported manifest schema")
107 }
108 tops := map[string]bool{}
109 for _, t := range m.Topologies {
110 if t.ID == "" {
111 return errors.New("empty topology id")
112 }
113 if tops[t.ID] {
114 return fmt.Errorf("duplicate topology id %q", t.ID)
115 }
116 if !canonicalTopologySet[t.ID] {
117 return fmt.Errorf("unknown topology id %q", t.ID)
118 }
119 if t.Geometry != "120x40" || t.RowsPerPane != 10000 {
120 return fmt.Errorf("invalid topology %q", t.ID)
121 }
122 tops[t.ID] = true
123 }
124 for _, v := range canonicalTopologyIDs {
125 if !tops[v] {
126 return fmt.Errorf("missing canonical topology %s", v)
127 }
128 }
129 works := map[string]bool{}
130 for _, workload := range m.Workloads {
131 if workload == "" {
132 return errors.New("empty workload id")
133 }
134 if works[workload] {
135 return fmt.Errorf("duplicate workload id %q", workload)
136 }
137 if !canonicalWorkloadSet[workload] {
138 return fmt.Errorf("unknown workload id %q", workload)
139 }
140 works[workload] = true
141 }
142 for _, v := range canonicalWorkloadIDs {
143 if !works[v] {
144 return fmt.Errorf("missing canonical workload %s", v)
145 }
146 }
147 trans := map[string]bool{}
148 for _, t := range m.Transports {
149 if t.ID == "" {
150 return errors.New("empty transport id")
151 }
152 if trans[t.ID] {
153 return fmt.Errorf("duplicate transport id %q", t.ID)
154 }
155 if err := validateTransportFixture(t); err != nil {
156 return err
157 }
158 trans[t.ID] = true
159 }
160 for _, v := range []string{"local", "ssh_stdio", "udp_baseline", "udp_25ms", "udp_100ms", "udp_loss_0pct", "udp_loss_1pct"} {
161 if !trans[v] {
162 return fmt.Errorf("missing canonical transport %s", v)
163 }
164 }
165 seen := map[string]bool{}
166 covered := map[string]bool{}
167 for _, s := range m.Scenarios {
168 if s.ID == "" || seen[s.ID] {
169 return fmt.Errorf("duplicate or empty scenario id %q", s.ID)
170 }
171 seen[s.ID] = true
172 if !tops[s.Topology] || !works[s.Workload] || !trans[s.Transport] {
173 return fmt.Errorf("scenario %s references unknown matrix entry", s.ID)
174 }
175 if s.InapplicableReason == "" && len(s.Roles) == 0 {
176 return fmt.Errorf("scenario %s has no process roles", s.ID)
177 }
178 if s.InapplicableReason != "" && len(s.Roles) != 0 {
179 return fmt.Errorf("inapplicable scenario %s must not launch roles", s.ID)
180 }
181 if s.InapplicableReason == "" && !equalRoleSet(s.Roles, requiredRoles(s.Transport)) {
182 return fmt.Errorf("scenario %s roles do not route transport %s", s.ID, s.Transport)
183 }
184 covered[s.Topology+"\x00"+s.Workload+"\x00"+s.Transport] = true
185 }
186 for topologyID := range tops {
187 for workload := range works {
188 for transportID := range trans {
189 key := topologyID + "\x00" + workload + "\x00" + transportID
190 if !covered[key] {
191 return fmt.Errorf("missing scenario or inapplicable reason for %s/%s/%s", topologyID, workload, transportID)
192 }
193 }
194 }
195 }
196 return nil
197}
198
199// selectScenario is intentionally called only after validateManifest. A bounded
200// local evidence run therefore cannot mask an incomplete canonical matrix.
201func selectScenario(m manifest, id string) ([]scenario, error) {
202 for _, s := range m.Scenarios {
203 if s.ID != id {
204 continue
205 }
206 if s.InapplicableReason != "" {
207 return nil, fmt.Errorf("scenario %s is inapplicable: %s", id, s.InapplicableReason)
208 }
209 return []scenario{s}, nil
210 }
211 return nil, fmt.Errorf("unknown canonical scenario %s", id)
212}
213
214func requiredRoles(transportID string) []string {
215 switch transportID {
216 case "local":
217 return []string{"daemon", "client"}
218 case "ssh_stdio":
219 return []string{"daemon", "client", "ssh_stdio_peer"}
220 default:
221 return []string{"daemon", "client", "udp_peer"}
222 }
223}
224
225func equalRoleSet(got, want []string) bool {
226 if len(got) != len(want) {
227 return false
228 }
229 seen := make(map[string]bool, len(got))
230 for _, role := range got {
231 seen[role] = true
232 }
233 for _, role := range want {
234 if !seen[role] {
235 return false
236 }
237 }
238 return true
239}
240
241func validateTransportFixture(t transport) error {
242 want := map[string]transport{
243 "local": {ID: "local", Kind: "local"},
244 "ssh_stdio": {ID: "ssh_stdio", Kind: "ssh_stdio"},
245 "udp_baseline": {ID: "udp_baseline", Kind: "udp"},
246 "udp_25ms": {ID: "udp_25ms", Kind: "udp", RTTMS: 25},
247 "udp_100ms": {ID: "udp_100ms", Kind: "udp", RTTMS: 100},
248 "udp_loss_0pct": {ID: "udp_loss_0pct", Kind: "udp", LossPercent: 0},
249 "udp_loss_1pct": {ID: "udp_loss_1pct", Kind: "udp", LossPercent: 1},
250 }
251 w, ok := want[t.ID]
252 if !ok || t.Kind != w.Kind || t.RTTMS != w.RTTMS || t.LossPercent != w.LossPercent {
253 return fmt.Errorf("invalid canonical transport fixture %+v", t)
254 }
255 return nil
256}