Add Perf jobs for PathKit
We have a similar ingestion strategy to Gold.
I tried to use something off the shelf like benchmark.js
but passing the PathKit context into the benchmarks was
non-trivial. Plus, making a basic benchmarking tool
ended up being not too hard.
We should be able to re-use the docker container/aggregator
for CanvasKit too.
Bug: skia:
Change-Id: I613dfc58ea57c31cf71566a8ac55f8df9272ad25
Reviewed-on: https://skia-review.googlesource.com/c/161620
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
Reviewed-by: Stephan Altmueller <stephana@google.com>
diff --git a/modules/pathkit/perf/effects.bench.js b/modules/pathkit/perf/effects.bench.js
new file mode 100644
index 0000000..d1be9ec
--- /dev/null
+++ b/modules/pathkit/perf/effects.bench.js
@@ -0,0 +1,143 @@
+
+
+describe('PathKit\'s Effects', function() {
+ // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
+ var PathKit = null;
+ const LoadPathKit = new Promise(function(resolve, reject) {
+ if (PathKit) {
+ resolve();
+ } else {
+ PathKitInit({
+ locateFile: (file) => '/pathkit/'+file,
+ }).then((_PathKit) => {
+ PathKit = _PathKit;
+ resolve();
+ });
+ }
+ });
+
+ // see https://fiddle.skia.org/c/@discrete_path
+ function drawStar(X=128, Y=128, R=116) {
+ let p = PathKit.NewPath();
+ p.moveTo(X + R, Y);
+ for (let i = 1; i < 8; i++) {
+ let a = 2.6927937 * i;
+ p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
+ }
+ p.closePath();
+ return p;
+ }
+
+ it('effects_dash', function(done) {
+ function setup(ctx) {
+ ctx.path = drawStar();
+ }
+
+ function test(ctx) {
+ let path = ctx.path.copy().dash(10, 3, 1);
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('effects_dash', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('effects_trim', function(done) {
+ function setup(ctx) {
+ ctx.path = drawStar();
+ }
+
+ function test(ctx) {
+ let path = ctx.path.copy().trim(0.25, .8);
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('effects_trim', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('effects_trim_complement', function(done) {
+ function setup(ctx) {
+ ctx.path = drawStar();
+ }
+
+ function test(ctx) {
+ let path = ctx.path.copy().trim(0.25, .8, true);
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('effects_trim_complement', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('effects_transform', function(done) {
+ function setup(ctx) {
+ ctx.path = drawStar();
+ }
+
+ function test(ctx) {
+ let path = ctx.path.copy().transform(3, 0, 0,
+ 0, 3, 0,
+ 0, 0, 1);
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('effects_transform', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('effects_stroke', function(done) {
+ function setup(ctx) {
+ ctx.path = drawStar();
+ }
+
+ function test(ctx) {
+ let path = ctx.path.copy().stroke({
+ width: 15,
+ join: PathKit.StrokeJoin.BEVEL,
+ cap: PathKit.StrokeCap.BUTT,
+ miter_limit: 2,
+ });
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('effects_stroke', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+});
\ No newline at end of file
diff --git a/modules/pathkit/perf/path.bench.js b/modules/pathkit/perf/path.bench.js
new file mode 100644
index 0000000..35d8b25
--- /dev/null
+++ b/modules/pathkit/perf/path.bench.js
@@ -0,0 +1,313 @@
+
+
+describe('PathKit\'s Path Behavior', function() {
+ // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
+ var PathKit = null;
+ const LoadPathKit = new Promise(function(resolve, reject) {
+ if (PathKit) {
+ resolve();
+ } else {
+ PathKitInit({
+ locateFile: (file) => '/pathkit/'+file,
+ }).then((_PathKit) => {
+ PathKit = _PathKit;
+ resolve();
+ });
+ }
+ });
+
+ function drawPath() {
+ let path = PathKit.NewPath();
+ path.moveTo(20, 5);
+ path.lineTo(30, 20);
+ path.lineTo(40, 10);
+ path.lineTo(50, 20);
+ path.lineTo(60, 0);
+ path.lineTo(20, 5);
+
+ path.moveTo(20, 80);
+ path.bezierCurveTo(90, 10, 160, 150, 190, 10);
+
+ path.moveTo(36, 148);
+ path.quadraticCurveTo(66, 188, 120, 136);
+ path.lineTo(36, 148);
+
+ path.rect(5, 170, 20, 20);
+
+ path.moveTo(150, 180);
+ path.arcTo(150, 100, 50, 200, 20);
+ path.lineTo(160, 160);
+
+ path.moveTo(20, 120);
+ path.arc(20, 120, 18, 0, 1.75 * Math.PI);
+ path.lineTo(20, 120);
+
+ let secondPath = PathKit.NewPath();
+ secondPath.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI, false);
+
+ path.addPath(secondPath);
+
+ let m = document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGMatrix();
+ m.a = 1; m.b = 0;
+ m.c = 0; m.d = 1;
+ m.e = 0; m.f = 20.5;
+
+ path.addPath(secondPath, m);
+ secondPath.delete();
+ return path;
+ }
+
+ it('path_path2dapi', function(done) {
+ function setup(ctx) { }
+
+ function test(ctx) {
+ path = drawPath();
+ path.delete();
+ }
+
+ function teardown(ctx) { }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('path_path2dapi', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ describe('import options', function() {
+ it('path_copy', function(done) {
+ function setup(ctx) {
+ ctx.path = PathKit.FromSVGString('M 205,5 L 795,5 L 595,295 L 5,295 L 205,5 z');
+ }
+
+ function test(ctx) {
+ let p = ctx.path.copy();
+ p.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('path_copy', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('path_from_api_calls', function(done) {
+ function setup(ctx) { }
+
+ function test(ctx) {
+ let p = PathKit.NewPath()
+ .moveTo(205, 5)
+ .lineTo(795, 5)
+ .lineTo(595, 295)
+ .lineTo(5, 295)
+ .lineTo(205, 5)
+ .close();
+ p.delete();
+ }
+
+ function teardown(ctx) { }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('path_from_api_calls', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('path_fromCmds', function(done) {
+ function setup(ctx) { }
+
+ function test(ctx) {
+ let p = PathKit.FromCmds(
+ [[PathKit.MOVE_VERB, 205, 5],
+ [PathKit.LINE_VERB, 795, 5],
+ [PathKit.LINE_VERB, 595, 295],
+ [PathKit.LINE_VERB, 5, 295],
+ [PathKit.LINE_VERB, 205, 5],
+ [PathKit.CLOSE_VERB]]);
+ p.delete();
+ }
+
+ function teardown(ctx) { }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('path_fromCmds', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('path_fromSVGString', function(done) {
+ function setup(ctx) {}
+
+ function test(ctx) {
+ // https://upload.wikimedia.org/wikipedia/commons/e/e7/Simple_parallelogram.svg
+ let p = PathKit.FromSVGString('M 205,5 L 795,5 L 595,295 L 5,295 L 205,5 z');
+ p.delete();
+ }
+
+ function teardown(ctx) { }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('path_fromSVGString', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+ });
+
+ describe('export options', function() {
+ it('path_toCmds', function(done) {
+ function setup(ctx) {
+ ctx.path = drawPath();
+ }
+
+ function test(ctx) {
+ ctx.path.toCmds();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('path_toCmds', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('path_toPath2D', function(done) {
+ function setup(ctx) {
+ ctx.path = drawPath();
+ }
+
+ function test(ctx) {
+ ctx.path.toPath2D();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('path_toPath2D', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('path_toSVGString', function(done) {
+ function setup(ctx) {
+ ctx.path = drawPath();
+ }
+
+ function test(ctx) {
+ ctx.path.toSVGString();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('path_toSVGString', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+ });
+
+ describe('matrix options', function() {
+ function drawTriangle() {
+ let path = PathKit.NewPath();
+ path.moveTo(0, 0);
+ path.lineTo(10, 0);
+ path.lineTo(10, 10);
+ path.close();
+ return path;
+ }
+
+ it('path_add_path_svgmatrix', function(done) {
+ function setup(ctx) {
+ ctx.path = drawTriangle();
+ }
+
+ function test(ctx) {
+ let path = PathKit.NewPath();
+ let m = document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGMatrix();
+ m.a = 1; m.b = 0;
+ m.c = 0; m.d = 1;
+ m.e = 0; m.f = 20.5;
+ path.addPath(ctx.path, m);
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('path_add_path_svgmatrix', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('path_add_path_svgmatrix_reuse', function(done) {
+ function setup(ctx) {
+ ctx.path = drawTriangle();
+ let m = document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGMatrix();
+ ctx.matrix = m;
+ }
+
+ function test(ctx) {
+ let path = PathKit.NewPath();
+ let m = ctx.matrix
+ m.a = 1; m.b = 0;
+ m.c = 0; m.d = 1;
+ m.e = 0; m.f = 20.5;
+ path.addPath(ctx.path, m);
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('path_add_path_svgmatrix_reuse', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('path_add_path_svgmatrix_bare', function(done) {
+ function setup(ctx) {
+ ctx.path = drawTriangle();
+ }
+
+ function test(ctx) {
+ let path = PathKit.NewPath();
+ path.addPath(ctx.path, 1, 0, 0, 1, 0, 20.5);
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('path_add_path_svgmatrix_bare', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+ });
+
+});
\ No newline at end of file
diff --git a/modules/pathkit/perf/pathops.bench.js b/modules/pathkit/perf/pathops.bench.js
new file mode 100644
index 0000000..2da6553
--- /dev/null
+++ b/modules/pathkit/perf/pathops.bench.js
@@ -0,0 +1,172 @@
+
+
+describe('PathKit\'s Pathops', function() {
+ // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
+ var PathKit = null;
+ const LoadPathKit = new Promise(function(resolve, reject) {
+ if (PathKit) {
+ resolve();
+ } else {
+ PathKitInit({
+ locateFile: (file) => '/pathkit/'+file,
+ }).then((_PathKit) => {
+ PathKit = _PathKit;
+ resolve();
+ });
+ }
+ });
+
+ // see https://fiddle.skia.org/c/@discrete_path
+ function drawStar(X=128, Y=128, R=116) {
+ let p = PathKit.NewPath();
+ p.moveTo(X + R, Y);
+ for (let i = 1; i < 8; i++) {
+ let a = 2.6927937 * i;
+ p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
+ }
+ p.closePath();
+ return p;
+ }
+
+ it('pathops_simplify', function(done) {
+ function setup(ctx) {
+ ctx.path = drawStar();
+ }
+
+ function test(ctx) {
+ let path = ctx.path.copy().simplify();
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('pathops_simplify', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('pathops_diff', function(done) {
+ function setup(ctx) {
+ // Values chosen abitrarily to have some overlap and some not.
+ ctx.path1 = drawStar(X=120, Y=120);
+ ctx.path2 = drawStar(X=140, Y=145);
+ }
+
+ function test(ctx) {
+ let path = PathKit.MakeFromOp(ctx.path1, ctx.path2, PathKit.PathOp.DIFFERENCE);
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path1.delete();
+ ctx.path2.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('pathops_diff', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('pathops_intersect', function(done) {
+ function setup(ctx) {
+ // Values chosen abitrarily to have some overlap and some not.
+ ctx.path1 = drawStar(X=120, Y=120);
+ ctx.path2 = drawStar(X=140, Y=145);
+ }
+
+ function test(ctx) {
+ let path = PathKit.MakeFromOp(ctx.path1, ctx.path2, PathKit.PathOp.INTERSECT);
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path1.delete();
+ ctx.path2.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('pathops_intersect', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('pathops_union', function(done) {
+ function setup(ctx) {
+ // Values chosen abitrarily to have some overlap and some not.
+ ctx.path1 = drawStar(X=120, Y=120);
+ ctx.path2 = drawStar(X=140, Y=145);
+ }
+
+ function test(ctx) {
+ let path = PathKit.MakeFromOp(ctx.path1, ctx.path2, PathKit.PathOp.UNION);
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path1.delete();
+ ctx.path2.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('pathops_union', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('pathops_xor', function(done) {
+ function setup(ctx) {
+ // Values chosen abitrarily to have some overlap and some not.
+ ctx.path1 = drawStar(X=120, Y=120);
+ ctx.path2 = drawStar(X=140, Y=145);
+ }
+
+ function test(ctx) {
+ let path = PathKit.MakeFromOp(ctx.path1, ctx.path2, PathKit.PathOp.XOR);
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path1.delete();
+ ctx.path2.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('pathops_xor', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+ it('pathops_reverse_diff', function(done) {
+ function setup(ctx) {
+ // Values chosen abitrarily to have some overlap and some not.
+ ctx.path1 = drawStar(X=120, Y=120);
+ ctx.path2 = drawStar(X=140, Y=145);
+ }
+
+ function test(ctx) {
+ let path = PathKit.MakeFromOp(ctx.path1, ctx.path2, PathKit.PathOp.REVERSE_DIFFERENCE);
+ path.delete();
+ }
+
+ function teardown(ctx) {
+ ctx.path1.delete();
+ ctx.path2.delete();
+ }
+
+ LoadPathKit.then(() => {
+ benchmarkAndReport('pathops_reverse_diff', setup, test, teardown).then(() => {
+ done();
+ }).catch(reportError(done));
+ });
+ });
+
+});
\ No newline at end of file
diff --git a/modules/pathkit/perf/perfReporter.js b/modules/pathkit/perf/perfReporter.js
new file mode 100644
index 0000000..d74110c
--- /dev/null
+++ b/modules/pathkit/perf/perfReporter.js
@@ -0,0 +1,72 @@
+const REPORT_URL = 'http://localhost:8081/report_perf_data'
+// Set this to enforce that the perf server must be up.
+// Typically used for debugging.
+const fail_on_no_perf = false;
+
+
+function benchmarkAndReport(benchName, setupFn, testFn, teardownFn) {
+ let ctx = {};
+ // warmup 3 times (arbitrary choice)
+ setupFn(ctx);
+ testFn(ctx);
+ testFn(ctx);
+ testFn(ctx);
+ teardownFn(ctx);
+
+ ctx = {};
+ setupFn(ctx);
+ let start = Date.now();
+ let now = start;
+ times = 0;
+ // See how many times we can do it in 100ms (arbitrary choice)
+ while (now - start < 100) {
+ testFn(ctx);
+ now = Date.now();
+ times++;
+ }
+
+ teardownFn(ctx);
+
+ // Try to make it go for 2 seconds (arbitrarily chosen)
+ // Since the pre-try took 100ms, multiply by 20 to get
+ // approximate tries in 2s
+ let goalTimes = times * 20;
+ setupFn(ctx);
+ start = Date.now();
+ times = 0;
+ while (times < goalTimes) {
+ testFn(ctx);
+ times++;
+ }
+ let end = Date.now();
+ teardownFn(ctx);
+
+ let us = (end - start) * 1000 / times;
+ console.log(benchName, `${us} microseconds`)
+ return _report(us, benchName);
+}
+
+
+function _report(microseconds, benchName) {
+ return fetch(REPORT_URL, {
+ method: 'POST',
+ mode: 'no-cors',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ 'bench_name': benchName,
+ 'time_us': microseconds,
+ })
+ }).then(() => console.log(`Successfully reported ${benchName} to perf aggregator`));
+}
+
+function reportError(done) {
+ return (e) => {
+ console.log("Error with fetching. Likely could not connect to aggegator server", e.message);
+ if (fail_on_no_perf) {
+ expect(e).toBeUndefined();
+ }
+ done();
+ };
+}
\ No newline at end of file