UNPKG

mc-benchmark

Version:

Build charts about load time of Minecraft modpack.

158 lines 6.86 kB
/** * @file Collect information about load time from Debug.log * and output it in .MD file * * @author Krutoy242 * @link https://github.com/Krutoy242 */ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import process from 'node:process'; import chalkWeak from 'chalk'; import { consola } from 'consola'; import { compose } from './hbs.js'; import { getFmlStuff, getJeiPlugins, getMcLoadTime, getMods, getTimeline, loaderSteps } from './parse.js'; import { columnSumm, secondsToMinutes, sum } from './utils.js'; const chalk = chalkWeak.constructor({ level: process.stderr.isTTY ? 3 : 0 }); //############################################################################ //############################################################################ export default async function parseDebugLog(_options) { const options = { mkdirSync, readFileSync, writeFileSync, ..._options, }; const log = consola; async function loadText(fpath, onError) { log.start(`Opening file "${fpath}"`); let fileContent; try { fileContent = options.readFileSync(resolve(options.cwd ?? './', fpath), 'utf8'); } catch (err) { await onError(err, fpath); return undefined; } return fileContent; } function saveText(txt, filename) { options.mkdirSync(dirname(filename), { recursive: true }); options.writeFileSync(filename, txt); } //############################################################################ // Numbers const debug_log = await loadText(options.input, (err, fpath) => log.error(`Can't open file "${fpath}".\n${err}`)); if (!debug_log) return; const debug_lines = debug_log.split('\n'); const crafttweaker_log = await loadText(options.ctlog, (_err, fpath) => log.warn(`Can't open file "${fpath}". Use option "--ctlog=path/to/crafttweker.log"`)); const mods = await getMods(debug_log, debug_lines, crafttweaker_log); if (Object.keys(mods).length === 0) { if (!debug_log.match(/\[main\/DEBUG\] \[FML\]/)) { return log.error(`The file "${options.input}" does not contain` + ` rich debugging information. It is most likely not actual debug.log.` + `\n\nHint:\nSome MC launchers disable generating of debug.log file by default.` + ` Find out how to enable it.`); } else { return log.error(`The file "${options.input}" not full.` + ` Your Minecraft not loaded completely or file is corrupted.`); } } const pie = []; for (const [name, mod] of Object.entries(mods).slice(0, options.detailed)) { const totalModTime = sum(mod.steps) + (mod.parts ?? []).reduce((acc, p) => acc + p.time, 0); let mainModTime = sum(mod.steps); const validParts = []; (mod.parts ?? []).forEach((part) => { if (part.time >= totalModTime * 0.15) { validParts.push(part); } else { mainModTime += part.time; } }); const modSlice = { name, color: mod.color, time: mainModTime }; pie.push(modSlice); validParts.forEach(part => pie.push(part)); } const totalTimes = Object.values(mods).map(m => sum(m.steps)).slice(options.detailed); async function piePush(color, text, filter) { const otherMods = totalTimes.filter(filter); if (otherMods.length) { pie.push({ name: `${otherMods.length} ${text}`, color, time: sum(otherMods), }); } else { log.info(`Cannot make pie section ` + `"${chalk.hex('007777')(text)}" since no mods found at all`); } } await piePush('444444', `Other mods`, t => t > 1.0); await piePush('333333', `'Fast' mods (1.0s - 0.1s)`, t => t >= 0.1 && t <= 1.0); await piePush('222222', `'Instant' mods (%3C 0.1s)`, t => t < 0.1); const mcLoadTime = getMcLoadTime(debug_log); const modsTime = sum(pie.map(o => o.time)); const fmlStuff = getFmlStuff(debug_log); const loaderStuffTime = mcLoadTime - modsTime; // Destinct "Other" section of chart const otherFmlStuffTime = loaderStuffTime - sum(fmlStuff.map(part => part.time)); if (otherFmlStuffTime > 0) fmlStuff.push({ color: '444444', name: 'Other', time: otherFmlStuffTime }); const timeline = getTimeline(debug_log, debug_lines); // Remove FML steps without time const filteredLoaderSteps = Object.entries(mods) .filter(([name]) => !name.match(/Just Enough Items|Had Enough Items/)); const sumLoaderSteps = columnSumm(filteredLoaderSteps.map(([, { steps }]) => steps)); const fmlStepFilter = sumLoaderSteps.map(n => n > 0); const removeUnusedSteps = (arr) => arr.filter((_, i) => fmlStepFilter[i]); filteredLoaderSteps.forEach(([, mod]) => mod.steps = removeUnusedSteps(mod.steps)); const data = { modpackName: options.modpack, mcLoadTime, mcLoadTimeMin: secondsToMinutes(mcLoadTime), loadingTimeline: timeline, modLoadingTime: pie, loaderStepNames: removeUnusedSteps(Object.keys(loaderSteps)), loaderSteps: Object.fromEntries(filteredLoaderSteps.map(([n, m]) => [n, m.steps]) .concat([[ '[Mod Average]', sumLoaderSteps .map(n => n / Math.max(1, filteredLoaderSteps.filter(([, m]) => sum(m.steps)).length)) .filter(Boolean), ]])), jeiPlugins: getJeiPlugins(debug_log), fmlStuff: { total: loaderStuffTime, list: fmlStuff, }, }; log.start('Composing output'); if (options.data) { log.start('Writing file'); const dataJson = JSON.stringify(data, null, 2) // prettify numerical arrays // eslint-disable-next-line regexp/no-super-linear-backtracking .replace(/\[(\s*\d+(\.\d+)?,?\s+)+\]/g, m => m.replace(/\s+/g, '')); try { saveText(dataJson, options.data); } catch (error) { log.error(`Can't save output file "${options.data}". Use option "--data=path/to/data.json"\n\n${error}`); } } let composed = await compose(data, options.template); if (options.nospaces) { composed = composed .replace(/(<img src="https:\/\/quickchart\.io\/chart.+?=\{)([\s\S]+?)(\}\s*"\s*\/\s*>)/g, (_, a, b, c) => a + b .replace(/\s*\n\s*/g, ' ') .replace(/\s+/g, '%20') + c); } process.stdout.write(composed); log.ready(`Load Time total: ${mcLoadTime}`); } //# sourceMappingURL=index.js.map