@taraai/read-write
Version:
Synchronous NoSQL/Firestore for React
251 lines (227 loc) • 6.61 kB
JavaScript
/* eslint-disable prefer-template */
/* istanbul ignore file */
import noop from 'lodash/noop';
import debug from 'debug';
const info = debug('readwrite:profile');
if (info.enabled && debug.enabled('readwrite:cache')) {
info(
`Capturing Reducer & Firestore load times.
See results with 'readwrite()'.`,
);
}
let win;
try {
// eslint-disable-next-line dot-notation
const nodeRequire = module[`require`].bind(module);
win = nodeRequire('perf_hooks');
} catch (e) {}
try {
win = window;
} catch (e) {}
const perf = win && win.performance;
/**
*
* @param {*} marker
* @param {*} isDone
* @param context
* @returns {Function}
*/
export default function mark(marker, context = '') {
if (
!debug.enabled('readwrite:cache') ||
!debug.enabled('readwrite:profile') ||
!perf
) {
return noop;
}
/* istanbul ignore next */
try {
const now = perf.now();
const start = `::readwrite/${marker}-${now}`;
perf.mark(start);
if (context) {
info(`${marker}.${context}`);
}
return () => {
perf.measure(`::readwrite/${marker}`, start);
};
} catch (err) {
// ensure timings never impact the user
return noop;
}
}
/**
*
* @param {*} marker
* @returns
*/
export function resource(meta, stringify) {
if (
!debug.enabled('readwrite:cache') ||
!debug.enabled('readwrite:profile') ||
!perf
) {
return noop;
}
/* istanbul ignore next */
try {
const now = perf.now();
const marker = stringify(meta);
let start = `::readwrite.load/${marker}-${now}`;
perf.mark(start);
return (count = '') => {
if (!start) return;
perf.measure(`::readwrite.load/${marker}.|${count}|`, start);
start = null; // ensure only first load for each query
};
} catch (err) {
// ensure timings never impact the user
return noop;
}
}
/* istanbul ignore next */
if (win) {
win.readwriteStats = (force = false) => {
if (
!debug.enabled('readwrite:cache') ||
!debug.enabled('readwrite:profile') ||
!perf
) {
if (force)
debug.enable(typeof force === 'string' ? force : 'readwrite:*');
return;
}
const getMarks = ({ name }) => name.indexOf('::readwrite/') === 0;
const getLoads = ({ name }) => name.indexOf('::readwrite.load/') === 0;
const duration = (stats, { duration, name }) => {
if (stats[name]) {
stats[name].push(duration);
} else {
// eslint-disable-next-line no-param-reassign
stats[name] = [duration];
}
return stats;
};
const formatTime = (seconds) => {
if (seconds < 1000) return seconds + 'ms';
if (seconds < 1000 * 60) return (seconds / 1000).toFixed(3) + 's';
return (seconds / (1000 * 60)).toFixed(3) + ' minutes';
};
const logStats = (grouped) => {
console.group(`Read Write Profiling`);
console.table(
Object.keys(grouped)
.map((name) => {
const arr = grouped[name];
const sum = arr.reduce((a, b) => a + b, 0);
return {
[name]: {
mean: parseFloat((sum / arr.length).toFixed(2)),
samples: arr.length,
min: parseFloat(
arr.reduce((a, b) => (a < b ? a : b), arr[0]).toFixed(2),
),
max: parseFloat(
arr.reduce((a, b) => (a > b ? a : b), arr[0]).toFixed(2),
),
sum: parseFloat(sum.toFixed(2)),
},
};
})
.reduce((result, item) => ({ ...result, ...item })),
);
console.groupEnd();
};
const marks = performance
.getEntriesByType('measure')
.filter(getMarks)
.reduce(duration, {});
logStats(marks);
const phases = (
(last, phase) =>
({ startTime, duration, name }) => {
if (last + 16 <= startTime) {
phase++;
}
const item = {
name,
start: parseFloat(startTime.toFixed(2)),
phase,
duration: formatTime(parseFloat(duration.toFixed(2))),
loaded: (/\|(\d+)\|/g.exec(name) || [0, 0])[1],
};
// eslint-disable-next-line no-param-reassign
last = startTime;
return item;
}
)(false, 0);
const group = (arr, prop) =>
arr.reduce((stats, { phase, name, start, duration, loaded }) => {
if (!stats[phase]) {
stats[phase] = {};
}
stats[phase][name] = { start, duration, loaded };
return stats;
}, {});
const logPhases = (phases) => {
let last = 0;
console.group(`Firestore Collection Loads`);
Object.keys(phases).forEach((key) => {
const start = Object.values(phases[key]).reduce(
(num, { start }) => Math.min(num, start),
Number.MAX_VALUE,
);
console.group(`Phase ${key} +${Math.floor(start - last)}ms`);
console.table(phases[key]);
console.groupEnd();
last = start;
});
console.groupEnd();
};
const loads = performance
.getEntriesByType('measure')
.filter(getLoads)
.map(phases);
logPhases(group(loads, 'phase'));
};
}
if (!Object.size) {
Object.size = (obj) => {
let bytes = 0;
const sizeOf = (obj) => {
if (obj !== null && obj !== undefined) {
// eslint-disable-next-line default-case
switch (typeof obj) {
case 'number':
bytes += 8;
break;
case 'string':
bytes += obj.length * 2;
break;
case 'boolean':
bytes += 4;
break;
case 'object':
const objClass = Object.prototype.toString.call(obj).slice(8, -1);
if (objClass === 'Object' || objClass === 'Array') {
// eslint-disable-next-line no-restricted-syntax
for (const key in obj) {
// eslint-disable-next-line no-continue,no-prototype-builtins
if (!obj.hasOwnProperty(key)) continue;
sizeOf(obj[key]);
}
} else bytes += obj.toString().length * 2;
break;
}
}
return bytes;
};
const formatByteSize = (total) => {
if (total < 1024) return total + ' bytes';
if (total < 1048576) return (total / 1024).toFixed(3) + ' KiB';
if (total < 1073741824) return (total / 1048576).toFixed(3) + ' MiB';
return (total / 1073741824).toFixed(3) + ' GiB';
};
return formatByteSize(sizeOf(obj));
};
}