Skip to content

Commit

Permalink
New: adds memory benchmarker
Browse files Browse the repository at this point in the history
  • Loading branch information
pustovitDmytro committed Nov 21, 2023
1 parent 0feaa28 commit a447ca5
Show file tree
Hide file tree
Showing 16 changed files with 364 additions and 148 deletions.
8 changes: 4 additions & 4 deletions .nycrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
],
"report-dir": "./reports/coverage",
"temp-dir": "./reports/coverage",
"lines": 98,
"statements": 96,
"functions": 97,
"branches": 92,
"lines": 99,
"statements": 97,
"functions": 98,
"branches": 93,
"check-coverage": true
}
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ We fight for democratic values, freedom, for our future! Once again Ukrainians h
- [Timers](#timers)
- [Reporters](#reporters)
- [Customization](#customization)
- [Memory](#memory)
- [Contribute](#contribute)

## Requirements
Expand Down Expand Up @@ -183,6 +184,31 @@ bench.calculate({
});
```

### Memory

use same api to benchmark memory usage:

```javascript
import { Memory, PlainReporter } from 'vesta';

const bench = new Memory();

const b1 = bench.start('before');
const a = Array.from({ length: 1e7 });

bench.end(b1);

console.log(bench.report(new PlainReporter(), { pretty: true }));

// ------------------------
// Label: before
// Rss: 76.05MB
// HeapTotal: 76.3MB
// HeapUsed: 76.29MB
// External: 0MB
// ArrayBuffers: 0MB
```

## Contribute

Make the changes to the code and tests. Then commit to your branch. Be sure to follow the commit message conventions. Read [Contributing Guidelines](.github/CONTRIBUTING.md) for details.
Expand Down
165 changes: 165 additions & 0 deletions src/BaseBenchMark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/* eslint-disable no-param-reassign */
import { mean, isValue, isObject } from 'myrmidon';
import PlainReporter from './reporters/PlainReporter';

export default class BaseBenchMark {
constructor({
counter,
shortSingleObservations = true
}) {
this._labels = [];
this.counter = counter;
this._start = this.counter.bench();
this._config = { shortSingleObservations };
this._iterations = [];
}

start(label, payload) {
const item = { start: this.counter.bench(), label };

if (payload) item.payload = payload;
const len = this._labels.push(item);

return len - 1;
}

end(pointer) {
this._labels[pointer].end = this.counter.bench();
}

iteration() {
const benchmark = new this.constructor({
counter : this.counter,
...this._config
});

this._iterations.push(benchmark);

return benchmark;
}

sequence(label) {
const item = { end: this.counter.bench(), label, sequence: true };

this._labels.push(item);
}

static metrics = {
total : arr => arr.length,
mean : arr => mean(arr)
};

static items = {};

calculateIntervals() {
this._labels.filter(l => l.sequence)
.forEach((item, index, sequence) => {
const startPoint = index === 0 ? this._start : sequence[index - 1].end;

item.bench = this.counter.diff(startPoint, item.end);
});

this._labels.filter(l => !l.sequence && l.start && l.end).forEach(item => {
item.bench = this.counter.diff(item.start, item.end);
});
}

_prepareMultiObservationReport(bench, metrics, report) {
for (const key of Object.keys(bench[0])) {
const arr = bench.map(b => b[key]);

report[key] = {};
this._prepareSingleObservationReport(arr, metrics, report[key]);
}
}

_prepareSingleObservationReport(bench, metrics, report) {
const metricNames = Object.keys(metrics)
.filter(metricName => metrics[metricName]);

for (const metricName of metricNames) {
report[metricName] = metrics[metricName](bench);
}
}

prepareReport(label, labels, { metrics, items }) {
const filtered = labels.filter(ll => ll.label === label && isValue(ll.bench));

if (filtered.length === 0) return;
const report = { label };

const bench = filtered.map(item => item.bench);
const multiObservation = isObject(bench[0]);

if (filtered.length === 1 && this._config.shortSingleObservations) {
if (multiObservation) {
for (const key of Object.keys(bench[0])) {
report[key] = bench[0][key];
}
} else {
report.benchmark = bench[0];
}


return report;
}


const mergedMetrics = {
...this.constructor.metrics,
...metrics
};

if (multiObservation) {
this._prepareMultiObservationReport(bench, mergedMetrics, report);
} else {
this._prepareSingleObservationReport(bench, mergedMetrics, report);
}

const mergedItems = {
...this.constructor.items,
...items
};

for (const itemLabel of Object.keys(mergedItems)) {
if (!mergedItems[itemLabel]) continue;
report[itemLabel] = mergedItems[itemLabel](
filtered,
report
);
}

return report;
}

calculate({
force = false,
metrics = {},
items = {}
} = {}) {
if (this._reports && !force) return this._reports;
this._reports = [];
const labels = [ ...this._labels ];

this._iterations.forEach(benchmark => labels.push(...benchmark._labels));
const labelsList = new Set(labels.map(l => l.label));

this.calculateIntervals();
this._iterations.forEach(b => b.calculateIntervals());

for (const label of labelsList) {
const report = this.prepareReport(label, labels, { metrics, items });

if (!report) continue;
this._reports.push(report);
}
}

report(reporter = new PlainReporter(), { pretty = false } = {}) {
this.calculate();

return reporter.run(this._reports, {
prettify : pretty && this.counter.constructor.prettify
});
}
}
126 changes: 7 additions & 119 deletions src/BenchMark.js
Original file line number Diff line number Diff line change
@@ -1,130 +1,18 @@
import { mean, quantile } from 'myrmidon';
import { quantile } from 'myrmidon';
import autoDetectCounter from './counters/autoDetector';
import PlainReporter from './reporters/PlainReporter';
import Base from './BaseBenchMark';

export default class BenchMark {
export default class BenchMark extends Base {
constructor({
counter = autoDetectCounter(),
shortSingleObservations = true
} = {}) {
this._labels = [];
this.counter = counter;
this._start = this.counter.bench();
this._config = { shortSingleObservations };
this._iterations = [];
}

start(label, payload) {
const item = { start: this.counter.bench(), label };

if (payload) item.payload = payload;
const len = this._labels.push(item);

return len - 1;
}

end(pointer) {
this._labels[pointer].end = this.counter.bench();
}

iteration() {
const benchmark = new BenchMark({
counter : this.counter,
...this._config
});

this._iterations.push(benchmark);

return benchmark;
}

sequence(label) {
const item = { end: this.counter.bench(), label, sequence: true };

this._labels.push(item);
super({ counter, shortSingleObservations });
}

static metrics = {
total : arr => arr.length,
mean : arr => mean(arr),
q25 : arr => quantile(arr, 0.25), // eslint-disable-line no-magic-numbers
q75 : arr => quantile(arr, 0.75) // eslint-disable-line no-magic-numbers
...Base.metrics,
q25 : arr => quantile(arr, 0.25), // eslint-disable-line no-magic-numbers
q75 : arr => quantile(arr, 0.75) // eslint-disable-line no-magic-numbers
};

calculateIntervals() {
/* eslint-disable no-param-reassign */
this._labels.filter(l => l.sequence).forEach((item, index, sequence) => {
const startPoint = index === 0 ? this._start : sequence[index - 1].end;

item.bench = this.counter.diff(startPoint, item.end);
});

this._labels.filter(l => !l.sequence && l.start && l.end).forEach(item => {
item.bench = this.counter.diff(item.start, item.end);
});
/* eslint-enable no-param-reassign */
}

prepareReport(label, labels, { metrics, items }) {
const filtered = labels.filter(ll => ll.label === label && ll.bench >= 0);

if (filtered.length === 0) return;
const report = { label };

if (filtered.length === 1 && this._config.shortSingleObservations) {
report.benchmark = filtered[0].bench;

return report;
}

const numbers = filtered.map(item => item.bench);
const mergedMetrics = {
...this.constructor.metrics,
...metrics
};

for (const metricName of Object.keys(mergedMetrics)) {
if (!mergedMetrics[metricName]) continue;
report[metricName] = mergedMetrics[metricName](numbers);
}

for (const itemLabel of Object.keys(items)) {
if (!items[itemLabel]) continue;
report[itemLabel] = items[itemLabel](
filtered,
report
);
}

return report;
}

calculate({
force = false,
metrics = {},
items = {}
} = {}) {
if (this._reports && !force) return this._reports;
this._reports = [];
const labels = [ ...this._labels ];

this._iterations.forEach(benchmark => labels.push(...benchmark._labels));
const labelsList = new Set(labels.map(l => l.label));

this.calculateIntervals();
this._iterations.forEach(b => b.calculateIntervals());

for (const label of labelsList) {
const report = this.prepareReport(label, labels, { metrics, items });

if (!report) continue;
this._reports.push(report);
}
}

report(reporter = new PlainReporter()) {
this.calculate();

return reporter.run(this._reports);
}
}
19 changes: 19 additions & 0 deletions src/Memory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import ProcessMemory from './counters/ProcessMemory';
import Base from './BaseBenchMark';

export default class Memory extends Base {
constructor({
counter = new ProcessMemory(),
shortSingleObservations = true
} = {}) {
super({ counter, shortSingleObservations });
}

static metrics = {
mean : Base.metrics.mean
};

static items = {
total : arr => arr.length
};
}
1 change: 1 addition & 0 deletions src/counters/Counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default class Counter {}
Loading

0 comments on commit a447ca5

Please sign in to comment.