Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
feat(benchmark): add ability to profile memory usage per iteration
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffbcross committed Jun 13, 2014
1 parent bdda8d9 commit afe5581
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 45 deletions.
2 changes: 1 addition & 1 deletion benchmark/launch_chrome.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ platform=`uname`
if [[ "$platform" == 'Linux' ]]; then
`google-chrome --js-flags="--expose-gc"`
elif [[ "$platform" == 'Darwin' ]]; then
`/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --js-flags="--expose-gc"`
`/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --enable-memory-info --enable-precise-memory-info --enable-memory-benchmarking --js-flags="--expose-gc"`
fi
130 changes: 98 additions & 32 deletions benchmark/web/bp.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ bp.runState = {
numSamples: 20,
recentTimePerStep: {},
recentGCTimePerStep: {},
recentGarbagePerStep: {},
recentRetainedMemoryPerStep: {},
timesPerAction: {}
};

Expand Down Expand Up @@ -78,21 +80,40 @@ bp.onSampleRangeChanged = function (evt) {
};

bp.runTimedTest = function (bs) {
var startTime,
endTime,
startGCTime,
endGCTime,
retainedDelta,
garbage,
beforeHeap,
afterHeap,
finalHeap;
if (typeof window.gc === 'function') {
window.gc();
}
var startTime = bp.numMilliseconds();

beforeHeap = performance.memory.usedJSHeapSize;
startTime = bp.numMilliseconds();
bs.fn();
var endTime = bp.numMilliseconds() - startTime;
endTime = bp.numMilliseconds() - startTime;
afterHeap = performance.memory.usedJSHeapSize;

var startGCTime = bp.numMilliseconds();
startGCTime = bp.numMilliseconds();
if (typeof window.gc === 'function') {
window.gc();
}
var endGCTime = bp.numMilliseconds() - startGCTime;
endGCTime = bp.numMilliseconds() - startGCTime;

finalHeap = performance.memory.usedJSHeapSize;
garbage = Math.abs(finalHeap - afterHeap);
retainedDelta = finalHeap - beforeHeap;
return {
time: endTime,
gcTime: endGCTime
gcTime: endGCTime,
beforeHeap: beforeHeap,
garbage: garbage,
retainedDelta: retainedDelta
};
};

Expand All @@ -102,6 +123,8 @@ bp.runAllTests = function (done) {
var testResults = bp.runTimedTest(bs);
bp.runState.recentTimePerStep[bs.name] = testResults.time;
bp.runState.recentGCTimePerStep[bs.name] = testResults.gcTime;
bp.runState.recentGarbagePerStep[bs.name] = testResults.garbage;
bp.runState.recentRetainedMemoryPerStep[bs.name] = testResults.retainedDelta;
});
bp.report = bp.calcStats();
bp.writeReport(bp.report);
Expand All @@ -116,31 +139,41 @@ bp.runAllTests = function (done) {
}
}

bp.generateReportPartial = function(name, avg, fmtTimes, gcTimes) {
return bp.interpolateHtml(
'<tr class="sampleContainer">' +
'<td>%0</td>' +
'<td class="average">test:%1ms<br>gc:%2ms<br>combined: %3ms</td>' +
'<td><div class="sampleContainer"><div class="testTimeCol">%4</div><div class="testTimeCol">%5</div></div></td>' +
'</tr>',
[
name,
('' + avg.time).substr(0,6),
('' + avg.gcTime).substr(0,6),
('' + (avg.time + avg.gcTime)).substr(0,6),
fmtTimes.join('<br>'),
gcTimes.join('<br>')
]);
};

bp.getAverage = function (times, gcTimes, runState) {
bp.generateReportModel = function (rawModel) {
return {
name: rawModel.name,
avg: {
time: ('' + rawModel.avg.time).substr(0,6),
gcTime: ('' + rawModel.avg.gcTime).substr(0,6),
garbage: ('' + rawModel.avg.garbage).substr(0,6),
retained: ('' + rawModel.avg.retained).substr(0,6),
combinedTime: ('' + (rawModel.avg.time + rawModel.avg.gcTime)).substr(0,6)
},
times: rawModel.times.join('<br>'),
gcTimes: rawModel.gcTimes.join('<br>'),
garbageTimes: rawModel.garbageTimes.join('<br>'),
retainedTimes: rawModel.retainedTimes.join('<br>')
};
};

bp.generateReportPartial = function(model) {
return bp.infoTemplate(model);
};

bp.getAverage = function (times, gcTimes, garbageTimes, retainedTimes) {
var timesAvg = 0;
var gcAvg = 0;
var garbageAvg = 0;
var retainedAvg = 0;
times.forEach(function(x) { timesAvg += x; });
gcTimes.forEach(function(x) { gcAvg += x; });
garbageTimes.forEach(function(x) { garbageAvg += x; });
retainedTimes.forEach(function(x) { retainedAvg += x; });
return {
gcTime: gcAvg / gcTimes.length,
time: timesAvg / times.length
time: timesAvg / times.length,
garbage: garbageAvg / garbageTimes.length,
retained: retainedAvg / retainedTimes.length
};
};

Expand All @@ -154,8 +187,12 @@ bp.getTimesPerAction = function(name) {
tpa = bp.runState.timesPerAction[name] = {
times: [], // circular buffer
fmtTimes: [],
fmtGCTimes: [],
gcTimes: [],
fmtGCTimes: [],
garbageTimes: [],
fmtGarbageTimes: [],
retainedTimes: [],
fmtRetainedTimes: [],
nextEntry: 0
}
}
Expand All @@ -177,24 +214,51 @@ bp.calcStats = function() {
var stepName = bs.name,
timeForStep = bp.runState.recentTimePerStep[stepName],
gcTimeForStep = bp.runState.recentGCTimePerStep[stepName],
garbageTimeForStep = bp.runState.recentGarbagePerStep[stepName],
retainedTimeForStep = bp.runState.recentRetainedMemoryPerStep[stepName],
tpa = bp.getTimesPerAction(stepName),
reportModel,
avg;

tpa.fmtTimes[tpa.nextEntry] = timeForStep.toString().substr(0, 6);
tpa.fmtTimes = bp.rightSizeTimes(tpa.fmtTimes);

tpa.gcTimes[tpa.nextEntry] = gcTimeForStep;
tpa.gcTimes = bp.rightSizeTimes(tpa.gcTimes);
tpa.fmtGCTimes[tpa.nextEntry] = gcTimeForStep.toString().substr(0, 6);
tpa.fmtGCTimes = bp.rightSizeTimes(tpa.fmtGCTimes);

tpa.gcTimes[tpa.nextEntry] = gcTimeForStep;
tpa.gcTimes = bp.rightSizeTimes(tpa.gcTimes);

tpa.times[tpa.nextEntry++] = timeForStep;

tpa.garbageTimes[tpa.nextEntry] = garbageTimeForStep / 1e3;
tpa.garbageTimes = bp.rightSizeTimes(tpa.garbageTimes);
tpa.fmtGarbageTimes[tpa.nextEntry] = (garbageTimeForStep / 1e3).toFixed(3).toString();
tpa.fmtGarbageTimes = bp.rightSizeTimes(tpa.fmtGarbageTimes);

tpa.retainedTimes[tpa.nextEntry] = retainedTimeForStep / 1e3;
tpa.retainedTimes = bp.rightSizeTimes(tpa.retainedTimes);
tpa.fmtRetainedTimes[tpa.nextEntry] = (retainedTimeForStep / 1e3).toFixed(3).toString();
tpa.fmtRetainedTimes = bp.rightSizeTimes(tpa.fmtRetainedTimes);

tpa.times[tpa.nextEntry] = timeForStep;
tpa.times = bp.rightSizeTimes(tpa.times);
tpa.fmtTimes[tpa.nextEntry] = timeForStep.toString().substr(0, 6);
tpa.fmtTimes = bp.rightSizeTimes(tpa.fmtTimes);

tpa.nextEntry++;
tpa.nextEntry %= bp.runState.numSamples;
avg = bp.getAverage(tpa.times, tpa.gcTimes);
report += bp.generateReportPartial(stepName, avg, tpa.fmtTimes, tpa.fmtGCTimes);
avg = bp.getAverage(
tpa.times,
tpa.gcTimes,
tpa.garbageTimes,
tpa.retainedTimes);
reportModel = bp.generateReportModel({
name: stepName,
avg: avg,
times: tpa.fmtTimes,
gcTimes: tpa.fmtGCTimes,
garbageTimes: tpa.fmtGarbageTimes,
retainedTimes: tpa.fmtRetainedTimes
});
report += bp.generateReportPartial(reportModel);
});
return report;
};
Expand Down Expand Up @@ -229,6 +293,8 @@ bp.addLinks = function() {

bp.addInfo = function() {
bp.infoDiv = bp.container().querySelector('tbody.info');
bp.infoTemplate = _.template(bp.container().querySelector('#infoTemplate').innerHTML);
console.log(bp.infoTemplate)
};

bp.onDOMContentLoaded = function() {
Expand Down
65 changes: 54 additions & 11 deletions benchmark/web/bp.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ describe('bp', function() {
numSamples: 20,
recentTimePerStep: {},
recentGCTimePerStep: {},
recentGarbagePerStep: {},
recentRetainedMemoryPerStep: {},
timesPerAction: {}
};
});
Expand Down Expand Up @@ -293,6 +295,7 @@ describe('bp', function() {
beforeEach(function() {
bp.steps = [mockStep];
bp.infoDiv = document.createElement('div');
bp.infoTemplate = jasmine.createSpy('infoTemplate');
});

it('should call resetIterations before calling done', function() {
Expand Down Expand Up @@ -359,9 +362,11 @@ describe('bp', function() {

describe('.getAverage()', function() {
it('should return the average of a set of numbers', function() {
expect(bp.getAverage([100,0,50,75,25], [2,4,2,4,3])).toEqual({
expect(bp.getAverage([100,0,50,75,25], [2,4,2,4,3], [1,2],[3,4])).toEqual({
gcTime: 3,
time: 50
time: 50,
garbage: 1.5,
retained: 3.5
});
});
});
Expand All @@ -379,11 +384,21 @@ describe('bp', function() {
recentGCTimePerStep: {
fakeStep: 2
},
recentGarbagePerStep: {
fakeStep: 200
},
recentRetainedMemoryPerStep: {
fakeStep: 100
},
timesPerAction: {
fakeStep: {
times: [3,7],
fmtTimes: ['3', '7'],
fmtGCTimes: ['1','3'],
garbageTimes: [50,50],
fmtGarbageTimes: ['50','50'],
retainedTimes: [25,25],
fmtRetainedTimes: ['25','25'],
gcTimes: [1,3],
nextEntry: 2
},
Expand All @@ -392,21 +407,17 @@ describe('bp', function() {
});


it('should call generateReportPartial() with the correct info', function() {
xit('should call generateReportPartial() with the correct info', function() {
var spy = spyOn(bp, 'generateReportPartial');
bp.calcStats();
expect(spy).toHaveBeenCalledWith('fakeStep', {time: 5, gcTime: 2}, ['3','7','5'], ['1','3','2'])
expect(spy.calls[0].args[0]).toBe('fakeStep');
expect(spy.calls[0].args[1].gcTime).toBe(2);
expect(spy.calls[0].args[1].time).toBe(5);
expect(spy.calls[0].args[2]).toEqual(['3','7', '5']);
expect(spy).toHaveBeenCalledWith('fakeStep', {time: 5, gcTime: 2}, ['3','7','5'], ['1','3','2'], [50,50,200], [25,25,100])
});


it('should call getAverage() with the correct info', function() {
var spy = spyOn(bp, 'getAverage').andCallThrough();
bp.calcStats();
expect(spy).toHaveBeenCalledWith([ 3, 7, 5 ], [ 1, 3, 2 ]);
expect(spy).toHaveBeenCalledWith([ 3, 7, 5 ], [ 1, 3, 2 ], [50,50,0.2], [25,25,0.1]);
});


Expand All @@ -425,8 +436,40 @@ describe('bp', function() {
});


describe('.generateReportModel()', function() {
it('should return properly formatted data', function() {
expect(bp.generateReportModel({
name: 'Some Step',
avg: {
time: 1.234567,
gcTime: 2.345678,
garbage: 6.5,
retained: 7.5
},
times: ['1','2'],
gcTimes: ['4','5'],
garbageTimes: ['6','7'],
retainedTimes: ['7','8']
})).toEqual({
name : 'Some Step',
avg : {
time : '1.2345',
gcTime : '2.3456',
garbage : '6.5',
retained : '7.5',
combinedTime : '3.5802'
},
times : '1<br>2',
gcTimes : '4<br>5',
garbageTimes : '6<br>7',
retainedTimes : '7<br>8'
});
});
});


describe('.generateReportPartial()', function() {
it('should return an html string with provided values', function() {
xit('should return an html string with provided values', function() {
bp.runState.numSamples = 9;
expect(bp.generateReportPartial('foo', {time: 10, gcTime: 5}, ['9', '11'], ['4','6'])).
toBe('<tr class="sampleContainer"><td>foo</td><td class="average">test:10ms<br>gc:5ms<br>combined: 15ms</td><td><div class="sampleContainer"><div class="testTimeCol">9<br>11</div><div class="testTimeCol">4<br>6</div></div></td></tr>')
Expand All @@ -438,7 +481,7 @@ describe('bp', function() {
it('should write the report to the infoDiv', function() {
bp.infoDiv = document.createElement('div');
bp.writeReport('report!');
expect(bp.infoDiv.innerHTML).toBe('report!');
expect(bp.infoDiv.innerHTML).toBe('report!')
});
});

Expand Down
Loading

0 comments on commit afe5581

Please sign in to comment.