diff --git a/skymp5-client/src/index.ts b/skymp5-client/src/index.ts index 397c322823..e47a33e233 100644 --- a/skymp5-client/src/index.ts +++ b/skymp5-client/src/index.ts @@ -50,6 +50,7 @@ import { BlockedAnimationsService } from "./services/services/blockedAnimationsS import { WorldView } from "./view/worldView"; import { KeyboardEventsService } from "./services/services/keyboardEventsService"; import { MagicSyncService } from "./services/services/magicSyncService"; +import { ProfilingService } from "./services/services/profilingService"; once("update", () => { Utility.setINIBool("bAlwaysActive:General", true); @@ -103,7 +104,8 @@ const main = () => { new BlockedAnimationsService(sp, controller), new WorldView(sp, controller), new KeyboardEventsService(sp, controller), - new MagicSyncService(sp, controller) + new MagicSyncService(sp, controller), + new ProfilingService(sp, controller) ]; SpApiInteractor.setup(listeners); } diff --git a/skymp5-client/src/services/services/profilingService.ts b/skymp5-client/src/services/services/profilingService.ts new file mode 100644 index 0000000000..9f2a9c320d --- /dev/null +++ b/skymp5-client/src/services/services/profilingService.ts @@ -0,0 +1,76 @@ +import { logTrace } from "../../logging"; +import { ClientListener, CombinedController, Sp } from "./clientListener"; +import { Session } from 'inspector'; +import * as fs from "fs"; + +export class ProfilingService extends ClientListener { + constructor(private sp: Sp, private controller: CombinedController) { + super(); + const settings = sp.settings["skymp5-client"]; + + if (!settings["enableProfiling"]) { + logTrace(this, "ProfilingService: disabled"); + return; + } + + const profilingDurationMs = this.getInteger(settings["profilingDurationMs"]) || 10000; + + const session = new Session(); + session.connect(); + + logTrace(this, "ProfilingService: start"); + + this.startProfiling(session, profilingDurationMs); + } + + private async startProfiling(session: Session, profilingDurationMs: number) { + await new Promise((resolve, reject) => { + session.post('Profiler.enable', (err: Error | null) => { + if (err) { + reject(err); + } + else { + resolve(undefined); + } + }); + }); + + await new Promise((resolve, reject) => { + session.post('Profiler.start', (err: Error | null) => { + if (err) { + reject(err); + } + else { + resolve(undefined); + } + }); + }); + + await new Promise((resolve) => { + setTimeout(resolve, profilingDurationMs); + }); + + logTrace(this, "ProfilingService: stop"); + + const { profile } = await new Promise((resolve, reject) => { + session.post('Profiler.stop', (err: Error | null, res) => { + if (err) { + reject(err); + } else { + resolve(res); + } + }); + }); + + fs.writeFileSync('./profile.cpuprofile', JSON.stringify(profile)); + } + + private getInteger(value: unknown) { + if (typeof value === 'number') { + if (Number.isInteger(value)) { + return value; + } + } + return undefined; + } +} diff --git a/skymp5-client/webpack.config.js b/skymp5-client/webpack.config.js index 580529d928..f68daa09cb 100644 --- a/skymp5-client/webpack.config.js +++ b/skymp5-client/webpack.config.js @@ -61,6 +61,7 @@ if (process.env['DEPLOY_PLUGIN']?.includes('true')) { } module.exports = { + target: "node", plugins, mode: 'development', devtool: 'inline-source-map',