rStats v0.1


Get it from GitHub https://github.com/spite/rstats

Welcome!

rStats aims to provide a way of measuring and visualizing performance of your code, mainly in apps based on an update loop, like games or interactive experiences.

It came to be when coding the Bumpy metaballs and SnowBox. I realised that the performance monitors I was using (the de facto standard mrdoob stats.js and the DevTools FPS Meter) are useful to keep an eye on your framerate and check if you're dropping frames. But when once you're doing great and keeping it under 16ms, how much are you really using? Is my shader executing in 15.99ms, or is it a way smaller fraction of the frame time? Can I have an idea of how much headroom have I still left?

So rStats started as a revamp of the original stats performance monitor, adding performance.now() for finer measuring. After the naive measuring, I started adding more features. It takes afer mrdoob stats.js, Jerome Etienne's threex.rendererstats and Ben Vanik's WebGL Inspector. It works on Chrome, Firefox, Safari, Opera; in OSX, Android, Windows.

Click here to see a Demo, but don't forget to read the whole page if you're interested!

Requires WebGL to run

Run Demo

Let's get started

To start, include the script in your code

<script src="rStats.js"></script>

This loads the rStats object, and the you can instantiate it:

var rS = new rStats();

This is the simplest way of creating an rStats object. Using it is as simple as placing the elements you'd like to measure in your update/render method:

function render() { rS( 'frame' ).start(); /* Do rendery stuff */ rS( 'frame' ).end(); rS().update(); requestAnimationFrame( render ); }

In this example, we use use rStats to simply measure and display the frames per second (FPS).

Notice that the value for frame is ~0. Since we're not doing anything in the function, it's exactly what we should expect.
Also, it's worth mentioning that right now the graphs slowly acommodate to the max value: the scale grows and shrinks over time to adjust the vertical axis to the maximum value. Good idea? Bad idea? It might make sense to specify a autoAdjust parameter for all graphs, or for specific values?

rS() returns the rStats object directly, and has a .update() method that redraws the widget. Call this method at the end of your update function. rS( id ) returns a counter object. A counter has five updating methods:

Additionally, .value() returns the current acumulated value for the counter.

Let's see a more extensive example:

function render() { rS( 'frame' ).start(); rS( 'rAF' ).tick(); rS( 'FPS' ).frame(); rS( 'action1' ).start(); /* Perform action #1 */ rS( 'action1' ).end(); rS( 'render' ).start(); /* Perform render */ rS( 'render' ).end(); rS( 'frame' ).end(); rS().update(); requestAnimationFrame( render ); }

In this example, we're using

That's it. With these examples you can add all the counter you want and measure performance for anything you want.

Cleaning up a bit

Using short ids like rAF or glCall is useful when writing code, but it makes the widget look a bit awful. You can add captions for your variables when creating the rStats object:

var rS = new rStats( { values: { frame: { caption: 'Total frame time (ms)' }, raf: { caption: 'Time since last rAF (ms)' }, fps: { caption: 'Framerate (FPS)' }, action1: { caption: 'Render action #1 (ms)' }, render: { caption: 'WebGL Render (ms)' } } } );

But wait, we can do even better. We can add alarms to quickly identify potential problems in the values we're monitoring. You can set up an alarm (the graph and the caption turn red) when values go over or below a specified threshold:

var rS = new rStats( { values: { frame: { caption: 'Total frame time (ms)', over: 16 }, raf: { caption: 'Time since last rAF (ms)' }, fps: { caption: 'Framerate (FPS)', below: 30 }, action1: { caption: 'Render action #1 (ms)' }, render: { caption: 'WebGL Render (ms)' } } } );

You can also add color and warningColor to specify your preferred colours for each graph.

Should the colour be configurable? What else do you think should be adjustable via parameters?

With these two changes, we get an alarm if the value frame is over 16ms, which means we're exceeding our frame budget and we're starting to drop frames. Similarly, we get an alarm if the value FPS is below 30, which would mean we're dropping so much frames we'll have a noticeable degraded experience. You can change this value to your acceptable framerate threshold :)

values is an object with the ids for the counters and their properties. The ids are case-insensitive. You provide caption, below and over.

Hopefully there'll be more values, like min and max values for the graph, colour. What else can you think of?

You can also tell the meter to show an averaged value over some time. Specify the values average: true to enable averaging of samples. Add avgMs: 100 specifying the number of ms over which the value is averaged (default is 1000ms).

Even better, we can add grouping, so values are clustered in categories:

var rS = new rStats( { values: { frame: { caption: 'Total frame time (ms)', over: 16 }, raf: { caption: 'Time since last rAF (ms)' }, fps: { caption: 'Framerate (FPS)', below: 30 }, action1: { caption: 'Render action #1 (ms)' }, render: { caption: 'WebGL Render (ms)' } }, groups: [ { caption: 'Framerate', values: [ 'fps', 'raf' ] }, { caption: 'Frame Budget', values: [ 'frame', 'action1', 'render' ] } ] } );

In this case, we group framerate-related values, and performance-related values. Clicking on the name of the group open and closes that group.

Let's extract some useful data from our values

Since we're going to be measuring performance/executing time for different processes that can be split in smaller actions, it might be interesting to see the percentage of the whole time that each step is taking.

var rS = new rStats( { values: { frame: { caption: 'Total frame time (ms)', over: 16 }, raf: { caption: 'Time since last rAF (ms)' }, fps: { caption: 'Framerate (FPS)', below: 30 }, action1: { caption: 'Render action #1 (ms)' }, render: { caption: 'WebGL Render (ms)' } }, groups: [ { caption: 'Framerate', values: [ 'fps', 'raf' ] }, { caption: 'Frame Budget', values: [ 'frame', 'action1', 'render' ] } ], fractions: [ { base: 'frame', steps: [ 'action1', 'render' ] } ] } );

With this, we're telling rStats to add a stacked area chart: the value of the counter specified in base (in this case, frames) is the 100%, and the values for the counters in steps (action1 and render) will be scaled to that base.

How do I add other counters and values?

It's as easy as deciding what do you want to measure and adding calls in your code. Wrap methods and statements between start() and end() to measure execution time. Use set() to store values and plot them.

Also, there's the possibility of adding plugins to your rStats object. So far there's one for three.js, and a very basic one for WebGL. Check the rStats.extras.js to get an idea of what this does.

Create your rStats object as usual, and add a plugins property:

glS = new glStats(); // init at any point tS = new threeStats( renderer ); // init after WebGLRenderer is created rS = new rStats( { values: { frame: { caption: 'Total frame time (ms)', over: 16 }, fps: { caption: 'Framerate (FPS)', below: 30 }, calls: { caption: 'Calls (three.js)', over: 3000 }, raf: { caption: 'Time since last rAF (ms)' }, rstats: { caption: 'rStats update (ms)' } }, groups: [ { caption: 'Framerate', values: [ 'fps', 'raf' ] }, { caption: 'Frame Budget', values: [ 'frame', 'texture', 'setup', 'render' ] } ], fractions: [ { base: 'frame', steps: [ 'action1', 'render' ] } ], plugins: [ tS, glS ] } );

And update the render method. The three.js plugin is transparent, but the WebGL one needs the start of the frame to be signaled (it resets the counters).

function render() { rS( 'frame' ).start(); glS.start(); rS( 'frame' ).start(); rS( 'rAF' ).tick(); rS( 'FPS' ).frame(); rS( 'action1' ).start(); /* Perform action #1 */ rS( 'action1' ).end(); rS( 'render' ).start(); /* Perform render */ rS( 'render' ).end(); rS( 'frame' ).end(); rS().update(); requestAnimationFrame( render ); }

Ok. Is that it?

Yes, so far it is. I'd like to know everyone's opinion and suggestions! Please use the issues tracker.

Ideas? Suggestions? Hit me on twitter or gmail!

Get it from GitHub spite/rstats.
Jaume Sanchez | http://www.clicktorelease.com