├── README.md ├── assets ├── screenshot-fps.png ├── screenshot-mb.png ├── screenshot-ms.png └── screenshot-ping.png ├── index.html ├── license ├── package.json └── stats.js /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/%40jordandelcros%2Fstats-js.svg)](https://www.npmjs.com/package/@jordandelcros/stats-js) 2 | 3 | # stats.js # 4 | 5 | #### JavaScript Performance Monitor #### 6 | 7 | This class is a rework of the original `stats.js` from mrdoob. 8 | 9 | It use a single canvas unlike the original that use DOM elements. 10 | 11 | So, it provides a simple info box that will help you monitor your code performance. 12 | 13 | * **FPS** Frames rendered in the last second. The higher the number the better. 14 | * **MS** Milliseconds needed to render a frame. The lower the number the better. 15 | * **MB** MBytes of allocated memory. (Run Chrome with `--enable-precise-memory-info`) 16 | * **PING** Milliseconds needed to ask request something to the server. The lower the number the better. 17 | 18 | ### Screenshots ### 19 | 20 | ![fps.png](https://rawgit.com/JordanDelcros/stats-js/master/assets/screenshot-fps.png) 21 | ![ms.png](https://rawgit.com/JordanDelcros/stats-js/master/assets/screenshot-ms.png) 22 | ![mb.png](https://rawgit.com/JordanDelcros/stats-js/master/assets/screenshot-mb.png) 23 | ![ping.png](https://rawgit.com/JordanDelcros/stats-js/master/assets/screenshot-ping.png) 24 | 25 | ### NPM ### 26 | 27 | ```bash 28 | npm install @jordandelcros/stats-js 29 | ``` 30 | 31 | ### Usage ### 32 | 33 | `Stats([realTime]);` 34 | 35 | ```javascript 36 | var stats = new Stats(false); 37 | 38 | document.body.appendChild(stats.domElement);); 39 | 40 | function update() { 41 | 42 | window.requestAnimationFrame(update); 43 | 44 | stats.begin(); 45 | 46 | // monitored code goes here 47 | 48 | stats.end(); 49 | 50 | }; 51 | 52 | window.requestAnimationFrame(update); 53 | ``` 54 | 55 | to get the **PING** you need to call special methods into server's requests: 56 | 57 | ```javascript 58 | // ... 59 | 60 | function fakeServerRequest(){ 61 | 62 | // Call on server request send 63 | stats.beginPing(); 64 | 65 | // Fake server latency 66 | setTimeout(function(){ 67 | 68 | // Call on server request response 69 | stats.endPing(); 70 | fakeServerRequest(); 71 | 72 | }, 65); 73 | 74 | }; 75 | 76 | fakeServerRequest(); 77 | 78 | // ... 79 | ``` -------------------------------------------------------------------------------- /assets/screenshot-fps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JordanDelcros/stats-js/fcfae0801e887eff7143d4bd7cec1b9684bbb942/assets/screenshot-fps.png -------------------------------------------------------------------------------- /assets/screenshot-mb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JordanDelcros/stats-js/fcfae0801e887eff7143d4bd7cec1b9684bbb942/assets/screenshot-mb.png -------------------------------------------------------------------------------- /assets/screenshot-ms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JordanDelcros/stats-js/fcfae0801e887eff7143d4bd7cec1b9684bbb942/assets/screenshot-ms.png -------------------------------------------------------------------------------- /assets/screenshot-ping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JordanDelcros/stats-js/fcfae0801e887eff7143d4bd7cec1b9684bbb942/assets/screenshot-ping.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stats.js 7 | 8 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jordan Delcros 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jordandelcros/stats-js", 3 | "version": "1.0.5", 4 | "description": "This class is a rework of the original stats.js from mrdoob, it use a single canvas unlike the original that use DOM elements.", 5 | "main": "stats.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/JordanDelcros/stats-js.git" 12 | }, 13 | "keywords": [ 14 | "stats", 15 | "stats.js", 16 | "stats-js", 17 | "js", 18 | "perfs", 19 | "performance", 20 | "webgl", 21 | "speed", 22 | "enhancement" 23 | ], 24 | "author": "Jordan Delcros", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/JordanDelcros/stats-js/issues" 28 | }, 29 | "homepage": "https://github.com/JordanDelcros/stats-js#readme" 30 | } -------------------------------------------------------------------------------- /stats.js: -------------------------------------------------------------------------------- 1 | (function( self ){ 2 | 3 | var Stats = function( realTime ){ 4 | 5 | if( this instanceof Stats ){ 6 | 7 | return Stats.methods.initialize(realTime); 8 | 9 | } 10 | else { 11 | 12 | return new Stats(realTime); 13 | 14 | }; 15 | 16 | }; 17 | 18 | var PIXEL_RATIO = Math.round(window.devicePixelRatio || 1); 19 | 20 | var SIZE = { 21 | WIDTH: 80 * PIXEL_RATIO, 22 | HEIGHT: 50 * PIXEL_RATIO, 23 | FRAMES: { 24 | WIDTH: 74 * PIXEL_RATIO, 25 | HEIGHT: 32 * PIXEL_RATIO, 26 | X: 3 * PIXEL_RATIO, 27 | Y: 15 * PIXEL_RATIO 28 | }, 29 | TEXT: { 30 | HEIGHT: 8 * PIXEL_RATIO, 31 | X: 75 * PIXEL_RATIO, 32 | Y: 10 * PIXEL_RATIO 33 | } 34 | }; 35 | 36 | var STYLE = { 37 | FPS: { 38 | DATAS: "#1AFFFF", 39 | FRAMES: "#1B314C", 40 | BACKGROUND: "#1A1A38" 41 | }, 42 | MS: { 43 | DATAS: "#1AFF1A", 44 | FRAMES: "#1B4C1B", 45 | BACKGROUND: "#1A381A" 46 | }, 47 | MB: { 48 | DATAS: "#FF1A94", 49 | FRAMES: "#4C1B34", 50 | BACKGROUND: "#381A29" 51 | }, 52 | PING: { 53 | DATAS: "#FFFFFF", 54 | FRAMES: "#555555", 55 | BACKGROUND: "#222222" 56 | } 57 | }; 58 | 59 | var MODES = { 60 | FPS: 0, 61 | MS: 1, 62 | MB: 2, 63 | PING: 3 64 | }; 65 | 66 | var SUPPORT_MODE_MB = (window.performance != undefined && window.performance.memory != undefined && window.performance.memory.usedJSHeapSize != undefined ? true : false); 67 | 68 | Stats.methods = { 69 | initialize: function( realTime ){ 70 | 71 | this.mode = MODES.FPS; 72 | 73 | this.realTime = (realTime || false); 74 | 75 | this.frameTime = 0; 76 | 77 | this.beginTime = 0; 78 | this.endTime = 0; 79 | 80 | this.isPinging = false; 81 | this.beginPinging = 0; 82 | this.endPinging = 0; 83 | 84 | this.fps = { 85 | value: 0, 86 | current: 0, 87 | min: Infinity, 88 | max: -Infinity, 89 | array: new Array(SIZE.FRAMES.WIDTH) 90 | }; 91 | 92 | this.ms = { 93 | value: 0, 94 | current: 0, 95 | min: Infinity, 96 | max: -Infinity, 97 | array: new Array(SIZE.FRAMES.WIDTH) 98 | }; 99 | 100 | this.mb = { 101 | value: 0, 102 | current: 0, 103 | min: Infinity, 104 | max: -Infinity, 105 | array: new Array(SIZE.FRAMES.WIDTH) 106 | }; 107 | 108 | this.ping = { 109 | value: 0, 110 | current: 0, 111 | min: Infinity, 112 | max: -Infinity, 113 | array: new Array(SIZE.FRAMES.WIDTH) 114 | }; 115 | 116 | this.domElement = document.createElement("canvas"); 117 | this.domElement.className = "statsjs"; 118 | 119 | this.domElement.width = SIZE.WIDTH; 120 | this.domElement.height = SIZE.HEIGHT; 121 | 122 | this.domElement.style.width = (SIZE.WIDTH / PIXEL_RATIO) + "px"; 123 | this.domElement.style.height = (SIZE.HEIGHT / PIXEL_RATIO) + "px"; 124 | 125 | this.domElement.addEventListener("click", function( event ){ 126 | 127 | this.switchMode(); 128 | 129 | }.bind(this), false); 130 | 131 | this.context = this.domElement.getContext("2d"); 132 | 133 | this.context.imageSmoothingEnabled = false; 134 | this.context.font = "bold " + SIZE.TEXT.HEIGHT + "px sans-serif"; 135 | this.context.textAlign = "right"; 136 | 137 | return this; 138 | 139 | }, 140 | switchMode: function(){ 141 | 142 | if( this.mode == MODES.FPS ){ 143 | 144 | this.mode = MODES.MS; 145 | 146 | } 147 | else if( this.mode == MODES.MS ){ 148 | 149 | if( SUPPORT_MODE_MB == true ){ 150 | 151 | this.mode = MODES.MB; 152 | 153 | } 154 | else { 155 | 156 | this.mode = MODES.PING; 157 | 158 | }; 159 | 160 | } 161 | else if( this.mode == MODES.MB ){ 162 | 163 | this.mode = MODES.PING; 164 | 165 | } 166 | else if( this.mode == MODES.PING ){ 167 | 168 | this.mode = MODES.FPS; 169 | 170 | }; 171 | 172 | this.draw(); 173 | 174 | }, 175 | begin: function(){ 176 | 177 | this.beginTime = window.performance.now(); 178 | 179 | }, 180 | end: function(){ 181 | 182 | var now = window.performance.now(); 183 | var deltaTime = (now - this.frameTime); 184 | 185 | this.endTime = now; 186 | 187 | this.fps.current++; 188 | this.fps.max = Math.max(this.fps.current, this.fps.max); 189 | 190 | this.ms.current = (this.endTime - this.beginTime).toFixed(0); 191 | this.ms.min = Math.min(this.ms.current, this.ms.min); 192 | this.ms.max = Math.max(this.ms.current, this.ms.max); 193 | 194 | if( SUPPORT_MODE_MB == true ){ 195 | 196 | this.mb.current = Math.round(window.performance.memory.usedJSHeapSize * 0.000000954); 197 | this.mb.min = Math.min(this.mb.current, this.mb.min); 198 | this.mb.max = Math.max(this.mb.current, this.mb.max); 199 | 200 | }; 201 | 202 | if( this.realTime == true ){ 203 | 204 | this.fps.value = this.fps.current; 205 | 206 | this.fps.array[this.fps.array.length - 1] = this.fps.value; 207 | 208 | }; 209 | 210 | if( deltaTime < 1000 && this.realTime == true ){ 211 | 212 | this.draw(); 213 | 214 | } 215 | else if( deltaTime >= 1000 ){ 216 | 217 | this.fps.min = Math.min(this.fps.current, this.fps.min); 218 | 219 | this.frameTime = now; 220 | 221 | this.fps.value = this.fps.current; 222 | this.ms.value = this.ms.current; 223 | this.mb.value = this.mb.current; 224 | this.ping.value = this.ping.current; 225 | 226 | for( var index = 0, length = SIZE.FRAMES.WIDTH; index < length; index++ ){ 227 | 228 | this.fps.array[index] = this.fps.array[index + 1]; 229 | this.ms.array[index] = this.ms.array[index + 1]; 230 | this.mb.array[index] = this.mb.array[index + 1]; 231 | this.ping.array[index] = this.ping.array[index + 1]; 232 | 233 | }; 234 | 235 | this.fps.array[this.fps.array.length - 1] = this.fps.value; 236 | this.ms.array[this.ms.array.length - 1] = this.ms.value; 237 | this.mb.array[this.mb.array.length - 1] = this.mb.value; 238 | this.ping.array[this.ping.array.length - 1] = this.ping.value; 239 | 240 | this.draw(); 241 | 242 | this.fps.current = 0; 243 | 244 | }; 245 | 246 | }, 247 | beginPing: function(){ 248 | 249 | this.beginPinging = window.performance.now(); 250 | 251 | }, 252 | endPing: function(){ 253 | 254 | this.endPinging = window.performance.now(); 255 | this.ping.current = parseInt(this.endPinging - this.beginPinging); 256 | 257 | this.ping.min = Math.min(this.ping.current, this.ping.min); 258 | this.ping.max = Math.max(this.ping.current, this.ping.max); 259 | 260 | }, 261 | draw: function(){ 262 | 263 | this.context.clearRect(0, 0, SIZE.WIDTH, SIZE.HEIGHT); 264 | 265 | if( this.mode == MODES.FPS ){ 266 | 267 | this.context.fillStyle = STYLE.FPS.BACKGROUND; 268 | this.context.fillRect(0, 0, SIZE.WIDTH, SIZE.HEIGHT); 269 | 270 | this.context.fillStyle = STYLE.FPS.FRAMES; 271 | this.context.fillRect(SIZE.FRAMES.X, SIZE.FRAMES.Y, SIZE.FRAMES.WIDTH, SIZE.FRAMES.HEIGHT); 272 | 273 | this.context.fillStyle = STYLE.FPS.DATAS; 274 | 275 | var min = (this.fps.min == Infinity ? "∞" : this.fps.min); 276 | var max = (this.fps.max == -Infinity ? "∞" : this.fps.max); 277 | 278 | if( this.realTime == true ){ 279 | 280 | this.context.fillText(this.fps.current + " FPS (" + min + "-" + max + ")", SIZE.TEXT.X, SIZE.TEXT.Y); 281 | 282 | } 283 | else { 284 | 285 | this.context.fillText(this.fps.value + " FPS (" + min + "-" + max + ")", SIZE.TEXT.X, SIZE.TEXT.Y); 286 | 287 | }; 288 | 289 | for( var line = 0, length = this.fps.array.length; line < length; line++ ){ 290 | 291 | var height = (((this.fps.array[line] / this.fps.max) * SIZE.FRAMES.HEIGHT) || 0); 292 | 293 | var x = SIZE.FRAMES.X + line; 294 | var y = (SIZE.FRAMES.Y + SIZE.FRAMES.HEIGHT) - height; 295 | 296 | this.context.fillRect(x, y, 1, height); 297 | 298 | }; 299 | 300 | } 301 | else if( this.mode == MODES.MS ){ 302 | 303 | this.context.fillStyle = STYLE.MS.BACKGROUND; 304 | this.context.fillRect(0, 0, SIZE.WIDTH, SIZE.HEIGHT); 305 | 306 | this.context.fillStyle = STYLE.MS.FRAMES; 307 | this.context.fillRect(SIZE.FRAMES.X, SIZE.FRAMES.Y, SIZE.FRAMES.WIDTH, SIZE.FRAMES.HEIGHT); 308 | 309 | this.context.fillStyle = STYLE.MS.DATAS; 310 | 311 | var min = (this.ms.min == Infinity ? "∞" : this.ms.min); 312 | var max = (this.ms.max == -Infinity ? "∞" : this.ms.max); 313 | 314 | if( this.realTime == true ){ 315 | 316 | this.context.fillText(this.ms.current + " MS (" + min + "-" + max + ")", SIZE.TEXT.X, SIZE.TEXT.Y); 317 | 318 | } 319 | else { 320 | 321 | this.context.fillText(this.ms.value + " MS (" + min + "-" + max + ")", SIZE.TEXT.X, SIZE.TEXT.Y); 322 | 323 | }; 324 | 325 | for( var line = 0, length = this.ms.array.length; line < length; line++ ){ 326 | 327 | var height = (((this.ms.array[line] / this.ms.max) * SIZE.FRAMES.HEIGHT) || 0); 328 | 329 | var x = SIZE.FRAMES.X + line; 330 | var y = (SIZE.FRAMES.Y + SIZE.FRAMES.HEIGHT) - height; 331 | 332 | this.context.fillRect(x, y, 1, height); 333 | 334 | }; 335 | 336 | } 337 | else if( this.mode == MODES.MB ){ 338 | 339 | this.context.fillStyle = STYLE.MB.BACKGROUND; 340 | this.context.fillRect(0, 0, SIZE.WIDTH, SIZE.HEIGHT); 341 | 342 | this.context.fillStyle = STYLE.MB.FRAMES; 343 | this.context.fillRect(SIZE.FRAMES.X, SIZE.FRAMES.Y, SIZE.FRAMES.WIDTH, SIZE.FRAMES.HEIGHT); 344 | 345 | this.context.fillStyle = STYLE.MB.DATAS; 346 | 347 | var min = (this.mb.min == Infinity ? "∞" : this.mb.min); 348 | var max = (this.mb.max == -Infinity ? "∞" : this.mb.max); 349 | 350 | if( this.realTime == true ){ 351 | 352 | this.context.fillText(this.mb.current + " MB (" + min + "-" + max + ")", SIZE.TEXT.X, SIZE.TEXT.Y); 353 | 354 | } 355 | else { 356 | 357 | this.context.fillText(this.mb.value + " MB (" + min + "-" + max + ")", SIZE.TEXT.X, SIZE.TEXT.Y); 358 | 359 | }; 360 | 361 | for( var line = 0, length = this.mb.array.length; line < length; line++ ){ 362 | 363 | var height = (((this.mb.array[line] / this.mb.max) * SIZE.FRAMES.HEIGHT) || 0); 364 | 365 | var x = SIZE.FRAMES.X + line; 366 | var y = (SIZE.FRAMES.Y + SIZE.FRAMES.HEIGHT) - height; 367 | 368 | this.context.fillRect(x, y, 1, height); 369 | 370 | }; 371 | 372 | } 373 | else if( this.mode == MODES.PING ){ 374 | 375 | this.context.fillStyle = STYLE.PING.BACKGROUND; 376 | this.context.fillRect(0, 0, SIZE.WIDTH, SIZE.HEIGHT); 377 | 378 | this.context.fillStyle = STYLE.PING.FRAMES; 379 | this.context.fillRect(SIZE.FRAMES.X, SIZE.FRAMES.Y, SIZE.FRAMES.WIDTH, SIZE.FRAMES.HEIGHT); 380 | 381 | this.context.fillStyle = STYLE.PING.DATAS; 382 | 383 | var min = (this.ping.min == Infinity ? "∞" : this.ping.min); 384 | var max = (this.ping.max == -Infinity ? "∞" : this.ping.max); 385 | 386 | if( this.realTime == true ){ 387 | 388 | this.context.fillText(this.ping.current + " PING (" + min + "-" + max + ")", SIZE.TEXT.X, SIZE.TEXT.Y); 389 | 390 | } 391 | else { 392 | 393 | this.context.fillText(this.ping.value + " PING (" + min + "-" + max + ")", SIZE.TEXT.X, SIZE.TEXT.Y); 394 | 395 | }; 396 | 397 | for( var line = 0, length = this.ping.array.length; line < length; line++ ){ 398 | 399 | var height = (((this.ping.array[line] / this.ping.max) * SIZE.FRAMES.HEIGHT) || 0); 400 | 401 | var x = SIZE.FRAMES.X + line; 402 | var y = (SIZE.FRAMES.Y + SIZE.FRAMES.HEIGHT) - height; 403 | 404 | this.context.fillRect(x, y, 1, height); 405 | 406 | }; 407 | 408 | }; 409 | 410 | } 411 | }; 412 | 413 | Stats.methods.initialize.prototype = Stats.methods; 414 | 415 | if( typeof define !== "undefined" && define instanceof Function && define.amd != undefined ){ 416 | 417 | define(function(){ 418 | 419 | return Stats; 420 | 421 | }); 422 | 423 | } 424 | else if( typeof module !== "undefined" && module.exports ){ 425 | 426 | module.exports = Stats; 427 | 428 | } 429 | else if( self != undefined ){ 430 | 431 | self.Stats = Stats; 432 | 433 | }; 434 | 435 | })(this || {}); 436 | --------------------------------------------------------------------------------