Skip to content

Commit bb4f247

Browse files
nirancodex
andauthored
feat(load-tests): label account-create reports and config overrides (#200)
* feat(report): label account-create load tests Co-authored-by: Codex <noreply@openai.com> * feat(loadtest): support config overrides in matrix rows * refactor(benchmark): clarify grouped params handling --------- Co-authored-by: Codex <noreply@openai.com>
1 parent cf1450c commit bb4f247

8 files changed

Lines changed: 340 additions & 79 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import { formatTransactions } from "./ConfigCard";
4+
5+
describe("formatTransactions", () => {
6+
it("formats a weighted transaction mix", () => {
7+
expect(
8+
formatTransactions({
9+
transactions: [
10+
{ type: "uniswap_v3", weight: 50 },
11+
{ type: "aerodrome_cl", weight: 50 },
12+
],
13+
}),
14+
).toBe("uniswap_v3 (50%) · aerodrome_cl (50%)");
15+
});
16+
17+
it("labels full fresh-recipient transfers as account-create", () => {
18+
expect(
19+
formatTransactions({
20+
fresh_recipient_ratio: 1,
21+
transactions: [{ type: "transfer", weight: 100 }],
22+
}),
23+
).toBe("account-create (100%)");
24+
});
25+
26+
it("keeps partial fresh-recipient transfer ratios visible", () => {
27+
expect(
28+
formatTransactions({
29+
fresh_recipient_ratio: 0.25,
30+
transactions: [{ type: "transfer", weight: 100 }],
31+
}),
32+
).toBe("transfer (100%, 25% account-create)");
33+
});
34+
});

report/src/components/ConfigCard.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,27 @@ interface Row {
1111
value: string;
1212
}
1313

14-
const formatTransactions = (txs: LoadTestConfig["transactions"]): string => {
14+
const percent = (value: number): string => `${Math.round(value * 100)}%`;
15+
16+
export const formatTransactions = (
17+
config: Pick<LoadTestConfig, "transactions" | "fresh_recipient_ratio">,
18+
): string => {
19+
const txs = config.transactions;
1520
if (!txs || txs.length === 0) return "—";
1621
const total = txs.reduce((acc, t) => acc + t.weight, 0);
1722
if (total === 0) return txs.map((t) => t.type).join(" · ");
23+
24+
const freshRecipientRatio = config.fresh_recipient_ratio ?? 0;
1825
return txs
19-
.map((t) => `${t.type} (${Math.round((t.weight / total) * 100)}%)`)
26+
.map((t) => {
27+
if (t.type === "transfer" && freshRecipientRatio >= 1) {
28+
return `account-create (${percent(t.weight / total)})`;
29+
}
30+
if (t.type === "transfer" && freshRecipientRatio > 0) {
31+
return `${t.type} (${percent(t.weight / total)}, ${percent(freshRecipientRatio)} account-create)`;
32+
}
33+
return `${t.type} (${percent(t.weight / total)})`;
34+
})
2035
.join(" · ");
2136
};
2237

@@ -94,7 +109,7 @@ const RowGroup = ({ rows }: { rows: Row[] }) => (
94109

95110
const ConfigCard = ({ config }: ConfigCardProps) => {
96111
const groups = buildRows(config);
97-
const txLine = formatTransactions(config.transactions);
112+
const txLine = formatTransactions(config);
98113

99114
return (
100115
<StatCard title="Run config">

report/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ export interface LoadTestConfig {
209209
seed: number;
210210
chain_id: number | null;
211211
transactions: Array<{ type: string; weight: number }>;
212+
fresh_recipient_ratio?: number;
212213
looper_contract: string | null;
213214
swap_token_amount: string;
214215
// Producer omits unless real-token setup is enabled; loosely typed.

runner/benchmark/benchmark.go

Lines changed: 127 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -37,90 +37,141 @@ func NewParamsFromValues(assignments map[string]interface{}) (*types.RunParams,
3737
params := *DefaultParams
3838

3939
for k, v := range assignments {
40-
switch k {
41-
case "payload":
42-
if vPtrStr, ok := v.(*string); ok {
43-
params.PayloadID = string(*vPtrStr)
44-
} else if vStr, ok := v.(string); ok {
45-
params.PayloadID = string(vStr)
46-
} else {
47-
return nil, fmt.Errorf("invalid payload %s", v)
48-
}
49-
case "node_type":
50-
if vStr, ok := v.(string); ok {
51-
params.NodeType = vStr
52-
} else {
53-
return nil, fmt.Errorf("invalid node type %s", v)
54-
}
55-
case "client_bin":
56-
if vStr, ok := v.(string); ok {
57-
params.ClientBinPath = vStr
58-
} else {
59-
return nil, fmt.Errorf("invalid client bin %s", v)
60-
}
61-
case "validator_node_type":
62-
if vStr, ok := v.(string); ok {
63-
params.ValidatorNodeType = vStr
64-
} else {
65-
return nil, fmt.Errorf("invalid validator node type %s", v)
66-
}
67-
case "gas_limit":
68-
if vInt, ok := v.(int); ok {
69-
params.GasLimit = uint64(vInt)
70-
} else {
71-
return nil, fmt.Errorf("invalid gas limit %s", v)
72-
}
73-
case "consensus_timing":
74-
if vStr, ok := v.(string); ok {
75-
if vStr != "" && vStr != types.ConsensusTimingModePreventLateFCU && vStr != types.ConsensusTimingModeBaseConsensus {
76-
return nil, fmt.Errorf("invalid consensus timing %s", v)
77-
}
78-
params.ConsensusTimingMode = vStr
79-
} else {
80-
return nil, fmt.Errorf("invalid consensus timing %s", v)
40+
if err := applyParam(&params, k, v); err != nil {
41+
return nil, err
42+
}
43+
}
44+
45+
return &params, nil
46+
}
47+
48+
func applyParam(params *types.RunParams, k string, v interface{}) error {
49+
if k == "params" {
50+
return applyParamGroup(params, v)
51+
}
52+
53+
switch k {
54+
case "payload":
55+
if vPtrStr, ok := v.(*string); ok {
56+
params.PayloadID = string(*vPtrStr)
57+
} else if vStr, ok := v.(string); ok {
58+
params.PayloadID = string(vStr)
59+
} else {
60+
return fmt.Errorf("invalid payload %s", v)
61+
}
62+
case "node_type":
63+
if vStr, ok := v.(string); ok {
64+
params.NodeType = vStr
65+
} else {
66+
return fmt.Errorf("invalid node type %s", v)
67+
}
68+
case "client_bin":
69+
if vStr, ok := v.(string); ok {
70+
params.ClientBinPath = vStr
71+
} else {
72+
return fmt.Errorf("invalid client bin %s", v)
73+
}
74+
case "validator_node_type":
75+
if vStr, ok := v.(string); ok {
76+
params.ValidatorNodeType = vStr
77+
} else {
78+
return fmt.Errorf("invalid validator node type %s", v)
79+
}
80+
case "gas_limit":
81+
if vInt, ok := v.(int); ok {
82+
params.GasLimit = uint64(vInt)
83+
} else {
84+
return fmt.Errorf("invalid gas limit %s", v)
85+
}
86+
case "load_test_config":
87+
overrides, err := normalizeStringKeyMap(v)
88+
if err != nil {
89+
return fmt.Errorf("invalid load test config %v", v)
90+
}
91+
params.LoadTestConfigOverrides = overrides
92+
case "consensus_timing":
93+
if vStr, ok := v.(string); ok {
94+
if vStr != "" && vStr != types.ConsensusTimingModePreventLateFCU && vStr != types.ConsensusTimingModeBaseConsensus {
95+
return fmt.Errorf("invalid consensus timing %s", v)
8196
}
82-
case "env":
83-
if vStr, ok := v.(string); ok {
84-
entries := strings.Split(vStr, ";")
85-
params.Env = make(map[string]string)
86-
for _, entry := range entries {
87-
kv := strings.Split(entry, "=")
88-
if len(kv) != 2 {
89-
return nil, fmt.Errorf("invalid env entry %s", entry)
90-
}
91-
params.Env[kv[0]] = kv[1]
97+
params.ConsensusTimingMode = vStr
98+
} else {
99+
return fmt.Errorf("invalid consensus timing %s", v)
100+
}
101+
case "env":
102+
if vStr, ok := v.(string); ok {
103+
entries := strings.Split(vStr, ";")
104+
params.Env = make(map[string]string)
105+
for _, entry := range entries {
106+
kv := strings.Split(entry, "=")
107+
if len(kv) != 2 {
108+
return fmt.Errorf("invalid env entry %s", entry)
92109
}
93-
} else {
94-
return nil, fmt.Errorf("invalid env %s", v)
110+
params.Env[kv[0]] = kv[1]
95111
}
96-
case "num_blocks":
97-
if vInt, ok := v.(int); ok {
98-
params.NumBlocks = vInt
99-
} else {
100-
return nil, fmt.Errorf("invalid num blocks %s", v)
101-
}
102-
case "node_args":
103-
// either a list of strings or a string (separated by spaces)
104-
if vStr, ok := v.(string); ok {
105-
params.NodeArgs = strings.Split(vStr, " ")
106-
} else if vArr, ok := v.([]interface{}); ok {
107-
// convert []interface{} to []string
108-
nodeArgs := make([]string, len(vArr))
109-
for i, arg := range vArr {
110-
arg, ok := arg.(string)
111-
if !ok {
112-
return nil, fmt.Errorf("invalid non-string node arg %v", arg)
113-
}
114-
nodeArgs[i] = arg
112+
} else {
113+
return fmt.Errorf("invalid env %s", v)
114+
}
115+
case "num_blocks":
116+
if vInt, ok := v.(int); ok {
117+
params.NumBlocks = vInt
118+
} else {
119+
return fmt.Errorf("invalid num blocks %s", v)
120+
}
121+
case "node_args":
122+
// either a list of strings or a string (separated by spaces)
123+
if vStr, ok := v.(string); ok {
124+
params.NodeArgs = strings.Split(vStr, " ")
125+
} else if vArr, ok := v.([]interface{}); ok {
126+
// convert []interface{} to []string
127+
nodeArgs := make([]string, len(vArr))
128+
for i, arg := range vArr {
129+
arg, ok := arg.(string)
130+
if !ok {
131+
return fmt.Errorf("invalid non-string node arg %v", arg)
115132
}
116-
params.NodeArgs = nodeArgs
117-
} else {
118-
return nil, fmt.Errorf("invalid node args %v", v)
133+
nodeArgs[i] = arg
119134
}
135+
params.NodeArgs = nodeArgs
136+
} else {
137+
return fmt.Errorf("invalid node args %v", v)
120138
}
121139
}
140+
return nil
141+
}
122142

123-
return &params, nil
143+
func applyParamGroup(params *types.RunParams, value interface{}) error {
144+
// A params value groups multiple assignments into one matrix dimension,
145+
// preserving relationships between values that should vary together.
146+
expanded, err := normalizeStringKeyMap(value)
147+
if err != nil {
148+
return fmt.Errorf("invalid params %v", value)
149+
}
150+
for param, value := range expanded {
151+
if err := applyParam(params, param, value); err != nil {
152+
return fmt.Errorf("invalid params.%s: %w", param, err)
153+
}
154+
}
155+
return nil
156+
}
157+
158+
func normalizeStringKeyMap(value interface{}) (map[string]interface{}, error) {
159+
switch typed := value.(type) {
160+
case map[string]interface{}:
161+
return typed, nil
162+
case map[interface{}]interface{}:
163+
out := make(map[string]interface{}, len(typed))
164+
for key, value := range typed {
165+
keyString, ok := key.(string)
166+
if !ok {
167+
return nil, fmt.Errorf("non-string key %v", key)
168+
}
169+
out[keyString] = value
170+
}
171+
return out, nil
172+
default:
173+
return nil, fmt.Errorf("expected mapping")
174+
}
124175
}
125176

126177
const MAX_GAS_LIMIT = math.MaxUint64

0 commit comments

Comments
 (0)