├── .eslintrc.json
├── .github
└── workflows
│ ├── ci.yml
│ ├── codspeed.yml
│ └── release.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .moon
├── tasks.yml
├── toolchain.yml
└── workspace.yml
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc.json
├── .vscode
├── c_cpp_properties.json
├── launch.json
└── settings.json
├── LICENSE
├── README.md
├── commitlint.config.js
├── docs
└── introspection.md
├── examples
├── with-javascript-cjs
│ ├── benchmark-js.js
│ ├── package.json
│ └── tinybench.js
├── with-javascript-esm
│ ├── benchmark-js.js
│ ├── package.json
│ └── tinybench.js
├── with-typescript-cjs
│ ├── bench
│ │ ├── benchmark.js
│ │ │ ├── fibo.bench.ts
│ │ │ ├── foobarbaz.bench.ts
│ │ │ └── index.bench.ts
│ │ └── tinybench
│ │ │ ├── fibo.bench.ts
│ │ │ ├── foobarbaz.bench.ts
│ │ │ └── index.bench.ts
│ ├── package.json
│ ├── src
│ │ ├── fibonacci.ts
│ │ └── foobarbaz.ts
│ └── tsconfig.json
├── with-typescript-esm
│ ├── bench
│ │ ├── benchmark.js
│ │ │ ├── fibo.bench.ts
│ │ │ ├── foobarbaz.bench.ts
│ │ │ └── index.bench.ts
│ │ └── tinybench
│ │ │ ├── fibo.bench.ts
│ │ │ ├── foobarbaz.bench.ts
│ │ │ └── index.bench.ts
│ ├── package.json
│ ├── src
│ │ ├── fibonacci.bench.ts
│ │ ├── fibonacci.test.ts
│ │ ├── fibonacci.ts
│ │ └── foobarbaz.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── with-typescript-simple-cjs
│ ├── benchmark-js.ts
│ ├── package.json
│ └── tinybench.ts
└── with-typescript-simple-esm
│ ├── benchmark-js.ts
│ ├── package.json
│ └── tinybench.ts
├── lerna.json
├── package.json
├── packages
├── benchmark.js-plugin
│ ├── README.md
│ ├── babel.config.js
│ ├── benches
│ │ ├── parsePr.ts
│ │ └── sample.ts
│ ├── jest.config.integ.js
│ ├── jest.config.js
│ ├── moon.yml
│ ├── package.json
│ ├── rollup.config.ts
│ ├── src
│ │ ├── __tests__
│ │ │ └── buildSuiteAdd.test.ts
│ │ ├── buildSuiteAdd.ts
│ │ ├── getCallingFile.ts
│ │ ├── index.ts
│ │ └── types.ts
│ ├── tests
│ │ ├── __snapshots__
│ │ │ └── index.integ.test.ts.snap
│ │ ├── index.integ.test.ts
│ │ ├── registerBenchmarks.ts
│ │ └── registerOtherBenchmarks.ts
│ ├── tsconfig.json
│ └── tsconfig.test.json
├── core
│ ├── .gitignore
│ ├── README.md
│ ├── binding.gyp
│ ├── jest.config.integ.js
│ ├── jest.config.js
│ ├── moon.yml
│ ├── package.json
│ ├── rollup.config.ts
│ ├── src
│ │ ├── index.ts
│ │ ├── introspection.ts
│ │ ├── mongoMeasurement.ts
│ │ ├── native_core
│ │ │ ├── index.ts
│ │ │ ├── linux_perf
│ │ │ │ ├── linux_perf.cc
│ │ │ │ ├── linux_perf.h
│ │ │ │ ├── linux_perf.ts
│ │ │ │ ├── linux_perf_listener.cc
│ │ │ │ └── utils.h
│ │ │ ├── measurement
│ │ │ │ ├── measurement.cc
│ │ │ │ ├── measurement.h
│ │ │ │ └── measurement.ts
│ │ │ └── native_core.cc
│ │ ├── optimization.ts
│ │ └── utils.ts
│ ├── tests
│ │ └── index.integ.test.ts
│ ├── tracer.spec.json
│ ├── tsconfig.json
│ └── tsconfig.test.json
├── tinybench-plugin
│ ├── README.md
│ ├── benches
│ │ ├── parsePr.ts
│ │ └── sample.ts
│ ├── jest.config.integ.js
│ ├── jest.config.js
│ ├── moon.yml
│ ├── package.json
│ ├── rollup.config.ts
│ ├── src
│ │ └── index.ts
│ ├── tests
│ │ ├── __snapshots__
│ │ │ └── index.integ.test.ts.snap
│ │ ├── index.integ.test.ts
│ │ ├── registerBenchmarks.ts
│ │ └── registerOtherBenchmarks.ts
│ ├── tsconfig.json
│ └── tsconfig.test.json
└── vitest-plugin
│ ├── README.md
│ ├── benches
│ ├── flat.bench.ts
│ ├── hooks.bench.ts
│ ├── parsePr.bench.ts
│ └── parsePr.ts
│ ├── moon.yml
│ ├── package.json
│ ├── rollup.config.ts
│ ├── src
│ ├── __tests__
│ │ ├── globalSetup.test.ts
│ │ ├── index.test.ts
│ │ └── runner.test.ts
│ ├── globalSetup.ts
│ ├── index.ts
│ └── runner.ts
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── vitest.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── rollup.options.js
├── scripts
└── release.sh
├── tsconfig.base.json
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": [
4 | "eslint:recommended",
5 | "plugin:@typescript-eslint/recommended",
6 | "plugin:import/recommended",
7 | "plugin:import/typescript"
8 | ],
9 | "ignorePatterns": [
10 | "**/dist/**",
11 | "**/node_modules/**",
12 | "**/rollup.config.ts",
13 | "**/jest.config.js"
14 | ],
15 | "settings": {
16 | "import/parsers": {
17 | "@typescript-eslint/parser": [".ts", ".tsx"]
18 | },
19 | "import/resolver": {
20 | "typescript": {
21 | "alwaysTryTypes": true,
22 | "project": [
23 | "tsconfig.json",
24 | "packages/*/tsconfig.json",
25 | "packages/*/tsconfig.*.json"
26 | ]
27 | }
28 | }
29 | },
30 | "rules": {
31 | "import/no-named-as-default": "off",
32 | "import/no-named-as-default-member": "off"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: "CI"
2 | on:
3 | push:
4 | branches:
5 | - "main"
6 | pull_request:
7 | workflow_dispatch:
8 |
9 | jobs:
10 | check:
11 | runs-on: "ubuntu-latest"
12 | steps:
13 | - uses: "actions/checkout@v4"
14 | with:
15 | fetch-depth: 0
16 | - name: Install valgrind
17 | run: |
18 | sudo apt-get update
19 | sudo apt-get install -y valgrind
20 | - uses: pnpm/action-setup@v2
21 | - uses: actions/setup-node@v3
22 | with:
23 | cache: pnpm
24 | node-version-file: .nvmrc
25 | - run: pnpm install --frozen-lockfile --prefer-offline
26 | - run: pnpm moon check --all
27 |
28 | list-examples:
29 | runs-on: "ubuntu-latest"
30 | name: List examples
31 | outputs:
32 | examples: ${{ steps.list-examples.outputs.examples }}
33 | steps:
34 | - uses: "actions/checkout@v4"
35 | # list the directories in ./examples and output them to a github action workflow variables as a JSON array
36 | - run: |
37 | examples=$(find ./examples -maxdepth 1 -mindepth 1 -type d -printf '%f\n' | jq -R -s -c 'split("\n") | map(select(length > 0))')
38 | echo "::set-output name=examples::$examples"
39 | id: list-examples
40 |
41 | node-versions:
42 | runs-on: "ubuntu-latest"
43 | name: "${{ matrix.example }} on Node ${{ matrix.node-version }}"
44 | needs: list-examples
45 | strategy:
46 | matrix:
47 | node-version: ["16", "18", "20.5.1"]
48 | example: ${{ fromJson(needs.list-examples.outputs.examples) }}
49 | fail-fast: false
50 | steps:
51 | - uses: "actions/checkout@v4"
52 | with:
53 | fetch-depth: 0
54 | - name: Install valgrind
55 | run: |
56 | sudo apt-get update
57 | sudo apt-get install -y valgrind
58 | - uses: pnpm/action-setup@v2
59 | - uses: actions/setup-node@v3
60 | with:
61 | cache: pnpm
62 | node-version: ${{ matrix.node-version }}
63 | - run: pnpm install --frozen-lockfile --prefer-offline
64 | - run: pnpm moon run :build
65 |
66 | - name: Run benchmarks with tinybench-plugin
67 | # use version from `main` branch to always test the latest version, in real projects, use a tag, like `@v2`
68 | uses: CodSpeedHQ/action@main
69 | with:
70 | run: pnpm --filter ${{ matrix.example }} bench-tinybench
71 | env:
72 | CODSPEED_SKIP_UPLOAD: true
73 | CODSPEED_DEBUG: true
74 | - name: Run benchmarks with benchmark.js-plugin
75 | # use version from `main` branch to always test the latest version, in real projects, use a tag, like `@v2`
76 | uses: CodSpeedHQ/action@main
77 | with:
78 | run: pnpm --filter ${{ matrix.example }} bench-benchmark-js
79 | env:
80 | CODSPEED_SKIP_UPLOAD: true
81 | CODSPEED_DEBUG: true
82 |
--------------------------------------------------------------------------------
/.github/workflows/codspeed.yml:
--------------------------------------------------------------------------------
1 | name: CodSpeed
2 | on:
3 | push:
4 | branches:
5 | - "main"
6 | pull_request:
7 | workflow_dispatch:
8 |
9 | jobs:
10 | codspeed:
11 | name: Run CodSpeed
12 | runs-on: "ubuntu-latest"
13 | steps:
14 | - uses: "actions/checkout@v4"
15 | with:
16 | fetch-depth: 0
17 | - name: Install valgrind
18 | run: |
19 | sudo apt-get update
20 | sudo apt-get install -y valgrind
21 | - uses: pnpm/action-setup@v2
22 | - uses: actions/setup-node@v3
23 | with:
24 | cache: pnpm
25 | node-version-file: .nvmrc
26 | - run: pnpm install --frozen-lockfile --prefer-offline
27 | - run: pnpm moon run :build
28 |
29 | - name: Run benchmarks
30 | # use version from `main` branch to always test the latest version, in real projects, use a tag, like `@v2`
31 | uses: CodSpeedHQ/action@main
32 | with:
33 | run: |
34 | pnpm moon run --concurrency 1 :bench
35 | pnpm --workspace-concurrency 1 -r bench-tinybench
36 | pnpm --workspace-concurrency 1 -r bench-benchmark-js
37 | pnpm --workspace-concurrency 1 -r bench-vitest
38 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release on tag
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 |
8 | permissions:
9 | id-token: write
10 | contents: write
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0
20 | - uses: pnpm/action-setup@v2
21 | - uses: actions/setup-node@v3
22 | with:
23 | cache: pnpm
24 | node-version-file: .nvmrc
25 | registry-url: "https://registry.npmjs.org"
26 | - run: pnpm install --frozen-lockfile --prefer-offline
27 | - name: Install valgrind
28 | run: |
29 | sudo apt-get update
30 | sudo apt-get install -y valgrind
31 |
32 | - name: Build the libraries
33 | run: pnpm moon run :build
34 |
35 | - name: Publish the libraries
36 | run: pnpm publish -r --access=public --no-git-checks
37 | env:
38 | NPM_CONFIG_PROVENANCE: true
39 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
40 |
41 | - name: Create a draft release
42 | run: |
43 | NEW_VERSION=$(pnpm lerna list --json | jq -r '.[] | select(.name == "@codspeed/core") | .version')
44 | gh release create v$NEW_VERSION --title "v$NEW_VERSION" --generate-notes -d
45 | env:
46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Serverless directories
108 | .serverless/
109 |
110 | # FuseBox cache
111 | .fusebox/
112 |
113 | # DynamoDB Local files
114 | .dynamodb/
115 |
116 | # TernJS port file
117 | .tern-port
118 |
119 | # Stores VSCode versions used for testing VSCode extensions
120 | .vscode-test
121 |
122 |
123 | pnpm-global
124 | packages/app/.env
125 |
126 | .vercel
127 | .DS_Store
128 |
129 | # moon
130 | .moon/cache
131 | .moon/docker
132 | .rollup.cache/
133 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | pnpm commitlint --edit ${1}
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | pnpm moon run :lint :format :typecheck --affected --status=staged
5 |
6 |
--------------------------------------------------------------------------------
/.moon/tasks.yml:
--------------------------------------------------------------------------------
1 | # https://moonrepo.dev/docs/config/global-project
2 | $schema: "https://moonrepo.dev/schemas/global-project.json"
3 |
4 | fileGroups:
5 | configs:
6 | - "*.config.{js,cjs,mjs,ts}"
7 | - ".eslintrc.js"
8 | - "tsconfig.*.json"
9 |
10 | sources:
11 | - "src/**/*"
12 | - "types/**/*"
13 | tests:
14 | - "tests/**/*.test.*"
15 | - "**/__tests__/**/*"
16 | dist:
17 | - "dist/**/*"
18 |
19 | tasks:
20 | format:
21 | command: "prettier --config @in(0) --ignore-path @in(1) --check ."
22 | inputs:
23 | - "/.prettierrc.json"
24 | - "/.prettierignore"
25 | - "@globs(sources)"
26 | - "@globs(tests)"
27 | - "@globs(configs)"
28 | lint:
29 | command: "eslint ."
30 | inputs:
31 | - "@globs(sources)"
32 | - "@globs(tests)"
33 | - ".eslintignore"
34 | - ".eslintrc.js"
35 | - "/.eslintrc.js"
36 | - "tsconfig.json"
37 | - "tsconfig.*.json"
38 | deps:
39 | - "build"
40 |
41 | typecheck:
42 | command: "tsc --noEmit --pretty"
43 | inputs:
44 | - "@globs(sources)"
45 | - "@globs(tests)"
46 | - "tsconfig.json"
47 | - "/tsconfig.json"
48 | - "/tsconfig.base.json"
49 | deps:
50 | - "build"
51 |
52 | build:
53 | command: "rollup -c rollup.config.ts --configPlugin typescript"
54 | inputs:
55 | - "@globs(sources)"
56 | - "rollup.config.ts"
57 | outputs:
58 | - "dist/"
59 | deps:
60 | - "^:build"
61 | env:
62 | NODE_NO_WARNINGS: "1"
63 |
64 | test:
65 | command: "jest --passWithNoTests --silent"
66 | inputs:
67 | - "@globs(sources)"
68 | - "@globs(tests)"
69 | - "@globs(configs)"
70 | - "tsconfig.json"
71 | - "/tsconfig.json"
72 | - "/tsconfig.base.json"
73 | deps:
74 | - "build"
75 | - "test/integ"
76 |
77 | test/integ:
78 | command: "jest --passWithNoTests --silent -c jest.config.integ.js"
79 | inputs:
80 | - "@globs(sources)"
81 | - "@globs(tests)"
82 | - "@globs(configs)"
83 | - "tsconfig.json"
84 | - "/tsconfig.json"
85 | - "/tsconfig.base.json"
86 | deps:
87 | - "build"
88 |
89 | clean:
90 | command: "rm -rf"
91 | args:
92 | - dist
93 | local: true
94 | options:
95 | cache: false
96 | platform: system
97 |
--------------------------------------------------------------------------------
/.moon/toolchain.yml:
--------------------------------------------------------------------------------
1 | # https://moonrepo.dev/docs/config/toolchain
2 | $schema: "https://moonrepo.dev/schemas/toolchain.json"
3 |
4 | node:
5 | packageManager: "pnpm"
6 | pnpm:
7 | version: "8.6.3"
8 |
9 | dedupeOnLockfileChange: false
10 | dependencyVersionFormat: "workspace"
11 | inferTasksFromScripts: true
12 | syncProjectWorkspaceDependencies: true
13 |
14 | typescript:
15 | createMissingConfig: false
16 | rootConfigFileName: "tsconfig.json"
17 | rootOptionsConfigFileName: "tsconfig.base.json"
18 |
19 | routeOutDirToCache: false
20 | syncProjectReferences: false
21 | syncProjectReferencesToPaths: false
22 |
--------------------------------------------------------------------------------
/.moon/workspace.yml:
--------------------------------------------------------------------------------
1 | # https://moonrepo.dev/docs/config/workspace
2 | $schema: 'https://moonrepo.dev/schemas/workspace.json'
3 |
4 | projects:
5 | - 'packages/*'
6 |
7 | vcs:
8 | manager: 'git'
9 | defaultBranch: 'main'
10 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | auto-install-peers=true
2 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 18.16.0
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .rollup.cache
3 | dist
4 | generated
5 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["prettier-plugin-organize-imports"]
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/c_cpp_properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "Linux",
5 | "includePath": [
6 | "${workspaceFolder}/**",
7 | "/usr/include/node"
8 | ],
9 | "defines": [],
10 | "compilerPath": "/usr/bin/clang",
11 | "cStandard": "c17",
12 | "cppStandard": "c++14",
13 | "intelliSenseMode": "linux-clang-x64",
14 | "configurationProvider": "ms-vscode.makefile-tools"
15 | }
16 | ],
17 | "version": 4
18 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "type": "node",
6 | "request": "attach",
7 | "name": "Attach to Node",
8 | "port": 9229
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "C_Cpp.default.configurationProvider": "ms-vscode.makefile-tools",
3 | "makefile.makeDirectory": "${workspaceRoot}/packages/core/build/",
4 | "files.exclude": {},
5 | "editor.defaultFormatter": "esbenp.prettier-vscode",
6 | "editor.formatOnSave": true,
7 | "editor.codeActionsOnSave": {
8 | "source.organizeImports": "never" // Import sorting is handled by prettier
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2023 CodSpeed
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
codspeed-node
3 |
4 | Node.js libraries to create CodSpeed benchmarks
5 |
6 | [](https://github.com/CodSpeedHQ/codspeed-node/actions/workflows/ci.yml)
7 | [](https://www.npmjs.com/org/codspeed)
8 | [](https://discord.com/invite/MxpaCfKSqF)
9 | [](https://codspeed.io/CodSpeedHQ/codspeed-node)
10 |
11 |
12 |
13 | ## Documentation
14 |
15 | Check out the [documentation](https://docs.codspeed.io/benchmarks/nodejs) for complete integration instructions.
16 |
17 | ## Packages
18 |
19 | This mono-repo contains the integration packages for using CodSpeed with Node.js:
20 |
21 | - [`@codspeed/vitest-plugin`](./packages/vitest-plugin): vitest compatibility layer for CodSpeed
22 | - [`@codspeed/tinybench-plugin`](./packages/tinybench-plugin): tinybench compatibility layer for CodSpeed
23 | - [`@codspeed/benchmark.js-plugin`](./packages/benchmark.js-plugin): Benchmark.js compatibility layer for CodSpeed
24 | - [`@codspeed/core`](./packages/core): The core library used to integrate with Codspeed runners
25 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ["@commitlint/config-conventional"] };
2 |
--------------------------------------------------------------------------------
/docs/introspection.md:
--------------------------------------------------------------------------------
1 | ## Testing introspection locally
2 |
3 | 1. Inside `codspeed-node` directory, run `export PATH="$pwd/../action/dist/bin:$PATH"`.
4 | This will ensure that the action's `dist/bin/node` file will be used instead of the
5 | system's `node` binary.
6 |
7 | 2. Replace the `CodSpeedHQ/action` grep filter with `CodSpeedHQ` in the `dist/bin/node`.
8 | Since we used `../action` in the `export PATH=...` command, the original grep filter will
9 | not work.
10 |
11 | 3. Run your command with the correct flags in `codspeed-node`, for example
12 |
13 | ```bash
14 | CI=1 CODSPEED_DEBUG=true pnpm --filter with-typescript-esm bench-tinybench
15 | ```
16 |
--------------------------------------------------------------------------------
/examples/with-javascript-cjs/benchmark-js.js:
--------------------------------------------------------------------------------
1 | const { withCodSpeed } = require("@codspeed/benchmark.js-plugin");
2 | const Benchmark = require("benchmark");
3 |
4 | const suite = withCodSpeed(new Benchmark.Suite());
5 |
6 | suite
7 | .add("RegExp#test", function () {
8 | /o/.test("Hello World!");
9 | })
10 | .add("String#indexOf", function () {
11 | "Hello World!".indexOf("o") > -1;
12 | })
13 | // add listeners
14 | .on("cycle", function (event) {
15 | console.log(String(event.target));
16 | })
17 | // run async
18 | .run({ async: true });
19 |
--------------------------------------------------------------------------------
/examples/with-javascript-cjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-javascript-cjs",
3 | "private": true,
4 | "scripts": {
5 | "bench-benchmark-js": "node benchmark-js.js",
6 | "bench-tinybench": "node tinybench.js"
7 | },
8 | "devDependencies": {
9 | "@codspeed/benchmark.js-plugin": "workspace:*",
10 | "@codspeed/tinybench-plugin": "workspace:*",
11 | "benchmark": "^2.1.4",
12 | "tinybench": "^2.5.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/with-javascript-cjs/tinybench.js:
--------------------------------------------------------------------------------
1 | const { withCodSpeed } = require("@codspeed/tinybench-plugin");
2 | const { Bench } = require("tinybench");
3 |
4 | const bench = withCodSpeed(new Bench({ time: 100 }));
5 |
6 | bench
7 | .add("switch 1", () => {
8 | let a = 1;
9 | let b = 2;
10 | const c = a;
11 | a = b;
12 | b = c;
13 | })
14 | .add("switch 2", () => {
15 | let a = 1;
16 | let b = 10;
17 | a = b + a;
18 | b = a - b;
19 | a = b - a;
20 | });
21 |
22 | bench.run().then(() => {
23 | console.table(bench.table());
24 | });
25 |
--------------------------------------------------------------------------------
/examples/with-javascript-esm/benchmark-js.js:
--------------------------------------------------------------------------------
1 | import { withCodSpeed } from "@codspeed/benchmark.js-plugin";
2 | import Benchmark from "benchmark";
3 |
4 | const suite = withCodSpeed(new Benchmark.Suite());
5 |
6 | suite
7 | .add("RegExp#test", function () {
8 | /o/.test("Hello World!");
9 | })
10 | .add("String#indexOf", function () {
11 | "Hello World!".indexOf("o") > -1;
12 | })
13 | // add listeners
14 | .on("cycle", function (event) {
15 | console.log(String(event.target));
16 | })
17 | // run async
18 | .run({ async: true });
19 |
--------------------------------------------------------------------------------
/examples/with-javascript-esm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-javascript-esm",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "bench-benchmark-js": "node benchmark-js.js",
7 | "bench-tinybench": "node tinybench.js"
8 | },
9 | "devDependencies": {
10 | "@codspeed/benchmark.js-plugin": "workspace:*",
11 | "@codspeed/tinybench-plugin": "workspace:*",
12 | "benchmark": "^2.1.4",
13 | "tinybench": "^2.5.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/with-javascript-esm/tinybench.js:
--------------------------------------------------------------------------------
1 | import { withCodSpeed } from "@codspeed/tinybench-plugin";
2 | import { Bench } from "tinybench";
3 |
4 | const bench = withCodSpeed(new Bench({ time: 100 }));
5 |
6 | bench
7 | .add("switch 1", () => {
8 | let a = 1;
9 | let b = 2;
10 | const c = a;
11 | a = b;
12 | b = c;
13 | })
14 | .add("switch 2", () => {
15 | let a = 1;
16 | let b = 10;
17 | a = b + a;
18 | b = a - b;
19 | a = b - a;
20 | });
21 |
22 | bench.run().then(() => {
23 | console.table(bench.table());
24 | });
25 |
--------------------------------------------------------------------------------
/examples/with-typescript-cjs/bench/benchmark.js/fibo.bench.ts:
--------------------------------------------------------------------------------
1 | import type { WithCodSpeedSuite } from "@codspeed/benchmark.js-plugin";
2 | import {
3 | iterativeFibonacci,
4 | recursiveCachedFibonacci,
5 | recursiveFibonacci,
6 | } from "../../src/fibonacci";
7 |
8 | export function registerFiboBenchmarks(suite: WithCodSpeedSuite) {
9 | suite
10 | .add("test_recursive_fibo_10", () => {
11 | recursiveFibonacci(10);
12 | })
13 | .add("test_recursive_fibo_20", () => {
14 | recursiveFibonacci(20);
15 | });
16 |
17 | suite
18 | .add("test_recursive_cached_fibo_10", () => {
19 | recursiveCachedFibonacci(10);
20 | })
21 | .add("test_recursive_cached_fibo_20", () => {
22 | recursiveCachedFibonacci(20);
23 | })
24 | .add("test_recursive_cached_fibo_30", () => {
25 | recursiveCachedFibonacci(30);
26 | });
27 |
28 | suite
29 | .add("test_iterative_fibo_10", () => {
30 | iterativeFibonacci(10);
31 | })
32 | .add("test_iterative_fibo_100", () => {
33 | iterativeFibonacci(100);
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/examples/with-typescript-cjs/bench/benchmark.js/foobarbaz.bench.ts:
--------------------------------------------------------------------------------
1 | import type { WithCodSpeedSuite } from "@codspeed/benchmark.js-plugin";
2 | import { baz } from "../../src/foobarbaz";
3 |
4 | export function registerFoobarbazBenchmarks(suite: WithCodSpeedSuite) {
5 | suite
6 | .add("test sync baz 10", () => {
7 | baz(10);
8 | })
9 | .add("test sync baz 100", () => {
10 | baz(100);
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/examples/with-typescript-cjs/bench/benchmark.js/index.bench.ts:
--------------------------------------------------------------------------------
1 | import { withCodSpeed } from "@codspeed/benchmark.js-plugin";
2 | import Benchmark from "benchmark";
3 | import { registerFiboBenchmarks } from "./fibo.bench";
4 | import { registerFoobarbazBenchmarks } from "./foobarbaz.bench";
5 |
6 | export const suite = withCodSpeed(new Benchmark.Suite());
7 |
8 | (async () => {
9 | registerFiboBenchmarks(suite);
10 | registerFoobarbazBenchmarks(suite);
11 |
12 | suite.on("cycle", function (event: Benchmark.Event) {
13 | console.log(String(event.target));
14 | });
15 |
16 | await suite.run({ async: true });
17 | })();
18 |
--------------------------------------------------------------------------------
/examples/with-typescript-cjs/bench/tinybench/fibo.bench.ts:
--------------------------------------------------------------------------------
1 | import { Bench } from "tinybench";
2 | import {
3 | iterativeFibonacci,
4 | recursiveCachedFibonacci,
5 | recursiveFibonacci,
6 | } from "../../src/fibonacci";
7 |
8 | export function registerFiboBenchmarks(bench: Bench) {
9 | bench
10 | .add("test_recursive_fibo_10", () => {
11 | recursiveFibonacci(10);
12 | })
13 | .add("test_recursive_fibo_20", () => {
14 | recursiveFibonacci(20);
15 | });
16 |
17 | bench
18 | .add("test_recursive_cached_fibo_10", () => {
19 | recursiveCachedFibonacci(10);
20 | })
21 | .add("test_recursive_cached_fibo_20", () => {
22 | recursiveCachedFibonacci(20);
23 | })
24 | .add("test_recursive_cached_fibo_30", () => {
25 | recursiveCachedFibonacci(30);
26 | });
27 |
28 | bench
29 | .add("test_iterative_fibo_10", () => {
30 | iterativeFibonacci(10);
31 | })
32 | .add("test_iterative_fibo_100", () => {
33 | iterativeFibonacci(100);
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/examples/with-typescript-cjs/bench/tinybench/foobarbaz.bench.ts:
--------------------------------------------------------------------------------
1 | import { Bench } from "tinybench";
2 | import { baz } from "../../src/foobarbaz";
3 |
4 | export function registerFoobarbazBenchmarks(bench: Bench) {
5 | bench
6 | .add("test sync baz 10", () => {
7 | baz(10);
8 | })
9 | .add("test sync baz 100", () => {
10 | baz(100);
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/examples/with-typescript-cjs/bench/tinybench/index.bench.ts:
--------------------------------------------------------------------------------
1 | import { withCodSpeed } from "@codspeed/tinybench-plugin";
2 | import { Bench } from "tinybench";
3 | import { registerFiboBenchmarks } from "./fibo.bench";
4 | import { registerFoobarbazBenchmarks } from "./foobarbaz.bench";
5 |
6 | export const bench = withCodSpeed(new Bench());
7 |
8 | (async () => {
9 | registerFiboBenchmarks(bench);
10 | registerFoobarbazBenchmarks(bench);
11 |
12 | await bench.run();
13 | console.table(bench.table());
14 | })();
15 |
--------------------------------------------------------------------------------
/examples/with-typescript-cjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-typescript-cjs",
3 | "private": true,
4 | "scripts": {
5 | "bench-benchmark-js": "node -r esbuild-register bench/benchmark.js/index.bench.ts",
6 | "bench-tinybench": "node -r esbuild-register bench/tinybench/index.bench.ts"
7 | },
8 | "devDependencies": {
9 | "@codspeed/benchmark.js-plugin": "workspace:*",
10 | "@codspeed/tinybench-plugin": "workspace:*",
11 | "@types/benchmark": "^2.1.2",
12 | "benchmark": "^2.1.4",
13 | "esbuild-register": "^3.4.2",
14 | "tinybench": "^2.5.0",
15 | "typescript": "^5.1.3"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/with-typescript-cjs/src/fibonacci.ts:
--------------------------------------------------------------------------------
1 | export function recursiveFibonacci(n: number): number {
2 | if (n < 2) {
3 | return n;
4 | }
5 | return recursiveFibonacci(n - 1) + recursiveFibonacci(n - 2);
6 | }
7 |
8 | export function recursiveCachedFibonacci(n: number) {
9 | const cache: Record = { 0: 0, 1: 1 };
10 | const fiboInner = (n: number) => {
11 | if (n in cache) {
12 | return cache[n];
13 | }
14 | cache[n] = fiboInner(n - 1) + fiboInner(n - 2);
15 | return cache[n];
16 | };
17 | return fiboInner(n);
18 | }
19 |
20 | export function iterativeFibonacci(n: number) {
21 | let a = 0;
22 | let b = 1;
23 | let c = 0;
24 | for (let i = 0; i < n; i++) {
25 | c = a + b;
26 | a = b;
27 | b = c;
28 | }
29 | return a;
30 | }
31 |
--------------------------------------------------------------------------------
/examples/with-typescript-cjs/src/foobarbaz.ts:
--------------------------------------------------------------------------------
1 | // Sync version
2 | function foo(n: number) {
3 | let result = 0;
4 | for (let i = 0; i < n; i++) {
5 | result += 1;
6 | }
7 | return result;
8 | }
9 |
10 | function bar(n: number) {
11 | foo(n);
12 | }
13 |
14 | export function baz(n: number) {
15 | bar(n);
16 | }
17 |
--------------------------------------------------------------------------------
/examples/with-typescript-cjs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["es2023"],
4 | "module": "Node16",
5 | "target": "es2022",
6 | "strict": true,
7 | "esModuleInterop": true,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "moduleResolution": "Node"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/bench/benchmark.js/fibo.bench.ts:
--------------------------------------------------------------------------------
1 | import type { WithCodSpeedSuite } from "@codspeed/benchmark.js-plugin";
2 | import {
3 | iterativeFibonacci,
4 | recursiveCachedFibonacci,
5 | recursiveFibonacci,
6 | } from "../../src/fibonacci";
7 |
8 | export function registerFiboBenchmarks(suite: WithCodSpeedSuite) {
9 | suite
10 | .add("test_recursive_fibo_10", () => {
11 | recursiveFibonacci(10);
12 | })
13 | .add("test_recursive_fibo_20", () => {
14 | recursiveFibonacci(20);
15 | });
16 |
17 | suite
18 | .add("test_recursive_cached_fibo_10", () => {
19 | recursiveCachedFibonacci(10);
20 | })
21 | .add("test_recursive_cached_fibo_20", () => {
22 | recursiveCachedFibonacci(20);
23 | })
24 | .add("test_recursive_cached_fibo_30", () => {
25 | recursiveCachedFibonacci(30);
26 | });
27 |
28 | suite
29 | .add("test_iterative_fibo_10", () => {
30 | iterativeFibonacci(10);
31 | })
32 | .add("test_iterative_fibo_100", () => {
33 | iterativeFibonacci(100);
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/bench/benchmark.js/foobarbaz.bench.ts:
--------------------------------------------------------------------------------
1 | import type { WithCodSpeedSuite } from "@codspeed/benchmark.js-plugin";
2 | import { baz } from "../../src/foobarbaz";
3 |
4 | export function registerFoobarbazBenchmarks(suite: WithCodSpeedSuite) {
5 | suite
6 | .add("test sync baz 10", () => {
7 | baz(10);
8 | })
9 | .add("test sync baz 100", () => {
10 | baz(100);
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/bench/benchmark.js/index.bench.ts:
--------------------------------------------------------------------------------
1 | import { withCodSpeed } from "@codspeed/benchmark.js-plugin";
2 | import Benchmark from "benchmark";
3 | import { registerFiboBenchmarks } from "./fibo.bench";
4 | import { registerFoobarbazBenchmarks } from "./foobarbaz.bench";
5 |
6 | export const suite = withCodSpeed(new Benchmark.Suite());
7 |
8 | (async () => {
9 | registerFiboBenchmarks(suite);
10 | registerFoobarbazBenchmarks(suite);
11 |
12 | suite.on("cycle", function (event: Benchmark.Event) {
13 | console.log(String(event.target));
14 | });
15 |
16 | await suite.run({ async: true });
17 | })();
18 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/bench/tinybench/fibo.bench.ts:
--------------------------------------------------------------------------------
1 | import { Bench } from "tinybench";
2 | import {
3 | iterativeFibonacci,
4 | recursiveCachedFibonacci,
5 | recursiveFibonacci,
6 | } from "../../src/fibonacci";
7 |
8 | export function registerFiboBenchmarks(bench: Bench) {
9 | bench
10 | .add("test_recursive_fibo_10", () => {
11 | recursiveFibonacci(10);
12 | })
13 | .add("test_recursive_fibo_20", () => {
14 | recursiveFibonacci(20);
15 | });
16 |
17 | bench
18 | .add("test_recursive_cached_fibo_10", () => {
19 | recursiveCachedFibonacci(10);
20 | })
21 | .add("test_recursive_cached_fibo_20", () => {
22 | recursiveCachedFibonacci(20);
23 | })
24 | .add("test_recursive_cached_fibo_30", () => {
25 | recursiveCachedFibonacci(30);
26 | });
27 |
28 | bench
29 | .add("test_iterative_fibo_10", () => {
30 | iterativeFibonacci(10);
31 | })
32 | .add("test_iterative_fibo_100", () => {
33 | iterativeFibonacci(100);
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/bench/tinybench/foobarbaz.bench.ts:
--------------------------------------------------------------------------------
1 | import { Bench } from "tinybench";
2 | import { baz } from "../../src/foobarbaz";
3 |
4 | export function registerFoobarbazBenchmarks(bench: Bench) {
5 | bench
6 | .add("test sync baz 10", () => {
7 | baz(10);
8 | })
9 | .add("test sync baz 100", () => {
10 | baz(100);
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/bench/tinybench/index.bench.ts:
--------------------------------------------------------------------------------
1 | import { withCodSpeed } from "@codspeed/tinybench-plugin";
2 | import { Bench } from "tinybench";
3 | import { registerFiboBenchmarks } from "./fibo.bench";
4 | import { registerFoobarbazBenchmarks } from "./foobarbaz.bench";
5 |
6 | export const bench = withCodSpeed(new Bench());
7 |
8 | (async () => {
9 | registerFiboBenchmarks(bench);
10 | registerFoobarbazBenchmarks(bench);
11 |
12 | await bench.run();
13 | console.table(bench.table());
14 | })();
15 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-typescript-esm",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "bench-benchmark-js": "node --loader esbuild-register/loader -r esbuild-register bench/benchmark.js/index.bench.ts",
7 | "bench-tinybench": "node --loader esbuild-register/loader -r esbuild-register bench/tinybench/index.bench.ts",
8 | "bench-vitest": "vitest bench --run"
9 | },
10 | "devDependencies": {
11 | "@codspeed/benchmark.js-plugin": "workspace:*",
12 | "@codspeed/tinybench-plugin": "workspace:*",
13 | "@codspeed/vitest-plugin": "workspace:*",
14 | "@types/benchmark": "^2.1.2",
15 | "benchmark": "^2.1.4",
16 | "esbuild-register": "^3.4.2",
17 | "tinybench": "^2.5.0",
18 | "typescript": "^5.1.3",
19 | "vitest": "^1.2.2"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/src/fibonacci.bench.ts:
--------------------------------------------------------------------------------
1 | import { bench, describe } from "vitest";
2 | import { iterativeFibonacci } from "./fibonacci";
3 |
4 | describe("iterativeFibonacci", () => {
5 | bench("fibo 10", () => {
6 | iterativeFibonacci(10);
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/src/fibonacci.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { iterativeFibonacci } from "./fibonacci";
3 |
4 | describe("iterativeFibonacci", () => {
5 | it("should return the correct value", () => {
6 | expect(iterativeFibonacci(1)).toBe(1);
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/src/fibonacci.ts:
--------------------------------------------------------------------------------
1 | export function recursiveFibonacci(n: number): number {
2 | if (n < 2) {
3 | return n;
4 | }
5 | return recursiveFibonacci(n - 1) + recursiveFibonacci(n - 2);
6 | }
7 |
8 | export function recursiveCachedFibonacci(n: number) {
9 | const cache: Record = { 0: 0, 1: 1 };
10 | const fiboInner = (n: number) => {
11 | if (n in cache) {
12 | return cache[n];
13 | }
14 | cache[n] = fiboInner(n - 1) + fiboInner(n - 2);
15 | return cache[n];
16 | };
17 | return fiboInner(n);
18 | }
19 |
20 | export function iterativeFibonacci(n: number) {
21 | let a = 0;
22 | let b = 1;
23 | let c = 0;
24 | for (let i = 0; i < n; i++) {
25 | c = a + b;
26 | a = b;
27 | b = c;
28 | }
29 | return a;
30 | }
31 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/src/foobarbaz.ts:
--------------------------------------------------------------------------------
1 | // Sync version
2 | function foo(n: number) {
3 | let result = 0;
4 | for (let i = 0; i < n; i++) {
5 | result += 1;
6 | }
7 | return result;
8 | }
9 |
10 | function bar(n: number) {
11 | foo(n);
12 | }
13 |
14 | export function baz(n: number) {
15 | bar(n);
16 | }
17 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["es2023"],
4 | "module": "ESNext",
5 | "verbatimModuleSyntax": true,
6 | "target": "es2022",
7 | "strict": true,
8 | "esModuleInterop": true,
9 | "skipLibCheck": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "moduleResolution": "Node"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/with-typescript-esm/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import codspeedPlugin from "@codspeed/vitest-plugin";
2 | import { defineConfig } from "vitest/config";
3 |
4 | export default defineConfig({
5 | plugins: [codspeedPlugin()],
6 | test: {
7 | benchmark: {
8 | exclude: ["**/bench/**/*", "**/node_modules/**/*"],
9 | },
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/examples/with-typescript-simple-cjs/benchmark-js.ts:
--------------------------------------------------------------------------------
1 | import { withCodSpeed } from "@codspeed/benchmark.js-plugin";
2 | import Benchmark from "benchmark";
3 |
4 | const suite = withCodSpeed(new Benchmark.Suite());
5 |
6 | suite
7 | .add("RegExp#test", function () {
8 | /o/.test("Hello World!");
9 | })
10 | .add("String#indexOf", function () {
11 | "Hello World!".indexOf("o") > -1;
12 | })
13 | // add listeners
14 | .on("cycle", function (event: Benchmark.Event) {
15 | console.log(String(event.target));
16 | })
17 | // run async
18 | .run({ async: true });
19 |
--------------------------------------------------------------------------------
/examples/with-typescript-simple-cjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-typescript-simple-cjs",
3 | "private": true,
4 | "scripts": {
5 | "bench-benchmark-js": "node -r esbuild-register benchmark-js.ts",
6 | "bench-tinybench": "node -r esbuild-register tinybench.ts"
7 | },
8 | "devDependencies": {
9 | "@codspeed/benchmark.js-plugin": "workspace:*",
10 | "@codspeed/tinybench-plugin": "workspace:*",
11 | "@types/benchmark": "^2.1.2",
12 | "benchmark": "^2.1.4",
13 | "esbuild-register": "^3.4.2",
14 | "tinybench": "^2.5.0",
15 | "typescript": "^5.1.3"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/with-typescript-simple-cjs/tinybench.ts:
--------------------------------------------------------------------------------
1 | import { withCodSpeed } from "@codspeed/tinybench-plugin";
2 | import { Bench } from "tinybench";
3 |
4 | const bench = withCodSpeed(new Bench({ time: 100 }));
5 |
6 | bench
7 | .add("switch 1", () => {
8 | let a = 1;
9 | let b = 2;
10 | const c = a;
11 | a = b;
12 | b = c;
13 | })
14 | .add("switch 2", () => {
15 | let a = 1;
16 | let b = 10;
17 | a = b + a;
18 | b = a - b;
19 | a = b - a;
20 | });
21 |
22 | bench.run().then(() => {
23 | console.table(bench.table());
24 | });
25 |
--------------------------------------------------------------------------------
/examples/with-typescript-simple-esm/benchmark-js.ts:
--------------------------------------------------------------------------------
1 | import { withCodSpeed } from "@codspeed/benchmark.js-plugin";
2 | import Benchmark from "benchmark";
3 |
4 | const suite = withCodSpeed(new Benchmark.Suite());
5 |
6 | suite
7 | .add("RegExp#test", function () {
8 | /o/.test("Hello World!");
9 | })
10 | .add("String#indexOf", function () {
11 | "Hello World!".indexOf("o") > -1;
12 | })
13 | // add listeners
14 | .on("cycle", function (event: Benchmark.Event) {
15 | console.log(String(event.target));
16 | })
17 | // run async
18 | .run({ async: true });
19 |
--------------------------------------------------------------------------------
/examples/with-typescript-simple-esm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-typescript-simple-esm",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "bench-benchmark-js": "node --loader esbuild-register/loader -r esbuild-register benchmark-js.ts",
7 | "bench-tinybench": "node --loader esbuild-register/loader -r esbuild-register tinybench.ts"
8 | },
9 | "devDependencies": {
10 | "@codspeed/benchmark.js-plugin": "workspace:*",
11 | "@codspeed/tinybench-plugin": "workspace:*",
12 | "@types/benchmark": "^2.1.2",
13 | "benchmark": "^2.1.4",
14 | "esbuild-register": "^3.4.2",
15 | "tinybench": "^2.5.0",
16 | "typescript": "^5.1.3"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/with-typescript-simple-esm/tinybench.ts:
--------------------------------------------------------------------------------
1 | import { withCodSpeed } from "@codspeed/tinybench-plugin";
2 | import { Bench } from "tinybench";
3 |
4 | const bench = withCodSpeed(new Bench({ time: 100 }));
5 |
6 | bench
7 | .add("switch 1", () => {
8 | let a = 1;
9 | let b = 2;
10 | const c = a;
11 | a = b;
12 | b = c;
13 | })
14 | .add("switch 2", () => {
15 | let a = 1;
16 | let b = 10;
17 | a = b + a;
18 | b = a - b;
19 | a = b - a;
20 | });
21 |
22 | bench.run().then(() => {
23 | console.table(bench.table());
24 | });
25 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "npmClient": "pnpm",
3 | "useWorkspaces": true,
4 | "packages": ["packages/*"],
5 | "$schema": "node_modules/lerna/schemas/lerna-schema.json",
6 | "version": "4.0.1"
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "private": true,
4 | "devDependencies": {
5 | "@commitlint/cli": "^17.5.1",
6 | "@commitlint/config-conventional": "^17.4.4",
7 | "@moonrepo/cli": "^1.19.3",
8 | "@rollup/plugin-commonjs": "^25.0.7",
9 | "@rollup/plugin-json": "^6.0.1",
10 | "@rollup/plugin-node-resolve": "^15.2.3",
11 | "@rollup/plugin-typescript": "^11.1.5",
12 | "@types/jest": "^29.5.0",
13 | "@types/node": "^18.15.11",
14 | "@typescript-eslint/eslint-plugin": "^5.58.0",
15 | "@typescript-eslint/parser": "^5.58.0",
16 | "esbuild": "^0.17.16",
17 | "esbuild-register": "^3.4.2",
18 | "eslint": "^7.32.0",
19 | "eslint-import-resolver-typescript": "^3.5.5",
20 | "eslint-plugin-import": "^2.27.5",
21 | "husky": "^7.0.4",
22 | "jest": "^29.5.0",
23 | "jest-config": "^29.5.0",
24 | "lerna": "^6.6.1",
25 | "prettier": "^2.8.7",
26 | "prettier-plugin-organize-imports": "^3.2.2",
27 | "rollup": "^4.4.1",
28 | "rollup-plugin-dts": "^6.1.0",
29 | "rollup-plugin-esbuild": "^6.1.0",
30 | "ts-jest": "^29.1.0",
31 | "tslib": "^2.5.0",
32 | "typescript": "4.9.4"
33 | },
34 | "packageManager": "pnpm@8.6.3",
35 | "engines": {
36 | "node": "18.16.0"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/README.md:
--------------------------------------------------------------------------------
1 |
2 |
@codspeed/benchmark.js-plugin
3 |
4 | Benchmark.js compatibility layer for CodSpeed
5 |
6 | [](https://github.com/CodSpeedHQ/codspeed-node/actions/workflows/ci.yml)
7 | [](https://www.npmjs.com/package/@codspeed/benchmark.js-plugin)
8 | [](https://discord.com/invite/MxpaCfKSqF)
9 | [](https://codspeed.io/CodSpeedHQ/codspeed-node)
10 |
11 |
12 |
13 | ## Documentation
14 |
15 | Check out the [documentation](https://docs.codspeed.io/benchmarks/nodejs/benchmark.js) for complete integration instructions.
16 |
17 | ## Installation
18 |
19 | First, install the plugin [`@codspeed/benchmark.js-plugin`](https://www.npmjs.com/package/@codspeed/benchmark.js-plugin) and `benchmark.js` (if not already installed):
20 |
21 | ```sh
22 | npm install --save-dev @codspeed/benchmark.js-plugin benchmark.js
23 | ```
24 |
25 | or with `yarn`:
26 |
27 | ```sh
28 | yarn add --dev @codspeed/benchmark.js-plugin benchmark.js
29 | ```
30 |
31 | or with `pnpm`:
32 |
33 | ```sh
34 | pnpm add --save-dev @codspeed/benchmark.js-plugin benchmark.js
35 | ```
36 |
37 | ## Usage
38 |
39 | Let's create a fibonacci function and benchmark it with benchmark.js and the CodSpeed plugin:
40 |
41 | ```js title="benches/bench.mjs"
42 | import Benchmark from "benchmark";
43 | import { withCodSpeed } from "@codspeed/benchmark.js-plugin";
44 |
45 | function fibonacci(n) {
46 | if (n < 2) {
47 | return n;
48 | }
49 | return fibonacci(n - 1) + fibonacci(n - 2);
50 | }
51 |
52 | const suite = withCodSpeed(new Benchmark.Suite());
53 |
54 | suite
55 | .add("fibonacci10", () => {
56 | fibonacci(10);
57 | })
58 | .add("fibonacci15", () => {
59 | fibonacci(15);
60 | })
61 | .on("cycle", function (event: Benchmark.Event) {
62 | console.log(String(event.target));
63 | })
64 | .run();
65 | ```
66 |
67 | Here, a few things are happening:
68 |
69 | - We create a simple recursive fibonacci function.
70 | - We create a new `Benchmark.Suite` instance with CodSpeed support by using the **`withCodSpeed`** helper. This step is **critical** to enable CodSpeed on your benchmarks.
71 | - We add two benchmarks to the suite and launch it, benching our `fibonacci` function with 10 and 15.
72 |
73 | Now, we can run our benchmarks locally to make sure everything is working as expected:
74 |
75 | ```sh
76 | $ node benches/bench.mjs
77 | [CodSpeed] 2 benches detected but no instrumentation found
78 | [CodSpeed] falling back to benchmark.js
79 |
80 | fibonacci10 x 2,155,187 ops/sec ±0.50% (96 runs sampled)
81 | fibonacci15 x 194,742 ops/sec ±0.48% (95 runs sampled)
82 | ```
83 |
84 | And... Congrats🎉, CodSpeed is installed in your benchmarking suite! Locally, CodSpeed will fallback to tinybench since the instrumentation is only available in the CI environment for now.
85 |
86 | You can now [run those benchmarks in your CI](https://docs.codspeed.io/benchmarks/nodejs/benchmark.js#running-the-benchmarks-in-your-ci) to continuously get consistent performance measurements.
87 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/babel.config.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-undef
2 | module.exports = {
3 | presets: [["@babel/preset-env", { targets: { node: "current" } }]],
4 | };
5 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/benches/parsePr.ts:
--------------------------------------------------------------------------------
1 | interface PullRequest {
2 | number: number;
3 | title: string;
4 | body: string;
5 | }
6 |
7 | function sendEvent(numberOfOperations: number): void {
8 | for (let i = 0; i < numberOfOperations; i++) {
9 | let a = i;
10 | a = a + 1;
11 | }
12 | }
13 |
14 | function logMetrics(
15 | numberOfOperations: number,
16 | numberOfDeepOperations: number
17 | ): void {
18 | for (let i = 0; i < numberOfOperations; i++) {
19 | for (let i = 0; i < numberOfOperations; i++) {
20 | let a = i;
21 | a = a + 1;
22 | a = a + 1;
23 | }
24 | sendEvent(numberOfDeepOperations);
25 | }
26 | }
27 |
28 | function parseTitle(title: string): void {
29 | logMetrics(10, 10);
30 | modifyTitle(title);
31 | }
32 |
33 | function modifyTitle(title: string): void {
34 | for (let i = 0; i < 100; i++) {
35 | let a = i;
36 | a = a + 1 + title.length;
37 | }
38 | }
39 |
40 | function prepareParsingBody(body: string): void {
41 | for (let i = 0; i < 100; i++) {
42 | let a = i;
43 | a = a + 1;
44 | }
45 | parseBody(body);
46 | }
47 |
48 | function parseBody(body: string): void {
49 | logMetrics(10, 10);
50 | for (let i = 0; i < 200; i++) {
51 | let a = i;
52 | a = a + 1;
53 | }
54 | parseIssueFixed(body);
55 | }
56 |
57 | function parseIssueFixed(body: string): number | null {
58 | const prefix = "fixes #";
59 | const index = body.indexOf(prefix);
60 | if (index === -1) {
61 | return null;
62 | }
63 |
64 | const start = index + prefix.length;
65 | let end = start;
66 | while (end < body.length && /\d/.test(body[end])) {
67 | end += 1;
68 | }
69 | return parseInt(body.slice(start, end));
70 | }
71 |
72 | export default function parsePr(pullRequest: PullRequest): void {
73 | parseTitle(pullRequest.title);
74 | prepareParsingBody(pullRequest.body);
75 | }
76 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/benches/sample.ts:
--------------------------------------------------------------------------------
1 | import Benchmark from "benchmark";
2 | import { withCodSpeed } from "..";
3 | import parsePr from "./parsePr";
4 |
5 | const LONG_BODY =
6 | new Array(1_000)
7 | .fill(
8 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Sunt, earum. Atque architecto vero veniam est tempora fugiat sint quo praesentium quia. Autem, veritatis omnis beatae iste delectus recusandae animi non."
9 | )
10 | .join("\n") + "fixes #123";
11 |
12 | const suite = withCodSpeed(new Benchmark.Suite());
13 |
14 | suite
15 | .add("RegExp#test", function () {
16 | /o/.test("Hello World!");
17 | })
18 | .add("String#indexOf", function () {
19 | "Hello World!".indexOf("o") > -1;
20 | })
21 | .add("short body", () => {
22 | parsePr({ body: "fixes #123", title: "test", number: 124 });
23 | })
24 | .add("long body", () => {
25 | parsePr({ body: LONG_BODY, title: "test", number: 124 });
26 | })
27 | .add("short body 2", () => {
28 | parsePr({ body: "fixes #123", title: "test", number: 124 });
29 | })
30 | .add("short body 3", () => {
31 | parsePr({ body: "fixes #123", title: "test", number: 124 });
32 | })
33 | .add("short body 4", () => {
34 | parsePr({ body: "fixes #123", title: "test", number: 124 });
35 | })
36 | .add("short body 5", () => {
37 | parsePr({ body: "fixes #123", title: "test", number: 124 });
38 | })
39 | // add listeners
40 | .on("cycle", function (event: Benchmark.Event) {
41 | console.log(String(event.target));
42 | })
43 | // run async
44 | .run({ async: true });
45 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/jest.config.integ.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
2 | // eslint-disable-next-line no-undef
3 | module.exports = {
4 | preset: "ts-jest",
5 | testEnvironment: "node",
6 | transform: {
7 | "^.+\\.tsx?$": [
8 | "ts-jest",
9 | {
10 | tsconfig: "tsconfig.test.json",
11 | },
12 | ],
13 | },
14 | testPathIgnorePatterns: [
15 | "/node_modules/",
16 | "/src/",
17 | "/.rollup.cache/",
18 | ],
19 | };
20 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/jest.config.js:
--------------------------------------------------------------------------------
1 | const esmModules = [
2 | "find-up",
3 | "locate-path",
4 | "p-locate",
5 | "p-limit",
6 | "yocto-queue",
7 | "path-exists",
8 | "stack-trace",
9 | ];
10 |
11 | /** @type {import('ts-jest').JestConfigWithTsJest} */
12 | module.exports = {
13 | preset: "ts-jest",
14 | testEnvironment: "node",
15 | transform: {
16 | "^.+\\.tsx?$": ["ts-jest"],
17 | // transform js with babel-jest
18 | "^.+\\.js$": "babel-jest",
19 | },
20 | testPathIgnorePatterns: [
21 | "/node_modules/",
22 | "/tests/",
23 | "/.rollup.cache/",
24 | ],
25 | transformIgnorePatterns: [
26 | `node_modules/(?!(?:.pnpm/)?(${esmModules.join("|")}))`,
27 | ],
28 | };
29 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/moon.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | bench:
3 | command: node -r esbuild-register benches/sample.ts
4 | inputs:
5 | - "benches/**"
6 | local: true
7 | platform: "system"
8 | options:
9 | cache: false
10 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@codspeed/benchmark.js-plugin",
3 | "version": "4.0.1",
4 | "description": "Benchmark.js compatibility layer for CodSpeed",
5 | "keywords": [
6 | "codspeed",
7 | "benchmark",
8 | "benchmark.js",
9 | "performance"
10 | ],
11 | "main": "dist/index.cjs.js",
12 | "module": "dist/index.es5.js",
13 | "types": "dist/index.d.ts",
14 | "files": [
15 | "dist"
16 | ],
17 | "author": "Arthur Pastel ",
18 | "repository": "https://github.com/CodSpeedHQ/codspeed-node",
19 | "homepage": "https://codspeed.io",
20 | "license": "Apache-2.0",
21 | "devDependencies": {
22 | "@babel/preset-env": "^7.22.5",
23 | "@types/benchmark": "^2.1.2",
24 | "@types/lodash": "^4.14.195",
25 | "@types/stack-trace": "^0.0.30",
26 | "benchmark": "^2.1.4",
27 | "jest-mock-extended": "^3.0.4"
28 | },
29 | "dependencies": {
30 | "@codspeed/core": "workspace:^4.0.1",
31 | "lodash": "^4.17.10",
32 | "stack-trace": "1.0.0-pre2"
33 | },
34 | "peerDependencies": {
35 | "benchmark": "^2.1.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/rollup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "rollup";
2 | import { declarationsPlugin, jsPlugins } from "../../rollup.options";
3 | import pkg from "./package.json" assert { type: "json" };
4 |
5 | const entrypoint = "src/index.ts";
6 |
7 | export default defineConfig([
8 | {
9 | input: entrypoint,
10 | output: [
11 | {
12 | file: pkg.types,
13 | format: "es",
14 | sourcemap: true,
15 | },
16 | ],
17 | plugins: declarationsPlugin({ compilerOptions: { composite: false } }),
18 | },
19 | {
20 | input: entrypoint,
21 | output: [
22 | {
23 | file: pkg.main,
24 | format: "cjs",
25 | sourcemap: true,
26 | },
27 | { file: pkg.module, format: "es", sourcemap: true },
28 | ],
29 | plugins: jsPlugins(pkg.version),
30 | external: ["@codspeed/core"],
31 | },
32 | ]);
33 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/src/__tests__/buildSuiteAdd.test.ts:
--------------------------------------------------------------------------------
1 | import { Suite } from "benchmark";
2 | import buildSuiteAdd from "../buildSuiteAdd";
3 | import { CodSpeedBenchmark } from "../types";
4 |
5 | describe("buildSuiteAdd", () => {
6 | let emptyBench: () => void;
7 | let suite: Suite;
8 |
9 | beforeEach(() => {
10 | emptyBench = () => {
11 | return;
12 | };
13 | suite = new Suite();
14 | });
15 |
16 | it("should register benchmark name when using (options: Options)", () => {
17 | suite.add = buildSuiteAdd(suite);
18 | suite.add({ name: "test", fn: emptyBench });
19 | suite.forEach((bench: CodSpeedBenchmark) =>
20 | expect(bench.uri).toBe(
21 | "packages/benchmark.js-plugin/src/__tests__/buildSuiteAdd.test.ts::test"
22 | )
23 | );
24 | });
25 |
26 | it("should register benchmark name when using (fn: Function, options?: Options)", () => {
27 | suite.add = buildSuiteAdd(suite);
28 | suite.add(emptyBench, { name: "test" });
29 | suite.forEach((bench: CodSpeedBenchmark) =>
30 | expect(bench.uri).toBe(
31 | "packages/benchmark.js-plugin/src/__tests__/buildSuiteAdd.test.ts::test"
32 | )
33 | );
34 | });
35 |
36 | it("should register benchmark name when using (name: string, options?: Options)", () => {
37 | suite.add = buildSuiteAdd(suite);
38 | suite.add("test", { fn: emptyBench });
39 | suite.forEach((bench: CodSpeedBenchmark) =>
40 | expect(bench.uri).toBe(
41 | "packages/benchmark.js-plugin/src/__tests__/buildSuiteAdd.test.ts::test"
42 | )
43 | );
44 | });
45 |
46 | it("should register benchmark name when using (name: string, fn: Function, options?: Options)", () => {
47 | suite.add = buildSuiteAdd(suite);
48 | suite.add("test", emptyBench);
49 | suite.forEach((bench: CodSpeedBenchmark) =>
50 | expect(bench.uri).toBe(
51 | "packages/benchmark.js-plugin/src/__tests__/buildSuiteAdd.test.ts::test"
52 | )
53 | );
54 | });
55 |
56 | it("should register benchmark name when suite name is defined", () => {
57 | suite.name = "suite";
58 | suite.add = buildSuiteAdd(suite);
59 | suite.add("test", emptyBench);
60 | suite.forEach((bench: CodSpeedBenchmark) =>
61 | expect(bench.uri).toBe(
62 | "packages/benchmark.js-plugin/src/__tests__/buildSuiteAdd.test.ts::suite::test"
63 | )
64 | );
65 | });
66 |
67 | it("should call rawAdd with options object", () => {
68 | const rawAdd = jest.fn();
69 | suite.add = rawAdd;
70 | suite.add = buildSuiteAdd(suite);
71 | const options = { name: "test", delay: 100 };
72 | suite.add(options);
73 | expect(rawAdd).toHaveBeenCalledWith(options);
74 | });
75 |
76 | it("should call rawAdd with function and options object", () => {
77 | const rawAdd = jest.fn();
78 | suite.add = rawAdd;
79 | suite.add = buildSuiteAdd(suite);
80 | const fn = emptyBench;
81 | const options = { name: "test", delay: 100 };
82 | suite.add("test", fn, options);
83 | expect(rawAdd).toHaveBeenCalledWith("test", fn, {
84 | ...options,
85 | uri: "packages/benchmark.js-plugin/src/__tests__/buildSuiteAdd.test.ts::test",
86 | });
87 | });
88 |
89 | it("should call rawAdd with name and options object", () => {
90 | const rawAdd = jest.fn();
91 | suite.add = rawAdd;
92 | suite.add = buildSuiteAdd(suite);
93 | const options = { name: "test", delay: 100 };
94 | suite.add("test", options);
95 | expect(rawAdd).toHaveBeenCalledWith("test", options);
96 | });
97 |
98 | it("should call rawAdd with function and undefined options", () => {
99 | const rawAdd = jest.fn();
100 | suite.add = rawAdd;
101 | suite.add = buildSuiteAdd(suite);
102 | const fn = emptyBench;
103 | suite.add("test", fn);
104 | expect(rawAdd).toHaveBeenCalledWith("test", fn, {
105 | uri: "packages/benchmark.js-plugin/src/__tests__/buildSuiteAdd.test.ts::test",
106 | });
107 | });
108 | });
109 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/src/buildSuiteAdd.ts:
--------------------------------------------------------------------------------
1 | import { Options, Suite } from "benchmark";
2 | import { isFunction, isPlainObject } from "lodash";
3 | import getCallingFile from "./getCallingFile";
4 |
5 | function isOptions(options: unknown): options is Options {
6 | return isPlainObject(options);
7 | }
8 |
9 | export default function buildSuiteAdd(suite: Suite) {
10 | const rawAdd = suite.add;
11 | const suiteName = suite.name;
12 |
13 | function registerBenchmarkName(name: string) {
14 | const callingFile = getCallingFile(2); // [here, suite.add, actual caller]
15 | let uri = callingFile;
16 | if (suiteName !== undefined) {
17 | uri += `::${suiteName}`;
18 | }
19 | uri += `::${name}`;
20 |
21 | return uri;
22 | }
23 |
24 | function add(options: Options): Suite;
25 | // eslint-disable-next-line @typescript-eslint/ban-types
26 | function add(fn: Function, options?: Options): Suite;
27 | function add(name: string, options?: Options): Suite;
28 | // eslint-disable-next-line @typescript-eslint/ban-types
29 | function add(name: string, fn: Function, options?: Options): Suite;
30 | function add(name: unknown, fn?: unknown, opts?: unknown) {
31 | // 1 argument: (options: Options)
32 | if (isOptions(name)) {
33 | if (name.name !== undefined) {
34 | const rawFn = name.fn;
35 | if (typeof rawFn === "function") {
36 | const uri = registerBenchmarkName(name.name);
37 | const options = Object.assign({}, name, { uri });
38 | return rawAdd.bind(suite)(options);
39 | }
40 | }
41 | return rawAdd.bind(suite)(name);
42 | }
43 |
44 | // 2 arguments: (fn: Function, options?: Options)
45 | if (isFunction(name) && (isOptions(fn) || fn === undefined)) {
46 | if (fn !== undefined) {
47 | if (fn.name !== undefined) {
48 | const uri = registerBenchmarkName(fn.name);
49 | const options = Object.assign({}, fn, { uri });
50 | return rawAdd.bind(suite)(name, options);
51 | }
52 | }
53 | return rawAdd.bind(suite)(name, fn);
54 | }
55 |
56 | // 2 arguments: (name: string, options?: Options)
57 | if (typeof name === "string" && (isOptions(fn) || fn === undefined)) {
58 | if (fn !== undefined && typeof fn.fn === "function") {
59 | const uri = registerBenchmarkName(name);
60 | const options = Object.assign({}, fn, { uri });
61 | return rawAdd.bind(suite)(name, options);
62 | }
63 | return rawAdd.bind(suite)(name, fn);
64 | }
65 |
66 | // 3 arguments: (name: string, fn: Function, options?: Options)
67 | if (
68 | typeof name === "string" &&
69 | isFunction(fn) &&
70 | (isOptions(opts) || opts === undefined)
71 | ) {
72 | const uri = registerBenchmarkName(name);
73 | const options = Object.assign({}, opts ?? {}, { uri });
74 | return rawAdd.bind(suite)(name, fn, options);
75 | }
76 | }
77 |
78 | return add;
79 | }
80 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/src/getCallingFile.ts:
--------------------------------------------------------------------------------
1 | import { getGitDir } from "@codspeed/core";
2 | import path from "path";
3 | import { get as getStackTrace } from "stack-trace";
4 | import { fileURLToPath } from "url";
5 |
6 | export default function getCallingFile(depth: number): string {
7 | const stack = getStackTrace();
8 | let callingFile = stack[depth + 1].getFileName();
9 | const gitDir = getGitDir(callingFile);
10 | if (gitDir === undefined) {
11 | throw new Error("Could not find a git repository");
12 | }
13 | if (callingFile.startsWith("file://")) {
14 | callingFile = fileURLToPath(callingFile);
15 | }
16 | return path.relative(gitDir, callingFile);
17 | }
18 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/src/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Measurement,
3 | mongoMeasurement,
4 | optimizeFunction,
5 | optimizeFunctionSync,
6 | setupCore,
7 | SetupInstrumentsRequestBody,
8 | SetupInstrumentsResponse,
9 | teardownCore,
10 | tryIntrospect,
11 | } from "@codspeed/core";
12 | import Benchmark from "benchmark";
13 | import buildSuiteAdd from "./buildSuiteAdd";
14 | import getCallingFile from "./getCallingFile";
15 | import { CodSpeedBenchmark } from "./types";
16 |
17 | declare const __VERSION__: string;
18 |
19 | tryIntrospect();
20 |
21 | interface WithCodSpeedBenchmark
22 | extends Omit<
23 | Benchmark,
24 | "run" | "abort" | "clone" | "compare" | "emit" | "off" | "on" | "reset"
25 | > {
26 | abort(): WithCodSpeedBenchmark;
27 | clone(options: Benchmark.Options): WithCodSpeedBenchmark;
28 | compare(benchmark: Benchmark): number;
29 | off(
30 | type?: string,
31 | listener?: CallableFunction
32 | ): Benchmark | Promise;
33 | off(types: string[]): WithCodSpeedBenchmark;
34 | on(type?: string, listener?: CallableFunction): WithCodSpeedBenchmark;
35 | on(types: string[]): WithCodSpeedBenchmark;
36 | reset(): WithCodSpeedBenchmark;
37 | // Makes run an async function
38 | run(options?: Benchmark.Options): Benchmark | Promise;
39 | }
40 |
41 | export interface WithCodSpeedSuite
42 | extends Omit<
43 | Benchmark.Suite,
44 | | "run"
45 | | "abort"
46 | | "clone"
47 | | "compare"
48 | | "emit"
49 | | "off"
50 | | "on"
51 | | "reset"
52 | | "add"
53 | | "filter"
54 | | "each"
55 | | "forEach"
56 | > {
57 | abort(): WithCodSpeedSuite;
58 | add(
59 | name: string,
60 | fn: CallableFunction | string,
61 | options?: Benchmark.Options
62 | ): WithCodSpeedSuite;
63 | add(
64 | fn: CallableFunction | string,
65 | options?: Benchmark.Options
66 | ): WithCodSpeedSuite;
67 | add(name: string, options?: Benchmark.Options): WithCodSpeedSuite;
68 | add(options: Benchmark.Options): WithCodSpeedSuite;
69 | clone(options: Benchmark.Options): WithCodSpeedSuite;
70 | filter(callback: CallableFunction | string): WithCodSpeedSuite;
71 | off(type?: string, callback?: CallableFunction): WithCodSpeedSuite;
72 | off(types: string[]): WithCodSpeedSuite;
73 | on(type?: string, callback?: CallableFunction): WithCodSpeedSuite;
74 | on(types: string[]): WithCodSpeedSuite;
75 | reset(): WithCodSpeedSuite;
76 | each(callback: CallableFunction): WithCodSpeedSuite;
77 | forEach(callback: CallableFunction): WithCodSpeedSuite;
78 |
79 | run(options?: Benchmark.Options): Benchmark.Suite | Promise;
80 | }
81 |
82 | export function withCodSpeed(suite: Benchmark): WithCodSpeedBenchmark;
83 | export function withCodSpeed(suite: Benchmark.Suite): WithCodSpeedSuite;
84 | export function withCodSpeed(item: unknown): unknown {
85 | if ((item as { length?: number }).length === undefined) {
86 | return withCodSpeedBenchmark(item as Benchmark);
87 | } else {
88 | return withCodSpeedSuite(item as Benchmark.Suite);
89 | }
90 | }
91 |
92 | function withCodSpeedBenchmark(bench: Benchmark): WithCodSpeedBenchmark {
93 | if (!Measurement.isInstrumented()) {
94 | const rawRun = bench.run;
95 | bench.run = (options?: Benchmark.Options) => {
96 | console.warn(
97 | `[CodSpeed] bench detected but no instrumentation found, falling back to benchmark.js`
98 | );
99 | return rawRun.bind(bench)(options);
100 | };
101 | return bench;
102 | }
103 | const callingFile = getCallingFile(2); // [here, withCodSpeed, actual caller]
104 | const codspeedBench = bench as BenchmarkWithOptions;
105 | if (codspeedBench.name !== undefined) {
106 | codspeedBench.uri = `${callingFile}::${bench.name}`;
107 | }
108 | // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/ban-ts-comment
109 | // @ts-ignore
110 | bench.run = async function (options?: Benchmark.Options): Promise {
111 | await runBenchmarks({
112 | benches: [codspeedBench],
113 | baseUri: callingFile,
114 | benchmarkCompletedListeners: bench.listeners("complete"),
115 | options,
116 | });
117 | return bench;
118 | };
119 | return bench;
120 | }
121 |
122 | function withCodSpeedSuite(suite: Benchmark.Suite): WithCodSpeedSuite {
123 | if (!Measurement.isInstrumented()) {
124 | const rawRun = suite.run;
125 | suite.run = (options?: Benchmark.Options) => {
126 | console.warn(
127 | `[CodSpeed] ${suite.length} benches detected but no instrumentation found, falling back to benchmark.js`
128 | );
129 | return rawRun.bind(suite)(options);
130 | };
131 | return suite as WithCodSpeedSuite;
132 | }
133 | suite.add = buildSuiteAdd(suite);
134 | const callingFile = getCallingFile(2); // [here, withCodSpeed, actual caller]
135 | // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/ban-ts-comment
136 | // @ts-ignore
137 | suite.run = async function (
138 | options?: Benchmark.Options
139 | ): Promise {
140 | const suiteName = suite.name;
141 | const benches = this as unknown as BenchmarkWithOptions[];
142 | let baseUri = callingFile;
143 | if (suiteName !== undefined) {
144 | baseUri += `::${suiteName}`;
145 | }
146 | await runBenchmarks({
147 | benches,
148 | baseUri,
149 | benchmarkCompletedListeners: suite.listeners("complete"),
150 | options,
151 | });
152 | return suite;
153 | };
154 | return suite as WithCodSpeedSuite;
155 | }
156 |
157 | type BenchmarkWithOptions = CodSpeedBenchmark & { options: Benchmark.Options };
158 |
159 | interface RunBenchmarksOptions {
160 | benches: BenchmarkWithOptions[];
161 | baseUri: string;
162 | benchmarkCompletedListeners: CallableFunction[];
163 | options?: Benchmark.Options;
164 | }
165 |
166 | async function runBenchmarks({
167 | benches,
168 | baseUri,
169 | benchmarkCompletedListeners,
170 | }: RunBenchmarksOptions): Promise {
171 | console.log(`[CodSpeed] running with @codspeed/benchmark.js v${__VERSION__}`);
172 | setupCore();
173 | for (let i = 0; i < benches.length; i++) {
174 | const bench = benches[i];
175 | const uri = bench.uri ?? `${baseUri}::unknown_${i}`;
176 | const isAsync = bench.options.async || bench.options.defer;
177 | let benchPayload;
178 | if (bench.options.defer) {
179 | benchPayload = () => {
180 | return new Promise((resolve, reject) => {
181 | (bench.fn as CallableFunction)({ resolve, reject });
182 | });
183 | };
184 | } else if (bench.options.async) {
185 | benchPayload = async () => {
186 | await (bench.fn as CallableFunction)();
187 | };
188 | } else {
189 | benchPayload = bench.fn as CallableFunction;
190 | }
191 |
192 | if (typeof bench.options.setup === "function") {
193 | await bench.options.setup();
194 | }
195 |
196 | if (isAsync) {
197 | await optimizeFunction(benchPayload);
198 | await mongoMeasurement.start(uri);
199 | global.gc?.();
200 | await (async function __codspeed_root_frame__() {
201 | Measurement.startInstrumentation();
202 | await benchPayload();
203 | Measurement.stopInstrumentation(uri);
204 | })();
205 | await mongoMeasurement.stop(uri);
206 | } else {
207 | optimizeFunctionSync(benchPayload);
208 | await mongoMeasurement.start(uri);
209 | (function __codspeed_root_frame__() {
210 | Measurement.startInstrumentation();
211 | benchPayload();
212 | Measurement.stopInstrumentation(uri);
213 | })();
214 | await mongoMeasurement.stop(uri);
215 | }
216 |
217 | if (typeof bench.options.teardown === "function") {
218 | await bench.options.teardown();
219 | }
220 |
221 | console.log(` ✔ Measured ${uri}`);
222 | benchmarkCompletedListeners.forEach((listener) => listener());
223 | }
224 | teardownCore();
225 | console.log(`[CodSpeed] Done running ${benches.length} benches.`);
226 | }
227 |
228 | /**
229 | * Dynamically setup the CodSpeed instruments.
230 | */
231 | export async function setupInstruments(
232 | body: SetupInstrumentsRequestBody
233 | ): Promise {
234 | if (!Measurement.isInstrumented()) {
235 | console.warn("[CodSpeed] No instrumentation found, using default mongoUrl");
236 |
237 | return { remoteAddr: body.mongoUrl };
238 | }
239 |
240 | return await mongoMeasurement.setupInstruments(body);
241 | }
242 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/src/types.ts:
--------------------------------------------------------------------------------
1 | import Benchmark, { Options } from "benchmark";
2 |
3 | export interface CodSpeedBenchOptions extends Options {
4 | uri: string;
5 | }
6 |
7 | export interface CodSpeedBenchmark extends Benchmark {
8 | uri: string;
9 | }
10 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/tests/__snapshots__/index.integ.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Benchmark check console output(instrumented=false) 1`] = `
4 | {
5 | "log": [],
6 | "warn": [
7 | [
8 | "[CodSpeed] bench detected but no instrumentation found, falling back to benchmark.js",
9 | ],
10 | ],
11 | }
12 | `;
13 |
14 | exports[`Benchmark check console output(instrumented=true) 1`] = `
15 | {
16 | "log": [
17 | [
18 | " ✔ Measured packages/benchmark.js-plugin/tests/index.integ.test.ts::RegExpSingle",
19 | ],
20 | [
21 | "[CodSpeed] Done running 1 benches.",
22 | ],
23 | ],
24 | "warn": [],
25 | }
26 | `;
27 |
28 | exports[`Benchmark.Suite check console output(instrumented=false) 1`] = `
29 | {
30 | "log": [],
31 | "warn": [
32 | [
33 | "[CodSpeed] 2 benches detected but no instrumentation found, falling back to benchmark.js",
34 | ],
35 | ],
36 | }
37 | `;
38 |
39 | exports[`Benchmark.Suite check console output(instrumented=true) 1`] = `
40 | {
41 | "log": [
42 | [
43 | " ✔ Measured packages/benchmark.js-plugin/tests/index.integ.test.ts::thesuite::RegExp",
44 | ],
45 | [
46 | " ✔ Measured packages/benchmark.js-plugin/tests/index.integ.test.ts::thesuite::unknown_1",
47 | ],
48 | [
49 | "[CodSpeed] Done running 2 benches.",
50 | ],
51 | ],
52 | "warn": [],
53 | }
54 | `;
55 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/tests/index.integ.test.ts:
--------------------------------------------------------------------------------
1 | import { mockDeep, mockReset } from "jest-mock-extended";
2 | const mockCore = mockDeep();
3 |
4 | import * as core from "@codspeed/core";
5 | import Benchmark from "benchmark";
6 | import { withCodSpeed } from "..";
7 | import { registerBenchmarks } from "./registerBenchmarks";
8 | import { registerOtherBenchmarks } from "./registerOtherBenchmarks";
9 |
10 | jest.mock("@codspeed/core", () => {
11 | mockCore.getGitDir = jest.requireActual("@codspeed/core").getGitDir;
12 | return mockCore;
13 | });
14 |
15 | beforeEach(() => {
16 | mockReset(mockCore);
17 | jest.clearAllMocks();
18 | });
19 |
20 | const benchOptions: Benchmark.Options = {
21 | maxTime: 0.01,
22 | };
23 |
24 | describe("Benchmark", () => {
25 | it("simple benchmark", async () => {
26 | mockCore.Measurement.isInstrumented.mockReturnValue(false);
27 | const bench = withCodSpeed(
28 | new Benchmark(
29 | "RegExp",
30 | function () {
31 | /o/.test("Hello World!");
32 | },
33 | benchOptions
34 | )
35 | );
36 | const onComplete = jest.fn();
37 | bench.on("complete", onComplete);
38 | await bench.run();
39 | expect(onComplete).toHaveBeenCalled();
40 | expect(mockCore.Measurement.startInstrumentation).not.toHaveBeenCalled();
41 | expect(mockCore.Measurement.stopInstrumentation).not.toHaveBeenCalled();
42 | });
43 | it("check core methods are called", async () => {
44 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
45 |
46 | const bench = withCodSpeed(
47 | new Benchmark(
48 | "RegExpSingle",
49 | function () {
50 | /o/.test("Hello World!");
51 | },
52 | benchOptions
53 | )
54 | );
55 | const onComplete = jest.fn();
56 | bench.on("complete", onComplete);
57 | await bench.run();
58 | expect(onComplete).toHaveBeenCalled();
59 | expect(mockCore.Measurement.startInstrumentation).toHaveBeenCalled();
60 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
61 | "packages/benchmark.js-plugin/tests/index.integ.test.ts::RegExpSingle"
62 | );
63 | });
64 | it("check error handling", async () => {
65 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
66 | const bench = withCodSpeed(
67 | new Benchmark(
68 | "throwing",
69 | () => {
70 | throw new Error("test");
71 | },
72 | benchOptions
73 | )
74 | );
75 | await expect(bench.run()).rejects.toThrowError("test");
76 | });
77 | it.each([true, false])(
78 | "check console output(instrumented=%p) ",
79 | async (instrumented) => {
80 | const logSpy = jest.spyOn(console, "log");
81 | const warnSpy = jest.spyOn(console, "warn");
82 | mockCore.Measurement.isInstrumented.mockReturnValue(instrumented);
83 | await withCodSpeed(
84 | new Benchmark(
85 | "RegExpSingle",
86 | function () {
87 | /o/.test("Hello World!");
88 | },
89 | benchOptions
90 | )
91 | ).run();
92 | if (instrumented) {
93 | expect(logSpy).toHaveBeenCalledWith(
94 | expect.stringContaining(
95 | "[CodSpeed] running with @codspeed/benchmark.js v"
96 | )
97 | );
98 | expect({
99 | log: logSpy.mock.calls.slice(1),
100 | warn: warnSpy.mock.calls,
101 | }).toMatchSnapshot();
102 | } else {
103 | expect({
104 | log: logSpy.mock.calls.slice(1),
105 | warn: warnSpy.mock.calls,
106 | }).toMatchSnapshot();
107 | }
108 | }
109 | );
110 | it("should call setup and teardown", async () => {
111 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
112 | const setup = jest.fn();
113 | const teardown = jest.fn();
114 | const bench = withCodSpeed(
115 | new Benchmark(
116 | "RegExpSingle",
117 | function () {
118 | /o/.test("Hello World!");
119 | },
120 | { ...benchOptions, setup, teardown }
121 | )
122 | );
123 | await bench.run();
124 | expect(setup).toHaveBeenCalled();
125 | expect(teardown).toHaveBeenCalled();
126 | });
127 | });
128 |
129 | describe("Benchmark.Suite", () => {
130 | it("simple suite", async () => {
131 | mockCore.Measurement.isInstrumented.mockReturnValue(false);
132 | const suite = withCodSpeed(new Benchmark.Suite());
133 | suite.add(
134 | "RegExp",
135 | function () {
136 | /o/.test("Hello World!");
137 | },
138 | benchOptions
139 | );
140 | const onComplete = jest.fn();
141 | suite.on("complete", onComplete);
142 | await suite.run({ maxTime: 0.1, initCount: 1 });
143 | expect(onComplete).toHaveBeenCalled();
144 | expect(mockCore.Measurement.startInstrumentation).not.toHaveBeenCalled();
145 | expect(mockCore.Measurement.stopInstrumentation).not.toHaveBeenCalled();
146 | });
147 | it("check core methods are called", async () => {
148 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
149 | const suite = withCodSpeed(new Benchmark.Suite()).add(
150 | "RegExp",
151 | function () {
152 | /o/.test("Hello World!");
153 | },
154 | benchOptions
155 | );
156 | const onComplete = jest.fn();
157 | suite.on("complete", onComplete);
158 | await suite.run({ maxTime: 0.1, initCount: 1 });
159 | expect(mockCore.Measurement.startInstrumentation).toHaveBeenCalled();
160 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
161 | "packages/benchmark.js-plugin/tests/index.integ.test.ts::RegExp"
162 | );
163 | });
164 | it("check suite name is in the uri", async () => {
165 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
166 | await withCodSpeed(new Benchmark.Suite("thesuite"))
167 | .add(
168 | "RegExp",
169 | function () {
170 | /o/.test("Hello World!");
171 | },
172 | benchOptions
173 | )
174 | .add(() => {
175 | /o/.test("Hello World!");
176 | }, benchOptions)
177 | .run();
178 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
179 | "packages/benchmark.js-plugin/tests/index.integ.test.ts::thesuite::RegExp"
180 | );
181 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
182 | "packages/benchmark.js-plugin/tests/index.integ.test.ts::thesuite::unknown_1"
183 | );
184 | });
185 | it("check error handling", async () => {
186 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
187 | const bench = withCodSpeed(new Benchmark.Suite("thesuite")).add(
188 | "throwing",
189 | () => {
190 | throw new Error("test");
191 | }
192 | );
193 | await expect(bench.run()).rejects.toThrowError("test");
194 | });
195 | it.each([true, false])(
196 | "check console output(instrumented=%p) ",
197 | async (instrumented) => {
198 | const logSpy = jest.spyOn(console, "log");
199 | const warnSpy = jest.spyOn(console, "warn");
200 | mockCore.Measurement.isInstrumented.mockReturnValue(instrumented);
201 | await withCodSpeed(new Benchmark.Suite("thesuite"))
202 | .add(
203 | "RegExp",
204 | function () {
205 | /o/.test("Hello World!");
206 | },
207 | benchOptions
208 | )
209 | .add(() => {
210 | /o/.test("Hello World!");
211 | }, benchOptions)
212 | .run();
213 | if (instrumented) {
214 | expect(logSpy).toHaveBeenCalledWith(
215 | expect.stringContaining(
216 | "[CodSpeed] running with @codspeed/benchmark.js v"
217 | )
218 | );
219 | expect({
220 | log: logSpy.mock.calls.slice(1),
221 | warn: warnSpy.mock.calls,
222 | }).toMatchSnapshot();
223 | } else {
224 | expect({
225 | log: logSpy.mock.calls.slice(1),
226 | warn: warnSpy.mock.calls,
227 | }).toMatchSnapshot();
228 | }
229 | }
230 | );
231 | it("check nested file path is in the uri when bench is registered in another file", async () => {
232 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
233 | const suite = withCodSpeed(new Benchmark.Suite("thesuite"));
234 | registerBenchmarks(suite);
235 | const onComplete = jest.fn();
236 | suite.on("complete", onComplete);
237 | await suite.run({ maxTime: 0.1, initCount: 1 });
238 | expect(mockCore.Measurement.startInstrumentation).toHaveBeenCalled();
239 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
240 | "packages/benchmark.js-plugin/tests/registerBenchmarks.ts::thesuite::RegExp"
241 | );
242 | });
243 | it("check that benchmarks with same name have different URIs when registered in different files", async () => {
244 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
245 | const suite = withCodSpeed(new Benchmark.Suite("thesuite"));
246 | registerBenchmarks(suite);
247 | registerOtherBenchmarks(suite);
248 | const onComplete = jest.fn();
249 | suite.on("complete", onComplete);
250 | await suite.run({ maxTime: 0.1, initCount: 1 });
251 | expect(mockCore.Measurement.startInstrumentation).toHaveBeenCalled();
252 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
253 | "packages/benchmark.js-plugin/tests/registerBenchmarks.ts::thesuite::RegExp"
254 | );
255 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
256 | "packages/benchmark.js-plugin/tests/registerOtherBenchmarks.ts::thesuite::RegExp"
257 | );
258 | });
259 | it("should call setupCore and teardownCore only once after run()", async () => {
260 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
261 | const suite = withCodSpeed(new Benchmark.Suite("thesuite"));
262 | registerBenchmarks(suite);
263 | registerOtherBenchmarks(suite);
264 |
265 | expect(mockCore.setupCore).not.toHaveBeenCalled();
266 | expect(mockCore.teardownCore).not.toHaveBeenCalled();
267 |
268 | await suite.run({ maxTime: 0.1, initCount: 1 });
269 |
270 | expect(mockCore.setupCore).toHaveBeenCalledTimes(1);
271 | expect(mockCore.teardownCore).toHaveBeenCalledTimes(1);
272 | });
273 | it("should call setup and teardown", async () => {
274 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
275 | const setup = jest.fn();
276 | const teardown = jest.fn();
277 |
278 | const suite = withCodSpeed(new Benchmark.Suite("thesuite")).add(
279 | "RegExpSingle",
280 | function () {
281 | /o/.test("Hello World!");
282 | },
283 | { ...benchOptions, setup, teardown }
284 | );
285 | await suite.run();
286 |
287 | expect(setup).toHaveBeenCalled();
288 | expect(teardown).toHaveBeenCalled();
289 | });
290 | });
291 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/tests/registerBenchmarks.ts:
--------------------------------------------------------------------------------
1 | import type { WithCodSpeedSuite } from "..";
2 |
3 | export function registerBenchmarks(suite: WithCodSpeedSuite) {
4 | suite.add(
5 | "RegExp",
6 | function () {
7 | /o/.test("Hello World!");
8 | },
9 | { maxTime: 0.1 }
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/tests/registerOtherBenchmarks.ts:
--------------------------------------------------------------------------------
1 | import type { WithCodSpeedSuite } from "..";
2 |
3 | export function registerOtherBenchmarks(suite: WithCodSpeedSuite) {
4 | suite.add(
5 | "RegExp",
6 | function () {
7 | /o/.test("Hello World!");
8 | },
9 | { maxTime: 0.1 }
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "rootDir": "src",
6 | "types": ["jest", "node"],
7 | "typeRoots": ["node_modules/@types", "../../node_modules/@types"]
8 | },
9 | "references": [{ "path": "../core" }],
10 | "include": ["src/**/*.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/benchmark.js-plugin/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["jest", "node"]
5 | },
6 | "include": ["tests/**/*.ts", "benches/**/*.ts", "jest.config.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | prebuilds
3 | generated
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 |
2 |
@codspeed/core
3 |
4 | The core Node library used to integrate with Codspeed runners
5 |
6 | [](https://github.com/CodSpeedHQ/codspeed-node/actions/workflows/ci.yml)
7 | [](https://www.npmjs.com/package/@codspeed/core)
8 | [](https://discord.com/invite/MxpaCfKSqF)
9 | [](https://codspeed.io/CodSpeedHQ/codspeed-node)
10 |
11 |
12 |
13 | For now, this package should not be used directly. Instead, use one of the integration packages:
14 |
15 | - [`@codspeed/vitest-plugin`](../vitest-plugin): vitest compatibility layer for CodSpeed
16 | - [`@codspeed/tinybench-plugin`](../tinybench-plugin): tinybench compatibility layer for CodSpeed
17 | - [`@codspeed/benchmark.js-plugin`](../benchmark.js-plugin): Benchmark.js compatibility layer for CodSpeed
18 |
--------------------------------------------------------------------------------
/packages/core/binding.gyp:
--------------------------------------------------------------------------------
1 | {
2 | "targets": [
3 | {
4 | "target_name": "native_core",
5 | "cflags!": [
6 | "-fno-exceptions"
7 | ],
8 | "cflags_cc!": [
9 | "-fno-exceptions"
10 | ],
11 | "sources": [
12 | "src/native_core/measurement/measurement.cc",
13 | "src/native_core/linux_perf/linux_perf.cc",
14 | "src/native_core/linux_perf/linux_perf_listener.cc",
15 | "src/native_core/native_core.cc"
16 | ],
17 | "include_dirs": [
18 | "/src/",
17 | "/.rollup.cache/",
18 | ],
19 | };
20 |
--------------------------------------------------------------------------------
/packages/core/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
2 | module.exports = {
3 | preset: "ts-jest",
4 | testEnvironment: "node",
5 | transform: {
6 | "^.+\\.tsx?$": [
7 | "ts-jest",
8 | {
9 | tsconfig: "tsconfig.test.json",
10 | },
11 | ],
12 | },
13 | testPathIgnorePatterns: [
14 | "/node_modules/",
15 | "/tests/",
16 | "/.rollup.cache/",
17 | ],
18 | };
19 |
--------------------------------------------------------------------------------
/packages/core/moon.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | clean:
3 | args:
4 | - build
5 | - generated/openapi
6 | build:
7 | deps:
8 | - build-native-addon
9 | - build-tracer-client
10 |
11 | build-native-addon:
12 | command: prebuildify --napi --strip
13 | inputs:
14 | - "src/native_core/**/*.cc"
15 | - "src/native_core/**/*.h"
16 | - "binding.gyp"
17 | outputs:
18 | - "prebuilds"
19 |
20 | build-tracer-client:
21 | inputs:
22 | - "./tracer.spec.json"
23 | outputs:
24 | - "src/generated/openapi"
25 | command: openapi --client axios --input ./tracer.spec.json --name MongoTracer --output ./src/generated/openapi
26 |
27 | typecheck:
28 | deps:
29 | - build-tracer-client
30 |
31 | lint:
32 | deps:
33 | - build-tracer-client
34 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@codspeed/core",
3 | "version": "4.0.1",
4 | "description": "The core Node library used to integrate with Codspeed runners",
5 | "keywords": [
6 | "codspeed",
7 | "benchmark",
8 | "performance"
9 | ],
10 | "files": [
11 | "dist",
12 | "prebuilds"
13 | ],
14 | "main": "dist/index.cjs.js",
15 | "module": "dist/index.es5.js",
16 | "types": "dist/index.d.ts",
17 | "gypfile": true,
18 | "author": "Arthur Pastel ",
19 | "repository": "https://github.com/CodSpeedHQ/codspeed-node",
20 | "homepage": "https://codspeed.io",
21 | "license": "Apache-2.0",
22 | "devDependencies": {
23 | "@types/find-up": "^4.0.0",
24 | "node-addon-api": "^5.1.0",
25 | "node-gyp": "^9.3.1",
26 | "openapi-typescript-codegen": "^0.23.0",
27 | "prebuildify": "^5.0.1"
28 | },
29 | "dependencies": {
30 | "axios": "^1.4.0",
31 | "find-up": "^6.3.0",
32 | "form-data": "^4.0.0",
33 | "node-gyp-build": "^4.6.0"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/core/rollup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "rollup";
2 | import { declarationsPlugin, jsPlugins } from "../../rollup.options";
3 |
4 | import pkg from "./package.json" assert { type: "json" };
5 | const entrypoint = "src/index.ts";
6 |
7 | export default defineConfig([
8 | {
9 | input: entrypoint,
10 | output: [
11 | {
12 | file: pkg.types,
13 | format: "es",
14 | sourcemap: true,
15 | },
16 | ],
17 | plugins: declarationsPlugin({ compilerOptions: { composite: false } }),
18 | },
19 | {
20 | input: entrypoint,
21 | output: [
22 | {
23 | file: pkg.main,
24 | format: "cjs",
25 | sourcemap: true,
26 | },
27 | { file: pkg.module, format: "es", sourcemap: true },
28 | ],
29 | plugins: jsPlugins(pkg.version),
30 | },
31 | ]);
32 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | import { checkV8Flags } from "./introspection";
2 | import { MongoMeasurement } from "./mongoMeasurement";
3 | import native_core from "./native_core";
4 |
5 | declare const __VERSION__: string;
6 |
7 | const linuxPerf = new native_core.LinuxPerf();
8 |
9 | export const isBound = native_core.isBound;
10 |
11 | export const mongoMeasurement = new MongoMeasurement();
12 |
13 | export const setupCore = () => {
14 | native_core.Measurement.stopInstrumentation(
15 | `Metadata: codspeed-node ${__VERSION__}`
16 | );
17 | linuxPerf.start();
18 | checkV8Flags();
19 | };
20 |
21 | export const teardownCore = () => {
22 | linuxPerf.stop();
23 | };
24 |
25 | export type {
26 | SetupInstrumentsRequestBody,
27 | SetupInstrumentsResponse,
28 | } from "./generated/openapi";
29 | export { getV8Flags, tryIntrospect } from "./introspection";
30 | export { optimizeFunction, optimizeFunctionSync } from "./optimization";
31 | export * from "./utils";
32 | export const Measurement = native_core.Measurement;
33 |
--------------------------------------------------------------------------------
/packages/core/src/introspection.ts:
--------------------------------------------------------------------------------
1 | import { writeFileSync } from "fs";
2 |
3 | const CUSTOM_INTROSPECTION_EXIT_CODE = 0;
4 |
5 | export const getV8Flags = () => {
6 | const nodeVersionMajor = parseInt(process.version.slice(1).split(".")[0]);
7 |
8 | const flags = [
9 | "--hash-seed=1",
10 | "--random-seed=1",
11 | "--no-opt",
12 | "--predictable",
13 | "--predictable-gc-schedule",
14 | "--interpreted-frames-native-stack",
15 | "--allow-natives-syntax",
16 | "--expose-gc",
17 | "--no-concurrent-sweeping",
18 | "--max-old-space-size=4096",
19 | ];
20 | if (nodeVersionMajor < 18) {
21 | flags.push("--no-randomize-hashes");
22 | }
23 | if (nodeVersionMajor < 20) {
24 | flags.push("--no-scavenge-task");
25 | }
26 | return flags;
27 | };
28 |
29 | export const tryIntrospect = () => {
30 | if (process.env.__CODSPEED_NODE_CORE_INTROSPECTION_PATH__ !== undefined) {
31 | const introspectionMetadata = {
32 | flags: getV8Flags(),
33 | };
34 | writeFileSync(
35 | process.env.__CODSPEED_NODE_CORE_INTROSPECTION_PATH__,
36 | JSON.stringify(introspectionMetadata)
37 | );
38 | process.exit(CUSTOM_INTROSPECTION_EXIT_CODE);
39 | }
40 | };
41 |
42 | export const checkV8Flags = () => {
43 | const requiredFlags = getV8Flags();
44 | const actualFlags = process.execArgv;
45 | const missingFlags = requiredFlags.filter(
46 | (flag) => !actualFlags.includes(flag)
47 | );
48 | if (missingFlags.length > 0) {
49 | console.warn(
50 | `[CodSpeed] missing required flags: ${missingFlags.join(", ")}`
51 | );
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/packages/core/src/mongoMeasurement.ts:
--------------------------------------------------------------------------------
1 | import {
2 | MongoTracer,
3 | SetupInstrumentsRequestBody,
4 | SetupInstrumentsResponse,
5 | } from "./generated/openapi";
6 |
7 | export type { SetupInstrumentsRequestBody };
8 |
9 | export class MongoMeasurement {
10 | private tracerClient: MongoTracer | undefined;
11 |
12 | constructor() {
13 | const serverUrl = process.env.CODSPEED_MONGO_INSTR_SERVER_ADDRESS;
14 |
15 | if (serverUrl !== undefined) {
16 | this.tracerClient = new MongoTracer({
17 | BASE: serverUrl,
18 | });
19 | }
20 | }
21 |
22 | public async setupInstruments(
23 | body: SetupInstrumentsRequestBody
24 | ): Promise {
25 | if (this.tracerClient === undefined) {
26 | throw new Error("MongoDB Instrumentation is not enabled");
27 | }
28 | return await this.tracerClient.instruments.setup(body);
29 | }
30 |
31 | public async start(uri: string) {
32 | if (this.tracerClient !== undefined) {
33 | await this.tracerClient.instrumentation.start({
34 | uri,
35 | });
36 | }
37 | }
38 |
39 | public async stop(uri: string) {
40 | if (this.tracerClient !== undefined) {
41 | await this.tracerClient.instrumentation.stop({
42 | uri,
43 | });
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/core/src/native_core/index.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import { LinuxPerf } from "./linux_perf/linux_perf";
3 | import { Measurement } from "./measurement/measurement";
4 | interface NativeCore {
5 | Measurement: Measurement;
6 | LinuxPerf: typeof LinuxPerf;
7 | }
8 |
9 | interface NativeCoreWithBindingStatus extends NativeCore {
10 | isBound: boolean;
11 | }
12 |
13 | let native_core: NativeCoreWithBindingStatus;
14 | try {
15 | // eslint-disable-next-line @typescript-eslint/no-var-requires
16 | const nativeCore = require("node-gyp-build")(
17 | path.dirname(__dirname)
18 | ) as NativeCore;
19 | native_core = {
20 | ...nativeCore,
21 | isBound: true,
22 | };
23 | } catch (e) {
24 | native_core = {
25 | Measurement: {
26 | isInstrumented: () => false,
27 | // eslint-disable-next-line @typescript-eslint/no-empty-function
28 | startInstrumentation: () => {},
29 | // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
30 | stopInstrumentation: (at) => {},
31 | },
32 | LinuxPerf: class LinuxPerf {
33 | start() {
34 | return false;
35 | }
36 | stop() {
37 | return false;
38 | }
39 | },
40 |
41 | isBound: false,
42 | };
43 | }
44 |
45 | export default native_core;
46 |
--------------------------------------------------------------------------------
/packages/core/src/native_core/linux_perf/linux_perf.cc:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "linux_perf.h"
4 | #include
5 |
6 | namespace codspeed_native {
7 |
8 | Napi::Object LinuxPerf::Initialize(Napi::Env env, Napi::Object exports) {
9 | Napi::Function func = DefineClass(env, "LinuxPerf",
10 | {InstanceMethod("start", &LinuxPerf::Start),
11 | InstanceMethod("stop", &LinuxPerf::Stop)});
12 |
13 | exports.Set("LinuxPerf", func);
14 | return exports;
15 | }
16 |
17 | LinuxPerf::LinuxPerf(const Napi::CallbackInfo &info)
18 | : Napi::ObjectWrap(info) {
19 | handler = nullptr;
20 | }
21 |
22 | Napi::Value LinuxPerf::Start(const Napi::CallbackInfo &info) {
23 | if (handler == nullptr) {
24 | v8::Isolate *isolate = v8::Isolate::GetCurrent();
25 | handler = new LinuxPerfHandler(isolate);
26 | handler->Enable();
27 | return Napi::Boolean::New(info.Env(), true);
28 | }
29 | return Napi::Boolean::New(info.Env(), false);
30 | }
31 |
32 | Napi::Value LinuxPerf::Stop(const Napi::CallbackInfo &info) {
33 | if (handler != nullptr) {
34 | handler->Disable();
35 | delete handler;
36 | handler = nullptr;
37 | return Napi::Boolean::New(info.Env(), true);
38 | }
39 | return Napi::Boolean::New(info.Env(), false);
40 | }
41 |
42 | } // namespace codspeed_native
--------------------------------------------------------------------------------
/packages/core/src/native_core/linux_perf/linux_perf.h:
--------------------------------------------------------------------------------
1 | #ifndef __LINUX_PERF_H
2 | #define __LINUX_PERF_H
3 |
4 | #include "v8-profiler.h"
5 | #include
6 | #include
7 | #include
8 |
9 | namespace codspeed_native {
10 |
11 | class LinuxPerfHandler : public v8::CodeEventHandler {
12 | public:
13 | explicit LinuxPerfHandler(v8::Isolate *isolate);
14 | ~LinuxPerfHandler() override;
15 |
16 | void Handle(v8::CodeEvent *code_event) override;
17 |
18 | private:
19 | std::ofstream mapFile;
20 | std::string FormatName(v8::CodeEvent *code_event);
21 | v8::Isolate *isolate_;
22 | };
23 |
24 | class LinuxPerf : public Napi::ObjectWrap {
25 | public:
26 | static Napi::Object Initialize(Napi::Env env, Napi::Object exports);
27 |
28 | LinuxPerf(const Napi::CallbackInfo &info);
29 | ~LinuxPerf() = default;
30 |
31 | Napi::Value Start(const Napi::CallbackInfo &info);
32 | Napi::Value Stop(const Napi::CallbackInfo &info);
33 |
34 | LinuxPerfHandler *handler;
35 | };
36 |
37 | } // namespace codspeed_native
38 |
39 | #endif // __LINUX_PERF_H
--------------------------------------------------------------------------------
/packages/core/src/native_core/linux_perf/linux_perf.ts:
--------------------------------------------------------------------------------
1 | export declare class LinuxPerf {
2 | constructor();
3 | start(): boolean;
4 | stop(): boolean;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/src/native_core/linux_perf/linux_perf_listener.cc:
--------------------------------------------------------------------------------
1 | #include "linux_perf.h"
2 | #include "utils.h"
3 | #include
4 | #include
5 |
6 | namespace codspeed_native {
7 |
8 | LinuxPerfHandler::LinuxPerfHandler(v8::Isolate *isolate)
9 | : v8::CodeEventHandler(isolate) {
10 | isolate_ = isolate;
11 | int pid = static_cast(uv_os_getpid());
12 | mapFile.open("/tmp/perf-" + std::to_string(pid) + ".map");
13 | }
14 |
15 | LinuxPerfHandler::~LinuxPerfHandler() { mapFile.close(); }
16 |
17 | std::string LinuxPerfHandler::FormatName(v8::CodeEvent *code_event) {
18 | std::string name = std::string(code_event->GetComment());
19 | if (name.empty()) {
20 | name = v8LocalStringToString(code_event->GetFunctionName());
21 | }
22 | return name;
23 | }
24 |
25 | void LinuxPerfHandler::Handle(v8::CodeEvent *code_event) {
26 | mapFile << std::hex << code_event->GetCodeStartAddress() << " "
27 | << code_event->GetCodeSize() << " ";
28 | mapFile << v8::CodeEvent::GetCodeEventTypeName(code_event->GetCodeType())
29 | << ":" << FormatName(code_event) << " "
30 | << v8LocalStringToString(code_event->GetScriptName()) << std::dec
31 | << ":" << code_event->GetScriptLine() << ":"
32 | << code_event->GetScriptColumn() << std::endl;
33 | }
34 |
35 | } // namespace codspeed_native
36 |
--------------------------------------------------------------------------------
/packages/core/src/native_core/linux_perf/utils.h:
--------------------------------------------------------------------------------
1 | #ifndef LINUX_PERF_UTILS_H
2 | #define LINUX_PERF_UTILS_H
3 |
4 | #include "v8-profiler.h"
5 |
6 | static inline std::string
7 | v8LocalStringToString(v8::Local v8String) {
8 | std::string buffer(v8String->Utf8Length(v8::Isolate::GetCurrent()) + 1, 0);
9 | v8String->WriteUtf8(v8::Isolate::GetCurrent(), &buffer[0],
10 | v8String->Utf8Length(v8::Isolate::GetCurrent()) + 1);
11 | // Sanitize name, removing unwanted \0 resulted from WriteUtf8
12 | return std::string(buffer.c_str());
13 | }
14 |
15 | #endif // LINUX_PERF_UTILS_H
--------------------------------------------------------------------------------
/packages/core/src/native_core/measurement/measurement.cc:
--------------------------------------------------------------------------------
1 | #include "measurement.h"
2 | #include
3 |
4 | namespace codspeed_native {
5 | namespace Measurement {
6 |
7 | Napi::Boolean IsInstrumented(const Napi::CallbackInfo &info) {
8 | Napi::Env env = info.Env();
9 | uint depth = RUNNING_ON_VALGRIND;
10 | return Napi::Boolean::New(env, depth > 0);
11 | }
12 |
13 | void StartInstrumentation(const Napi::CallbackInfo &info) {
14 | CALLGRIND_ZERO_STATS;
15 | CALLGRIND_START_INSTRUMENTATION;
16 | return;
17 | }
18 |
19 | void StopInstrumentation(const Napi::CallbackInfo &info) {
20 | CALLGRIND_STOP_INSTRUMENTATION;
21 | Napi::Env env = info.Env();
22 | if (info.Length() != 1) {
23 | Napi::TypeError::New(env, "Wrong number of arguments")
24 | .ThrowAsJavaScriptException();
25 | return;
26 | }
27 | if (!info[0].IsString()) {
28 | Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
29 | return;
30 | }
31 |
32 | std::string pos = info[0].As().Utf8Value();
33 | CALLGRIND_DUMP_STATS_AT(&pos.c_str()[0]);
34 | return;
35 | }
36 |
37 | Napi::Object Initialize(Napi::Env env, Napi::Object exports) {
38 | Napi::Object measurementObj = Napi::Object::New(env);
39 |
40 | measurementObj.Set(Napi::String::New(env, "isInstrumented"),
41 | Napi::Function::New(env, IsInstrumented));
42 | measurementObj.Set(Napi::String::New(env, "startInstrumentation"),
43 | Napi::Function::New(env, StartInstrumentation));
44 | measurementObj.Set(Napi::String::New(env, "stopInstrumentation"),
45 | Napi::Function::New(env, StopInstrumentation));
46 |
47 | exports.Set(Napi::String::New(env, "Measurement"), measurementObj);
48 |
49 | return exports;
50 | }
51 |
52 | } // namespace Measurement
53 | } // namespace codspeed_native
--------------------------------------------------------------------------------
/packages/core/src/native_core/measurement/measurement.h:
--------------------------------------------------------------------------------
1 | #ifndef MEASUREMENT_H
2 | #define MEASUREMENT_H
3 |
4 | #include
5 |
6 | namespace codspeed_native {
7 | namespace Measurement {
8 |
9 | Napi::Boolean IsInstrumented(const Napi::CallbackInfo &info);
10 | void StartInstrumentation(const Napi::CallbackInfo &info);
11 | void StopInstrumentation(const Napi::CallbackInfo &info);
12 | Napi::Object Initialize(Napi::Env env, Napi::Object exports);
13 |
14 | } // namespace Measurement
15 | } // namespace codspeed_native
16 |
17 | #endif // MEASUREMENT_H
18 |
--------------------------------------------------------------------------------
/packages/core/src/native_core/measurement/measurement.ts:
--------------------------------------------------------------------------------
1 | export interface Measurement {
2 | isInstrumented: () => boolean;
3 | startInstrumentation: () => void;
4 | stopInstrumentation: (pos: string) => void;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/src/native_core/native_core.cc:
--------------------------------------------------------------------------------
1 | #include "linux_perf/linux_perf.h"
2 | #include "measurement/measurement.h"
3 | #include
4 |
5 | namespace codspeed_native {
6 |
7 | Napi::Object Initialize(Napi::Env env, Napi::Object exports) {
8 | codspeed_native::LinuxPerf::Initialize(env, exports);
9 | codspeed_native::Measurement::Initialize(env, exports);
10 |
11 | return exports;
12 | }
13 |
14 | NODE_API_MODULE(native_core, Initialize)
15 |
16 | } // namespace codspeed_native
17 |
--------------------------------------------------------------------------------
/packages/core/src/optimization.ts:
--------------------------------------------------------------------------------
1 | export const optimizeFunction = async (fn: CallableFunction) => {
2 | // Source: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#optimization-killers
3 | // a total of 7 calls seems to be the sweet spot
4 | await fn();
5 | await fn();
6 | await fn();
7 | await fn();
8 | await fn();
9 | await fn();
10 | eval("%OptimizeFunctionOnNextCall(fn)");
11 | await fn(); // optimize
12 | };
13 |
14 | export const optimizeFunctionSync = (fn: CallableFunction) => {
15 | // Source: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#optimization-killers
16 | // a total of 7 calls seems to be the sweet spot
17 | fn();
18 | fn();
19 | fn();
20 | fn();
21 | fn();
22 | fn();
23 | eval("%OptimizeFunctionOnNextCall(fn)");
24 | fn(); // optimize
25 | };
26 |
--------------------------------------------------------------------------------
/packages/core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { findUpSync, Options as FindupOptions } from "find-up";
2 | import { dirname } from "path";
3 |
4 | export function getGitDir(path: string): string | undefined {
5 | const dotGitPath = findUpSync(".git", {
6 | cwd: path,
7 | type: "directory",
8 | } as FindupOptions);
9 | return dotGitPath ? dirname(dotGitPath) : undefined;
10 | }
11 |
12 | /**
13 | * Log debug messages if the environment variable `CODSPEED_DEBUG` is set.
14 | */
15 | export function logDebug(...args: unknown[]) {
16 | if (process.env.CODSPEED_DEBUG) {
17 | console.log(...args);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/tests/index.integ.test.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | export {}; // Make this a module
3 |
4 | beforeEach(() => {
5 | jest.resetModules();
6 | });
7 |
8 | describe("with bindings", () => {
9 | it("should be bound", () => {
10 | const isBound = require("..").isBound as boolean;
11 | expect(isBound).toBe(true);
12 | });
13 | });
14 |
15 | describe("without bindings", () => {
16 | const initialEnv = process.env;
17 | beforeAll(() => {
18 | process.env.npm_config_arch = "unknown";
19 | });
20 | afterAll(() => {
21 | process.env = initialEnv;
22 | });
23 | it("should not be bound", () => {
24 | const isBound = require("..").isBound as boolean;
25 | expect(isBound).toBe(false);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/packages/core/tracer.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "openapi": "3.0.3",
3 | "info": {
4 | "title": "CodSpeed MongoDB Tracer",
5 | "description": "Instrumentation API for CodSpeed Tracer",
6 | "version": "0.2.0"
7 | },
8 | "paths": {
9 | "/benchmark/start": {
10 | "post": {
11 | "tags": ["instrumentation"],
12 | "operationId": "start",
13 | "requestBody": {
14 | "content": {
15 | "application/json": {
16 | "schema": {
17 | "$ref": "#/components/schemas/InstrumentationRequestBody"
18 | }
19 | }
20 | },
21 | "required": true
22 | },
23 | "responses": {
24 | "200": {
25 | "description": "successful operation",
26 | "content": {
27 | "application/json": {
28 | "schema": {
29 | "$ref": "#/components/schemas/InstrumentationStatus"
30 | }
31 | }
32 | }
33 | },
34 | "4XX": {
35 | "$ref": "#/components/responses/Error"
36 | },
37 | "5XX": {
38 | "$ref": "#/components/responses/Error"
39 | }
40 | }
41 | }
42 | },
43 | "/benchmark/stop": {
44 | "post": {
45 | "tags": ["instrumentation"],
46 | "operationId": "stop",
47 | "requestBody": {
48 | "content": {
49 | "application/json": {
50 | "schema": {
51 | "$ref": "#/components/schemas/InstrumentationRequestBody"
52 | }
53 | }
54 | },
55 | "required": true
56 | },
57 | "responses": {
58 | "200": {
59 | "description": "successful operation",
60 | "content": {
61 | "application/json": {
62 | "schema": {
63 | "$ref": "#/components/schemas/InstrumentationStatus"
64 | }
65 | }
66 | }
67 | },
68 | "4XX": {
69 | "$ref": "#/components/responses/Error"
70 | },
71 | "5XX": {
72 | "$ref": "#/components/responses/Error"
73 | }
74 | }
75 | }
76 | },
77 | "/instruments/setup": {
78 | "post": {
79 | "tags": ["instruments"],
80 | "summary": "Start the instruments (proxy and aggregator) for the given `body.mongo_url`.",
81 | "description": "If other endpoints of the instrumentation server are called before this one, they will likely fail as the proxy and aggregator are not running yet.",
82 | "operationId": "setup",
83 | "requestBody": {
84 | "content": {
85 | "application/json": {
86 | "schema": {
87 | "$ref": "#/components/schemas/SetupInstrumentsRequestBody"
88 | }
89 | }
90 | },
91 | "required": true
92 | },
93 | "responses": {
94 | "200": {
95 | "description": "successful operation",
96 | "content": {
97 | "application/json": {
98 | "schema": {
99 | "$ref": "#/components/schemas/SetupInstrumentsResponse"
100 | }
101 | }
102 | }
103 | },
104 | "4XX": {
105 | "$ref": "#/components/responses/Error"
106 | },
107 | "5XX": {
108 | "$ref": "#/components/responses/Error"
109 | }
110 | }
111 | }
112 | },
113 | "/status": {
114 | "get": {
115 | "tags": ["instrumentation"],
116 | "operationId": "status",
117 | "responses": {
118 | "200": {
119 | "description": "successful operation",
120 | "content": {
121 | "application/json": {
122 | "schema": {
123 | "$ref": "#/components/schemas/InstrumentationStatus"
124 | }
125 | }
126 | }
127 | },
128 | "4XX": {
129 | "$ref": "#/components/responses/Error"
130 | },
131 | "5XX": {
132 | "$ref": "#/components/responses/Error"
133 | }
134 | }
135 | }
136 | },
137 | "/terminate": {
138 | "post": {
139 | "tags": ["instrumentation"],
140 | "operationId": "terminate",
141 | "responses": {
142 | "200": {
143 | "description": "successful operation",
144 | "content": {
145 | "application/json": {
146 | "schema": {
147 | "$ref": "#/components/schemas/AggregatorStore"
148 | }
149 | }
150 | }
151 | },
152 | "4XX": {
153 | "$ref": "#/components/responses/Error"
154 | },
155 | "5XX": {
156 | "$ref": "#/components/responses/Error"
157 | }
158 | }
159 | }
160 | }
161 | },
162 | "components": {
163 | "responses": {
164 | "Error": {
165 | "description": "Error",
166 | "content": {
167 | "application/json": {
168 | "schema": {
169 | "$ref": "#/components/schemas/Error"
170 | }
171 | }
172 | }
173 | }
174 | },
175 | "schemas": {
176 | "AggregatorMetadata": {
177 | "type": "object",
178 | "properties": {
179 | "name": {
180 | "type": "string"
181 | },
182 | "version": {
183 | "type": "string"
184 | }
185 | },
186 | "required": ["name", "version"]
187 | },
188 | "AggregatorStore": {
189 | "type": "object",
190 | "properties": {
191 | "metadata": {
192 | "nullable": true,
193 | "allOf": [
194 | {
195 | "$ref": "#/components/schemas/AggregatorMetadata"
196 | }
197 | ]
198 | },
199 | "queries": {
200 | "type": "object",
201 | "additionalProperties": {
202 | "type": "array",
203 | "items": {
204 | "$ref": "#/components/schemas/MongoQuery"
205 | }
206 | }
207 | }
208 | },
209 | "required": ["queries"]
210 | },
211 | "Document": {
212 | "type": "object"
213 | },
214 | "Error": {
215 | "description": "Error information from a response.",
216 | "type": "object",
217 | "properties": {
218 | "error_code": {
219 | "type": "string"
220 | },
221 | "message": {
222 | "type": "string"
223 | },
224 | "request_id": {
225 | "type": "string"
226 | }
227 | },
228 | "required": ["message", "request_id"]
229 | },
230 | "InstrumentationRequestBody": {
231 | "type": "object",
232 | "properties": {
233 | "uri": {
234 | "type": "string"
235 | }
236 | },
237 | "required": ["uri"]
238 | },
239 | "InstrumentationStatus": {
240 | "type": "object",
241 | "properties": {
242 | "currentUri": {
243 | "nullable": true,
244 | "type": "string"
245 | }
246 | }
247 | },
248 | "MongoQuery": {
249 | "type": "object",
250 | "properties": {
251 | "collection": {
252 | "type": "string"
253 | },
254 | "database": {
255 | "type": "string"
256 | },
257 | "explanation": {
258 | "nullable": true,
259 | "allOf": [
260 | {
261 | "$ref": "#/components/schemas/Document"
262 | }
263 | ]
264 | },
265 | "op": {
266 | "type": "string"
267 | },
268 | "query_documents": {
269 | "type": "array",
270 | "items": {
271 | "$ref": "#/components/schemas/Document"
272 | }
273 | },
274 | "response_documents": {
275 | "type": "array",
276 | "items": {
277 | "$ref": "#/components/schemas/Document"
278 | }
279 | }
280 | },
281 | "required": [
282 | "collection",
283 | "database",
284 | "op",
285 | "query_documents",
286 | "response_documents"
287 | ]
288 | },
289 | "SetupInstrumentsRequestBody": {
290 | "type": "object",
291 | "properties": {
292 | "mongoUrl": {
293 | "description": "The full `MONGO_URL` that is usually used to connect to the database.",
294 | "type": "string"
295 | }
296 | },
297 | "required": ["mongoUrl"]
298 | },
299 | "SetupInstrumentsResponse": {
300 | "type": "object",
301 | "properties": {
302 | "remoteAddr": {
303 | "description": "The patched `MONGO_URL` that should be used to connect to the database.",
304 | "type": "string"
305 | }
306 | },
307 | "required": ["remoteAddr"]
308 | }
309 | }
310 | },
311 | "tags": [
312 | {
313 | "name": "instrumentation"
314 | },
315 | {
316 | "name": "instruments"
317 | }
318 | ]
319 | }
320 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "rootDir": "src"
6 | },
7 | "include": ["src/**/*.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["jest", "node"]
5 | },
6 | "include": ["tests/**/*.ts", "benches/**/*.ts", "jest.config.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/README.md:
--------------------------------------------------------------------------------
1 |
2 |
@codspeed/tinybench-plugin
3 |
4 | tinybench compatibility layer for CodSpeed
5 |
6 | [](https://github.com/CodSpeedHQ/codspeed-node/actions/workflows/ci.yml)
7 | [](https://www.npmjs.com/package/@codspeed/tinybench-plugin)
8 | [](https://discord.com/invite/MxpaCfKSqF)
9 | [](https://codspeed.io/CodSpeedHQ/codspeed-node)
10 |
11 |
12 |
13 | ## Documentation
14 |
15 | Check out the [documentation](https://docs.codspeed.io/benchmarks/nodejs/tinybench) for complete integration instructions.
16 |
17 | ## Installation
18 |
19 | First, install the plugin [`@codspeed/tinybench-plugin`](https://www.npmjs.com/package/@codspeed/tinybench-plugin) and `tinybench` (if not already installed):
20 |
21 | ```sh
22 | npm install --save-dev @codspeed/tinybench-plugin tinybench
23 | ```
24 |
25 | or with `yarn`:
26 |
27 | ```sh
28 | yarn add --dev @codspeed/tinybench-plugin tinybench
29 | ```
30 |
31 | or with `pnpm`:
32 |
33 | ```sh
34 | pnpm add --save-dev @codspeed/tinybench-plugin tinybench
35 | ```
36 |
37 | ## Usage
38 |
39 | Let's create a fibonacci function and benchmark it with tinybench and the CodSpeed plugin:
40 |
41 | ```js title="benches/bench.mjs"
42 | import { Bench } from "tinybench";
43 | import { withCodSpeed } from "@codspeed/tinybench-plugin";
44 |
45 | function fibonacci(n) {
46 | if (n < 2) {
47 | return n;
48 | }
49 | return fibonacci(n - 1) + fibonacci(n - 2);
50 | }
51 |
52 | const bench = withCodSpeed(new Bench());
53 |
54 | bench
55 | .add("fibonacci10", () => {
56 | fibonacci(10);
57 | })
58 | .add("fibonacci15", () => {
59 | fibonacci(15);
60 | });
61 |
62 | await bench.run();
63 | console.table(bench.table());
64 | ```
65 |
66 | Here, a few things are happening:
67 |
68 | - We create a simple recursive fibonacci function.
69 | - We create a new `Bench` instance with CodSpeed support by using the **`withCodSpeed`** helper. This step is **critical** to enable CodSpeed on your benchmarks.
70 |
71 | - We add two benchmarks to the suite and launch it, benching our `fibonacci` function for 10 and 15.
72 |
73 | Now, we can run our benchmarks locally to make sure everything is working as expected:
74 |
75 | ```sh
76 | $ node benches/bench.mjs
77 | [CodSpeed] 2 benches detected but no instrumentation found
78 | [CodSpeed] falling back to tinybench
79 |
80 | ┌─────────┬───────────────┬─────────────┬───────────────────┬──────────┬─────────┐
81 | │ (index) │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │
82 | ├─────────┼───────────────┼─────────────┼───────────────────┼──────────┼─────────┤
83 | │ 0 │ 'fibonacci10' │ '1,810,236' │ 552.4139857896414 │ '±0.18%' │ 905119 │
84 | │ 1 │ 'fibonacci15' │ '177,516' │ 5633.276191749634 │ '±0.14%' │ 88759 │
85 | └─────────┴───────────────┴─────────────┴───────────────────┴──────────┴─────────┘
86 | ```
87 |
88 | And... Congrats🎉, CodSpeed is installed in your benchmarking suite! Locally, CodSpeed will fallback to tinybench since the instrumentation is only available in the CI environment for now.
89 |
90 | You can now [run those benchmarks in your CI](https://docs.codspeed.io/benchmarks/nodejs/tinybench#running-the-benchmarks-in-your-ci) to continuously get consistent performance measurements.
91 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/benches/parsePr.ts:
--------------------------------------------------------------------------------
1 | interface PullRequest {
2 | number: number;
3 | title: string;
4 | body: string;
5 | }
6 |
7 | function sendEvent(numberOfOperations: number): void {
8 | for (let i = 0; i < numberOfOperations; i++) {
9 | let a = i;
10 | a = a + 1;
11 | }
12 | }
13 |
14 | function logMetrics(
15 | numberOfOperations: number,
16 | numberOfDeepOperations: number
17 | ): void {
18 | for (let i = 0; i < numberOfOperations; i++) {
19 | for (let i = 0; i < numberOfOperations; i++) {
20 | let a = i;
21 | a = a + 1;
22 | a = a + 1;
23 | }
24 | sendEvent(numberOfDeepOperations);
25 | }
26 | }
27 |
28 | function parseTitle(title: string): void {
29 | logMetrics(10, 10);
30 | modifyTitle(title);
31 | }
32 |
33 | function modifyTitle(title: string): void {
34 | for (let i = 0; i < 100; i++) {
35 | let a = i;
36 | a = a + 1 + title.length;
37 | }
38 | }
39 |
40 | function prepareParsingBody(body: string): void {
41 | for (let i = 0; i < 100; i++) {
42 | let a = i;
43 | a = a + 1;
44 | }
45 | parseBody(body);
46 | }
47 |
48 | function parseBody(body: string): void {
49 | logMetrics(10, 10);
50 | for (let i = 0; i < 200; i++) {
51 | let a = i;
52 | a = a + 1;
53 | }
54 | parseIssueFixed(body);
55 | }
56 |
57 | function parseIssueFixed(body: string): number | null {
58 | const prefix = "fixes #";
59 | const index = body.indexOf(prefix);
60 | if (index === -1) {
61 | return null;
62 | }
63 |
64 | const start = index + prefix.length;
65 | let end = start;
66 | while (end < body.length && /\d/.test(body[end])) {
67 | end += 1;
68 | }
69 | return parseInt(body.slice(start, end));
70 | }
71 |
72 | export default function parsePr(pullRequest: PullRequest): void {
73 | parseTitle(pullRequest.title);
74 | prepareParsingBody(pullRequest.body);
75 | }
76 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/benches/sample.ts:
--------------------------------------------------------------------------------
1 | import { Bench } from "tinybench";
2 | import { withCodSpeed } from "..";
3 | import parsePr from "./parsePr";
4 |
5 | const LONG_BODY =
6 | new Array(1_000)
7 | .fill(
8 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Sunt, earum. Atque architecto vero veniam est tempora fugiat sint quo praesentium quia. Autem, veritatis omnis beatae iste delectus recusandae animi non."
9 | )
10 | .join("\n") + "fixes #123";
11 |
12 | const bench = withCodSpeed(new Bench({ time: 100 }));
13 |
14 | bench
15 | .add("switch 1", () => {
16 | let a = 1;
17 | let b = 2;
18 | const c = a;
19 | a = b;
20 | b = c;
21 | })
22 | .add("switch 2", () => {
23 | let a = 1;
24 | let b = 10;
25 | a = b + a;
26 | b = a - b;
27 | a = b - a;
28 | })
29 | .add("short body", () => {
30 | parsePr({ body: "fixes #123", title: "test", number: 124 });
31 | })
32 | .add("long body", () => {
33 | parsePr({ body: LONG_BODY, title: "test", number: 124 });
34 | });
35 |
36 | bench.run().then(() => {
37 | console.table(bench.table());
38 | });
39 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/jest.config.integ.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
2 | // eslint-disable-next-line no-undef
3 | module.exports = {
4 | preset: "ts-jest",
5 | testEnvironment: "node",
6 | transform: {
7 | "^.+\\.tsx?$": [
8 | "ts-jest",
9 | {
10 | tsconfig: "tsconfig.test.json",
11 | },
12 | ],
13 | },
14 | testPathIgnorePatterns: [
15 | "/node_modules/",
16 | "/src/",
17 | "/.rollup.cache/",
18 | ],
19 | };
20 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
2 | module.exports = {
3 | preset: "ts-jest",
4 | testEnvironment: "node",
5 | transform: {
6 | "^.+\\.tsx?$": [
7 | "ts-jest",
8 | {
9 | tsconfig: "tsconfig.test.json",
10 | },
11 | ],
12 | },
13 | testPathIgnorePatterns: [
14 | "/node_modules/",
15 | "/tests/",
16 | "/.rollup.cache/",
17 | ],
18 | };
19 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/moon.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | bench:
3 | command: node -r esbuild-register benches/sample.ts
4 | inputs:
5 | - "benches/**"
6 | local: true
7 | platform: "system"
8 | options:
9 | cache: false
10 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@codspeed/tinybench-plugin",
3 | "version": "4.0.1",
4 | "description": "tinybench compatibility layer for CodSpeed",
5 | "keywords": [
6 | "codspeed",
7 | "benchmark",
8 | "tinybench",
9 | "performance"
10 | ],
11 | "main": "dist/index.cjs.js",
12 | "module": "dist/index.es5.js",
13 | "types": "dist/index.d.ts",
14 | "files": [
15 | "dist"
16 | ],
17 | "author": "Arthur Pastel ",
18 | "repository": "https://github.com/CodSpeedHQ/codspeed-node",
19 | "homepage": "https://codspeed.io",
20 | "license": "Apache-2.0",
21 | "devDependencies": {
22 | "@types/stack-trace": "^0.0.30",
23 | "jest-mock-extended": "^3.0.4",
24 | "tinybench": "^2.5.0"
25 | },
26 | "dependencies": {
27 | "@codspeed/core": "workspace:^4.0.1",
28 | "stack-trace": "1.0.0-pre2"
29 | },
30 | "peerDependencies": {
31 | "tinybench": "^2.3.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/rollup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "rollup";
2 | import { declarationsPlugin, jsPlugins } from "../../rollup.options";
3 | import pkg from "./package.json" assert { type: "json" };
4 |
5 | const entrypoint = "src/index.ts";
6 |
7 | export default defineConfig([
8 | {
9 | input: entrypoint,
10 | output: [
11 | {
12 | file: pkg.types,
13 | format: "es",
14 | sourcemap: true,
15 | },
16 | ],
17 | plugins: declarationsPlugin({ compilerOptions: { composite: false } }),
18 | },
19 | {
20 | input: entrypoint,
21 | output: [
22 | {
23 | file: pkg.main,
24 | format: "cjs",
25 | sourcemap: true,
26 | },
27 | { file: pkg.module, format: "es", sourcemap: true },
28 | ],
29 | plugins: jsPlugins(pkg.version),
30 | external: ["@codspeed/core"],
31 | },
32 | ]);
33 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/src/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getGitDir,
3 | Measurement,
4 | mongoMeasurement,
5 | optimizeFunction,
6 | setupCore,
7 | SetupInstrumentsRequestBody,
8 | SetupInstrumentsResponse,
9 | teardownCore,
10 | tryIntrospect,
11 | } from "@codspeed/core";
12 | import path from "path";
13 | import { get as getStackTrace } from "stack-trace";
14 | import { Bench, Task } from "tinybench";
15 | import { fileURLToPath } from "url";
16 |
17 | declare const __VERSION__: string;
18 |
19 | tryIntrospect();
20 |
21 | type CodSpeedBenchOptions = Task["opts"] & {
22 | uri: string;
23 | };
24 |
25 | function isCodSpeedBenchOptions(
26 | options: Task["opts"]
27 | ): options is CodSpeedBenchOptions {
28 | return "uri" in options;
29 | }
30 |
31 | export function withCodSpeed(bench: Bench): Bench {
32 | if (!Measurement.isInstrumented()) {
33 | const rawRun = bench.run;
34 | bench.run = async () => {
35 | console.warn(
36 | `[CodSpeed] ${bench.tasks.length} benches detected but no instrumentation found, falling back to tinybench`
37 | );
38 | return await rawRun.bind(bench)();
39 | };
40 | return bench;
41 | }
42 |
43 | const rawAdd = bench.add;
44 | bench.add = (name, fn, opts: CodSpeedBenchOptions) => {
45 | const callingFile = getCallingFile();
46 | const uri = `${callingFile}::${name}`;
47 | const options = Object.assign({}, opts ?? {}, { uri });
48 | return rawAdd.bind(bench)(name, fn, options);
49 | };
50 | const rootCallingFile = getCallingFile();
51 |
52 | bench.run = async () => {
53 | console.log(`[CodSpeed] running with @codspeed/tinybench v${__VERSION__}`);
54 | setupCore();
55 | for (const task of bench.tasks) {
56 | const uri = isCodSpeedBenchOptions(task.opts)
57 | ? task.opts.uri
58 | : `${rootCallingFile}::${task.name}`;
59 |
60 | await task.opts.beforeAll?.call(task);
61 |
62 | // run optimizations
63 | await optimizeFunction(async () => {
64 | await task.opts.beforeEach?.call(task);
65 | await task.fn();
66 | await task.opts.afterEach?.call(task);
67 | });
68 |
69 | // run instrumented benchmark
70 | await task.opts.beforeEach?.call(task);
71 |
72 | await mongoMeasurement.start(uri);
73 | global.gc?.();
74 | await (async function __codspeed_root_frame__() {
75 | Measurement.startInstrumentation();
76 | await task.fn();
77 | Measurement.stopInstrumentation(uri);
78 | })();
79 | await mongoMeasurement.stop(uri);
80 |
81 | await task.opts.afterEach?.call(task);
82 |
83 | await task.opts.afterAll?.call(task);
84 |
85 | // print results
86 | console.log(` ✔ Measured ${uri}`);
87 | }
88 | teardownCore();
89 | console.log(`[CodSpeed] Done running ${bench.tasks.length} benches.`);
90 | return bench.tasks;
91 | };
92 | return bench;
93 | }
94 |
95 | function getCallingFile(): string {
96 | const stack = getStackTrace();
97 | let callingFile = stack[2].getFileName(); // [here, withCodSpeed, actual caller]
98 | const gitDir = getGitDir(callingFile);
99 | if (gitDir === undefined) {
100 | throw new Error("Could not find a git repository");
101 | }
102 | if (callingFile.startsWith("file://")) {
103 | callingFile = fileURLToPath(callingFile);
104 | }
105 | return path.relative(gitDir, callingFile);
106 | }
107 |
108 | /**
109 | * Dynamically setup the CodSpeed instruments.
110 | */
111 | export async function setupInstruments(
112 | body: SetupInstrumentsRequestBody
113 | ): Promise {
114 | if (!Measurement.isInstrumented()) {
115 | console.warn("[CodSpeed] No instrumentation found, using default mongoUrl");
116 |
117 | return { remoteAddr: body.mongoUrl };
118 | }
119 |
120 | return await mongoMeasurement.setupInstruments(body);
121 | }
122 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/tests/__snapshots__/index.integ.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Benchmark.Suite check console output(instrumented=false) 1`] = `
4 | {
5 | "log": [],
6 | "warn": [
7 | [
8 | "[CodSpeed] 2 benches detected but no instrumentation found, falling back to tinybench",
9 | ],
10 | ],
11 | }
12 | `;
13 |
14 | exports[`Benchmark.Suite check console output(instrumented=true) 1`] = `
15 | {
16 | "log": [
17 | [
18 | " ✔ Measured packages/tinybench-plugin/tests/index.integ.test.ts::RegExp",
19 | ],
20 | [
21 | " ✔ Measured packages/tinybench-plugin/tests/index.integ.test.ts::RegExp2",
22 | ],
23 | [
24 | "[CodSpeed] Done running 2 benches.",
25 | ],
26 | ],
27 | "warn": [],
28 | }
29 | `;
30 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/tests/index.integ.test.ts:
--------------------------------------------------------------------------------
1 | import { mockDeep, mockReset } from "jest-mock-extended";
2 | const mockCore = mockDeep();
3 |
4 | import * as core from "@codspeed/core";
5 | import { Bench } from "tinybench";
6 | import { withCodSpeed } from "..";
7 | import { registerBenchmarks } from "./registerBenchmarks";
8 | import { registerOtherBenchmarks } from "./registerOtherBenchmarks";
9 |
10 | jest.mock("@codspeed/core", () => {
11 | mockCore.getGitDir = jest.requireActual("@codspeed/core").getGitDir;
12 | return mockCore;
13 | });
14 |
15 | beforeEach(() => {
16 | mockReset(mockCore);
17 | jest.clearAllMocks();
18 | });
19 |
20 | describe("Benchmark.Suite", () => {
21 | it("simple suite", async () => {
22 | mockCore.Measurement.isInstrumented.mockReturnValue(false);
23 | const bench = withCodSpeed(new Bench({ time: 100 }));
24 | const onComplete = jest.fn();
25 | bench.add("RegExp", function () {
26 | /o/.test("Hello World!");
27 | });
28 | bench.getTask("RegExp")?.addEventListener("complete", onComplete);
29 | await bench.run();
30 |
31 | expect(onComplete).toHaveBeenCalled();
32 | expect(mockCore.mongoMeasurement.start).not.toHaveBeenCalled();
33 | expect(mockCore.mongoMeasurement.stop).not.toHaveBeenCalled();
34 | expect(mockCore.Measurement.startInstrumentation).not.toHaveBeenCalled();
35 | expect(mockCore.Measurement.stopInstrumentation).not.toHaveBeenCalled();
36 | });
37 | it("check core methods are called", async () => {
38 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
39 | await withCodSpeed(new Bench())
40 | .add("RegExp", function () {
41 | /o/.test("Hello World!");
42 | })
43 | .run();
44 |
45 | expect(mockCore.mongoMeasurement.start).toHaveBeenCalledWith(
46 | "packages/tinybench-plugin/tests/index.integ.test.ts::RegExp"
47 | );
48 | expect(mockCore.mongoMeasurement.stop).toHaveBeenCalledTimes(1);
49 | expect(mockCore.Measurement.startInstrumentation).toHaveBeenCalled();
50 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
51 | "packages/tinybench-plugin/tests/index.integ.test.ts::RegExp"
52 | );
53 | });
54 | it("check suite name is in the uri", async () => {
55 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
56 | await withCodSpeed(new Bench())
57 | .add("RegExp", function () {
58 | /o/.test("Hello World!");
59 | })
60 | .add("RegExp2", () => {
61 | /o/.test("Hello World!");
62 | })
63 | .run();
64 |
65 | expect(mockCore.mongoMeasurement.start).toHaveBeenCalledWith(
66 | "packages/tinybench-plugin/tests/index.integ.test.ts::RegExp"
67 | );
68 | expect(mockCore.mongoMeasurement.start).toHaveBeenCalledWith(
69 | "packages/tinybench-plugin/tests/index.integ.test.ts::RegExp2"
70 | );
71 | expect(mockCore.mongoMeasurement.stop).toHaveBeenCalledTimes(2);
72 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
73 | "packages/tinybench-plugin/tests/index.integ.test.ts::RegExp"
74 | );
75 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
76 | "packages/tinybench-plugin/tests/index.integ.test.ts::RegExp2"
77 | );
78 | });
79 | it("check error handling", async () => {
80 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
81 | const bench = withCodSpeed(new Bench());
82 | bench.add("throwing", async () => {
83 | throw new Error("test");
84 | });
85 | await expect(bench.run()).rejects.toThrowError("test");
86 | });
87 | it.each([true, false])(
88 | "check console output(instrumented=%p) ",
89 | async (instrumented) => {
90 | const logSpy = jest.spyOn(console, "log");
91 | const warnSpy = jest.spyOn(console, "warn");
92 | mockCore.Measurement.isInstrumented.mockReturnValue(instrumented);
93 | await withCodSpeed(new Bench({ time: 100 }))
94 | .add("RegExp", function () {
95 | /o/.test("Hello World!");
96 | })
97 | .add("RegExp2", () => {
98 | /o/.test("Hello World!");
99 | })
100 | .run();
101 | // Check that the first log contains "[CodSpeed] running with @codspeed/tinybench v"
102 | if (instrumented) {
103 | expect(logSpy).toHaveBeenCalledWith(
104 | expect.stringContaining(
105 | "[CodSpeed] running with @codspeed/tinybench v"
106 | )
107 | );
108 | expect({
109 | log: logSpy.mock.calls.slice(1),
110 | warn: warnSpy.mock.calls,
111 | }).toMatchSnapshot();
112 | } else {
113 | expect({
114 | log: logSpy.mock.calls,
115 | warn: warnSpy.mock.calls,
116 | }).toMatchSnapshot();
117 | }
118 | }
119 | );
120 | it("check nested file path is in the uri when bench is registered in another file", async () => {
121 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
122 | const bench = withCodSpeed(new Bench());
123 | registerBenchmarks(bench);
124 | await bench.run();
125 | expect(mockCore.Measurement.startInstrumentation).toHaveBeenCalled();
126 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
127 | "packages/tinybench-plugin/tests/registerBenchmarks.ts::RegExp"
128 | );
129 | });
130 | // TODO: this is not supported at the moment as tinybench does not support tasks with same name
131 | // remove `.failing` when tinybench supports it
132 | it.failing(
133 | "check that benchmarks with same name have different URIs when registered in different files",
134 | async () => {
135 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
136 | const bench = withCodSpeed(new Bench());
137 | registerBenchmarks(bench);
138 | registerOtherBenchmarks(bench);
139 | await bench.run();
140 | expect(mockCore.Measurement.startInstrumentation).toHaveBeenCalled();
141 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
142 | "packages/tinybench-plugin/tests/registerBenchmarks.ts::RegExp"
143 | );
144 | expect(mockCore.Measurement.stopInstrumentation).toHaveBeenCalledWith(
145 | "packages/tinybench-plugin/tests/registerOtherBenchmarks.ts::RegExp"
146 | );
147 | }
148 | );
149 |
150 | it("should run before and after hooks", async () => {
151 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
152 | mockCore.optimizeFunction.mockImplementation(async (fn) => {
153 | await fn();
154 | });
155 | const beforeAll = jest.fn();
156 | const beforeEach = jest.fn();
157 | const afterEach = jest.fn();
158 | const afterAll = jest.fn();
159 |
160 | await withCodSpeed(new Bench())
161 | .add(
162 | "RegExp",
163 | function () {
164 | /o/.test("Hello World!");
165 | },
166 | { afterAll, afterEach, beforeAll, beforeEach }
167 | )
168 | .add(
169 | "RegExp2",
170 | () => {
171 | /o/.test("Hello World!");
172 | },
173 | { afterAll, afterEach, beforeAll, beforeEach }
174 | )
175 | .run();
176 |
177 | // since the optimization is running the benchmark once before the actual run, the each hooks are called twice
178 | expect(beforeEach).toHaveBeenCalledTimes(4);
179 | expect(afterEach).toHaveBeenCalledTimes(4);
180 |
181 | expect(beforeAll).toHaveBeenCalledTimes(2);
182 | expect(afterAll).toHaveBeenCalledTimes(2);
183 | });
184 |
185 | it("should call setupCore and teardownCore only once after run()", async () => {
186 | mockCore.Measurement.isInstrumented.mockReturnValue(true);
187 | const bench = withCodSpeed(new Bench())
188 | .add("RegExp", function () {
189 | /o/.test("Hello World!");
190 | })
191 | .add("RegExp2", () => {
192 | /o/.test("Hello World!");
193 | });
194 |
195 | expect(mockCore.setupCore).not.toHaveBeenCalled();
196 | expect(mockCore.teardownCore).not.toHaveBeenCalled();
197 |
198 | await bench.run();
199 |
200 | expect(mockCore.setupCore).toHaveBeenCalledTimes(1);
201 | expect(mockCore.teardownCore).toHaveBeenCalledTimes(1);
202 | });
203 | });
204 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/tests/registerBenchmarks.ts:
--------------------------------------------------------------------------------
1 | import type { Bench } from "tinybench";
2 |
3 | export function registerBenchmarks(bench: Bench) {
4 | bench.add("RegExp", function () {
5 | /o/.test("Hello World!");
6 | });
7 | }
8 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/tests/registerOtherBenchmarks.ts:
--------------------------------------------------------------------------------
1 | import type { Bench } from "tinybench";
2 |
3 | export function registerOtherBenchmarks(bench: Bench) {
4 | bench.add("RegExp", function () {
5 | /o/.test("Hello World!");
6 | });
7 | }
8 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "rootDir": "src",
6 | "typeRoots": ["node_modules/@types", "../../node_modules/@types"]
7 | },
8 | "references": [{ "path": "../core" }],
9 | "include": ["src/**/*.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/tinybench-plugin/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["jest", "node"]
5 | },
6 | "include": ["tests/**/*.ts", "benches/**/*.ts", "jest.config.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/README.md:
--------------------------------------------------------------------------------
1 |
2 |
@codspeed/vitest-plugin
3 |
4 | [Vitest](https://vitest.dev) plugin for [CodSpeed](https://codspeed.io)
5 |
6 | [](https://github.com/CodSpeedHQ/codspeed-node/actions/workflows/ci.yml)
7 | [](https://www.npmjs.com/package/@codspeed/tinybench-plugin)
8 | [](https://discord.com/invite/MxpaCfKSqF)
9 | [](https://codspeed.io/CodSpeedHQ/codspeed-node)
10 |
11 |
12 |
13 | ## Documentation
14 |
15 | Check out the [documentation](https://docs.codspeed.io/benchmarks/nodejs/vitest) for complete integration instructions.
16 |
17 | ## Installation
18 |
19 | First, install the plugin [`@codspeed/vitest-plugin`](https://www.npmjs.com/package/@codspeed/vitest-plugin) and `vitest` (if not already installed):
20 |
21 | > [!NOTE]
22 | > The CodSpeed plugin is only compatible with
23 | > [vitest@1.2.2](https://www.npmjs.com/package/vitest/v/1.2.2)
24 | > and above.
25 |
26 | ```sh
27 | npm install --save-dev @codspeed/vitest-plugin vitest
28 | ```
29 |
30 | or with `yarn`:
31 |
32 | ```sh
33 | yarn add --dev @codspeed/vitest-plugin vitest
34 | ```
35 |
36 | or with `pnpm`:
37 |
38 | ```sh
39 | pnpm add --save-dev @codspeed/vitest-plugin vitest
40 | ```
41 |
42 | ## Usage
43 |
44 | Let's create a fibonacci function and benchmark it with `vitest.bench`:
45 |
46 | ```ts title="benches/fibo.bench.ts"
47 | import { describe, bench } from "vitest";
48 |
49 | function fibonacci(n: number): number {
50 | if (n < 2) {
51 | return n;
52 | }
53 | return fibonacci(n - 1) + fibonacci(n - 2);
54 | }
55 |
56 | describe("fibonacci", () => {
57 | bench("fibonacci10", () => {
58 | fibonacci(10);
59 | });
60 |
61 | bench("fibonacci15", () => {
62 | fibonacci(15);
63 | });
64 | });
65 | ```
66 |
67 | Create or update your `vitest.config.ts` file to use the CodSpeed runner:
68 |
69 | ```ts title="vitest.config.ts"
70 | import { defineConfig } from "vitest/config";
71 | import codspeedPlugin from "@codspeed/vitest-plugin";
72 |
73 | export default defineConfig({
74 | plugins: [codspeedPlugin()],
75 | // ...
76 | });
77 | ```
78 |
79 | Finally, run your benchmarks (here with `pnpm`):
80 |
81 | ```bash
82 | $ pnpm vitest bench --run
83 | [CodSpeed] bench detected but no instrumentation found, falling back to default vitest runner
84 |
85 | ... Regular `vitest bench` output
86 | ```
87 |
88 | And... Congrats 🎉, CodSpeed is installed in your benchmarking suite! Locally, CodSpeed will fallback to vitest since the instrumentation is only available in the CI environment for now.
89 |
90 | You can now [run those benchmarks in your CI](https://docs.codspeed.io/benchmarks/nodejs/vitest#running-the-benchmarks-in-your-ci) to continuously get consistent performance measurements.
91 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/benches/flat.bench.ts:
--------------------------------------------------------------------------------
1 | import { bench, describe } from "vitest";
2 | import parsePr from "./parsePr";
3 |
4 | const LONG_BODY =
5 | new Array(1_000)
6 | .fill(
7 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Sunt, earum. Atque architecto vero veniam est tempora fugiat sint quo praesentium quia. Autem, veritatis omnis beatae iste delectus recusandae animi non."
8 | )
9 | .join("\n") + "fixes #123";
10 |
11 | describe("parsePr", () => {
12 | bench("short body", () => {
13 | parsePr({ body: "fixes #123", title: "test-1", number: 1 });
14 | });
15 |
16 | bench("long body", () => {
17 | parsePr({ body: LONG_BODY, title: "test-2", number: 2 });
18 | });
19 | });
20 |
21 | function fibo(n: number): number {
22 | if (n < 2) return 1;
23 | return fibo(n - 1) + fibo(n - 2);
24 | }
25 |
26 | describe("fibo", () => {
27 | bench("fibo 10", () => {
28 | fibo(10);
29 | });
30 | bench("fibo 15", () => {
31 | fibo(15);
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/benches/hooks.bench.ts:
--------------------------------------------------------------------------------
1 | import {
2 | afterAll,
3 | afterEach,
4 | beforeAll,
5 | beforeEach,
6 | bench,
7 | describe,
8 | expect,
9 | } from "vitest";
10 |
11 | let count = -1;
12 |
13 | beforeAll(() => {
14 | count += 1;
15 | });
16 |
17 | beforeEach(() => {
18 | count += 1;
19 | });
20 |
21 | // the count is multiplied by 2 because the bench function is called twice with codspeed (once for the optimization and once for the actual measurement)
22 | bench("one", () => {
23 | expect(count).toBe(1 * 2);
24 | });
25 |
26 | describe("level1", () => {
27 | bench("two", () => {
28 | expect(count).toBe(2 * 2);
29 | });
30 |
31 | bench("three", () => {
32 | expect(count).toBe(3 * 2);
33 | });
34 |
35 | describe("level 2", () => {
36 | beforeEach(() => {
37 | count += 1;
38 | });
39 |
40 | bench("five", () => {
41 | expect(count).toBe(5 * 2);
42 | });
43 |
44 | describe("level 3", () => {
45 | bench("seven", () => {
46 | expect(count).toBe(7 * 2);
47 | });
48 | });
49 | });
50 |
51 | describe("level 2 bench nested beforeAll", () => {
52 | beforeAll(() => {
53 | count = 0;
54 | });
55 |
56 | bench("one", () => {
57 | expect(count).toBe(1 * 2);
58 | });
59 | });
60 |
61 | bench("two", () => {
62 | expect(count).toBe(2 * 2);
63 | });
64 | });
65 |
66 | describe("hooks cleanup", () => {
67 | let cleanUpCount = 0;
68 | describe("run", () => {
69 | beforeAll(() => {
70 | cleanUpCount += 10;
71 | });
72 | beforeEach(() => {
73 | cleanUpCount += 1;
74 | });
75 | afterEach(() => {
76 | cleanUpCount -= 1;
77 | });
78 | afterAll(() => {
79 | cleanUpCount -= 10;
80 | });
81 |
82 | bench("one", () => {
83 | expect(cleanUpCount).toBe(11);
84 | });
85 | bench("two", () => {
86 | expect(cleanUpCount).toBe(11);
87 | });
88 | });
89 | bench("end", () => {
90 | expect(cleanUpCount).toBe(0);
91 | });
92 | });
93 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/benches/parsePr.bench.ts:
--------------------------------------------------------------------------------
1 | import { bench, describe } from "vitest";
2 | import parsePr from "./parsePr";
3 |
4 | const LONG_BODY =
5 | new Array(1_000)
6 | .fill(
7 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Sunt, earum. Atque architecto vero veniam est tempora fugiat sint quo praesentium quia. Autem, veritatis omnis beatae iste delectus recusandae animi non."
8 | )
9 | .join("\n") + "fixes #123";
10 |
11 | describe("parsePr", () => {
12 | bench("short body", () => {
13 | parsePr({ body: "fixes #123", title: "test", number: 124 });
14 | });
15 |
16 | bench("long body", () => {
17 | parsePr({ body: LONG_BODY, title: "test", number: 124 });
18 | });
19 |
20 | describe("nested suite", () => {
21 | bench("short body", () => {
22 | parsePr({ body: "fixes #123", title: "test", number: 124 });
23 | });
24 |
25 | bench("long body", () => {
26 | parsePr({ body: LONG_BODY, title: "test", number: 124 });
27 | });
28 |
29 | describe("deeply nested suite", () => {
30 | bench("short body", () => {
31 | parsePr({ body: "fixes #123", title: "test", number: 124 });
32 | });
33 | });
34 | });
35 | });
36 |
37 | describe("another parsePr", () => {
38 | bench("short body", () => {
39 | parsePr({ body: "fixes #123", title: "test", number: 124 });
40 | });
41 |
42 | bench("long body", () => {
43 | parsePr({ body: LONG_BODY, title: "test", number: 124 });
44 | });
45 |
46 | describe("nested suite", () => {
47 | bench("short body", () => {
48 | parsePr({ body: "fixes #123", title: "test", number: 124 });
49 | });
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/benches/parsePr.ts:
--------------------------------------------------------------------------------
1 | interface PullRequest {
2 | number: number;
3 | title: string;
4 | body: string;
5 | }
6 |
7 | function sendEvent(numberOfOperations: number): void {
8 | for (let i = 0; i < numberOfOperations; i++) {
9 | let a = i;
10 | a = a + 1;
11 | }
12 | }
13 |
14 | function logMetrics(
15 | numberOfOperations: number,
16 | numberOfDeepOperations: number
17 | ): void {
18 | for (let i = 0; i < numberOfOperations; i++) {
19 | for (let i = 0; i < numberOfOperations; i++) {
20 | let a = i;
21 | a = a + 1;
22 | a = a + 1;
23 | }
24 | sendEvent(numberOfDeepOperations);
25 | }
26 | }
27 |
28 | function parseTitle(title: string): void {
29 | logMetrics(10, 10);
30 | modifyTitle(title);
31 | }
32 |
33 | function modifyTitle(title: string): void {
34 | for (let i = 0; i < 100; i++) {
35 | let a = i;
36 | a = a + 1 + title.length;
37 | }
38 | }
39 |
40 | function prepareParsingBody(body: string): void {
41 | for (let i = 0; i < 100; i++) {
42 | let a = i;
43 | a = a + 1;
44 | }
45 | parseBody(body);
46 | }
47 |
48 | function parseBody(body: string): void {
49 | logMetrics(10, 10);
50 | for (let i = 0; i < 200; i++) {
51 | let a = i;
52 | a = a + 1;
53 | }
54 | parseIssueFixed(body);
55 | }
56 |
57 | function parseIssueFixed(body: string): number | null {
58 | const prefix = "fixes #";
59 | const index = body.indexOf(prefix);
60 | if (index === -1) {
61 | return null;
62 | }
63 |
64 | const start = index + prefix.length;
65 | let end = start;
66 | while (end < body.length && /\d/.test(body[end])) {
67 | end += 1;
68 | }
69 | return parseInt(body.slice(start, end));
70 | }
71 |
72 | export default function parsePr(pullRequest: PullRequest): void {
73 | parseTitle(pullRequest.title);
74 | prepareParsingBody(pullRequest.body);
75 | }
76 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/moon.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | bench:
3 | command: vitest bench --run
4 | inputs:
5 | - "benches/**"
6 | local: true
7 | options:
8 | cache: false
9 |
10 | test:
11 | command: vitest --run
12 | inputs:
13 | - "./vitest.config.ts"
14 |
15 | test/integ:
16 | command: noop
17 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@codspeed/vitest-plugin",
3 | "version": "4.0.1",
4 | "description": "vitest plugin for CodSpeed",
5 | "keywords": [
6 | "codspeed",
7 | "benchmark",
8 | "vitest",
9 | "performance"
10 | ],
11 | "module": "./dist/index.mjs",
12 | "types": "./dist/index.d.ts",
13 | "exports": {
14 | ".": {
15 | "types": "./dist/index.d.ts",
16 | "import": "./dist/index.mjs"
17 | }
18 | },
19 | "type": "module",
20 | "files": [
21 | "dist"
22 | ],
23 | "author": "Adrien Cacciaguerra ",
24 | "repository": "https://github.com/CodSpeedHQ/codspeed-node",
25 | "homepage": "https://codspeed.io",
26 | "license": "Apache-2.0",
27 | "scripts": {
28 | "bench": "vitest bench"
29 | },
30 | "dependencies": {
31 | "@codspeed/core": "workspace:^4.0.1"
32 | },
33 | "peerDependencies": {
34 | "vite": "^4.2.0 || ^5.0.0 || ^6.0.0",
35 | "vitest": ">=1.2.2"
36 | },
37 | "devDependencies": {
38 | "@total-typescript/shoehorn": "^0.1.1",
39 | "execa": "^8.0.1",
40 | "vite": "^5.0.0",
41 | "vitest": "^1.2.2"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/rollup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "rollup";
2 | import { declarationsPlugin, jsPlugins } from "../../rollup.options";
3 | import pkg from "./package.json" assert { type: "json" };
4 |
5 | export default defineConfig([
6 | {
7 | input: "src/index.ts",
8 | output: { file: pkg.module, format: "es" },
9 | plugins: jsPlugins(pkg.version),
10 | external: ["@codspeed/core", /^vitest/],
11 | },
12 | {
13 | input: "src/index.ts",
14 | output: { file: pkg.types, format: "es" },
15 | plugins: declarationsPlugin({ compilerOptions: { composite: false } }),
16 | },
17 | {
18 | input: "src/globalSetup.ts",
19 | output: { file: "dist/globalSetup.mjs", format: "es" },
20 | plugins: jsPlugins(pkg.version),
21 | external: ["@codspeed/core", /^vitest/],
22 | },
23 | {
24 | input: "src/runner.ts",
25 | output: { file: "dist/runner.mjs", format: "es" },
26 | plugins: jsPlugins(pkg.version),
27 | external: ["@codspeed/core", /^vitest/],
28 | },
29 | ]);
30 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/src/__tests__/globalSetup.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from "vitest";
2 | import globalSetup from "../globalSetup";
3 |
4 | console.log = vi.fn();
5 |
6 | describe("globalSetup", () => {
7 | it("should log the correct message on setup and teardown, and fail when teardown is called twice", async () => {
8 | const teardown = globalSetup();
9 |
10 | expect(console.log).toHaveBeenCalledWith(
11 | "[CodSpeed] @codspeed/vitest-plugin v1.0.0 - setup"
12 | );
13 |
14 | teardown();
15 |
16 | expect(console.log).toHaveBeenCalledWith(
17 | "[CodSpeed] @codspeed/vitest-plugin v1.0.0 - teardown"
18 | );
19 |
20 | expect(() => teardown()).toThrowError("teardown called twice");
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/src/__tests__/index.test.ts:
--------------------------------------------------------------------------------
1 | import { fromPartial } from "@total-typescript/shoehorn";
2 | import { describe, expect, it, vi } from "vitest";
3 | import codspeedPlugin from "../index";
4 |
5 | const coreMocks = vi.hoisted(() => {
6 | return {
7 | Measurement: {
8 | isInstrumented: vi.fn(),
9 | startInstrumentation: vi.fn(),
10 | stopInstrumentation: vi.fn(),
11 | },
12 | };
13 | });
14 |
15 | const resolvedCodSpeedPlugin = codspeedPlugin();
16 | const applyPluginFunction = resolvedCodSpeedPlugin.apply;
17 | if (typeof applyPluginFunction !== "function")
18 | throw new Error("applyPluginFunction is not a function");
19 |
20 | vi.mock("@codspeed/core", async (importOriginal) => {
21 | const mod = await importOriginal();
22 | return { ...mod, ...coreMocks };
23 | });
24 |
25 | console.warn = vi.fn();
26 |
27 | describe("codSpeedPlugin", () => {
28 | it("should have a name", async () => {
29 | expect(resolvedCodSpeedPlugin.name).toBe("codspeed:vitest");
30 | });
31 |
32 | it("should enforce to run after the other plugins", async () => {
33 | expect(resolvedCodSpeedPlugin.enforce).toBe("post");
34 | });
35 |
36 | describe("apply", () => {
37 | it("should not apply the plugin when the mode is not benchmark", async () => {
38 | const applyPlugin = applyPluginFunction(
39 | {},
40 | fromPartial({ mode: "test" })
41 | );
42 |
43 | expect(applyPlugin).toBe(false);
44 | });
45 |
46 | it("should apply the plugin when there is no instrumentation", async () => {
47 | coreMocks.Measurement.isInstrumented.mockReturnValue(false);
48 |
49 | const applyPlugin = applyPluginFunction(
50 | {},
51 | fromPartial({ mode: "benchmark" })
52 | );
53 |
54 | expect(console.warn).toHaveBeenCalledWith(
55 | "[CodSpeed] bench detected but no instrumentation found"
56 | );
57 | expect(applyPlugin).toBe(true);
58 | });
59 |
60 | it("should apply the plugin when there is instrumentation", async () => {
61 | coreMocks.Measurement.isInstrumented.mockReturnValue(true);
62 |
63 | const applyPlugin = applyPluginFunction(
64 | {},
65 | fromPartial({ mode: "benchmark" })
66 | );
67 |
68 | expect(applyPlugin).toBe(true);
69 | });
70 | });
71 |
72 | it("should apply the codspeed config", async () => {
73 | const config = resolvedCodSpeedPlugin.config;
74 | if (typeof config !== "function")
75 | throw new Error("config is not a function");
76 |
77 | expect(config({}, fromPartial({}))).toStrictEqual({
78 | test: {
79 | globalSetup: [
80 | expect.stringContaining("packages/vitest-plugin/src/globalSetup.ts"),
81 | ],
82 | pool: "forks",
83 | poolOptions: {
84 | forks: {
85 | execArgv: [
86 | "--hash-seed=1",
87 | "--random-seed=1",
88 | "--no-opt",
89 | "--predictable",
90 | "--predictable-gc-schedule",
91 | "--interpreted-frames-native-stack",
92 | "--allow-natives-syntax",
93 | "--expose-gc",
94 | "--no-concurrent-sweeping",
95 | "--max-old-space-size=4096",
96 | "--no-scavenge-task",
97 | ],
98 | },
99 | },
100 | runner: expect.stringContaining("packages/vitest-plugin/src/runner.ts"),
101 | },
102 | });
103 | });
104 | });
105 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/src/__tests__/runner.test.ts:
--------------------------------------------------------------------------------
1 | import { fromPartial } from "@total-typescript/shoehorn";
2 | import { describe, expect, it, Suite, vi } from "vitest";
3 | import { getBenchFn } from "vitest/suite";
4 | import CodSpeedRunner from "../runner";
5 |
6 | const coreMocks = vi.hoisted(() => {
7 | return {
8 | Measurement: {
9 | startInstrumentation: vi.fn(),
10 | stopInstrumentation: vi.fn(),
11 | },
12 | setupCore: vi.fn(),
13 | teardownCore: vi.fn(),
14 | mongoMeasurement: {
15 | start: vi.fn(),
16 | stop: vi.fn(),
17 | },
18 | };
19 | });
20 |
21 | global.eval = vi.fn();
22 |
23 | vi.mock("@codspeed/core", async (importOriginal) => {
24 | const mod = await importOriginal();
25 | return { ...mod, ...coreMocks };
26 | });
27 |
28 | console.log = vi.fn();
29 |
30 | vi.mock("vitest/suite", async (importOriginal) => {
31 | const actual = await importOriginal();
32 | return {
33 | ...actual,
34 | getBenchFn: vi.fn(),
35 | };
36 | });
37 | const mockedGetBenchFn = vi.mocked(getBenchFn);
38 |
39 | describe("CodSpeedRunner", () => {
40 | it("should run the bench functions only twice", async () => {
41 | const benchFn = vi.fn();
42 | mockedGetBenchFn.mockReturnValue(benchFn);
43 |
44 | const runner = new CodSpeedRunner(fromPartial({}));
45 | const suite = fromPartial({
46 | filepath: __filename,
47 | name: "test suite",
48 | tasks: [{ mode: "run", meta: { benchmark: true }, name: "test bench" }],
49 | });
50 | suite.tasks[0].suite = suite;
51 | await runner.runSuite(suite);
52 |
53 | // setup
54 | expect(coreMocks.setupCore).toHaveBeenCalledTimes(1);
55 | expect(console.log).toHaveBeenCalledWith(
56 | "[CodSpeed] running suite packages/vitest-plugin/src/__tests__/runner.test.ts"
57 | );
58 |
59 | // run
60 | expect(coreMocks.mongoMeasurement.start).toHaveBeenCalledWith(
61 | "packages/vitest-plugin/src/__tests__/runner.test.ts::test bench"
62 | );
63 | expect(coreMocks.Measurement.startInstrumentation).toHaveBeenCalledTimes(1);
64 | expect(benchFn).toHaveBeenCalledTimes(8);
65 | expect(coreMocks.Measurement.stopInstrumentation).toHaveBeenCalledTimes(1);
66 | expect(coreMocks.mongoMeasurement.stop).toHaveBeenCalledTimes(1);
67 | expect(console.log).toHaveBeenCalledWith(
68 | "[CodSpeed] packages/vitest-plugin/src/__tests__/runner.test.ts::test bench done"
69 | );
70 |
71 | // teardown
72 | expect(console.log).toHaveBeenCalledWith(
73 | "[CodSpeed] running suite packages/vitest-plugin/src/__tests__/runner.test.ts done"
74 | );
75 | expect(coreMocks.teardownCore).toHaveBeenCalledTimes(1);
76 | });
77 |
78 | it("should run nested suites", async () => {
79 | const benchFn = vi.fn();
80 | mockedGetBenchFn.mockReturnValue(benchFn);
81 |
82 | const runner = new CodSpeedRunner(fromPartial({}));
83 | const rootSuite = fromPartial({
84 | filepath: __filename,
85 | name: "test suite",
86 | tasks: [
87 | {
88 | type: "suite",
89 | name: "nested suite",
90 | mode: "run",
91 | tasks: [
92 | {
93 | mode: "run",
94 | meta: { benchmark: true },
95 | name: "test bench",
96 | },
97 | ],
98 | },
99 | ],
100 | });
101 | rootSuite.tasks[0].suite = rootSuite;
102 | // @ts-expect-error type is not narrow enough, but it is fine
103 | rootSuite.tasks[0].tasks[0].suite = rootSuite.tasks[0];
104 |
105 | await runner.runSuite(rootSuite);
106 |
107 | // setup
108 | expect(coreMocks.setupCore).toHaveBeenCalledTimes(1);
109 | expect(console.log).toHaveBeenCalledWith(
110 | "[CodSpeed] running suite packages/vitest-plugin/src/__tests__/runner.test.ts"
111 | );
112 |
113 | // run
114 | expect(coreMocks.mongoMeasurement.start).toHaveBeenCalledWith(
115 | "packages/vitest-plugin/src/__tests__/runner.test.ts::nested suite::test bench"
116 | );
117 | expect(coreMocks.Measurement.startInstrumentation).toHaveBeenCalledTimes(1);
118 | expect(benchFn).toHaveBeenCalledTimes(8);
119 | expect(coreMocks.Measurement.stopInstrumentation).toHaveBeenCalledTimes(1);
120 | expect(coreMocks.mongoMeasurement.stop).toHaveBeenCalledTimes(1);
121 | expect(console.log).toHaveBeenCalledWith(
122 | "[CodSpeed] packages/vitest-plugin/src/__tests__/runner.test.ts::nested suite::test bench done"
123 | );
124 |
125 | // teardown
126 | expect(console.log).toHaveBeenCalledWith(
127 | "[CodSpeed] running suite packages/vitest-plugin/src/__tests__/runner.test.ts done"
128 | );
129 | expect(coreMocks.teardownCore).toHaveBeenCalledTimes(1);
130 | });
131 | });
132 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/src/globalSetup.ts:
--------------------------------------------------------------------------------
1 | declare const __VERSION__: string;
2 |
3 | /**
4 | * @deprecated
5 | * TODO: try to use something like `updateTask` from `@vitest/runner` instead to use the output
6 | * of vitest instead console.log but at the moment, `updateTask` is not exposed
7 | */
8 | function logCodSpeed(message: string) {
9 | console.log(`[CodSpeed] ${message}`);
10 | }
11 |
12 | let teardownHappened = false;
13 |
14 | export default function () {
15 | logCodSpeed(`@codspeed/vitest-plugin v${__VERSION__} - setup`);
16 |
17 | return () => {
18 | if (teardownHappened) throw new Error("teardown called twice");
19 | teardownHappened = true;
20 |
21 | logCodSpeed(`@codspeed/vitest-plugin v${__VERSION__} - teardown`);
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/src/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getV8Flags,
3 | Measurement,
4 | mongoMeasurement,
5 | SetupInstrumentsRequestBody,
6 | SetupInstrumentsResponse,
7 | } from "@codspeed/core";
8 | import { join } from "path";
9 | import { Plugin } from "vite";
10 | import { UserConfig } from "vitest/config";
11 |
12 | // get this file's directory path from import.meta.url
13 | const __dirname = new URL(".", import.meta.url).pathname;
14 | const isFileInTs = import.meta.url.endsWith(".ts");
15 |
16 | function getCodSpeedFileFromName(name: string) {
17 | const fileExtension = isFileInTs ? "ts" : "mjs";
18 |
19 | return join(__dirname, `${name}.${fileExtension}`);
20 | }
21 |
22 | export default function codspeedPlugin(): Plugin {
23 | return {
24 | name: "codspeed:vitest",
25 | apply(_, { mode }) {
26 | if (mode !== "benchmark") {
27 | return false;
28 | }
29 | if (!Measurement.isInstrumented()) {
30 | console.warn("[CodSpeed] bench detected but no instrumentation found");
31 | }
32 | return true;
33 | },
34 | enforce: "post",
35 | config(): UserConfig {
36 | return {
37 | test: {
38 | pool: "forks",
39 | poolOptions: {
40 | forks: {
41 | execArgv: getV8Flags(),
42 | },
43 | },
44 | runner: getCodSpeedFileFromName("runner"),
45 | globalSetup: [getCodSpeedFileFromName("globalSetup")],
46 | },
47 | };
48 | },
49 | };
50 | }
51 |
52 | /**
53 | * Dynamically setup the CodSpeed instruments.
54 | */
55 | export async function setupInstruments(
56 | body: SetupInstrumentsRequestBody
57 | ): Promise {
58 | if (!Measurement.isInstrumented()) {
59 | console.warn("[CodSpeed] No instrumentation found, using default mongoUrl");
60 |
61 | return { remoteAddr: body.mongoUrl };
62 | }
63 |
64 | return await mongoMeasurement.setupInstruments(body);
65 | }
66 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/src/runner.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getGitDir,
3 | logDebug,
4 | Measurement,
5 | mongoMeasurement,
6 | optimizeFunction,
7 | setupCore,
8 | teardownCore,
9 | } from "@codspeed/core";
10 | import path from "path";
11 | import { Benchmark, chai, Suite, Task } from "vitest";
12 | import { NodeBenchmarkRunner } from "vitest/runners";
13 | import { getBenchFn, getHooks } from "vitest/suite";
14 |
15 | type SuiteHooks = ReturnType;
16 |
17 | function getSuiteHooks(suite: Suite, name: keyof SuiteHooks) {
18 | return getHooks(suite)?.[name] ?? [];
19 | }
20 |
21 | export async function callSuiteHook(
22 | suite: Suite,
23 | currentTask: Task,
24 | name: T
25 | ): Promise {
26 | if (name === "beforeEach" && suite?.suite) {
27 | await callSuiteHook(suite.suite, currentTask, name);
28 | }
29 |
30 | const hooks = getSuiteHooks(suite, name);
31 |
32 | await Promise.all(hooks.map((fn) => fn()));
33 |
34 | if (name === "afterEach" && suite?.suite) {
35 | await callSuiteHook(suite.suite, currentTask, name);
36 | }
37 | }
38 |
39 | const currentFileName =
40 | typeof __filename === "string"
41 | ? __filename
42 | : new URL("runner.mjs", import.meta.url).pathname;
43 |
44 | /**
45 | * @deprecated
46 | * TODO: try to use something like `updateTask` from `@vitest/runner` instead to use the output
47 | * of vitest instead console.log but at the moment, `updateTask` is not exposed
48 | */
49 | function logCodSpeed(message: string) {
50 | console.log(`[CodSpeed] ${message}`);
51 | }
52 |
53 | async function runBench(benchmark: Benchmark, currentSuiteName: string) {
54 | const uri = `${currentSuiteName}::${benchmark.name}`;
55 | const fn = getBenchFn(benchmark);
56 |
57 | await callSuiteHook(benchmark.suite, benchmark, "beforeEach");
58 | try {
59 | await optimizeFunction(fn);
60 | } catch (e) {
61 | // if the error is not an assertion error, we want to fail the run
62 | // we allow assertion errors because we want to be able to use `expect` in the benchmark to allow for better authoring
63 | // assertions are allowed to fail in the optimization phase since it might be linked to stateful code
64 | if (!(e instanceof chai.AssertionError)) {
65 | throw e;
66 | }
67 | }
68 | await callSuiteHook(benchmark.suite, benchmark, "afterEach");
69 |
70 | await callSuiteHook(benchmark.suite, benchmark, "beforeEach");
71 | await mongoMeasurement.start(uri);
72 | global.gc?.();
73 | await (async function __codspeed_root_frame__() {
74 | Measurement.startInstrumentation();
75 | // @ts-expect-error we do not need to bind the function to an instance of tinybench's Bench
76 | await fn();
77 | Measurement.stopInstrumentation(uri);
78 | })();
79 | await mongoMeasurement.stop(uri);
80 | await callSuiteHook(benchmark.suite, benchmark, "afterEach");
81 |
82 | logCodSpeed(`${uri} done`);
83 | }
84 |
85 | async function runBenchmarkSuite(suite: Suite, parentSuiteName?: string) {
86 | const currentSuiteName = parentSuiteName
87 | ? parentSuiteName + "::" + suite.name
88 | : suite.name;
89 |
90 | // do not call `beforeAll` if we are in the root suite, since it is already called by vitest
91 | // see https://github.com/vitest-dev/vitest/blob/1fee63f2598edc228017f18eca325f85ee54aee0/packages/runner/src/run.ts#L293
92 | if (parentSuiteName !== undefined) {
93 | await callSuiteHook(suite, suite, "beforeAll");
94 | }
95 |
96 | for (const task of suite.tasks) {
97 | if (task.mode !== "run") continue;
98 |
99 | if (task.meta?.benchmark) {
100 | await runBench(task as Benchmark, currentSuiteName);
101 | } else if (task.type === "suite") {
102 | await runBenchmarkSuite(task, currentSuiteName);
103 | }
104 | }
105 |
106 | // do not call `afterAll` if we are in the root suite, since it is already called by vitest
107 | // see https://github.com/vitest-dev/vitest/blob/1fee63f2598edc228017f18eca325f85ee54aee0/packages/runner/src/run.ts#L324
108 | if (parentSuiteName !== undefined) {
109 | await callSuiteHook(suite, suite, "afterAll");
110 | }
111 | }
112 |
113 | function patchRootSuiteWithFullFilePath(suite: Suite) {
114 | if (suite.filepath === undefined) {
115 | throw new Error("filepath is undefined is the root suite");
116 | }
117 | const gitDir = getGitDir(suite.filepath);
118 | if (gitDir === undefined) {
119 | throw new Error("Could not find a git repository");
120 | }
121 | suite.name = path.relative(gitDir, suite.filepath);
122 | }
123 |
124 | class CodSpeedRunner extends NodeBenchmarkRunner {
125 | async runSuite(suite: Suite): Promise {
126 | logDebug(`PROCESS PID: ${process.pid} in ${currentFileName}`);
127 | setupCore();
128 |
129 | patchRootSuiteWithFullFilePath(suite);
130 |
131 | logCodSpeed(`running suite ${suite.name}`);
132 |
133 | await runBenchmarkSuite(suite);
134 | logCodSpeed(`running suite ${suite.name} done`);
135 |
136 | teardownCore();
137 | }
138 | }
139 |
140 | export default CodSpeedRunner;
141 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "rootDir": "src",
6 | "typeRoots": ["node_modules/@types", "../../node_modules/@types"]
7 | },
8 | "references": [{ "path": "../core" }],
9 | "include": ["src/**/*.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["jest", "node"]
5 | },
6 | "include": ["tests/**/*.ts", "benches/**/*.ts", "jest.config.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/vitest-plugin/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 | import codspeedPlugin from "./dist/index.mjs";
3 |
4 | export default defineConfig({
5 | // @ts-expect-error - TODO: investigate why importing from '.' wants to import only "main" field and thus fail
6 | plugins: [codspeedPlugin()],
7 | define: {
8 | __VERSION__: JSON.stringify("1.0.0"),
9 | },
10 | test: {
11 | exclude: ["**/tests/**/*", "**/.rollup.cache/**/*"],
12 | mockReset: true,
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "examples/*"
3 | - "packages/*"
4 |
--------------------------------------------------------------------------------
/rollup.options.js:
--------------------------------------------------------------------------------
1 | import commonjs from "@rollup/plugin-commonjs";
2 | import json from "@rollup/plugin-json";
3 | import resolve from "@rollup/plugin-node-resolve";
4 | import dts from "rollup-plugin-dts";
5 | import esbuild from "rollup-plugin-esbuild";
6 |
7 | /**
8 | * @typedef {import('rollup-plugin-dts').Options} DtsOptions
9 | */
10 |
11 | /**
12 | * @param {DtsOptions} options
13 | */
14 | export const declarationsPlugin = (options) => [dts(options)];
15 |
16 | export const jsPlugins = (version) => [
17 | json(),
18 | esbuild({
19 | define: {
20 | __VERSION__: '"' + version + '"',
21 | },
22 | }),
23 | commonjs(),
24 | resolve(),
25 | ];
26 |
--------------------------------------------------------------------------------
/scripts/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Usage: ./scripts/release.sh
3 | set -ex
4 |
5 | # Fail if not on main
6 | if [ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]; then
7 | echo "Not on default branch"
8 | exit 1
9 | fi
10 |
11 | if [ $# -ne 1 ]; then
12 | echo "Usage: ./release.sh "
13 | exit 1
14 | fi
15 |
16 | # Fail if there are any unstaged changes left
17 | git diff --exit-code
18 |
19 | pnpm lerna version $1 --force-publish --no-private
20 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "moduleResolution": "node",
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "lib": ["esnext"],
8 | "types": ["node"],
9 | "strict": true,
10 | "allowSyntheticDefaultImports": true,
11 | "experimentalDecorators": true,
12 | "sourceMap": true,
13 | "emitDecoratorMetadata": true,
14 | "skipLibCheck": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "esModuleInterop": true,
17 | "resolveJsonModule": true,
18 | "isolatedModules": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "include": [],
4 | "exclude": ["**/node_modules"],
5 | "references": [
6 | {
7 | "path": "packages/benchmark.js-plugin"
8 | },
9 | {
10 | "path": "packages/tinybench-plugin"
11 | },
12 | {
13 | "path": "packages/vitest-plugin"
14 | },
15 | {
16 | "path": "packages/core"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------