Skip to content

Commit da53356

Browse files
committed
feat: add cheapest-region pricing mode for aws perf harness
1 parent 4821013 commit da53356

File tree

3 files changed

+412
-0
lines changed

3 files changed

+412
-0
lines changed

docs/BENCHMARK.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,14 @@ If you do not want to benchmark on your laptop/network, use the AWS CLI harness:
8080

8181
Note: the runner clones from `origin` by default, so both refs must exist in the remote repository (push your branch/commit first if needed).
8282
Default region is `us-west-2`; pass `--region <aws-region>` to override.
83+
Use `--cheapest-region` to auto-pick the cheapest on-demand region for your selected instance type.
8384

8485
The script:
8586

8687
- Launches a short-lived EC2 instance with SSM (no inbound SSH required).
8788
- Runs the same benchmark scenarios for `--base-ref` and `--head-ref` on the same host.
8889
- Produces `tmp/aws-perf/<run-id>/summary.json` with per-scenario deltas and a pass/fail gate.
90+
- Produces `tmp/aws-perf/<run-id>/pricing.json` with on-demand and recent spot pricing info.
8991
- Terminates the instance automatically unless `--keep-instance` is passed.
9092

9193
Useful options:
@@ -94,6 +96,13 @@ Useful options:
9496
- `--instance-type c6i.large` (default) for low cost and stable throughput.
9597
- `--threshold-pct 5` to set the regression gate.
9698
- `--scenarios 'wreq.session.get.small;wreq.session.get.4kb;wreq.isolated.get.small'` to limit scope.
99+
- `--cheapest-region` to choose the cheapest on-demand region automatically.
100+
101+
Pricing-only check (no EC2 launch):
102+
103+
```bash
104+
./scripts/aws-perf/ec2-compare.sh --instance-type c6i.large --cheapest-region --pricing-only
105+
```
97106

98107
Safety cleanup:
99108

scripts/aws-perf/ec2-compare.sh

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ DEFAULT_REGION="us-west-2"
1313
REGION="${AWS_REGION:-${AWS_DEFAULT_REGION:-$DEFAULT_REGION}}"
1414
INSTANCE_PROFILE_NAME="wreq-js-perf-ssm-profile"
1515
INSTANCE_TYPE="c6i.large"
16+
AUTO_CHEAPEST_REGION=false
17+
PRICING_ONLY=false
1618
USE_SPOT=true
1719
FALLBACK_ON_DEMAND=true
1820
SUBNET_ID=""
@@ -28,13 +30,16 @@ WARMUP=2
2830
CONCURRENCY=8
2931
SCENARIOS="wreq.session.get.small;wreq.transport.get.small;wreq.session.get.4kb;wreq.session.post.32b;wreq.isolated.get.small;node.fetch.get.small"
3032
OUTPUT_DIR="$ROOT_DIR/tmp/aws-perf/$RUN_ID"
33+
PRICING_JSON=""
34+
CHEAPEST_REPORT_PATH=""
3135

3236
usage() {
3337
cat <<'EOF'
3438
Usage: scripts/aws-perf/ec2-compare.sh [options]
3539
3640
Options:
3741
--region <aws-region> AWS region (default: us-west-2)
42+
--cheapest-region Auto-select cheapest on-demand region for instance type
3843
--instance-profile <name> IAM instance profile with AmazonSSMManagedInstanceCore
3944
--instance-type <type> EC2 instance type (default: c6i.large)
4045
--subnet-id <subnet-id> Optional; auto-detected when omitted
@@ -50,6 +55,7 @@ Options:
5055
--threshold-pct <n> Throughput drop threshold for regression (default: 5)
5156
--ttl-hours <n> Auto-expiry tag horizon (default: 2)
5257
--output-dir <path> Local output directory
58+
--pricing-only Print pricing info and exit without launching EC2
5359
--on-demand Disable Spot and use on-demand instance
5460
--no-fallback-on-demand Do not retry on-demand when Spot launch fails
5561
--keep-instance Do not terminate instance after run
@@ -92,7 +98,111 @@ base64_encode_file() {
9298
fi
9399
}
94100

101+
pricing_script_path() {
102+
printf '%s/pricing-info.mjs' "$SCRIPT_DIR"
103+
}
104+
105+
resolve_cheapest_region() {
106+
local pricing_script="$1"
107+
local report_path="$2"
108+
109+
node "$pricing_script" --instance-type "$INSTANCE_TYPE" --region "$REGION" --cheapest >"$report_path"
110+
111+
local cheapest_region
112+
cheapest_region="$(node - "$report_path" <<'NODE'
113+
const fs = require("node:fs");
114+
const report = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
115+
process.stdout.write(report.cheapestOnDemand?.regionCode || "");
116+
NODE
117+
)"
118+
119+
[[ -n "$cheapest_region" ]] || fail "Unable to determine cheapest region for instance type: $INSTANCE_TYPE"
120+
121+
local cheapest_details
122+
cheapest_details="$(node - "$report_path" <<'NODE'
123+
const fs = require("node:fs");
124+
const report = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
125+
const cheapest = report.cheapestOnDemand;
126+
if (!cheapest) {
127+
process.exit(1);
128+
}
129+
const usd = Number(cheapest.usdPerHour);
130+
const rate = Number.isFinite(usd) ? usd.toFixed(4) : "n/a";
131+
process.stdout.write(`${cheapest.regionCode}|${cheapest.location || "unknown"}|${rate}`);
132+
NODE
133+
)"
134+
135+
local cheapest_code cheapest_location cheapest_rate
136+
IFS='|' read -r cheapest_code cheapest_location cheapest_rate <<<"$cheapest_details"
137+
log "Cheapest on-demand region for $INSTANCE_TYPE: $cheapest_code ($cheapest_location) at \$$cheapest_rate/hour"
138+
REGION="$cheapest_region"
139+
}
140+
141+
collect_pricing_report() {
142+
local pricing_script="$1"
143+
local report_path="$2"
144+
node "$pricing_script" --instance-type "$INSTANCE_TYPE" --region "$REGION" >"$report_path"
145+
}
146+
147+
merge_pricing_reports() {
148+
local selected_path="$1"
149+
local cheapest_path="$2"
150+
node - "$selected_path" "$cheapest_path" <<'NODE'
151+
const fs = require("node:fs");
152+
const selectedPath = process.argv[2];
153+
const cheapestPath = process.argv[3];
154+
155+
const selected = JSON.parse(fs.readFileSync(selectedPath, "utf8"));
156+
const cheapest = JSON.parse(fs.readFileSync(cheapestPath, "utf8"));
157+
158+
selected.cheapestOnDemand = cheapest.cheapestOnDemand || null;
159+
selected.topOnDemand = cheapest.topOnDemand || [];
160+
selected.regionCountSampled = cheapest.regionCountSampled || 0;
161+
162+
fs.writeFileSync(selectedPath, JSON.stringify(selected, null, 2));
163+
NODE
164+
}
165+
166+
print_pricing_summary() {
167+
local report_path="$1"
168+
while IFS= read -r line; do
169+
log "$line"
170+
done < <(
171+
node - "$report_path" <<'NODE'
172+
const fs = require("node:fs");
173+
const report = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
174+
175+
const onDemand = report.selectedOnDemand;
176+
if (onDemand) {
177+
console.log(
178+
`Pricing selected region: ${onDemand.regionCode} (${onDemand.location || "unknown"}) at $${Number(onDemand.usdPerHour).toFixed(4)}/hour on-demand`,
179+
);
180+
} else {
181+
console.log(`Pricing selected region: ${report.selectedRegion} (on-demand price unavailable)`);
182+
}
183+
184+
const spot = report.selectedSpot24h;
185+
if (spot) {
186+
console.log(
187+
`Spot (last ${spot.windowHours}h): avg $${Number(spot.avgUsdPerHour).toFixed(4)}/hour, min $${Number(spot.minUsdPerHour).toFixed(4)}, max $${Number(spot.maxUsdPerHour).toFixed(4)}, samples ${spot.samples}`,
188+
);
189+
}
190+
191+
if (report.cheapestOnDemand) {
192+
const c = report.cheapestOnDemand;
193+
console.log(
194+
`Cheapest on-demand sampled: ${c.regionCode} (${c.location || "unknown"}) at $${Number(c.usdPerHour).toFixed(4)}/hour`,
195+
);
196+
}
197+
NODE
198+
)
199+
}
200+
95201
cleanup() {
202+
if [[ -n "$CHEAPEST_REPORT_PATH" ]]; then
203+
rm -f "$CHEAPEST_REPORT_PATH" || true
204+
fi
205+
96206
if [[ -n "$PARAMS_FILE" ]]; then
97207
rm -f "$PARAMS_FILE" || true
98208
fi
@@ -107,6 +217,7 @@ trap cleanup EXIT INT TERM
107217
while [[ $# -gt 0 ]]; do
108218
case "$1" in
109219
--region) REGION="$2"; shift 2 ;;
220+
--cheapest-region) AUTO_CHEAPEST_REGION=true; shift ;;
110221
--instance-profile) INSTANCE_PROFILE_NAME="$2"; shift 2 ;;
111222
--instance-type) INSTANCE_TYPE="$2"; shift 2 ;;
112223
--subnet-id) SUBNET_ID="$2"; shift 2 ;;
@@ -122,6 +233,7 @@ while [[ $# -gt 0 ]]; do
122233
--threshold-pct) THRESHOLD_PCT="$2"; shift 2 ;;
123234
--ttl-hours) TTL_HOURS="$2"; shift 2 ;;
124235
--output-dir) OUTPUT_DIR="$2"; shift 2 ;;
236+
--pricing-only) PRICING_ONLY=true; shift ;;
125237
--on-demand) USE_SPOT=false; shift ;;
126238
--no-fallback-on-demand) FALLBACK_ON_DEMAND=false; shift ;;
127239
--keep-instance) SHOULD_CLEANUP=false; shift ;;
@@ -138,8 +250,38 @@ requires mktemp
138250
requires node
139251

140252
[[ -n "$REGION" ]] || fail "AWS region is required (use --region or set AWS_REGION)"
253+
[[ -n "$INSTANCE_TYPE" ]] || fail "Instance type is required"
254+
255+
if [[ "$AUTO_CHEAPEST_REGION" == true ]] && [[ -n "$SUBNET_ID" || -n "$SECURITY_GROUP_ID" ]]; then
256+
fail "--cheapest-region cannot be combined with --subnet-id or --security-group-id"
257+
fi
141258

142259
mkdir -p "$OUTPUT_DIR"
260+
PRICING_JSON="$OUTPUT_DIR/pricing.json"
261+
262+
PRICING_SCRIPT="$(pricing_script_path)"
263+
[[ -f "$PRICING_SCRIPT" ]] || fail "Missing pricing helper script: $PRICING_SCRIPT"
264+
265+
if [[ "$AUTO_CHEAPEST_REGION" == true ]]; then
266+
CHEAPEST_REPORT_PATH="$(mktemp)"
267+
log "Detecting cheapest on-demand region for instance type: $INSTANCE_TYPE"
268+
resolve_cheapest_region "$PRICING_SCRIPT" "$CHEAPEST_REPORT_PATH"
269+
fi
270+
271+
collect_pricing_report "$PRICING_SCRIPT" "$PRICING_JSON"
272+
273+
if [[ -n "$CHEAPEST_REPORT_PATH" ]]; then
274+
merge_pricing_reports "$PRICING_JSON" "$CHEAPEST_REPORT_PATH"
275+
rm -f "$CHEAPEST_REPORT_PATH" || true
276+
fi
277+
278+
print_pricing_summary "$PRICING_JSON"
279+
log "Pricing report: $PRICING_JSON"
280+
281+
if [[ "$PRICING_ONLY" == true ]]; then
282+
log "Pricing-only mode enabled; exiting without launching EC2"
283+
exit 0
284+
fi
143285

144286
log "Resolving refs"
145287
BASE_REF="$(git -C "$ROOT_DIR" rev-parse "$BASE_REF")"

0 commit comments

Comments
 (0)