-
Notifications
You must be signed in to change notification settings - Fork 453
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: remove node globals and reduce size
- adds buffer require - adds globalThis where needed - streaming-iterables was remove and new utils created, this will be consolidated in `ipfs-utils` and backported here to normalize all these iterator helper functions - latency-monitor was copied inside the repo - iso-random-stream is now used instead of node crypto - the test `should ignore self on discovery` was moved to node only Size: 172.97KB 47.03KB below the 220KB limit. `aegir build --node false` and `aegir test -t browser --node false` now work 🎉
- Loading branch information
1 parent
04ca1c8
commit 2f8039e
Showing
23 changed files
with
489 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
'use strict' | ||
|
||
/* global window */ | ||
const globalThis = require('ipfs-utils/src/globalthis') | ||
const EventEmitter = require('events') | ||
const VisibilityChangeEmitter = require('./visibility-change-emitter') | ||
const debug = require('debug')('latency-monitor:LatencyMonitor') | ||
|
||
/** | ||
* @typedef {Object} SummaryObject | ||
* @property {Number} events How many events were called | ||
* @property {Number} minMS What was the min time for a cb to be called | ||
* @property {Number} maxMS What was the max time for a cb to be called | ||
* @property {Number} avgMs What was the average time for a cb to be called | ||
* @property {Number} lengthMs How long this interval was in ms | ||
*/ | ||
|
||
/** | ||
* A class to monitor latency of any async function which works in a browser or node. This works by periodically calling | ||
* the asyncTestFn and timing how long it takes the callback to be called. It can also periodically emit stats about this. | ||
* This can be disabled and stats can be pulled via setting dataEmitIntervalMs = 0. | ||
* | ||
* The default implementation is an event loop latency monitor. This works by firing periodic events into the event loop | ||
* and timing how long it takes to get back. | ||
* | ||
* @example | ||
* const monitor = new LatencyMonitor(); | ||
* monitor.on('data', (summary) => console.log('Event Loop Latency: %O', summary)); | ||
* | ||
* @example | ||
* const monitor = new LatencyMonitor({latencyCheckIntervalMs: 1000, dataEmitIntervalMs: 60000, asyncTestFn:ping}); | ||
* monitor.on('data', (summary) => console.log('Ping Pong Latency: %O', summary)); | ||
*/ | ||
class LatencyMonitor extends EventEmitter { | ||
/** | ||
* @param {Number} [latencyCheckIntervalMs=500] How often to add a latency check event (ms) | ||
* @param {Number} [dataEmitIntervalMs=5000] How often to summarize latency check events. null or 0 disables event firing | ||
* @param {function} [asyncTestFn] What cb-style async function to use | ||
* @param {Number} [latencyRandomPercentage=5] What percent (+/-) of latencyCheckIntervalMs should we randomly use? This helps avoid alignment to other events. | ||
*/ | ||
constructor ({ latencyCheckIntervalMs, dataEmitIntervalMs, asyncTestFn, latencyRandomPercentage } = {}) { | ||
super() | ||
const that = this | ||
|
||
// 0 isn't valid here, so its ok to use || | ||
that.latencyCheckIntervalMs = latencyCheckIntervalMs || 500 // 0.5s | ||
that.latencyRandomPercentage = latencyRandomPercentage || 10 | ||
that._latecyCheckMultiply = 2 * (that.latencyRandomPercentage / 100.0) * that.latencyCheckIntervalMs | ||
that._latecyCheckSubtract = that._latecyCheckMultiply / 2 | ||
|
||
that.dataEmitIntervalMs = (dataEmitIntervalMs === null || dataEmitIntervalMs === 0) ? undefined | ||
: dataEmitIntervalMs || 5 * 1000 // 5s | ||
debug('latencyCheckIntervalMs: %s dataEmitIntervalMs: %s', | ||
that.latencyCheckIntervalMs, that.dataEmitIntervalMs) | ||
if (that.dataEmitIntervalMs) { | ||
debug('Expecting ~%s events per summary', that.latencyCheckIntervalMs / that.dataEmitIntervalMs) | ||
} else { | ||
debug('Not emitting summaries') | ||
} | ||
|
||
that.asyncTestFn = asyncTestFn // If there is no asyncFn, we measure latency | ||
|
||
// If process: use high resolution timer | ||
if (globalThis.process && globalThis.process.hrtime) { | ||
debug('Using process.hrtime for timing') | ||
that.now = globalThis.process.hrtime | ||
that.getDeltaMS = (startTime) => { | ||
const hrtime = that.now(startTime) | ||
return (hrtime[0] * 1000) + (hrtime[1] / 1000000) | ||
} | ||
// Let's try for a timer that only monotonically increases | ||
} else if (typeof window !== 'undefined' && window.performance && window.performance.now) { | ||
debug('Using performance.now for timing') | ||
that.now = window.performance.now.bind(window.performance) | ||
that.getDeltaMS = (startTime) => Math.round(that.now() - startTime) | ||
} else { | ||
debug('Using Date.now for timing') | ||
that.now = Date.now | ||
that.getDeltaMS = (startTime) => that.now() - startTime | ||
} | ||
|
||
that._latencyData = that._initLatencyData() | ||
|
||
// We check for isBrowser because of browsers set max rates of timeouts when a page is hidden, | ||
// so we fall back to another library | ||
// See: http://stackoverflow.com/questions/6032429/chrome-timeouts-interval-suspended-in-background-tabs | ||
if (isBrowser()) { | ||
that._visibilityChangeEmitter = new VisibilityChangeEmitter() | ||
that._visibilityChangeEmitter.on('visibilityChange', (pageInFocus) => { | ||
if (pageInFocus) { | ||
that._startTimers() | ||
} else { | ||
that._emitSummary() | ||
that._stopTimers() | ||
} | ||
}) | ||
} | ||
|
||
if (!that._visibilityChangeEmitter || that._visibilityChangeEmitter.isVisible()) { | ||
that._startTimers() | ||
} | ||
} | ||
|
||
/** | ||
* Start internal timers | ||
* @private | ||
*/ | ||
_startTimers () { | ||
// Timer already started, ignore this | ||
if (this._checkLatencyID) { | ||
return | ||
} | ||
this._checkLatency() | ||
if (this.dataEmitIntervalMs) { | ||
this._emitIntervalID = setInterval(() => this._emitSummary(), this.dataEmitIntervalMs) | ||
if (typeof this._emitIntervalID.unref === 'function') { | ||
this._emitIntervalID.unref() // Doesn't block exit | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Stop internal timers | ||
* @private | ||
*/ | ||
_stopTimers () { | ||
if (this._checkLatencyID) { | ||
clearTimeout(this._checkLatencyID) | ||
this._checkLatencyID = undefined | ||
} | ||
if (this._emitIntervalID) { | ||
clearInterval(this._emitIntervalID) | ||
this._emitIntervalID = undefined | ||
} | ||
} | ||
|
||
/** | ||
* Emit summary only if there were events. It might not have any events if it was forced via a page hidden/show | ||
* @private | ||
*/ | ||
_emitSummary () { | ||
const summary = this.getSummary() | ||
if (summary.events > 0) { | ||
this.emit('data', summary) | ||
} | ||
} | ||
|
||
/** | ||
* Calling this function will end the collection period. If a timing event was already fired and somewhere in the queue, | ||
* it will not count for this time period | ||
* @returns {SummaryObject} | ||
*/ | ||
getSummary () { | ||
// We might want to adjust for the number of expected events | ||
// Example: first 1 event it comes back, then such a long blocker that the next emit check comes | ||
// Then this fires - looks like no latency!! | ||
const latency = { | ||
events: this._latencyData.events, | ||
minMs: this._latencyData.minMs, | ||
maxMs: this._latencyData.maxMs, | ||
avgMs: this._latencyData.events ? this._latencyData.totalMs / this._latencyData.events | ||
: Number.POSITIVE_INFINITY, | ||
lengthMs: this.getDeltaMS(this._latencyData.startTime) | ||
} | ||
this._latencyData = this._initLatencyData() // Clear | ||
|
||
debug('Summary: %O', latency) | ||
return latency | ||
} | ||
|
||
/** | ||
* Randomly calls an async fn every roughly latencyCheckIntervalMs (plus some randomness). If no async fn is found, | ||
* it will simply report on event loop latency. | ||
* | ||
* @private | ||
*/ | ||
_checkLatency () { | ||
const that = this | ||
// Randomness is needed to avoid alignment by accident to regular things in the event loop | ||
const randomness = (Math.random() * that._latecyCheckMultiply) - that._latecyCheckSubtract | ||
|
||
// We use this to ensure that in case some overlap somehow, we don't take the wrong startTime/offset | ||
const localData = { | ||
deltaOffset: Math.ceil(that.latencyCheckIntervalMs + randomness), | ||
startTime: that.now() | ||
} | ||
|
||
const cb = () => { | ||
// We are already stopped, ignore this datapoint | ||
if (!this._checkLatencyID) { | ||
return | ||
} | ||
const deltaMS = that.getDeltaMS(localData.startTime) - localData.deltaOffset | ||
that._checkLatency() // Start again ASAP | ||
|
||
// Add the data point. If this gets complex, refactor it | ||
that._latencyData.events++ | ||
that._latencyData.minMs = Math.min(that._latencyData.minMs, deltaMS) | ||
that._latencyData.maxMs = Math.max(that._latencyData.maxMs, deltaMS) | ||
that._latencyData.totalMs += deltaMS | ||
debug('MS: %s Data: %O', deltaMS, that._latencyData) | ||
} | ||
debug('localData: %O', localData) | ||
|
||
this._checkLatencyID = setTimeout(() => { | ||
// This gets rid of including event loop | ||
if (that.asyncTestFn) { | ||
// Clear timing related things | ||
localData.deltaOffset = 0 | ||
localData.startTime = that.now() | ||
that.asyncTestFn(cb) | ||
} else { | ||
// setTimeout is not more accurate than 1ms, so this will ensure positive numbers. Add 1 to emitted data to remove. | ||
// This is not the best, but for now it'll be just fine. This isn't meant to be sub ms accurate. | ||
localData.deltaOffset -= 1 | ||
// If there is no function to test, we mean check latency which is a special case that is really cb => cb() | ||
// We avoid that for the few extra function all overheads. Also, we want to keep the timers different | ||
cb() | ||
} | ||
}, localData.deltaOffset) | ||
|
||
if (typeof this._checkLatencyID.unref === 'function') { | ||
this._checkLatencyID.unref() // Doesn't block exit | ||
} | ||
} | ||
|
||
_initLatencyData () { | ||
return { | ||
startTime: this.now(), | ||
minMs: Number.POSITIVE_INFINITY, | ||
maxMs: Number.NEGATIVE_INFINITY, | ||
events: 0, | ||
totalMs: 0 | ||
} | ||
} | ||
} | ||
|
||
function isBrowser () { | ||
return typeof window !== 'undefined' | ||
} | ||
|
||
module.exports = LatencyMonitor |
Oops, something went wrong.