bree icon indicating copy to clipboard operation
bree copied to clipboard

[fix] Unexpected high CPU use with 1s interval job

Open brenc opened this issue 3 years ago • 0 comments

Describe the bug

Node.js version: v16.17.0

OS version: Docker node:16-bullseye-slim

Description: Just started using Bree to schedule some jobs inside a Docker container. Today I was working on a job that runs every 1s. I noticed that the job runner was using unusually high CPU for a very simple job. If I run my job in a while loop with a 1s sleep, it uses barely any CPU. If I run it using Bree, the job runner will consistently use 20-40% CPU.

Actual behavior

High CPU use.

Expected behavior

Much lower CPU use.

Code to reproduce

Here's the entire job runner:

const Bree = require('bree');
const Graceful = require('@ladjs/graceful');
const logger = require('./logger'); // this is just winston going to console

const bree = new Bree({
  jobs: [
    {
      name: 'aggregate-icecast-status',
      interval: '1s',
    },
  ],
  logger,
});

const graceful = new Graceful({ brees: [bree] });
graceful.listen();

(async () => {
  logger.error('starting...');
  await bree.start();
})();

Here's the job:

const { forEach } = require('modern-async');
const dns = require('dns').promises;
const fetch = require('node-fetch');
const Redis = require('ioredis');

const logger = require('../logger').child({
  extraInfo: 'aggregate-icecast-status',
});

const redis = new Redis({
  host: 'redis',
  keyPrefix: 'ngradio:status:',
});
const resolver = new dns.Resolver();

async function main() {
  logger.debug('aggregating icecast status');

  let addresses;
  try {
    addresses = await resolver.resolve4('icecast');
  } catch (err) {
    logger.warn(`error resolving Icecast hosts: ${err.message}`);
    return;
  }

  logger.debug('Icecast addresses: %o', addresses);

  let totalListeners = 0;
  let listenersPerHost = new Map();
  let listenerPeaksPerHost = new Map();
  let title;
  await forEach(addresses, async (address) => {
    logger.debug(`fetching status from ${address}`);

    let data;
    let response;
    try {
      response = await fetch(`http://${address}:8000/status-json.xsl`);
      data = await response.json();
      // logger.debug('%o', data);
    } catch (err) {
      logger.warn(`erroring collecting status from ${address}: ${err.message}`);
      return;
    }

    const listeners = parseInt(data?.mounts?.['/radio.mp3']?.listeners, 10);
    const listenerPeak = parseInt(
      data?.mounts?.['/radio.mp3']?.listener_peak,
      10
    );
    if (!title) {
      title = data?.mounts?.['/radio.mp3']?.title;
    }

    if (isNaN(listeners)) {
      logger.warn(`error parsing response from ${address}: listeners was NaN`);
    } else {
      totalListeners += listeners;
      listenersPerHost.set(address, listeners);
    }

    if (isNaN(listenerPeak)) {
      logger.warn(
        `error parsing response from ${address}: listener peak was NaN`
      );
    } else {
      listenerPeaksPerHost.set(address, listenerPeak);
    }
  });

  await redis.del('listenerPeaksPerHost');
  await redis.del('listenersPerHost');
  await redis.hset('listenerPeaksPerHost', listenerPeaksPerHost);
  await redis.hset('listenersPerHost', listenersPerHost);
  await redis.set('totalListeners', totalListeners, 'EX', 5);
  await redis.set('title', title, 'EX', 5);

  logger.debug(
    'Total listeners: %d, Listener peaks: %o, Listeners per host: %o, ' +
      'title: "%s"',
    totalListeners,
    listenerPeaksPerHost,
    listenersPerHost,
    title
  );

  await redis.quit();
}

(async () => {
  await main();
})();

Checklist

  • [X] I have searched through GitHub issues for similar issues.
  • [X] I have completely read through the README and documentation.
  • [X] I have tested my code with the latest version of Node.js and this package and confirmed it is still not working.

brenc avatar Sep 04 '22 16:09 brenc