import fs from "fs"; import assert from "assert"; import readline from "readline"; import { program } from "commander"; import glob from "fast-glob"; import format from "date-fns/format/index.js"; import asciichart from "asciichart"; program .option("--min ", "The lowest count to print. Stop at this.", 1) .option("--errors", "Show the errors so you can fix them.", false) .option("--outfile ", "Save to file rather than stdout.") .option("--max-days ", "Only display charts for entries with at least this many days.", 10) .option("--height ", "Height of the ascii graph.", 5) .requiredOption("--input ", "Input file glob.") .description("Loads the output of a chain parser and produces a report.") .version(0.1); program.parse(); const OPTS = program.opts(); OPTS.min = parseInt(OPTS.min); assert(!isNaN(OPTS.min), `min must be a number, you have ${OPTS.min}`); OPTS.height = parseInt(OPTS.height); assert(!isNaN(OPTS.height), `height must be a number, you have ${OPTS.height}`); const sort_request_chains = (chains, min) => { const converted = []; for(let [url, stats] of Object.entries(chains)) { if(stats.count < min) continue; // skip below min if(stats.comes_from) { converted.push([stats.count, `[${stats.comes_from}] ${stats.full_chain.join(' ')}`]); } else { converted.push([stats.count, `${stats.full_chain.join(' ')}`]); } } return converted.sort((a, b) => b[0] - a[0]); } const { stats, domain, result, generated_on} = JSON.parse(fs.readFileSync(OPTS.input)); const sorted = []; const keys = Object.keys(result).sort(); const by_chain = {}; for(let key of keys) { const chain = sort_request_chains(result[key], OPTS.min); for(let [count, urls] of chain) { const series = by_chain[urls] || []; series.push(count); by_chain[urls] = series; } } console.log("FROM", keys[0], "TO", keys[keys.length - 1]); for(let [chain, counts] of Object.entries(by_chain)) { if(counts.length > OPTS.maxDays) { console.log(chain); console.log(asciichart.plot(counts, {height: OPTS.height})); } }