├── .ci ├── azure-pipelines.yml ├── esy-build-steps.yml ├── publish-build-cache.yml ├── restore-build-cache.yml └── use-node.yml ├── .gitignore ├── README.md ├── azure-pipelines.yml ├── dune-project ├── package.json ├── reperf-sample-tests.opam ├── reperf.opam ├── src ├── Config.re ├── Options.re ├── Reperf.re ├── Reporter.re ├── Result.re └── dune └── test ├── exe ├── Bench.re └── dune └── lib ├── TestAppend.re ├── TestFramework.re └── dune /.ci/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | name: $(Build.SourceVersion) 7 | jobs: 8 | - job: Linux 9 | timeoutInMinutes: 0 10 | pool: 11 | vmImage: 'Ubuntu 16.04' 12 | 13 | variables: 14 | STAGING_DIRECTORY: /home/vsts/STAGING 15 | STAGING_DIRECTORY_UNIX: /home/vsts/STAGING 16 | ESY__CACHE_INSTALL_PATH: /home/vsts/.esy/3_____________________________________________________________________/i 17 | ESY__CACHE_SOURCE_TARBALL_PATH: /home/vsts/.esy/source/i 18 | # ESY__NPM_ROOT: /opt/hostedtoolcache/node/8.14.0/x64/lib/node_modules/esy 19 | 20 | steps: 21 | - template: .ci/use-node.yml 22 | - template: .ci/restore-build-cache.yml 23 | - template: .ci/esy-build-steps.yml 24 | - template: .ci/publish-build-cache.yml 25 | 26 | - job: MacOS 27 | timeoutInMinutes: 0 28 | pool: 29 | vmImage: 'macOS 10.13' 30 | 31 | variables: 32 | STAGING_DIRECTORY: /Users/vsts/STAGING 33 | STAGING_DIRECTORY_UNIX: /Users/vsts/STAGING 34 | ESY__CACHE_INSTALL_PATH: /Users/vsts/.esy/3____________________________________________________________________/i 35 | ESY__CACHE_SOURCE_TARBALL_PATH: /Users/vsts/.esy/source/i 36 | # ESY__NPM_ROOT: /usr/local/lib/node_modules/esy 37 | 38 | steps: 39 | - template: .ci/use-node.yml 40 | - template: .ci/restore-build-cache.yml 41 | - template: .ci/esy-build-steps.yml 42 | - template: .ci/publish-build-cache.yml 43 | 44 | - job: Windows 45 | timeoutInMinutes: 0 46 | pool: 47 | vmImage: 'vs2017-win2016' 48 | 49 | variables: 50 | STAGING_DIRECTORY: C:\Users\VssAdministrator\STAGING 51 | STAGING_DIRECTORY_UNIX: /C/Users/VssAdministrator/STAGING 52 | ESY__CACHE_INSTALL_PATH: /C/Users/VssAdministrator/.esy/3_/i 53 | ESY__CACHE_SOURCE_TARBALL_PATH: /C/Users/VssAdministrator/.esy/source/i 54 | # ESY__NPM_ROOT: /C/npm/prefix/node_modules/esy 55 | 56 | steps: 57 | - template: .ci/use-node.yml 58 | - template: .ci/restore-build-cache.yml 59 | - template: .ci/esy-build-steps.yml 60 | - template: .ci/publish-build-cache.yml 61 | 62 | - job: Release 63 | timeoutInMinutes: 0 64 | displayName: Release 65 | dependsOn: 66 | - Linux 67 | - MacOS 68 | - Windows 69 | condition: succeeded() 70 | pool: 71 | vmImage: ubuntu-16.04 72 | steps: 73 | - task: PublishBuildArtifacts@1 74 | displayName: 'Release Package' 75 | inputs: 76 | PathtoPublish: '.' 77 | ArtifactName: npm-package 78 | -------------------------------------------------------------------------------- /.ci/esy-build-steps.yml: -------------------------------------------------------------------------------- 1 | # Cross-platform set of build steps for building esy projects 2 | 3 | steps: 4 | - script: npm install -g esy@0.4.3 5 | displayName: 'npm install -g esy@0.4.3' 6 | - script: esy install 7 | displayName: 'esy install' 8 | - script: esy build 9 | displayName: 'esy build' 10 | # Run tests or any additional steps here 11 | # - script: esy b dune runtest 12 | -------------------------------------------------------------------------------- /.ci/publish-build-cache.yml: -------------------------------------------------------------------------------- 1 | # Steps for publishing project cache 2 | 3 | steps: 4 | - bash: 'mkdir -p $(STAGING_DIRECTORY_UNIX)' 5 | condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 6 | displayName: '[Cache][Publish] Create cache directory' 7 | 8 | - bash: 'cd $(ESY__CACHE_INSTALL_PATH) && tar -czf $(STAGING_DIRECTORY_UNIX)/esy-cache.tar .' 9 | workingDirectory: '' 10 | condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 11 | displayName: '[Cache][Publish] Tar esy cache directory' 12 | 13 | # - bash: 'cd $(ESY__NPM_ROOT) && tar -czf $(STAGING_DIRECTORY_UNIX)/npm-cache.tar .' 14 | # condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 15 | # displayName: '[Cache][Publish] Tar npm cache directory' 16 | 17 | - task: PublishBuildArtifacts@1 18 | displayName: '[Cache][Publish] Upload tarball' 19 | condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 20 | inputs: 21 | pathToPublish: '$(STAGING_DIRECTORY)' 22 | artifactName: 'cache-$(Agent.OS)-install' 23 | parallel: true 24 | parallelCount: 8 25 | -------------------------------------------------------------------------------- /.ci/restore-build-cache.yml: -------------------------------------------------------------------------------- 1 | # Steps for restoring project cache 2 | 3 | steps: 4 | - task: DownloadBuildArtifacts@0 5 | condition: and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/master')) 6 | displayName: '[Cache][Restore] Restore install' 7 | inputs: 8 | buildType: 'specific' 9 | project: '$(System.TeamProject)' 10 | pipeline: '$(Build.DefinitionName)' 11 | branchName: 'refs/heads/master' 12 | buildVersionToDownload: 'latestFromBranch' 13 | downloadType: 'single' 14 | artifactName: 'cache-$(Agent.OS)-install' 15 | downloadPath: '$(STAGING_DIRECTORY)' 16 | continueOnError: true 17 | 18 | - bash: 'mkdir -p $(ESY__CACHE_INSTALL_PATH)' 19 | condition: and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/master')) 20 | displayName: '[Cache][Restore] Create cache directory' 21 | 22 | # - bash: 'cd $(ESY__NPM_ROOT) && tar -xf $(STAGING_DIRECTORY_UNIX)/cache-$(Agent.OS)-install/npm-cache.tar -C .' 23 | # continueOnError: true 24 | # condition: and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/master')) 25 | # displayName: '[Cache][Restore] Untar npm cache directory' 26 | 27 | - bash: 'cd $(ESY__CACHE_INSTALL_PATH) && tar -xf $(STAGING_DIRECTORY_UNIX)/cache-$(Agent.OS)-install/esy-cache.tar -C .' 28 | continueOnError: true 29 | condition: and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/master')) 30 | displayName: '[Cache][Restore] Untar esy cache directory' 31 | 32 | - bash: 'rm -rf *' 33 | continueOnError: true 34 | workingDirectory: '$(STAGING_DIRECTORY)' 35 | condition: and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/master')) 36 | displayName: '[Cache][Restore] Clean up' 37 | -------------------------------------------------------------------------------- /.ci/use-node.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: NodeTool@0 3 | displayName: 'Use Node 8.x' 4 | inputs: 5 | versionSpec: 8.x 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # OCaml / Reason 64 | .merlin 65 | *.install 66 | 67 | _esy/ 68 | esy.lock/ 69 | _build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reperf 2 | 3 | ### Native Reason + JSOO Cross-Platform Performance Benchmarking Tools 4 | 5 | ## Why? 6 | 7 | A key value proposition of native [Reason](https://reasonml.github.io) is building _fast_, _native_ apps. 8 | 9 | However, apps and tools can often start out fast when they are simple - but decay as more features and complexity are added. 10 | 11 | `reperf` is inspired by the [core_bench](https://github.com/janestreet/core_bench) tools from Janestreet. Unfortunately, at least at time of writing, the `core_bench` OPAM package does not work on Windows. (Or rather - its dependencies, like [`spawn`](https://opam.ocaml.org/packages/spawn/) do not work on Windows). 12 | 13 | ## Features 14 | 15 | `reperf` helps with the following: 16 | - __Timing__ - how much time am I spending in a code block? 17 | - __Call count__ - how often is this code-path being called? 18 | - __Allocations__ - how is a code-block impacting the garbage collector? 19 | 20 | In addition, `reperf` supports _benchmarks_, which are test cases that exercise performance scenarios. `reperf` can output a JSON performance report, and compare it with previous iterations - and fail if a regression is detected. This helps you keep your app fast! 21 | 22 | ## Usage 23 | 24 | ## License 25 | 26 | [MIT License](LICENSE) 27 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | name: $(Build.SourceVersion) 7 | jobs: 8 | - job: Linux 9 | timeoutInMinutes: 0 10 | pool: 11 | vmImage: 'Ubuntu 16.04' 12 | 13 | variables: 14 | STAGING_DIRECTORY: /home/vsts/STAGING 15 | STAGING_DIRECTORY_UNIX: /home/vsts/STAGING 16 | ESY__CACHE_INSTALL_PATH: /home/vsts/.esy/3_____________________________________________________________________/i 17 | ESY__CACHE_SOURCE_TARBALL_PATH: /home/vsts/.esy/source/i 18 | # ESY__NPM_ROOT: /opt/hostedtoolcache/node/8.14.0/x64/lib/node_modules/esy 19 | 20 | steps: 21 | - template: .ci/use-node.yml 22 | - template: .ci/restore-build-cache.yml 23 | - template: .ci/esy-build-steps.yml 24 | - template: .ci/publish-build-cache.yml 25 | 26 | - job: MacOS 27 | timeoutInMinutes: 0 28 | pool: 29 | vmImage: 'macOS 10.13' 30 | 31 | variables: 32 | STAGING_DIRECTORY: /Users/vsts/STAGING 33 | STAGING_DIRECTORY_UNIX: /Users/vsts/STAGING 34 | ESY__CACHE_INSTALL_PATH: /Users/vsts/.esy/3____________________________________________________________________/i 35 | ESY__CACHE_SOURCE_TARBALL_PATH: /Users/vsts/.esy/source/i 36 | # ESY__NPM_ROOT: /usr/local/lib/node_modules/esy 37 | 38 | steps: 39 | - template: .ci/use-node.yml 40 | - template: .ci/restore-build-cache.yml 41 | - template: .ci/esy-build-steps.yml 42 | - template: .ci/publish-build-cache.yml 43 | 44 | - job: Windows 45 | timeoutInMinutes: 0 46 | pool: 47 | vmImage: 'vs2017-win2016' 48 | 49 | variables: 50 | STAGING_DIRECTORY: C:\Users\VssAdministrator\STAGING 51 | STAGING_DIRECTORY_UNIX: /C/Users/VssAdministrator/STAGING 52 | ESY__CACHE_INSTALL_PATH: /C/Users/VssAdministrator/.esy/3_/i 53 | ESY__CACHE_SOURCE_TARBALL_PATH: /C/Users/VssAdministrator/.esy/source/i 54 | # ESY__NPM_ROOT: /C/npm/prefix/node_modules/esy 55 | 56 | steps: 57 | - template: .ci/use-node.yml 58 | - template: .ci/restore-build-cache.yml 59 | - template: .ci/esy-build-steps.yml 60 | - template: .ci/publish-build-cache.yml 61 | 62 | - job: Release 63 | timeoutInMinutes: 0 64 | displayName: Release 65 | dependsOn: 66 | - Linux 67 | - MacOS 68 | - Windows 69 | condition: succeeded() 70 | pool: 71 | vmImage: ubuntu-16.04 72 | steps: 73 | - task: PublishBuildArtifacts@1 74 | displayName: 'Release Package' 75 | inputs: 76 | PathtoPublish: '.' 77 | ArtifactName: npm-package 78 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reperf", 3 | "version": "1.5.1", 4 | "description": "Native performance tools", 5 | "license": "MIT", 6 | "esy": { 7 | "buildsInSource": "_build", 8 | "build": "dune build -p reperf", 9 | "install": [ 10 | "esy-installer reperf.install" 11 | ] 12 | }, 13 | "dependencies": { 14 | "@opam/dune": "*", 15 | "@opam/printbox": "*", 16 | "@opam/reason": "^3.4.0", 17 | "ocaml": ">=4.4.0" 18 | }, 19 | "devDependencies": { 20 | "@opam/merlin": "*", 21 | "ocaml": "~4.11" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /reperf-sample-tests.opam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryphe/reperf/68ef2f96899c09e6ac7d929b0375f7a806aee067/reperf-sample-tests.opam -------------------------------------------------------------------------------- /reperf.opam: -------------------------------------------------------------------------------- 1 | opam-version: "1.2" 2 | version: "dev" 3 | maintainer: "bryphe@outlook.com" 4 | author: ["Bryan Phelps"] 5 | build: [ 6 | 7 | ] -------------------------------------------------------------------------------- /src/Config.re: -------------------------------------------------------------------------------- 1 | /* 2 | * Config.re 3 | * 4 | * Configuration for a Reperf session 5 | */ 6 | 7 | type t = { 8 | snapshotDir: string, 9 | options: Options.t, 10 | }; 11 | 12 | let create = (~snapshotDir, ~options=Options.create(), ()) => { 13 | let ret: t = {snapshotDir, options}; 14 | ret; 15 | }; 16 | -------------------------------------------------------------------------------- /src/Options.re: -------------------------------------------------------------------------------- 1 | /* 2 | * Options.re 3 | */ 4 | 5 | type t = {iterations: int}; 6 | 7 | let create = (~iterations=1000000, ()) => { 8 | let ret: t = {iterations: iterations}; 9 | ret; 10 | }; 11 | -------------------------------------------------------------------------------- /src/Reperf.re: -------------------------------------------------------------------------------- 1 | module Config = Config; 2 | module Options = Options; 3 | 4 | module type Args = {let config: Config.t;}; 5 | 6 | module Make = (Config: Args) => { 7 | // open Pastel; 8 | 9 | module Benchmark = { 10 | type benchmarkFunction = unit => Result.t; 11 | 12 | type t = { 13 | name: string, 14 | f: benchmarkFunction, 15 | }; 16 | }; 17 | 18 | let _benchmarks: ref(list(Benchmark.t)) = ref([]); 19 | 20 | let _getTime = Unix.gettimeofday; 21 | 22 | type setupFunction('a) = unit => 'a; 23 | type testFunction('a) = 'a => unit; 24 | 25 | /* let defaultSetupFunction: setupFunction(unit) = () => (); */ 26 | 27 | let bench = 28 | (~name: string, ~setup:setupFunction('a), ~f: testFunction('a), ~options=Config.config.options, ()) => { 29 | let newCase = () => { 30 | let opts = options; 31 | let iter = () => { 32 | /* Garbage collect to clear out env */ 33 | 34 | let setupValue = setup(); 35 | 36 | Gc.full_major(); 37 | let beforeState = Gc.quick_stat(); 38 | let startTime = _getTime(); 39 | let count = ref(0); 40 | while (count^ < opts.iterations) { 41 | f(setupValue); 42 | count := count^ + 1; 43 | }; 44 | let endTime = _getTime(); 45 | 46 | let afterState = Gc.quick_stat(); 47 | let result: Result.t = { 48 | name, 49 | iterations: opts.iterations, 50 | time: endTime -. startTime, 51 | minorWords: int_of_float(afterState.minor_words) - int_of_float(beforeState.minor_words), 52 | promotedWords: int_of_float(afterState.promoted_words) - int_of_float(beforeState.promoted_words), 53 | majorWords: int_of_float(afterState.major_words) - int_of_float(beforeState.major_words), 54 | minorCollections: 55 | afterState.minor_collections - beforeState.minor_collections, 56 | majorCollections: afterState.major_collections - beforeState.major_collections, 57 | }; 58 | 59 | result; 60 | }; 61 | 62 | iter(); 63 | }; 64 | 65 | let benchmark: Benchmark.t = {name, f: newCase}; 66 | 67 | _benchmarks := List.append([benchmark], _benchmarks^); 68 | }; 69 | 70 | let cli = () => { 71 | /* print_endline ({"hello"}); */ 72 | /* print_endline ("Cases: " ++ string_of_int(List.length(_benchmarks^))); */ 73 | 74 | let benchmarks = List.rev(_benchmarks^); 75 | let results = List.map((t: Benchmark.t) => t.f(), benchmarks); 76 | 77 | Reporter.print(results); 78 | (); 79 | }; 80 | }; 81 | -------------------------------------------------------------------------------- /src/Reporter.re: -------------------------------------------------------------------------------- 1 | let pad = s => " " ++ s ++ " "; 2 | 3 | let print = (results: list(Result.t)) => { 4 | let header = [| 5 | " BENCHMARK ", 6 | " ITERATIONS ", 7 | " TIME ", 8 | " MINOR GC ", 9 | " MAJOR GC ", 10 | " MINOR ALLOC ", 11 | " PROMOTED ", 12 | " MAJOR ALLOC " 13 | |]; 14 | 15 | let rows = 16 | results 17 | |> List.map((v: Result.t) => 18 | [| 19 | pad(v.name), 20 | pad(string_of_int(v.iterations)), 21 | pad(string_of_float(v.time)), 22 | pad(string_of_int(v.minorCollections)), 23 | pad(string_of_int(v.majorCollections)), 24 | pad(string_of_int(v.minorWords)), 25 | pad(string_of_int(v.promotedWords)), 26 | pad(string_of_int(v.majorWords)), 27 | |] 28 | ) 29 | |> Array.of_list; 30 | 31 | let table = Array.append([|header|], rows); 32 | PrintBox.(frame(grid_text(table))) |> PrintBox_text.output(stdout); 33 | }; 34 | -------------------------------------------------------------------------------- /src/Result.re: -------------------------------------------------------------------------------- 1 | type t = { 2 | name: string, 3 | 4 | /* Number of times the test was run */ 5 | iterations: int, 6 | time: float, 7 | minorWords: int, 8 | promotedWords: int, 9 | majorWords: int, 10 | minorCollections: int, 11 | majorCollections: int, 12 | /* TODO: Counters */ 13 | /* TODO: Timings */ 14 | }; 15 | 16 | let create = 17 | ( 18 | ~name, 19 | ~iterations, 20 | ~time, 21 | ~minorWords, 22 | ~promotedWords, 23 | ~majorWords, 24 | ~minorCollections, 25 | ~majorCollections, 26 | ) => { 27 | name, 28 | iterations, 29 | time, 30 | minorWords, 31 | promotedWords, 32 | majorWords, 33 | minorCollections, 34 | majorCollections, 35 | }; 36 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name reperf) 3 | (flags (-w -40 -w +26)) 4 | (libraries unix printbox) 5 | (public_name reperf.lib)) 6 | -------------------------------------------------------------------------------- /test/exe/Bench.re: -------------------------------------------------------------------------------- 1 | ReperfSampleTests.TestFramework.cli(); 2 | -------------------------------------------------------------------------------- /test/exe/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name bench) 3 | (public_name bench) 4 | (libraries reperf-sample-tests.lib) 5 | (package reperf-sample-tests) 6 | ) 7 | -------------------------------------------------------------------------------- /test/lib/TestAppend.re: -------------------------------------------------------------------------------- 1 | open TestFramework; 2 | 3 | let arrayAppend = () => { 4 | let _ = Array.append([|"a"|], [|"b"|]); 5 | (); 6 | }; 7 | 8 | let listAppend = () => { 9 | let _ = List.append(["a"], ["b"]); 10 | (); 11 | }; 12 | 13 | bench(~name="Append: Array", ~f=arrayAppend, ()); 14 | 15 | bench(~name="Append: List", ~f=listAppend, ()); 16 | -------------------------------------------------------------------------------- /test/lib/TestFramework.re: -------------------------------------------------------------------------------- 1 | include Reperf.Make({ 2 | let config = 3 | Reperf.Config.create(~snapshotDir="test/lib/__snapshots__", ()); 4 | }); 5 | -------------------------------------------------------------------------------- /test/lib/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name ReperfSampleTests) 3 | (public_name reperf-sample-tests.lib) 4 | (ocamlopt_flags -linkall) 5 | (libraries reperf.lib) 6 | ) 7 | --------------------------------------------------------------------------------