Como faço para medir o tempo de execução do código JavaScript com retornos de chamada?


319

Eu tenho um pedaço de código JavaScript que estou executando usando o node.jsintérprete.

for(var i = 1; i < LIMIT; i++) {
  var user = {
    id: i,
    name: "MongoUser [" + i + "]"
  };
  db.users.save(user, function(err, saved) {
    if(err || !saved) {
      console.log("Error");
    } else {
      console.log("Saved");
    }
  });
}

Como posso medir o tempo gasto por essas operações de inserção de banco de dados? Eu poderia calcular a diferença dos valores de data antes e antes desse trecho de código, mas isso seria incorreto devido à natureza assíncrona do código.


8
Basta ler a hora de início antes da chamada db, e o tempo do fim DENTRO a chamada de retorno ..
BFil

Existe a possibilidade de que o tempo em que o DB termine a inserção e o tempo em que o retorno de chamada seja executado não seja o mesmo e isso introduza um erro na medição?
Stormshadow

1
Não, não se preocupe, se o código da biblioteca db for bem projetado e não manipular nenhuma outra operação antes de disparar o retorno de chamada, você deverá obter uma boa medida. Você também pode perfil a inserção, colocando as marcas de tempo dentro do código da biblioteca, onde a inserção é realmente realizado, em vez de seu próprio país, mas, novamente, eu não me preocuparia com isso
BFil

Eu recomendaria tentar o NodeTime, que parece ser um bom ajuste para o que você está tentando fazer.
Julian Knight

Eu escrevi timerlogque é semelhante, console.time()mas com recursos adicionais; github.com/brillout/timerlog
brillout

Respostas:


718

Use o Node.js console.time()e console.timeEnd():

var i;
console.time("dbsave");

for(i = 1; i < LIMIT; i++){
    db.users.save({id : i, name : "MongoUser [" + i + "]"}, end);
}

end = function(err, saved) {
    console.log(( err || !saved )?"Error":"Saved");
    if(--i === 1){console.timeEnd("dbsave");}
};

31
Solução limpa e integrada para o nó.
Behlül Uçar 23/09

45
> Quero saber como medir o tempo gasto por essas operações de inserção de banco de dados. --- console.timeEnd ("dbsave") apenas envia para o console. Você não pode usar isso mais e é menos flexível. Se você precisar o valor tempo real, como na pergunta original, você não pode usar console.timeEnd ( "dbsave")
gogaman

@gogaman esse é um bom ponto, já que você não pode capturar a saída do console.timeEnd (). Talvez seja útil canalizar a saída para um arquivo e utilizar a partir daí?
Doug Molineux 25/09

5
Então, qual é a diferença entre console.time () e process.hrtime () na resposta abaixo?
yellow-saint

3
Vale a pena adicionar uma nota de que o tempo de execução é impresso, apenas para novos usuários agora.
precisa

208

Existe um método projetado para isso. Confira process.hrtime (); .

Então, eu basicamente coloquei isso no topo do meu aplicativo.

var start = process.hrtime();

var elapsed_time = function(note){
    var precision = 3; // 3 decimal places
    var elapsed = process.hrtime(start)[1] / 1000000; // divide by a million to get nano to milli
    console.log(process.hrtime(start)[0] + " s, " + elapsed.toFixed(precision) + " ms - " + note); // print message + time
    start = process.hrtime(); // reset the timer
}

Então eu uso para ver quanto tempo as funções levam. Aqui está um exemplo básico que imprime o conteúdo de um arquivo de texto chamado "output.txt":

var debug = true;
http.createServer(function(request, response) {

    if(debug) console.log("----------------------------------");
    if(debug) elapsed_time("recieved request");

    var send_html = function(err, contents) {
        if(debug) elapsed_time("start send_html()");
        response.writeHead(200, {'Content-Type': 'text/html' } );
        response.end(contents);
        if(debug) elapsed_time("end send_html()");
    }

    if(debug) elapsed_time("start readFile()");
    fs.readFile('output.txt', send_html);
    if(debug) elapsed_time("end readFile()");

}).listen(8080);

Aqui está um teste rápido que você pode executar em um terminal (shell BASH):

for i in {1..100}; do echo $i; curl http://localhost:8080/; done

3
superior à solução console.time?
#

31
Sim, é muito mais preciso e você pode armazenar o resultado em uma variável
Dallas Clark

Este funciona para mim, já que eu queria chamar o cronômetro várias vezes
tbh__

2
Por que você liga process.hrtime(start)duas vezes? Existe uma razão específica para isso?
Sohail Si

1
process.hrtime ([time]), em que time é um parâmetro opcional que deve ser o resultado de uma chamada process.hrtime () anterior para diferenciar o horário atual. Ele fornece a diferença entre a chamada atual e a chamada hrtime anterior.
Nilesh Jain #

72

A chamada console.time('label')registrará a hora atual em milissegundos e, posteriormente, a chamada console.timeEnd('label')exibirá a duração a partir desse ponto.

O tempo em milissegundos será impresso automaticamente ao lado da etiqueta, para que você não precise fazer uma chamada separada para console.log para imprimir uma etiqueta:

console.time('test');
//some code
console.timeEnd('test'); //Prints something like that-> test: 11374.004ms

Para mais informações, consulte os documentos para desenvolvedores da Mozilla emconsole.time .


O que isso acrescenta à resposta aceita ?
Dan Dascalescu

1
A resposta aceita foi modificado após a minha resposta para usar o meu código
jfcorugedo

24

Surpreendido, ninguém mencionou ainda as novas bibliotecas construídas:

Disponível em Nó> = 8.5 e deve estar em Modern Browers

https://developer.mozilla.org/en-US/docs/Web/API/Performance

https://nodejs.org/docs/latest-v8.x/api/perf_hooks.html#

Nó 8.5 ~ 9.x (Firefox, Chrome)

// const { performance } = require('perf_hooks'); // enable for node
const delay = time => new Promise(res=>setTimeout(res,time))
async function doSomeLongRunningProcess(){
  await delay(1000);
}
performance.mark('A');
(async ()=>{
  await doSomeLongRunningProcess();
  performance.mark('B');
  performance.measure('A to B', 'A', 'B');
  const measure = performance.getEntriesByName('A to B')[0];
  // firefox appears to only show second precision.
  console.log(measure.duration);
  performance.clearMeasures(); // apparently you should remove entries...
  // Prints the number of milliseconds between Mark 'A' and Mark 'B'
})();

https://repl.it/@CodyGeisler/NodeJsPerformanceHooks

Nó 10.x

https://nodejs.org/docs/latest-v10.x/api/perf_hooks.html

const { PerformanceObserver, performance } = require('perf_hooks');
const delay = time => new Promise(res => setTimeout(res, time))
async function doSomeLongRunningProcess() {
    await delay(1000);
}
const obs = new PerformanceObserver((items) => {
    console.log('PerformanceObserver A to B',items.getEntries()[0].duration);
    performance.clearMarks();
});
obs.observe({ entryTypes: ['measure'] });

performance.mark('A');

(async function main(){
    try{
        await performance.timerify(doSomeLongRunningProcess)();
        performance.mark('B');
        performance.measure('A to B', 'A', 'B');
    }catch(e){
        console.log('main() error',e);
    }
})();

1
Dá-me TypeError: performance.getEntriesByName is not a functionno nó v10.4.1
Jeremy Thille

Fiz o exemplo para que você possa executá-lo online. É o nó 9.7.1. Se não funcionar na v10.4.1, então me pergunto o que pode estar mudando!
C #:

1
Stability: 1 - Experimentaltalvez? :) nodejs.org/docs/latest-v8.x/api/…
Jeremy Thille

Sim, com certeza isso mudou. Há um novo observador na v10, você pode ver os documentos em nodejs.org/docs/latest-v10.x/api/documentation.html . Vou atualizar quando tiver a chance!
C #

19

Para quem deseja obter o valor decorrido no tempo em vez da saída do console:

use process.hrtime () como sugestão @ D.Deriso, abaixo está minha abordagem mais simples:

function functionToBeMeasured() {
    var startTime = process.hrtime();
    // do some task...
    // ......
    var elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime));
    console.log('It takes ' + elapsedSeconds + 'seconds');
}

function parseHrtimeToSeconds(hrtime) {
    var seconds = (hrtime[0] + (hrtime[1] / 1e9)).toFixed(3);
    return seconds;
}

16
var start = +new Date();
var counter = 0;
for(var i = 1; i < LIMIT; i++){
    ++counter;
    db.users.save({id : i, name : "MongoUser [" + i + "]"}, function(err, saved) {
          if( err || !saved ) console.log("Error");
          else console.log("Saved");
          if (--counter === 0) 
          {
              var end = +new Date();
              console.log("all users saved in " + (end-start) + " milliseconds");
          }
    });
}

5
Eu tive que procurar a sintaxe '+ new Date ()' para descobrir o que isso significava. De acordo com os comentários nesta resposta ( stackoverflow.com/a/221565/5114 ), não é uma boa ideia usar esse formulário por motivos de desempenho e legibilidade. Eu prefiro algo um pouco mais detalhado, para que fique mais claro para o leitor. Veja também esta resposta: stackoverflow.com/a/5036460/5114
Mnebuerquo

3
Costumo usar var start = process.hrtime(); ... var end = process.hrtime(start);para obter tempo de alta resolução (se eu precisar esperar uma precisão abaixo de milissegundos)
Andrey Sidorov

9

Pergunta antiga, mas para uma API simples e uma solução leve; você pode usar o perfy, que usa tempo real de alta resolução ( process.hrtime) internamente.

var perfy = require('perfy');

function end(label) {
    return function (err, saved) {
        console.log(err ? 'Error' : 'Saved'); 
        console.log( perfy.end(label).time ); // <——— result: seconds.milliseconds
    };
}

for (var i = 1; i < LIMIT; i++) {
    var label = 'db-save-' + i;
    perfy.start(label); // <——— start and mark time
    db.users.save({ id: i, name: 'MongoUser [' + i + ']' }, end(label));
}

Observe que cada vez que perfy.end(label)é chamada, essa instância é destruída automaticamente.

Divulgação: Escreveu este módulo, inspirado na resposta de D.Deriso . Documentos aqui .


2

Você pode experimentar o Benchmark.js . Ele suporta muitas plataformas, entre elas também o node.js.


11
Seria bom se você pudesse adicionar um exemplo de como usar o benchmark.js para este caso de uso.
Petah

2

Você também pode tentar exectimer . Fornece feedback como:

var t = require("exectimer");

var myFunction() {
   var tick = new t.tick("myFunction");
   tick.start();
   // do some processing and end this tick
   tick.stop();
}

// Display the results
console.log(t.timers.myFunction.duration()); // total duration of all ticks
console.log(t.timers.myFunction.min()); // minimal tick duration
console.log(t.timers.myFunction.max()); // maximal tick duration
console.log(t.timers.myFunction.mean()); // mean tick duration
console.log(t.timers.myFunction.median()); // median tick duration

[edit] Agora existe uma maneira mais simples de usar o exectimer, porque agora ele pode quebrar o código a ser medido. Seu código pode ser agrupado da seguinte maneira:

var t = require('exectimer'),
Tick = t.Tick;

for(var i = 1; i < LIMIT; i++){
    Tick.wrap(function saveUsers(done) {
        db.users.save({id : i, name : "MongoUser [" + i + "]"}, function(err, saved) {
            if( err || !saved ) console.log("Error");
            else console.log("Saved");
            done();
        });
    });
}

// Display the results
console.log(t.timers.myFunction.duration()); // total duration of all ticks
console.log(t.timers.saveUsers.min()); // minimal tick duration
console.log(t.timers.saveUsers.max()); // maximal tick duration
console.log(t.timers.saveUsers.mean()); // mean tick duration
console.log(t.timers.saveUsers.median()); // median tick duration


0

E outra opção é usar a ferramenta express-debug :

express-debug é uma ferramenta de desenvolvimento para express. É um middleware simples que injeta saída de depuração útil em seu html, de maneira não obstrutiva.

Convenientemente, oferece um painel de criação de perfil:

tempo total de processamento de solicitações. horários de middleware, param e rota.

Além disso. Para adicionar às respostas acima, você pode verificar esta resposta para ativar qualquer código de criação de perfil apenas para o ambiente de desenvolvimento.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.