GithubHelp home page GithubHelp logo

async-profile's Introduction

Node async-profile profiles CPU usage in node apps.

It lets you see at a glance how much CPU time is being taken up by a given part of your app, even if that part of your app is also doing asynchronous IO.

I built it at Bugsnag to help us understand why our background processors were using 100% CPU all the time.

Installation

This currently only works on node 0.10. 0.11 support should be easy to add, and much lower overhead :).

npm install async-profile

Usage

Call AsyncProfile.profile with a function. That function will be called asynchronously, and all of the timeouts and network events it causes will also be profiled. A summary will then be printed.

var AsyncProfile = require('async-profile')

AsyncProfile.profile(function () {

    // doStuff
    setTimeout(function () {
        // doAsyncStuff
    });

});

For more options see the advanced usage section

Interpreting the output

The output looks something like this: (taken from a profile of bugsnag's backend)

total: 1.823ms (in 2.213ms real time, CPU load: 0.8, wait time: 3.688ms)
0.879: 0.011ms    at Function.Project.fromCache (/0/bugsnag/event-worker/lib/project.coffee:12:16) (0.072ms)
0.970: 0.363ms    [no mark] (0.250ms)
1.589: 0.002ms        at /0/bugsnag/event-worker/workers/notify.coffee:29:13 (0.000ms)
1.622: 0.010ms        at /0/bugsnag/event-worker/workers/notify.coffee:30:13 (0.000ms)
1.668: 0.043ms        at Event.hash (/0/bugsnag/event-worker/lib/event/event.coffee:238:16) (0.061ms)
1.780: 0.064ms          at /0/bugsnag/event-worker/lib/event/event.coffee:246:21 (0.098ms)
2.016: 0.064ms            at Object.exports.count (/0/bugsnag/event-worker/lib/throttling.coffee:12:14) (0.122ms)
2.250: 0.052ms            REDIS EVAL SCRIPT (0.123)
2.506: 0.166ms                at throttleProjectEvent (/0/bugsnag/event-worker/lib/throttling.coffee:125:14) (0.295ms)
2.433: 0.002ms                at throttleProjectEvent (/0/bugsnag/event-worker/lib/throttling.coffee:125:14) (0.000ms)
2.211: 0.002ms              at throttleAccountEvent (/0/bugsnag/event-worker/lib/throttling.coffee:73:14) (0.000ms)
1.947: 0.002ms            at Object.exports.count (/0/bugsnag/event-worker/lib/throttling.coffee:12:14) (0.000ms)
1.593: 0.001ms        at Event.hash (/0/bugsnag/event-worker/lib/event/event.coffee:238:16) (0.000ms)
0.775: 0.003ms    at Function.Project.fromCache (/0/bugsnag/event-worker/lib/project.coffee:12:16) (0.000ms)

The first line contains 4 numbers:

  • total — the total amount of time spent running CPU.
  • real time — the amount of time between the first callack starting and the last callback ending.
  • CPU load — is just total / real time. As node is singlethreaded, this number ranges between 0 (CPU wasn't doing anything) and 1 (CPU was running the whole time).
  • wait time — the sum of the times between each callback being created and being called. High wait times can happen either because you're waiting for a lot of parallel IO events, or because you're waiting for other callbacks to stop using the CPU.

Each subsequent line contains 4 bits of information:

  • start: The time since you called new AsyncProfile() and when this callback started running.
  • cpu time: The amount of CPU time it took to execute this callback.
  • location: The point in your code at which this callback was created. (see also marking).
  • overhead: The amount of CPU time it took to calculate location (see also speed) which has been subtraced from the cpu time column.

Additionally the indentation lets you re-construct the tree of callbacks.

Marking

Sometimes it's hard to figure out exactly what's running when, particularly as the point at which the underlying async callback is created might not correspond to the location of a callback function in your code. At any point while the profiler is running you can mark the current callback to make it easy to spot in the profiler output.

AsyncProfile.mark 'SOMETHING EASY TO SPOT'

For example in the above output, I've done that for the callback that was running redis.eval and marked it as 'REDIS EVAL SCRIPT'.

Advanced

If you need advanced behaviour, you need to create the profiler manually, and then run some code. The profiler will be active for any callbacks created synchronously after it was.

setTimeout(function () {
    p = new AsyncProfiler();

    setTimeout(function () {
        // doStuff

    });
});

Speed

Like all profilers, this one comes with some overhead. In fact, by default it has so much overhead that I had to calculate it and then subtract it from the results :p.

There is some overhead not included in the overhead numbers, but it should hopefully be fairly insignficant (1-10μs or so per async call) and also not included in the profiler output.

You can make the profiler faster by creating it with the fast option. This disables both stack-trace calculation, and overhead calculation.

new AsyncProfile({fast: true})

Stopping

also known as "help, it's not displaying anything"

If your process happens to make an infinite cascade of callbacks (often this happens with promises libraries), then you will have to manually stop the profiler manually. For example using a promise you might want to do something like:

var p = new AsyncProfile()
Promise.try(doWork).finally(function () {
    p.stop();
});

Custom reports

You can pass a callback into the constructor to generate your own output. The default callback looks like this:

new AsyncProfile({
    callback: function (result) {
        result.print();
    }
);

The result object looks like this:

{
    start: [1, 000000000], # process.hrtime()
    end:   [9, 000000000], # process.hrtime()
    ticks: [
        {
            queue: [1, 000000000], # when the callback was created
            start: [2, 000000000], # when the callback was called
            end:   [3, 000000000], # when the callback finished
            overhead: [0, 000100000], # how much time was spent inside the profiler itself
            parent: { ... }, # the tick that was running when the callback was created
        }
    ]
}

This gives you a flattened tree of ticks, sorted by queue time. The parent will always come before its children in the array.

Common problems

No output is produced

Try manually stopping the profiler. You might have an infinite chain of callbacks, or no callbacks at all.

Some callbacks are missing

We're using async-listener under the hood, and it sometimes can't "see" beyond some libraries (like redis or mongo) that use connection queues.

The solution is to manually create a bridge over the asynchronous call. You can look at the code to see how I did it for mongo and redis. Pull requests are welcome.

Crashes on require with async-listener polyfill warning.

Either you're using node 0.11 (congrats!) or you're including async-listener from multiple places.

You can fix this by sending a pull request :).

Meta-fu

async-profile is licensed under the MIT license. Comments, pull-requests and issue reports are welcome.

async-profile's People

Contributors

conradirwin avatar weisjohn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

async-profile's Issues

the only function name i get is `[no mark]`

code:

var asyncProfile = require('async-profile');

function doStuff() {
    console.log('do')
    setTimeout(function stuff() {
        console.log('stuff')
    }, 1e3);
}

process.nextTick(function() {
    new asyncProfile();
    process.nextTick(doStuff);
});
$ node index.js 
do
stuff
total: 1.880ms (in 1004.292ms real time, CPU load: 0.0, wait time: 1003.656ms)
0.728: 1.512ms  [no mark] (0.133)  
2.416: 0.082ms    [no mark] (0.000)  
1004.209: 0.275ms    [no mark] (0.080)  
1004.683: 0.011ms      [no mark] (0.000)  

no child callbacks listed, plus a runtime error

I might be misunderstanding the usage of this module, but I tried a simple test which walks a directory and attempts to profile it, and I can't get it to do anything beyond printing the summary and then crashing. Here's my test code:

    var AsyncProfile = require('async-profile');
    var wrench = require('wrench');

    process.nextTick(function(){

        var profile = new AsyncProfile();

        process.nextTick(function(){
            // note: "music" is a deep directory tree with lots of files
            wrench.readdirRecursive("music", function(error, curFiles) {
                if(!error && curFiles) {
                    curFiles.forEach(function(f) {
                        console.log("file: " + f);
                    });
                }
            });
        });
    });

And here's my output:

[list of files printed by script removed]
total: 2018.866ms (in 3909.790ms real time, CPU load: 0.5, wait time: 12731813.305ms)

/Users/ntucker/testprof/node_modules/async-profile/lib/index.js:233
      stack = Error.prepareStackTrace(new Error("ohai"), stack);
                    ^
TypeError: Property 'prepareStackTrace' of object function Error() { [native code] } is not a function
    at AsyncProfile.getLineFromStack (/Users/ntucker/testprof/node_modules/async-profile/lib/index.js:233:21)
    at AsyncProfile.print (/Users/ntucker/testprof/node_modules/async-profile/lib/index.js:220:28)
    at Object.AsyncProfile.opts.callback (/Users/ntucker/testprof/node_modules/async-profile/lib/index.js:60:25)
    at AsyncProfile.after (/Users/ntucker/testprof/node_modules/async-profile/lib/index.js:146:19)
    at AsyncProfile.listener.process.createAsyncListener.after (/Users/ntucker/testprof/node_modules/async-profile/lib/index.js:100:26)
    at /Users/ntucker/testprof/node_modules/async-profile/node_modules/async-listener/glue.js:185:42
    at Object.oncomplete (fs.js:107:15)
$ node -v
v0.10.26
$

Don't require polyfill unless needed

D:\pro\node_modules\async-profile\node_modules\async-listener\index.js:3
if (process.addAsyncListener) throw new Error("Don't require polyfill unless n
eeded");
                              ^

Error: Don't require polyfill unless needed
    at Object.<anonymous> (D:\pro\node_modules\async-profile\node_modules\asyn
c-listener\index.js:3:37)
    at Module._compile (module.js:569:30)
    at Module._extensions..js (module.js:580:10)
    at Object.require.extensions.(anonymous function) [as .js] (D:\pro\apm\nod
e_modules\babel-register\lib\node.js:152:7)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)
    at Function.Module._load (module.js:458:3)
    at Module.require (module.js:513:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (D:\pro\node_modules\async-profile\lib\polyfill.js:5
:3)
    at Object.<anonymous> (D:\pro\node_modules\async-profile\lib\polyfill.js:4
9:4)
    at Module._compile (module.js:569:30)
    at Module._extensions..js (module.js:580:10)
    at Object.require.extensions.(anonymous function) [as .js] (D:\pro\apm\nod
e_modules\babel-register\lib\node.js:152:7)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)
    at Function.Module._load (module.js:458:3)
    at Module.require (module.js:513:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (D:\pro\node_modules\async-profile\lib\index.js:8:15
)
    at Object.<anonymous> (D:\pro\node_modules\async-profile\lib\index.js:175:
4)
    at Module._compile (module.js:569:30)
    at Module._extensions..js (module.js:580:10)
    at Object.require.extensions.(anonymous function) [as .js] (D:\pro\apm\nod
e_modules\babel-register\lib\node.js:152:7)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)
    at Function.Module._load (module.js:458:3)
    at Module.require (module.js:513:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (D:/pro/apm/apis/index.js:5:20)
    at Module._compile (module.js:569:30)
    at loader (D:\pro\apm\node_modules\babel-register\lib\node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .js] (D:\pro\apm\nod
e_modules\babel-register\lib\node.js:154:7)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)
    at Function.Module._load (module.js:458:3)
    at Module.require (module.js:513:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (D:/pro/apm/routes/users.js:5:22)
    at Module._compile (module.js:569:30)
    at loader (D:\pro\apm\node_modules\babel-register\lib\node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .js] (D:\pro\apm\nod
e_modules\babel-register\lib\node.js:154:7)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)
    at Function.Module._load (module.js:458:3)
    at Module.require (module.js:513:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (D:/pro/apm/app.js:10:13)
    at Module._compile (module.js:569:30)
    at loader (D:\pro\apm\node_modules\babel-register\lib\node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .js] (D:\pro\apm\nod
e_modules\babel-register\lib\node.js:154:7)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)
    at Function.Module._load (module.js:458:3)
    at Module.require (module.js:513:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (D:\pro\apm\bin\www:7:11)
    at Module._compile (module.js:569:30)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)
    at Function.Module._load (module.js:458:3)
    at Function.Module.runMain (module.js:605:10)
    at startup (bootstrap_node.js:158:16)
    at bootstrap_node.js:575:3

Is It caused by babel-register??? I hava tried:

  • re-assign value to process.addAsyncListener
if(process.addAsyncListener) {
  process.addAsyncListener = null;
}
  • comment source code
// if(process.addAsyncListener){ throw new Error(...) }

but there is a new problem

D:\pro\node_modules\async-profile\node_modules\async-listener\glue.js:351
  for (var i = 0; i < length; ++i) list[i].listener();
                                           ^

TypeError: list[i].listener is not a function

Always print AsyncProfile.create

0.127: 0.455ms  at AsyncProfile.create (/Users/dan/test-async-profile/node_modules/async-profile/lib/index.js:61:27) (0.072)
5.560: 2.849ms    at AsyncProfile.create (/Users/dan/test-async-profile/node_modules/async-profile/lib/index.js:61:27) (0.169)
8.590: 0.006ms      at AsyncProfile.create (/Users/dan/test-async-profile/async-profile/lib/index.js:61:27) (0.000)
8.600: 0.003ms      at AsyncProfile.create

Example App

Hi @ConradIrwin,

Do you happen to have any code examples on how to properly use your module? Do I just drop the usage code anywhere?

Thanks
Marcello

Question: How to use it in the context of a webserver like Express.js

I tried using async-profile within an endpoint of an express app, but I can't get it to work. Its immediately returning of making a instance an within the endpoint. Here a stripped down demo of my code:

//controller.js

var AsyncProfile = require('async-profile');

function someEndpoint(req, res){

    var profiler = new AsyncProfile({
        callback: function(results){
            console.log("error", "CPU profiling", results);
        }
    });

   //Its return immediately after the instantiation 

   dosomething(function returnCallback(){

    profiler.stop();
    //Expected it to stop here
 });



}

How to use bridges?

Can anyone show some examples of how to use bridging correctly? I try to profile my node-sqlite calls.

I failed to use it through polyfill in async-profile, so just wrapped my callbacks in wrapCallback = require('async-listener/glue');. Doing this way, it only adds second row to callstack pointing at asyncWrapper function. After wrapping also all intermediate callbacks in wrapCallback, they also added additional rows, pointing at the same asyncWrapper. So, how to do it right?

api design suggestion

In terms of dev-friendliness, I think it would be better for async-profile to expose a profile() function that simplifies dev use:

function profile(fn) {
    process.nextTick(function() {
        new asyncProfile();
        process.nextTick(fn);
    });
}

That would allow you to:

var profile = require('async-profile').profile;

function doStuff() {
    console.log('do')
    setTimeout(function stuff() {
        console.log('stuff')
    }, 1e3);
}

function doStuffWithArgs(arg) {
    console.log('do')
    setTimeout(function stuff() {
        console.log('stuff with', arg)
    }, 1e3);
}

profile(doStuff);
profile(doStuffWithArgs.bind('arg1'));

Profiling ends immediately

I must be misunderstanding something, but wherever I use async-profiler it doesn't actually profile anything. I immediately get the result, like this:

total: 0.009ms (in 2.350ms real time, CPU load: 0.0, wait time: 4.299ms)
2.348: 0.002ms  at AsyncProfile.create (/srv/www/node_modules/async-profile/lib/index.js:61:27) (0.000)  
2.352: 0.002ms  at AsyncProfile.create (/srv/www/node_modules/async-profile/lib/index.js:61:27) (0.000)  
2.356: 0.003ms  at AsyncProfile.create (/srv/www/node_modules/async-profile/lib/index.js:61:27) (0.000)  
2.361: 0.002ms  at AsyncProfile.create (/srv/www/node_modules/async-profile/lib/index.js:61:27) (0.000)

Now, most of my callbacks are truly asynchronous, there are multiple setImmediate calls down the line. I guess it should be able to catch these, because otherwise there isn't a lot of 'async' about it, right?

I've also tried the advanced configuration (where I call p.stop() in the final callback) but no dice.

Rollups

The default print function was built for the use-case I originally had, which was a few callbacks each taking a significant time. The example in #3 generates N identical frames, which it'd be nice to group together by default.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.