Viewing files at commit c10cefe7abe5Commit detailsView latest

Browse files
oakmark/transport.go 116 lines · 4.3 KB · go
1
package main
2
3
import (
4
	"context"
5
	"fmt"
6
	"math/rand"
7
	"strings"
8
)
9
10
// FileSpec is one file in a synthetic changeset. Stable Paths ("file-0.txt")
11
// mean successive changesets modify the same working set — the realistic shape
12
// of an agent editing a small set of files — exactly as ForgeMark does.
13
type FileSpec struct {
14
	Path    string
15
	Content []byte
16
}
17
18
// Transport is the per-agent backend the harness drives. Each agent owns one
19
// Transport instance, used only from that agent's single goroutine, so
20
// implementations need no internal locking. This is the one seam that differs
21
// between the `cli` (shell out to `oak`) and `protocol` (in-process oak-engine)
22
// backends; everything above it — the loops, sampling, warm-up exclusion, and
23
// percentile math — is shared and identical to ForgeMark's.
24
type Transport interface {
25
	// Init prepares the agent's local workspace on its own branch (branch/repo
26
	// strategy). Not measured. Called once before the push loop.
27
	Init(ctx context.Context) error
28
	// Push publishes a changeset to the agent's branch. nil = landed; a non-nil
29
	// error is passed to classifyOak to bucket OK / CAS / error.
30
	Push(ctx context.Context, files []FileSpec) error
31
32
	// CloneSession clones the base repo into fresh local state (session
33
	// strategy; this IS the measured read). Called at the top of each session.
34
	CloneSession(ctx context.Context) error
35
	// PushCheckpoint publishes a changeset to the current session's ephemeral
36
	// branch (session strategy).
37
	PushCheckpoint(ctx context.Context, session int, files []FileSpec) error
38
	// CleanupSession abandons the ephemeral session branch (best-effort, not
39
	// measured), so refs don't accumulate on the target and bias later latency.
40
	CleanupSession(session int)
41
42
	// Close releases the agent's resources (temp dirs, child processes).
43
	Close()
44
}
45
46
// commitConfig is the subset of run config an agent needs to build a changeset.
47
// Same knobs and defaults as ForgeMark (-files-min/-files-max/-file-size).
48
type commitConfig struct {
49
	filesMin int
50
	filesMax int
51
	fileSize int
52
}
53
54
// sessionConfig drives the "session" strategy: each agent repeatedly
55
// shallow-clones the base repo, pushes `commits` checkpoints to a fresh
56
// ephemeral branch, then abandons it — the realistic agent lifecycle that
57
// interleaves read (clone) and write (push) load. Mirrors ForgeMark's
58
// sessionConfig; cloneDepth 0 = full history, else shallow.
59
type sessionConfig struct {
60
	commits    int
61
	cloneDepth int
62
}
63
64
// generateFiles builds a changeset of filesMin..filesMax files, each fileSize
65
// random bytes. Deterministic per agent (seeded rng) so runs are reproducible;
66
// random content guarantees a distinct blob every push (no dedup short-circuit
67
// that would make the server's write path look artificially fast). Mirrors
68
// ForgeMark's agent.commit file loop exactly.
69
func generateFiles(rng *rand.Rand, cfg *commitConfig) []FileSpec {
70
	n := cfg.filesMin
71
	if cfg.filesMax > cfg.filesMin {
72
		n += rng.Intn(cfg.filesMax - cfg.filesMin + 1)
73
	}
74
	files := make([]FileSpec, n)
75
	for i := range n {
76
		buf := make([]byte, cfg.fileSize)
77
		_, _ = rng.Read(buf) // math/rand never errors
78
		files[i] = FileSpec{Path: fmt.Sprintf("file-%d.txt", i), Content: buf}
79
	}
80
	return files
81
}
82
83
// oakCASMarkers are substrings that mark a push rejection as contention (a
84
// compare-and-set on the branch head lost), not a transport/server error. Oak
85
// signals a rejected push as HTTP 200 + success:false (ConflictDetected); the
86
// engine and CLI surface that as text containing one of these.
87
var oakCASMarkers = []string{
88
	"success:false", "success: false",
89
	"conflict", "non-fast-forward", "fast-forward",
90
	"diverged", "reference has changed", "expected head",
91
	"compare-and-set", "cas",
92
}
93
94
// classifyOak maps an oak push result to an outcome — the oak sibling of
95
// ForgeMark's git-oriented classify(). nil is success; a conflict/CAS rejection
96
// is contention; everything else (transport, auth, 5xx) is an error.
97
func classifyOak(err error) outcome {
98
	if err == nil {
99
		return outcomeOK
100
	}
101
	msg := strings.ToLower(err.Error())
102
	for _, m := range oakCASMarkers {
103
		if strings.Contains(msg, m) {
104
			return outcomeCAS
105
		}
106
	}
107
	return outcomeErr
108
}
109
110
// readOutcome maps a clone/pull result to an outcome (reads have no CAS).
111
func readOutcome(err error) outcome {
112
	if err == nil {
113
		return outcomeOK
114
	}
115
	return outcomeErr
116
}