Source: src/memWatcher.js

// Copyright 2019-2020 Campbell Crowley. All rights reserved.
// Author: Campbell Crowley (dev@campbellcrowley.com)
const SubModule = require('./subModule.js');
const v8 = require('v8');

/**
 * @description Manages watching memory limits and attempts to mitigate issues
 * that may arise as a result.
 * @augments SubModule
 */
class MemWatcher extends SubModule {
  /**
   * @description Manages watching memory limits and attempts to mitigate issues
   * that may arise as a result.
   */
  constructor() {
    super();
    /** @inheritdoc */
    this.myName = 'MemWatcher';
    /**
     * @description How often to check memory usage.
     * @private
     * @type {number}
     * @default 5 Minutes
     * @constant
     */
    this._frequency = 5 * 60 * 1000;
    /**
     * @description Amount of memory in bytes away from the limit to force this
     * process to suicide.
     * @private
     * @type {number}
     * @default 100000000 bytes (100MB)
     * @constant
     */
    this._threshold = 100 * 1000 * 1000;

    this.check = this.check.bind(this);
    this.suicide = this.suicide.bind(this);
  }
  /** @inheritdoc */
  initialize() {
    /**
     * @description Created interval for checking memory usage.
     * @private
     * @type {?number}
     */
    this._interval = setInterval(this.check, this._frequency);
    this.check();

    this.command.on('sweep', (...args) => this._commandSweep(...args));

    /**
     * Sweep users from cache to release memory.
     *
     * @see {@link MemWatcher~sweepUsers}
     *
     * @public
     * @param {*} [args] Passed args.
     * @returns {null} Nothing.
     */
    this.client.sweepUsers = (...args) => this.sweepUsers(...args);
  }
  /** @inheritdoc */
  shutdown() {
    clearInterval(this._interval);
    this.command.removeListener('sweep');
    this.client.sweepUsers = null;
  }
  /** @inheritdoc */
  save() {}

  /**
   * Trigger sweeping of users from cache.
   *
   * @private
   * @type {commandHandler}
   * @param {Discord~Message} msg Message that triggered command.
   * @listens Command#sweep
   */
  _commandSweep(msg) {
    if (msg.author.id != this.common.spikeyId) {
      // this.common.reply(msg, 'You can\'t use this command.');
      return;
    }
    const num = this.client.users.cache.size;
    if (this.client.shard) {
      this.client.shard.broadcastEval('this.sweepUsers();')
          .then(() => {
            this.common.reply(
                msg, 'Sweeping users.',
                `${num} --> ${this.client.users.cache.size}`);
          })
          .catch((err) => {
            this.error('Failed to sweep users on shards.');
            console.error(err);
          });
    } else {
      this.sweepUsers();
      this.common.reply(
          msg, 'Sweeping users.', `${num} --> ${this.client.users.cache.size}`);
    }
  }
  /**
   * Cause stale users to be purged from cache.
   *
   * @public
   */
  sweepUsers() {
    const now = Date.now();
    let swept = 0;
    let total = 0;
    let fresh = 0;

    this.client.users.cache.sweep((user) => {
      if (!user.firstSweepTimestamp) {
        user.firstSweepTimestamp = now;
        fresh++;
      }
      total++;
      const sweep = now - user.firstSweepTimestamp > 6 * 60 * 60 * 1000;
      if (sweep) swept++;
      return sweep;
    });

    this.debug(`Swept ${swept} users of ${total}, with ${fresh} new users.`);
  }


  /**
   * @description Check the current memory usage. Called in the interval, but
   * can also be fired manually.
   * @public
   */
  check() {
    const mem = v8.getHeapStatistics();
    const limit = mem.heap_size_limit;
    const total = mem.total_heap_size;

    const mult = 1000000;

    const limitHR = Math.round(limit / mult * 100) / 100;
    const totalHR = Math.round(total / mult * 100) / 100;
    const nums = `${total}/${limit}B (${totalHR}/${limitHR}MB)`;
    const thresh =
        `${limit-this._threshold-total} from threshold (${this._threshold})`;

    this.debug(`Heap Snapshot: ${nums} ${thresh}`);

    if (total >= limit - this._threshold) {
      this.suicide();
      return;
    }

    // this.sweepUsers();
  }
  /**
   * @description Attempts to force the bot to die because we are probably about
   * to run out of memory and are about to crash anyways.
   * @public
   */
  suicide() {
    this.warn(
        'Causing process to suicide due to memory threshold being crossed.');
    process.exit(-2);
  }
}
module.exports = new MemWatcher();