A generic server that starts, logs via POST into database, can process and serve logs, and can be stopped
Gathering data from a running application or script and processing the outcome afterwards.
Use LokiJS as an in-memory document database to store log entries. Create a REST api that allows resetting the entries, adding a new entry and processing the entries. Make the server generic, allowing for entry points to configure how the data is stored in the database and how the result is produced.
yarn
or
npm install
node index.js
node index.js -f ./path/to/implementation.js
- First start the service (automatically starts when running) to clear existing entries.
curl -X POST \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
'http://127.0.0.1:3000/log-start'
- Log your entries
curl -X POST \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
-d '{"component":"MarkdownComponent", "page": "Welcome", "time": 1000}' \
'http://127.0.0.1:3000/log'
- Produce final report
curl -X POST \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
'http://127.0.0.1:3000/log-end'
Custom implementations need to define 3 functions:
key
:(requestBody: object) => string;
value
:(requestBody: object) => string | object;
produce
:(cacheObject: object) => string | object;
function key(body) {
const { component, page } = body;
return [page, component].join(".");
}
function value(body) {
const { time } = body;
return time;
}
function produce(cache) {
return Object.entries(cache).reduce(reducePages, {});
}
function reducePages(accum, [page, componentsObj]) {
return {
...accum,
[page]: Object.entries(componentsObj).reduce(reduceComponents, {})
};
}
function reduceComponents(componentsAccum, [component, times]) {
return {
...componentsAccum,
[component]: {
avg: times.reduce((a, b) => a + b, 0) / times.length,
_count: times.length
}
};
}
module.exports = { key, value, produce };
NOTE: the cacheObject
in the produce function is an object whose shape will be determined by the entries' keys (see entriesToObject)
// entriesToObject example
const entries = [
{ key: "a.b", value: 1 },
{ key: "a.b", value: 2 },
{ key: "a.c", value: 3 }
];
// groupBy(entries, "key") + 🍫
return { a: { b: [1, 2], c: [3] } }; // cacheObject
How to use React's experimental new profiler feature
Using profile implementation.
// withAsyncBenchmark.js
import React from "react";
function withAsyncBenchmark(WrappedComponent, id = "BenchmarkComponent") {
const Profiler = React.unstable_Profiler;
const LOG_SERVER_URL = "https://127.0.0.1:3000/log";
const LOG_SERVER_METHOD = "POST";
const LOG_SERVER_HEADERS = {
"Content-Type": "application/json",
Accept: "application/json"
};
function onRender(component, mode, actualTime) {
fetch(LOG_SERVER_URL, {
body: JSON.stringify({ component, mode, value: actualTime }),
method: LOG_SERVER_METHOD,
headers: LOG_SERVER_HEADERS
});
}
return function WithProfiler(props) {
return (
<Profiler id={id} onRender={onRender}>
<WrappedComponent {...props} />
</Profiler>
);
};
}
// ./Page.js
import withAsyncBenchmark from "./withAsyncBenchmark";
import ComponentToBenchmark from "./Page.component";
export default withAsyncBenchmark(ComponentToBenchmark, "Page");
// Sample payload send to log-server via `onRender` function
{ component: 'Page', mode: 'mount', value: 12.210000189952552 }
// Sample aggregation of entries
{
Page: {
mount: {
avg: 0.1593013390228786,
max: 1.4900000533089042,
min: 0.054999953135848045,
_count: 823
}
}
}
MIT Licence (c) Gonzalo Beviglia