🤖 AIGC disclosure: created with “AI-assisted generation + human testing”, published after community-terms review.
#!/usr/bin/env node
// cost-calc.js — bandwidth / traffic / 95th-percentile billing calculator (zero dependencies, Node 18+)
// Usage:
// node cost-calc.js traffic 100
// node cost-calc.js p95 samples.txt
// node cost-calc.js compare 85 2.5 "100:184.8,1000:1057.92"
"use strict";
const fs = require("fs");
const HOURS = 720; // a 30-day month
function traffic(portMbps) {
const fullTB = (portMbps * 0.125 * 3600 * HOURS) / 1000 / 1000; // Mbps→MB/s→GB→TB
return {
port_mbps: portMbps,
full_load_tb: +fullTB.toFixed(1),
at_10pct_tb: +(fullTB * 0.1).toFixed(1),
at_30pct_tb: +(fullTB * 0.3).toFixed(1),
at_50pct_tb: +(fullTB * 0.5).toFixed(1),
};
}
function p95(file) {
const xs = fs.readFileSync(file, "utf8").split(/\s+/)
.map(Number).filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
if (!xs.length) throw new Error("sample file is empty or not numeric");
const q = (p) => xs[Math.min(xs.length - 1, Math.floor(xs.length * p))];
return { n: xs.length, p50: q(0.5), p95: q(0.95), max: xs[xs.length - 1] };
}
function compare(p95Mbps, unit95, tiersStr) {
const tiers = tiersStr.split(",").map((t) => {
const [mbps, price] = t.split(":").map(Number);
return { mbps, price };
}).filter((t) => t.mbps >= p95Mbps).sort((a, b) => a.price - b.price);
const flat = tiers[0] || null; // smallest usable flat tier
const billing95 = p95Mbps * unit95; // 95th-percentile billing
const peakEst = p95Mbps * 1.5 * unit95; // rough peak-billing estimate (peak ≈ P95×1.5 rule of thumb)
const opts = [
{ mode: "95th-percentile", monthly: +billing95.toFixed(2) },
flat && { mode: `flat-port-${flat.mbps}M`, monthly: +flat.price.toFixed(2) },
{ mode: "peak-estimate", monthly: +peakEst.toFixed(2), note: "for reference, usually the most expensive" },
].filter(Boolean);
const best = opts.slice().sort((a, b) => a.monthly - b.monthly)[0];
return {
inputs: { p95_mbps: p95Mbps, unit95_per_mbps: unit95 },
options: opts,
recommendation: best.mode,
ratio_p95_over_peak: "P95/peak > 0.7 favours a flat port; < 0.4 favours 95th-percentile billing (see the bandwidth-market article)",
};
}
// ── Built-in self-test (node cost-calc.js test) ─────────────
function test() {
const assert = require("assert");
assert.strictEqual(traffic(100).full_load_tb, 32.4); // 100M at full load = 32.4 TB/month
assert.strictEqual(traffic(8).full_load_tb, 2.6);
const c = compare(85, 2.5, "100:184.8,1000:1057.92");
assert.strictEqual(c.options[0].monthly, 212.5); // 85×2.5
assert.strictEqual(c.recommendation, "flat-port-100M"); // 184.8 < 212.5
console.log("self-test ok");
}
if (require.main === module) {
const [cmd, ...args] = process.argv.slice(2);
const out =
cmd === "traffic" ? traffic(+args[0]) :
cmd === "p95" ? p95(args[0]) :
cmd === "compare" ? compare(+args[0], +args[1], args[2]) :
cmd === "test" ? test() :
{ error: "usage: traffic <Mbps> | p95 <file> | compare <P95> <unit price> <tier list> | test" };
if (out !== undefined) console.log(JSON.stringify(out, null, 1));
} else {
module.exports = { traffic, p95, compare };
}
──────────────────────
[Test log · fixed version] `node cost-calc.js test` now passes: traffic(100) = 32.4 TB, matching the hand calculation; the compare assertions hold (85 Mbps × $2.5 = $212.5 > the $184.8 flat tier → flat port recommended).
[What was fixed] The TB conversion divided by 1000 once instead of twice, so every traffic figure was 1000× too large (100 Mbps full load printed 32400 "TB" instead of 32.4 TB) and the built-in self-test failed. The fix is `/ 1000 / 1000` (Mbps → MB/s → GB → TB); no other line changed.
[Where the samples come from] A cron job writes one line every 5 minutes with the interface rate (vnstat, or a diff of /proc/net/dev); a week gives you 2,016 samples — after that, P95 is no longer a guess.
[Scope limits] The ×1.5 in peak-estimate is a rule of thumb, not a promise; the sampling granularity (5 minutes) and the exempted percentage (5%) of 95th-percentile billing follow the provider's contract.