diff --git a/packages/agoric-cli/package.json b/packages/agoric-cli/package.json index 5786b3594f5..24b4f910ad6 100644 --- a/packages/agoric-cli/package.json +++ b/packages/agoric-cli/package.json @@ -69,6 +69,7 @@ "@endo/patterns": "^1.4.0", "@endo/promise-kit": "^1.1.2", "@iarna/toml": "^2.2.3", + "@zip.js/zip.js": "^2.7.45", "anylogger": "^0.21.0", "chalk": "^5.2.0", "commander": "^11.1.0", diff --git a/packages/agoric-cli/scripts/stat-bundle.js b/packages/agoric-cli/scripts/stat-bundle.js new file mode 100755 index 00000000000..43de05222f7 --- /dev/null +++ b/packages/agoric-cli/scripts/stat-bundle.js @@ -0,0 +1,9 @@ +#!/usr/bin/env node +import assert from 'node:assert'; +import process from 'node:process'; +import { statBundle } from '../src/lib/bundles.js'; + +const filename = process.argv[2]; +assert(filename, 'usage: stat-bundle.js '); + +await statBundle(filename); diff --git a/packages/agoric-cli/scripts/stat-plans.js b/packages/agoric-cli/scripts/stat-plans.js new file mode 100755 index 00000000000..34382985fba --- /dev/null +++ b/packages/agoric-cli/scripts/stat-plans.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node +import process from 'node:process'; +import { statPlans } from '../src/lib/bundles.js'; + +await statPlans(process.cwd()); diff --git a/packages/agoric-cli/src/lib/bundles.js b/packages/agoric-cli/src/lib/bundles.js new file mode 100644 index 00000000000..d2519e26331 --- /dev/null +++ b/packages/agoric-cli/src/lib/bundles.js @@ -0,0 +1,89 @@ +/* global Buffer */ + +import { BlobReader, TextWriter, ZipReader } from '@zip.js/zip.js'; // @endo/zip was tried but doesn't provide compressedSize +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import { join } from 'node:path'; + +/** @import {Bundle} from '@agoric/swingset-vat'; */ +/** @import {CoreEvalPlan} from '@agoric/deploy-script-support/src/writeCoreEvalParts.js' */ + +const PACKAGE_NAME_RE = /(?.*-v[\d.]+)\//; + +/** + * @typedef CompartmentMap + * @property {string[]} tags + * @property {{compartment: string, module: string}} entry + * @property {Record}} compartments + */ + +/** @param {Bundle} bundleObj*/ +export const extractBundleInfo = async bundleObj => { + await null; + if (bundleObj.moduleFormat === 'endoZipBase64') { + const contents = Buffer.from(bundleObj.endoZipBase64, 'base64'); + const zipBlob = new Blob([contents], { type: 'application/zip' }); + + const zipFileReader = new BlobReader(zipBlob); + const writer = new TextWriter('utf-8'); + + const zipReader = new ZipReader(zipFileReader); + const entries = await zipReader.getEntries(); + await zipReader.close(); + + assert(entries[0].filename, 'compartment-map.json'); + const cmapStr = await entries[0].getData(writer); + /** @type {CompartmentMap} */ + const compartmentMap = JSON.parse(cmapStr); + + const fileSizes = Object.fromEntries( + entries.map(e => [e.filename, e.compressedSize]), + ); + + return { compartmentMap, fileSizes }; + } +}; + +// UNTIL https://github.com/endojs/endo/issues/1656 +/** @param {string} bundleFilename */ +export const statBundle = async bundleFilename => { + const bundle = fs.readFileSync(bundleFilename, 'utf8'); + /** @type {Bundle} */ + const bundleObj = JSON.parse(bundle); + console.log('\nBUNDLE', bundleObj.moduleFormat, bundleFilename); + + const info = await extractBundleInfo(bundleObj); + + /** @type {Record} */ + const byPackage = {}; + let totalSize = 0; + for (const [filename, size] of Object.entries(info.fileSizes)) { + totalSize += size; + if (filename === 'compartment-map.json') { + continue; + } + const { packageName } = filename.match(PACKAGE_NAME_RE).groups; + byPackage[packageName] ||= 0; + byPackage[packageName] += size; + } + + console.log('Sum of compressed file sizes in each package:'); + console.table(byPackage); + + console.log('total size:', totalSize); +}; + +/** @param {string} path */ +export const statPlans = async path => { + const files = await fs.promises.readdir(path); + const planfiles = files.filter(f => f.endsWith('plan.json')); + + for (const planfile of planfiles) { + /** @type {CoreEvalPlan} */ + const plan = JSON.parse(fs.readFileSync(join(path, planfile), 'utf8')); + console.log('\n**\nPLAN', plan.name); + for (const bundle of plan.bundles) { + await statBundle(bundle.fileName); + } + } +}; diff --git a/yarn.lock b/yarn.lock index 2f68ae27e00..508f05a21f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3785,6 +3785,11 @@ js-yaml "^3.10.0" tslib "^2.4.0" +"@zip.js/zip.js@^2.7.45": + version "2.7.45" + resolved "https://registry.yarnpkg.com/@zip.js/zip.js/-/zip.js-2.7.45.tgz#823fe2789401d8c1d836ce866578379ec1bd6f0b" + integrity sha512-Mm2EXF33DJQ/3GWWEWeP1UCqzpQ5+fiMvT3QWspsXY05DyqqxWu7a9awSzU4/spHMHVFrTjani1PR0vprgZpow== + "@zkochan/js-yaml@0.0.6": version "0.0.6" resolved "https://registry.yarnpkg.com/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz#975f0b306e705e28b8068a07737fa46d3fc04826"