|
|
|
|
# SafetyNet - Performance regression detection for PDFium
|
|
|
|
|
|
|
|
|
|
[TOC]
|
|
|
|
|
|
|
|
|
|
This document explains how to use SafetyNet to detect performance regressions
|
|
|
|
|
in PDFium.
|
|
|
|
|
|
|
|
|
|
## Comparing performance of two versions of PDFium
|
|
|
|
|
|
|
|
|
|
safetynet_compare.py is a script that compares the performance between two
|
|
|
|
|
versions of pdfium. This can be used to verify if a given change has caused
|
|
|
|
|
or will cause any positive or negative changes in performance for a set of test
|
|
|
|
|
cases.
|
|
|
|
|
|
|
|
|
|
The supported profilers are exclusive to Linux, so for now this can only be run
|
|
|
|
|
on Linux.
|
|
|
|
|
|
|
|
|
|
An illustrative example is below, comparing the local code version to an older
|
|
|
|
|
version. Positive % changes mean an increase in time/instructions to run the
|
|
|
|
|
test - a regression, while negative % changes mean a decrease in
|
|
|
|
|
time/instructions, therefore an improvement.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
$ testing/tools/safetynet_compare.py ~/test_pdfs --branch-before beef5e4
|
|
|
|
|
================================================================================
|
|
|
|
|
% Change Time after Test case
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
-0.1980% 45,703,820,326 ~/test_pdfs/PDF Reference 1-7.pdf
|
|
|
|
|
-0.5678% 42,038,814 ~/test_pdfs/Page 24 - PDF Reference 1-7.pdf
|
|
|
|
|
+0.2666% 10,983,158,809 ~/test_pdfs/Rival.pdf
|
|
|
|
|
+0.0447% 10,413,890,748 ~/test_pdfs/dynamic.pdf
|
|
|
|
|
-7.7228% 26,161,171 ~/test_pdfs/encrypted1234.pdf
|
|
|
|
|
-0.2763% 102,084,398 ~/test_pdfs/ghost.pdf
|
|
|
|
|
-3.7005% 10,800,642,262 ~/test_pdfs/musician.pdf
|
|
|
|
|
-0.2266% 45,691,618,789 ~/test_pdfs/no_metadata.pdf
|
|
|
|
|
+1.4440% 38,442,606,162 ~/test_pdfs/test7.pdf
|
|
|
|
|
+0.0335% 9,286,083 ~/test_pdfs/testbulletpoint.pdf
|
|
|
|
|
================================================================================
|
|
|
|
|
Test cases run: 10
|
|
|
|
|
Failed to measure: 0
|
|
|
|
|
Regressions: 0
|
|
|
|
|
Improvements: 2
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Usage
|
|
|
|
|
|
|
|
|
|
Run the safetynet_compare.py script in testing/tools to perform a comparison.
|
|
|
|
|
Pass one or more paths with test cases - each path can be either a .pdf file or
|
|
|
|
|
a directory containing .pdf files. Other files in those directories are
|
|
|
|
|
ignored.
|
|
|
|
|
|
|
|
|
|
The following comparison modes are supported:
|
|
|
|
|
|
|
|
|
|
1. Compare uncommitted changes against clean branch:
|
|
|
|
|
```shell
|
|
|
|
|
$ testing/tools/safetynet_compare.py path/to/pdfs
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2. Compare current branch with another branch or commit:
|
|
|
|
|
```shell
|
|
|
|
|
$ testing/tools/safetynet_compare.py path/to/pdfs --branch-before another_branch
|
|
|
|
|
$ testing/tools/safetynet_compare.py path/to/pdfs --branch-before 1a3c5e7
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
3. Compare two other branches or commits:
|
|
|
|
|
```shell
|
|
|
|
|
$ testing/tools/safetynet_compare.py path/to/pdfs --branch-after another_branch --branch-before yet_another_branch
|
|
|
|
|
$ testing/tools/safetynet_compare.py path/to/pdfs --branch-after 1a3c5e7 --branch-before 0b2d4f6
|
|
|
|
|
$ testing/tools/safetynet_compare.py path/to/pdfs --branch-after another_branch --branch-before 0b2d4f6
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
4. Compare two build flag configurations:
|
|
|
|
|
```shell
|
|
|
|
|
$ gn args out/BuildConfig1
|
|
|
|
|
$ gn args out/BuildConfig2
|
|
|
|
|
$ testing/tools/safetynet_compare.py path/to/pdfs --build-dir out/BuildConfig2 --build-dir-before out/BuildConfig1
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
safetynet_compare.py takes care of checking out the appropriate branch, building
|
|
|
|
|
it, running the test cases and comparing results.
|
|
|
|
|
|
|
|
|
|
### Profilers
|
|
|
|
|
|
|
|
|
|
safetynet_compare.py uses callgrind as a profiler by default. Use --profiler
|
|
|
|
|
to specify another one. The supported ones are:
|
|
|
|
|
|
|
|
|
|
#### perfstat
|
|
|
|
|
|
|
|
|
|
Only works on Linux.
|
|
|
|
|
Make sure you have perf by typing in the terminal:
|
|
|
|
|
```shell
|
|
|
|
|
$ perf
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This is a fast profiler, but uses sampling so it's slightly inaccurate.
|
|
|
|
|
Expect variations of up to 1%, which is below the cutoff to consider a
|
|
|
|
|
change significant.
|
|
|
|
|
|
|
|
|
|
Use this when running over large test sets to get good enough results.
|
|
|
|
|
|
|
|
|
|
#### callgrind
|
|
|
|
|
|
|
|
|
|
Only works on Linux.
|
|
|
|
|
Make sure valgrind is installed:
|
|
|
|
|
```shell
|
|
|
|
|
$ valgrind
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Add `ro_segment_workaround_for_valgrind=true` to `args.gn` for symbols to appear
|
|
|
|
|
correctly.
|
|
|
|
|
|
|
|
|
|
This is a slow and accurate profiler. Expect variations of around 100
|
|
|
|
|
instructions. However, this takes about 50 times longer to run than perf stat.
|
|
|
|
|
|
|
|
|
|
Use this when looking for small variations (< 1%).
|
|
|
|
|
|
|
|
|
|
One advantage is that callgrind can generate `callgrind.out` files (by passing
|
|
|
|
|
--output-dir to safetynet_compare.py), which contain profiling information that
|
|
|
|
|
can be analyzed to find the cause of a regression. KCachegrind is a good
|
|
|
|
|
visualizer for these files.
|
|
|
|
|
|
|
|
|
|
#### none
|
|
|
|
|
|
|
|
|
|
Run without any profiler, giving a performance score of 1 always. useful for
|
|
|
|
|
running image comparisons or debugging the script.
|
|
|
|
|
|
|
|
|
|
### Common Options
|
|
|
|
|
|
|
|
|
|
Arguments commonly passed to safetynet_compare.py.
|
|
|
|
|
|
|
|
|
|
* --profiler: described above.
|
|
|
|
|
* --build-dir: this specified the build config with a relative path from the
|
|
|
|
|
pdfium src directory to the build directory. Defaults to out/Release.
|
|
|
|
|
* --output-dir: where to place the profiling output files. These are
|
|
|
|
|
callgrind.out.[test_case] files for callgrind, perfstat does not produce them.
|
|
|
|
|
By default they are not written.
|
|
|
|
|
* --case-order: sort test case results according to this metric. Can be "after",
|
|
|
|
|
"before", "ratio" and "rating". If not specified, sort by path.
|
|
|
|
|
* --this-repo: use the repository where the script is instead of checking out a
|
|
|
|
|
temporary one. This is faster and does not require downloads. Although it
|
|
|
|
|
restores the state of the local repo, if the script is killed or crashes the
|
|
|
|
|
uncommitted changes can remain stashed and you may be on another branch.
|
|
|
|
|
|
|
|
|
|
### Other Options
|
|
|
|
|
|
|
|
|
|
Most of the time these don't need to be used.
|
|
|
|
|
|
|
|
|
|
* --build-dir-before: if comparing different build dirs (say, to test what a
|
|
|
|
|
flag flip does), specify the build dir for the “before” branch here and the
|
|
|
|
|
build dir for the “after” branch with --build-dir.
|
|
|
|
|
* --interesting-section: only the interesting section should be measured instead
|
|
|
|
|
of all the execution of the test harness. This only works in debug, since in
|
|
|
|
|
release the delimiters are stripped out. This does not work to compare branches
|
|
|
|
|
that don’t have the callgrind delimiters, as it would otherwise be unfair to
|
|
|
|
|
compare a whole run vs the interesting section of another run.
|
|
|
|
|
* --machine-readable: output a json with the results that is easier to read by
|
|
|
|
|
code.
|
|
|
|
|
* --num-workers: how many workers to use to parallelize test case runs. Defaults
|
|
|
|
|
to # of CPUs in the machine.
|
|
|
|
|
* --threshold-significant: highlight differences that exceed this value.
|
|
|
|
|
Defaults to 0.02.
|
|
|
|
|
* --tmp-dir: directory in which temporary repos will be cloned and downloads
|
|
|
|
|
will be cached, if --this-repo is not enabled. Defaults to /tmp.
|
|
|
|
|
|
|
|
|
|
## Setup a nightly job
|
|
|
|
|
|
|
|
|
|
Create a separate checkout of pdfium in a new directory, for example `~/job`.
|
|
|
|
|
The safetynet_job.py script will run from this directory. This checkout needs to
|
|
|
|
|
be `git pull`'ed when there are changes to the SafetyNet scripts, but otherwise
|
|
|
|
|
it can be left alone.
|
|
|
|
|
|
|
|
|
|
Create a directory to contain the job results, for example `~/job_results`. In
|
|
|
|
|
each run, a `.log` file with the results will be written to this directory and a
|
|
|
|
|
subdirectory will be created with the other artifacts.
|
|
|
|
|
|
|
|
|
|
Setup a cron job to run safetynet_job.py nightly. The example below runs it at
|
|
|
|
|
1:42 AM, over the corpus in two directories: `~/pdf_samples/thousand_pdfs` and
|
|
|
|
|
`~/pdf_samples/i18n`
|
|
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
@ crontab -e
|
|
|
|
|
42 1 * * * bash -lc '~/job/pdfium/testing/tools/safetynet_job.py ~/job_results ~/pdf_samples/thousand_pdfs ~/pdf_samples/i18n --output-to-log >> ~/job_results/cron_nightly.log 2>&1'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The first time the job runs, it will just create a checkpoint as
|
|
|
|
|
`~/job_results/last_revision_covered`. From then on, since a checkpoint is
|
|
|
|
|
available, each run will compare performance with the last checkpoint and update
|
|
|
|
|
the checkpoint.
|
|
|
|
|
|
|
|
|
|
## Run image comparison
|
|
|
|
|
|
|
|
|
|
Pass the `--png-dir` option pointing at an output directory to compare the output
|
|
|
|
|
images from rendering the "before" and the "after" branches with pdfium_test.
|
|
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
$ mkdir ~/output_images
|
|
|
|
|
$ testing/tools/safetynet_compare.py ~/pdf_samples --branch-before before_visual_changes --branch-after after_visual_changes --png-dir ~/output_images
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This will output and automatically open a `~/output_images/compare.html` file
|
|
|
|
|
showing the before/after and the diff. Hover the mouse cursor over the
|
|
|
|
|
before/after image on the left for an easier visual comparison. The "before"
|
|
|
|
|
image is displayed until the cursor hovers over the image, which is then
|
|
|
|
|
replaced with the "after" image.
|
|
|
|
|
|
|
|
|
|
It is recommended to use `--profiler=none` with this option.
|