diff --git a/packages/stats/src/Core.ts b/packages/stats/src/Core.ts index b7e8b8da..98989656 100644 --- a/packages/stats/src/Core.ts +++ b/packages/stats/src/Core.ts @@ -1,4 +1,5 @@ import DrawCallHook from "./hooks/DrawCallHook"; +import { RequestHook } from "./hooks/RequestHook"; import ShaderHook from "./hooks/ShaderHook"; import TextureHook from "./hooks/TextureHook"; @@ -16,6 +17,7 @@ export class Core { private drawCallHook: DrawCallHook; private textureHook: TextureHook; private shaderHook: ShaderHook; + private requestHook: RequestHook; private samplingFrames: number = 60; private samplingIndex: number = 0; private updateCounter: number = 0; @@ -30,6 +32,7 @@ export class Core { this.drawCallHook = new DrawCallHook(gl); this.textureHook = new TextureHook(gl); this.shaderHook = new ShaderHook(gl); + this.requestHook = new RequestHook(); } /** @@ -68,15 +71,21 @@ export class Core { let data: PerformanceData = { fps: Math.round((this.updateCounter * 1000) / (now - this.updateTime)), - memory: performance.memory && (performance.memory.usedJSHeapSize / 1048576) >> 0, + memory: + performance.memory && + (performance.memory.usedJSHeapSize / 1048576) >> 0, drawCall: this.drawCallHook.drawCall, triangles: this.drawCallHook.triangles, lines: this.drawCallHook.lines, points: this.drawCallHook.points, textures: this.textureHook.textures, + size: this.requestHook.size, shaders: this.shaderHook.shaders, webglContext: - window.hasOwnProperty("WebGL2RenderingContext") && this.gl instanceof WebGL2RenderingContext ? "2.0" : "1.0" + window.hasOwnProperty("WebGL2RenderingContext") && + this.gl instanceof WebGL2RenderingContext + ? "2.0" + : "1.0", }; this.reset(); @@ -97,5 +106,6 @@ interface PerformanceData { points: number; textures: number; shaders: number; + size: string; webglContext: string; } diff --git a/packages/stats/src/Monitor.ts b/packages/stats/src/Monitor.ts index 80996cf7..dab79e3e 100644 --- a/packages/stats/src/Monitor.ts +++ b/packages/stats/src/Monitor.ts @@ -14,6 +14,8 @@ let tpl = `
0
Shaders
0
+
Network Size (MB)
+
0
WebGL
@@ -62,7 +64,16 @@ export default class Monitor { constructor(gl: WebGLRenderingContext | WebGL2RenderingContext) { this.core = new Core(gl); this.items = []; - this.items = ["fps", "memory", "drawCall", "triangles", "textures", "shaders", "webglContext"]; + this.items = [ + "fps", + "memory", + "drawCall", + "triangles", + "textures", + "shaders", + "size", + "webglContext", + ]; this.createContainer(); this.update = this.update.bind(this); } diff --git a/packages/stats/src/hooks/RequestHook.ts b/packages/stats/src/hooks/RequestHook.ts new file mode 100644 index 00000000..41cc133a --- /dev/null +++ b/packages/stats/src/hooks/RequestHook.ts @@ -0,0 +1,90 @@ +let requestSize = 0; + +let originalSend = XMLHttpRequest.prototype.send; + +const cacheMap = new Map(); +function addRequestSize(url: string, size: number) { + if (cacheMap.get(url) == undefined) { + cacheMap.set(url, size); + console.log(`request(${size}): ${url}`); + requestSize += size; + } +} + +XMLHttpRequest.prototype.send = function (body) { + this.addEventListener( + "load", + function () { + let size = 0; + if (this.responseType === "" || this.responseType === "text") { + size = new Blob([JSON.stringify(this.responseText)]).size; + } else if (this.response instanceof Blob) { + size = this.response.size; + } else if (this.response instanceof ArrayBuffer) { + size = this.response.byteLength; + } else if (this.responseType === "json") { + size = new Blob([JSON.stringify(this.response)]).size; + } + + addRequestSize((this as XMLHttpRequest).responseURL, size); + }, + false + ); + + originalSend.call(this, body); + + var originalImageSrc = Object.getOwnPropertyDescriptor( + Image.prototype, + "src" + ).set; + + this.originalImageSrc = originalImageSrc; + + Object.defineProperty(Image.prototype, "src", { + set: function (value) { + fetch(value).then((response) => { + if (response.ok) { + response.blob().then((blob) => { + addRequestSize((this as XMLHttpRequest).responseURL, blob.size); + }); + } + }); + originalImageSrc.call(this, value); + }, + }); +}; + +export class RequestHook { + private _originalSend; + private _hooked = false; + + get size() { + return formatNumber(requestSize / 1024 / 1024); + } + + constructor() { + this._hooked = true; + } + + public reset(): void { + requestSize = 0; + } + + public release(): void { + if (this._hooked) { + XMLHttpRequest.prototype.send = this._originalSend; + Object.defineProperty(Image.prototype, "src", { + set: function (value) { + this.src.call(this, value); + }, + }); + } + this._hooked = false; + } +} + +function formatNumber(num: number): string { + return Number(num).toFixed( + Math.max(6 - num.toString().split(".")[0].length, 0) + ); +}