Merge pull request #2373 from MartinFournier/feature/mem-verbose

Add detailed ram cost to mem command
This commit is contained in:
hydroflame 2022-01-06 11:25:41 -05:00 committed by GitHub
commit c722ff5982
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 55 additions and 22 deletions

@ -16,6 +16,17 @@ import { WorkerScript } from "../Netscript/WorkerScript";
import { areImportsEquals } from "../Terminal/DirectoryHelpers"; import { areImportsEquals } from "../Terminal/DirectoryHelpers";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
export interface RamUsageEntry {
type: 'ns' | 'dom' | 'fn' | 'misc';
name: string;
cost: number;
}
export interface RamCalculation {
cost: number;
entries?: RamUsageEntry[];
}
// These special strings are used to reference the presence of a given logical // These special strings are used to reference the presence of a given logical
// construct within a user script. // construct within a user script.
const specialReferenceIF = "__SPECIAL_referenceIf"; const specialReferenceIF = "__SPECIAL_referenceIf";
@ -38,7 +49,7 @@ async function parseOnlyRamCalculate(
otherScripts: Script[], otherScripts: Script[],
code: string, code: string,
workerScript: WorkerScript, workerScript: WorkerScript,
): Promise<number | RamCalculationErrorCode> { ): Promise<RamCalculation> {
try { try {
/** /**
* Maps dependent identifiers to their dependencies. * Maps dependent identifiers to their dependencies.
@ -98,12 +109,12 @@ async function parseOnlyRamCalculate(
} }
} catch (e) { } catch (e) {
console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`); console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`);
return RamCalculationErrorCode.URLImportError; return { cost: RamCalculationErrorCode.URLImportError };
} }
} else { } else {
if (!Array.isArray(otherScripts)) { if (!Array.isArray(otherScripts)) {
console.warn(`parseOnlyRamCalculate() not called with array of scripts`); console.warn(`parseOnlyRamCalculate() not called with array of scripts`);
return RamCalculationErrorCode.ImportError; return { cost: RamCalculationErrorCode.ImportError };
} }
let script = null; let script = null;
@ -116,7 +127,7 @@ async function parseOnlyRamCalculate(
} }
if (script == null) { if (script == null) {
return RamCalculationErrorCode.ImportError; // No such script on the server return { cost: RamCalculationErrorCode.ImportError }; // No such script on the server
} }
code = script.code; code = script.code;
@ -128,6 +139,7 @@ async function parseOnlyRamCalculate(
// Finally, walk the reference map and generate a ram cost. The initial set of keys to scan // Finally, walk the reference map and generate a ram cost. The initial set of keys to scan
// are those that start with __SPECIAL_INITIAL_MODULE__. // are those that start with __SPECIAL_INITIAL_MODULE__.
let ram = RamCostConstants.ScriptBaseRamCost; let ram = RamCostConstants.ScriptBaseRamCost;
const detailedCosts: RamUsageEntry[] = [{ type: 'misc', name: 'baseCost', cost: RamCostConstants.ScriptBaseRamCost}];
const unresolvedRefs = Object.keys(dependencyMap).filter((s) => s.startsWith(initialModule)); const unresolvedRefs = Object.keys(dependencyMap).filter((s) => s.startsWith(initialModule));
const resolvedRefs = new Set(); const resolvedRefs = new Set();
while (unresolvedRefs.length > 0) { while (unresolvedRefs.length > 0) {
@ -137,15 +149,19 @@ async function parseOnlyRamCalculate(
// Check if this is one of the special keys, and add the appropriate ram cost if so. // Check if this is one of the special keys, and add the appropriate ram cost if so.
if (ref === "hacknet" && !resolvedRefs.has("hacknet")) { if (ref === "hacknet" && !resolvedRefs.has("hacknet")) {
ram += RamCostConstants.ScriptHacknetNodesRamCost; ram += RamCostConstants.ScriptHacknetNodesRamCost;
detailedCosts.push({ type: 'ns', name: 'hacknet', cost: RamCostConstants.ScriptHacknetNodesRamCost});
} }
if (ref === "document" && !resolvedRefs.has("document")) { if (ref === "document" && !resolvedRefs.has("document")) {
ram += RamCostConstants.ScriptDomRamCost; ram += RamCostConstants.ScriptDomRamCost;
detailedCosts.push({ type: 'dom', name: 'document', cost: RamCostConstants.ScriptDomRamCost});
} }
if (ref === "window" && !resolvedRefs.has("window")) { if (ref === "window" && !resolvedRefs.has("window")) {
ram += RamCostConstants.ScriptDomRamCost; ram += RamCostConstants.ScriptDomRamCost;
detailedCosts.push({ type: 'dom', name: 'window', cost: RamCostConstants.ScriptDomRamCost});
} }
if (ref === "corporation" && !resolvedRefs.has("corporation")) { if (ref === "corporation" && !resolvedRefs.has("corporation")) {
ram += RamCostConstants.ScriptCorporationRamCost; ram += RamCostConstants.ScriptCorporationRamCost;
detailedCosts.push({ type: 'ns', name: 'corporation', cost: RamCostConstants.ScriptCorporationRamCost});
} }
resolvedRefs.add(ref); resolvedRefs.add(ref);
@ -187,34 +203,45 @@ async function parseOnlyRamCalculate(
// This accounts for namespaces (Bladeburner, CodingCpntract, etc.) // This accounts for namespaces (Bladeburner, CodingCpntract, etc.)
let func; let func;
let refDetail = 'n/a';
if (ref in workerScript.env.vars.bladeburner) { if (ref in workerScript.env.vars.bladeburner) {
func = workerScript.env.vars.bladeburner[ref]; func = workerScript.env.vars.bladeburner[ref];
refDetail = `bladeburner.${ref}`;
} else if (ref in workerScript.env.vars.codingcontract) { } else if (ref in workerScript.env.vars.codingcontract) {
func = workerScript.env.vars.codingcontract[ref]; func = workerScript.env.vars.codingcontract[ref];
refDetail = `codingcontract.${ref}`;
} else if (ref in workerScript.env.vars.stanek) { } else if (ref in workerScript.env.vars.stanek) {
func = workerScript.env.vars.stanek[ref]; func = workerScript.env.vars.stanek[ref];
refDetail = `stanek.${ref}`;
} else if (ref in workerScript.env.vars.gang) { } else if (ref in workerScript.env.vars.gang) {
func = workerScript.env.vars.gang[ref]; func = workerScript.env.vars.gang[ref];
refDetail = `gang.${ref}`;
} else if (ref in workerScript.env.vars.sleeve) { } else if (ref in workerScript.env.vars.sleeve) {
func = workerScript.env.vars.sleeve[ref]; func = workerScript.env.vars.sleeve[ref];
refDetail = `sleeve.${ref}`;
} else if (ref in workerScript.env.vars.stock) { } else if (ref in workerScript.env.vars.stock) {
func = workerScript.env.vars.stock[ref]; func = workerScript.env.vars.stock[ref];
refDetail = `stock.${ref}`;
} else if (ref in workerScript.env.vars.ui) { } else if (ref in workerScript.env.vars.ui) {
func = workerScript.env.vars.ui[ref]; func = workerScript.env.vars.ui[ref];
refDetail = `ui.${ref}`;
} else { } else {
func = workerScript.env.vars[ref]; func = workerScript.env.vars[ref];
refDetail = `${ref}`;
} }
ram += applyFuncRam(func); const fnRam = applyFuncRam(func);
ram += fnRam;
detailedCosts.push({ type: 'fn', name: refDetail, cost: fnRam});
} catch (error) { } catch (error) {
continue; continue;
} }
} }
return ram; return { cost: ram, entries: detailedCosts.filter(e => e.cost > 0) };
} catch (error) { } catch (error) {
// console.info("parse or eval error: ", error); // console.info("parse or eval error: ", error);
// This is not unexpected. The user may be editing a script, and it may be in // This is not unexpected. The user may be editing a script, and it may be in
// a transitory invalid state. // a transitory invalid state.
return RamCalculationErrorCode.SyntaxError; return { cost: RamCalculationErrorCode.SyntaxError };
} }
} }
@ -382,7 +409,7 @@ export async function calculateRamUsage(
player: IPlayer, player: IPlayer,
codeCopy: string, codeCopy: string,
otherScripts: Script[], otherScripts: Script[],
): Promise<RamCalculationErrorCode | number> { ): Promise<RamCalculation> {
// We don't need a real WorkerScript for this. Just an object that keeps // We don't need a real WorkerScript for this. Just an object that keeps
// track of whatever's needed for RAM calculations // track of whatever's needed for RAM calculations
const workerScript = { const workerScript = {
@ -397,8 +424,8 @@ export async function calculateRamUsage(
} catch (e) { } catch (e) {
console.error(`Failed to parse script for RAM calculations:`); console.error(`Failed to parse script for RAM calculations:`);
console.error(e); console.error(e);
return RamCalculationErrorCode.SyntaxError; return { cost: RamCalculationErrorCode.SyntaxError };
} }
return RamCalculationErrorCode.SyntaxError; return { cost: RamCalculationErrorCode.SyntaxError };
} }

@ -4,7 +4,7 @@
* This does NOT represent a script that is actively running and * This does NOT represent a script that is actively running and
* being evaluated. See RunningScript for that * being evaluated. See RunningScript for that
*/ */
import { calculateRamUsage } from "./RamCalculations"; import { calculateRamUsage, RamUsageEntry } from "./RamCalculations";
import { ScriptUrl } from "./ScriptUrl"; import { ScriptUrl } from "./ScriptUrl";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
@ -38,6 +38,7 @@ export class Script {
// Amount of RAM this Script requres to run // Amount of RAM this Script requres to run
ramUsage = 0; ramUsage = 0;
ramUsageEntries?: RamUsageEntry[];
// hostname of server that this script is on. // hostname of server that this script is on.
server = ""; server = "";
@ -131,8 +132,9 @@ export class Script {
*/ */
async updateRamUsage(player: IPlayer, otherScripts: Script[]): Promise<void> { async updateRamUsage(player: IPlayer, otherScripts: Script[]): Promise<void> {
const res = await calculateRamUsage(player, this.code, otherScripts); const res = await calculateRamUsage(player, this.code, otherScripts);
if (res > 0) { if (res.cost > 0) {
this.ramUsage = roundToTwo(res); this.ramUsage = roundToTwo(res.cost);
this.ramUsageEntries = res.entries;
} }
this.markUpdated(); this.markUpdated();
} }

@ -227,11 +227,11 @@ export function Root(props: IProps): React.ReactElement {
setUpdatingRam(true); setUpdatingRam(true);
const codeCopy = newCode + ""; const codeCopy = newCode + "";
const ramUsage = await calculateRamUsage(props.player, codeCopy, props.player.getCurrentServer().scripts); const ramUsage = await calculateRamUsage(props.player, codeCopy, props.player.getCurrentServer().scripts);
if (ramUsage > 0) { if (ramUsage.cost > 0) {
debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage)); debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage.cost));
return; return;
} }
switch (ramUsage) { switch (ramUsage.cost) {
case RamCalculationErrorCode.ImportError: { case RamCalculationErrorCode.ImportError: {
debouncedSetRAM("RAM: Import Error"); debouncedSetRAM("RAM: Import Error");
break; break;

@ -38,6 +38,11 @@ export function mem(
terminal.print( terminal.print(
`This script requires ${numeralWrapper.formatRAM(ramUsage)} of RAM to run for ${numThreads} thread(s)`, `This script requires ${numeralWrapper.formatRAM(ramUsage)} of RAM to run for ${numThreads} thread(s)`,
); );
const verboseEntries = script.ramUsageEntries?.sort((a, b) => b.cost - a.cost) ?? [];
for (const entry of verboseEntries) {
terminal.print(`${numeralWrapper.formatRAM(entry.cost * numThreads).padStart(8)} | ${entry.name} (${entry.type})`);
}
} catch (e) { } catch (e) {
terminal.error(e + ""); terminal.error(e + "");
} }

@ -36,11 +36,11 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () {
const code = fnDesc.join(".") + "(); "; const code = fnDesc.join(".") + "(); ";
const calculated = await calculateRamUsage(Player, code, []); const calculated = (await calculateRamUsage(Player, code, [])).cost;
testEquality(calculated, expected + ScriptBaseCost); testEquality(calculated, expected + ScriptBaseCost);
const multipleCallsCode = code.repeat(3); const multipleCallsCode = code.repeat(3);
const multipleCallsCalculated = await calculateRamUsage(Player, multipleCallsCode, []); const multipleCallsCalculated = (await calculateRamUsage(Player, multipleCallsCode, [])).cost;
expect(multipleCallsCalculated).toEqual(calculated); expect(multipleCallsCalculated).toEqual(calculated);
} }
@ -60,11 +60,10 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () {
expect(expected).toEqual(0); expect(expected).toEqual(0);
const code = fnDesc.join(".") + "(); "; const code = fnDesc.join(".") + "(); ";
const calculated = (await calculateRamUsage(Player, code, [])).cost;
const calculated = await calculateRamUsage(Player, code, []);
testEquality(calculated, ScriptBaseCost); testEquality(calculated, ScriptBaseCost);
const multipleCallsCalculated = await calculateRamUsage(Player, code, []); const multipleCallsCalculated = (await calculateRamUsage(Player, code, [])).cost;
expect(multipleCallsCalculated).toEqual(ScriptBaseCost); expect(multipleCallsCalculated).toEqual(ScriptBaseCost);
} }
@ -511,7 +510,7 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () {
} }
const calculated = await calculateRamUsage(Player, code, []); const calculated = await calculateRamUsage(Player, code, []);
testEquality(calculated, ScriptBaseCost + HacknetNamespaceCost); testEquality(calculated.cost, ScriptBaseCost + HacknetNamespaceCost);
}); });
}); });