@@ -13,6 +13,8 @@ DEFAULT_REGION="us-west-2"
1313REGION=" ${AWS_REGION:- ${AWS_DEFAULT_REGION:- $DEFAULT_REGION } } "
1414INSTANCE_PROFILE_NAME=" wreq-js-perf-ssm-profile"
1515INSTANCE_TYPE=" c6i.large"
16+ AUTO_CHEAPEST_REGION=false
17+ PRICING_ONLY=false
1618USE_SPOT=true
1719FALLBACK_ON_DEMAND=true
1820SUBNET_ID=" "
@@ -28,13 +30,16 @@ WARMUP=2
2830CONCURRENCY=8
2931SCENARIOS=" wreq.session.get.small;wreq.transport.get.small;wreq.session.get.4kb;wreq.session.post.32b;wreq.isolated.get.small;node.fetch.get.small"
3032OUTPUT_DIR=" $ROOT_DIR /tmp/aws-perf/$RUN_ID "
33+ PRICING_JSON=" "
34+ CHEAPEST_REPORT_PATH=" "
3135
3236usage () {
3337 cat << 'EOF '
3438Usage: scripts/aws-perf/ec2-compare.sh [options]
3539
3640Options:
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+
95201cleanup () {
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
107217while [[ $# -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
138250requires 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
142259mkdir -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
144286log " Resolving refs"
145287BASE_REF=" $( git -C " $ROOT_DIR " rev-parse " $BASE_REF " ) "
0 commit comments