main #1

Merged
LeenkxTeam merged 7 commits from Dante/Kmake:main into main 2026-05-31 05:24:14 +00:00
41618 changed files with 13344900 additions and 1 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

11
BSDmakefile Normal file
View File

@ -0,0 +1,11 @@
# pmake might add -J (private)
FLAGS=${.MAKEFLAGS:C/\-J ([0-9]+,?)+//W}
all: .DEFAULT
.DEFAULT:
@command -v gmake > /dev/null 2>&1 ||\
(echo "GMake is required for node.js to build.\
Install and try again" && exit 1)
@gmake ${.FLAGS} ${.TARGETS}
.PHONY: test

14
BUILD.gn Normal file
View File

@ -0,0 +1,14 @@
##############################################################################
# #
# DO NOT EDIT THIS FILE! #
# #
##############################################################################
# This file is used by GN for building, which is NOT the build system used for
# building official binaries.
# Please modify the gyp files if you are making changes to build system.
import("unofficial.gni")
node_gn_build("node") {
}

1029
BUILDING.md Normal file

File diff suppressed because it is too large Load Diff

1383
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

4
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,4 @@
# Code of Conduct
* [Node.js Code of Conduct](https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md)
* [Node.js Moderation Policy](https://github.com/nodejs/admin/blob/HEAD/Moderation-Policy.md)

76
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,76 @@
# Contributing to Node.js
Contributions to Node.js include code, documentation, answering user questions,
running the project's infrastructure, and advocating for all types of Node.js
users.
The Node.js project welcomes all contributions from anyone willing to work in
good faith with other contributors and the community. No contribution is too
small and all contributions are valued.
The Node.js project has an open governance model.
Individuals making significant and valuable contributions are made
Collaborators and given commit-access to the project. See the
[GOVERNANCE.md](./GOVERNANCE.md) document for more information about how this
works.
## Contents
* [Code of Conduct](#code-of-conduct)
* [Issues](#issues)
* [Pull Requests](#pull-requests)
* [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin-11)
## [Code of Conduct](./doc/contributing/code-of-conduct.md)
The Node.js project has a
[Code of Conduct](https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md)
to which all contributors must adhere.
See [details on our policy on Code of Conduct](./doc/contributing/code-of-conduct.md).
## [Issues](./doc/contributing/issues.md)
* [Asking for General Help](./doc/contributing/issues.md#asking-for-general-help)
* [Discussing non-technical topics](./doc/contributing/issues.md#discussing-non-technical-topics)
* [Submitting a Bug Report](./doc/contributing/issues.md#submitting-a-bug-report)
* [Triaging a Bug Report](./doc/contributing/issues.md#triaging-a-bug-report)
## [Pull Requests](./doc/contributing/pull-requests.md)
Pull Requests are the way concrete changes are made to the code, documentation,
dependencies, and tools contained in the `nodejs/node` repository.
* [Dependencies](./doc/contributing/pull-requests.md#dependencies)
* [Setting up your local environment](./doc/contributing/pull-requests.md#setting-up-your-local-environment)
* [The Process of Making Changes](./doc/contributing/pull-requests.md#the-process-of-making-changes)
* [Reviewing Pull Requests](./doc/contributing/pull-requests.md#reviewing-pull-requests)
* [Notes](./doc/contributing/pull-requests.md#notes)
## Developer's Certificate of Origin 1.1
```text
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```

305
GOVERNANCE.md Normal file
View File

@ -0,0 +1,305 @@
# Node.js Project Governance
<!-- TOC -->
* [Triagers](#triagers)
* [Collaborators](#collaborators)
* [Collaborator activities](#collaborator-activities)
* [Technical steering committee](#technical-steering-committee)
* [TSC meetings](#tsc-meetings)
* [Collaborator nominations](#collaborator-nominations)
* [Who can nominate Collaborators?](#who-can-nominate-collaborators)
* [Ideal Nominees](#ideal-nominees)
* [Nominating a new Collaborator](#nominating-a-new-collaborator)
* [Onboarding](#onboarding)
* [Consensus seeking process](#consensus-seeking-process)
<!-- /TOC -->
## Triagers
Triagers assess newly-opened issues in the [nodejs/node][] and [nodejs/help][]
repositories. The GitHub team for Node.js triagers is @nodejs/issue-triage.
Triagers are given the "Triage" GitHub role and have:
* Ability to label issues and pull requests
* Ability to comment, close, and reopen issues and pull requests
See:
* [List of triagers](./README.md#triagers)
* [A guide for triagers](./doc/contributing/issues.md#triaging-a-bug-report)
## Collaborators
Node.js core collaborators maintain the [nodejs/node][] GitHub repository.
The GitHub team for Node.js core collaborators is @nodejs/collaborators.
Collaborators have:
* Commit access to the [nodejs/node][] repository
* Access to the Node.js continuous integration (CI) jobs
Both collaborators and non-collaborators may propose changes to the Node.js
source code. The mechanism to propose such a change is a GitHub pull request.
Collaborators review and merge (_land_) pull requests.
Two collaborators must approve a pull request before the pull request can land.
(One collaborator approval is enough if the pull request has been open for more
than 7 days.) Approving a pull request indicates that the collaborator accepts
responsibility for the change. Approval must be from collaborators who are not
authors of the change.
If a collaborator opposes a proposed change, then the change cannot land. The
exception is if the TSC votes to approve the change despite the opposition.
Usually, involving the TSC is unnecessary. Often, discussions or further changes
result in collaborators removing their opposition.
See:
* [List of collaborators](./README.md#current-project-team-members)
* [A guide for collaborators](./doc/contributing/collaborator-guide.md)
### Collaborator activities
* Helping users and novice contributors
* Contributing code and documentation changes that improve the project
* Reviewing and commenting on issues and pull requests
* Participation in working groups
* Merging pull requests
The TSC can remove inactive collaborators or provide them with _emeritus_
status. Emeriti may request that the TSC restore them to active status.
A collaborator is automatically made emeritus (and removed from active
collaborator status) if it has been more than 12 months since the collaborator
has authored or approved a commit that has landed.
## Technical Steering Committee
A subset of the collaborators forms the Technical Steering Committee (TSC).
The TSC has final authority over this project, including:
* Technical direction
* Project governance and process (including this policy)
* Contribution policy
* GitHub repository hosting
* Conduct guidelines
* Maintaining the list of collaborators
The current list of TSC members is in
[the project README](./README.md#current-project-team-members).
The [TSC Charter][] governs the operations of the TSC. All changes to the
Charter need approval by the OpenJS Foundation Cross-Project Council (CPC).
### TSC meetings
The TSC meets in a video conference call. Each year, the TSC elects a chair to
run the meetings. The TSC streams its meetings for public viewing on YouTube.
The TSC agenda includes issues that are at an impasse. The intention of the
agenda is not to review or approve all patches. Collaborators review and approve
patches on GitHub.
Any community member can create a GitHub issue asking that the TSC review
something. If consensus-seeking fails for an issue, a collaborator may apply the
`tsc-agenda` label. That will add it to the TSC meeting agenda.
Before each TSC meeting, the meeting chair will share the agenda with members of
the TSC. TSC members can also add items to the agenda at the beginning of each
meeting. The meeting chair and the TSC cannot veto or remove items.
The TSC may invite people to take part in a non-voting capacity.
During the meeting, the TSC chair ensures that someone takes minutes. After the
meeting, the TSC chair ensures that someone opens a pull request with the
minutes.
The TSC seeks to resolve as many issues as possible outside meetings using
[the TSC issue tracker](https://github.com/nodejs/TSC/issues). The process in
the issue tracker is:
* A TSC member opens an issue explaining the proposal/issue and @-mentions
@nodejs/tsc.
* The proposal passes if, after 72 hours, there are two or more TSC voting
member approvals and no TSC voting member opposition.
* If there is an extended impasse, a TSC member may make a motion for a vote.
## Collaborator nominations
### Who can nominate Collaborators?
Existing Collaborators can nominate someone to become a Collaborator.
### Ideal Nominees
Nominees should have significant and valuable contributions across the Node.js
organization.
Contributions can be:
* Opening pull requests.
* Comments and reviews.
* Opening new issues.
* Participation in other projects, teams, and working groups of the Node.js
organization.
Collaborators should be people volunteering to do unglamorous work because it's
the right thing to do, they find the work itself satisfying, and they care about
Node.js and its users. People should get collaborator status because they're
doing work and are likely to continue doing work where having the abilities that
come with collaborator status are helpful (abilities like starting CI jobs,
reviewing and approving PRs, etc.). That will usually--but, very importantly, not
always--be work involving committing to the `nodejs/node` repository. For an example
of an exception, someone working primarily on the website might benefit from being
able to start Jenkins CI jobs to test changes to documentation tooling. That,
along with signals indicating commitment to Node.js, personal integrity, etc.,
should be enough for a successful nomination.
It is important to understand that potential collaborators may have vastly
different areas and levels of expertise, interest, and skill. The Node.js
project is large and complex, and it is not expected that every collaborator
will have the same level of expertise in every area of the project. The
complexity or "sophistication" of an individuals contributions, or even their
relative engineering "skill" level, are not primary factors in determining
whether they should be a collaborator. The primary factors do include the quality
of their contributions (do the contributions make sense, do they add value, do
they follow documented guidelines, are they authentic and well-intentioned,
etc.), their commitment to the project, can their judgement be trusted, and do
they have the ability to work well with others.
#### The Authenticity of Contributors
The Node.js project does not require that contributors use their legal names or
provide any personal information verifying their identity.
It is not uncommon for malicious actors to attempt to gain commit access to
open-source projects in order to inject malicious code or for other nefarious
purposes. The Node.js project has a number of mechanisms in place to prevent
this, but it is important to be vigilant. If you have concerns about the
authenticity of a contributor, please raise them with the TSC. Anyone nominating
a new collaborator should take reasonable steps to verify that the contributions
of the nominee are authentic and made in good faith. This is not always easy,
but it is important.
### Nominating a new Collaborator
To nominate a new Collaborator:
1. **Optional but strongly recommended**: open a
[discussion in the nodejs/collaborators][] repository. Provide a summary of
the nominee's contributions (see below for an example).
2. **Optional but strongly recommended**: After sufficient wait time (e.g. 72
hours), if the nomination proposal has received some support and no explicit
block, and any questions/concerns have been addressed, add a comment in the
private discussion stating you're planning on opening a public issue, e.g.
"I see a number of approvals and no block, I'll be opening a public
nomination issue if I don't hear any objections in the next 72 hours".
3. **Optional but strongly recommended**: Privately contact the nominee to make
sure they're comfortable with the nomination.
4. Open an issue in the [nodejs/node][] repository. Provide a summary of
the nominee's contributions (see below for an example). Mention
@nodejs/collaborators in the issue to notify other collaborators about
the nomination.
The _Optional but strongly recommended_ steps are optional in the sense that
skipping them would not invalidate the nomination, but it could put the nominee
in a very awkward situation if a nomination they didn't ask for pops out of
nowhere only to be rejected. Do not skip those steps unless you're absolutely
certain the nominee is fine with the public scrutiny.
Example of list of contributions:
* Commits in the [nodejs/node][] repository.
* Use the link `https://github.com/nodejs/node/commits?author=GITHUB_ID`
* Pull requests and issues opened in the [nodejs/node][] repository.
* Use the link `https://github.com/nodejs/node/issues?q=author:GITHUB_ID`
* Comments on pull requests and issues in the [nodejs/node][] repository
* Use the link `https://github.com/nodejs/node/issues?q=commenter:GITHUB_ID`
* Reviews on pull requests in the [nodejs/node][] repository
* Use the link `https://github.com/nodejs/node/pulls?q=reviewed-by:GITHUB_ID`
* Help provided to end-users and novice contributors
* Pull requests and issues opened throughout the Node.js organization
* Use the link `https://github.com/search?q=author:GITHUB_ID+org:nodejs`
* Comments on pull requests and issues throughout the Node.js organization
* Use the link `https://github.com/search?q=commenter:GITHUB_ID+org:nodejs`
* Participation in other projects, teams, and working groups of the Node.js
organization
* Other participation in the wider Node.js community
The nomination passes if no collaborators oppose it (as described in the
following section) after one week. In the case of an objection, the TSC is
responsible for working with the individuals involved and finding a resolution.
The TSC may, following typical TSC consensus seeking processes, choose to
advance a nomination that has otherwise failed to reach a natural consensus or
clear path forward even if there are outstanding objections. The TSC may also
choose to prevent a nomination from advancing if the TSC determines that any
objections have not been adequately addressed.
#### How to review a collaborator nomination
A collaborator nomination can be reviewed in the same way one would review a PR
adding a feature:
* If you see the nomination as something positive to the project, say so!
* If you are neutral, or feel you don't know enough to have an informed opinion,
it's certainly OK to not interact with the nomination.
* If you think the nomination was made too soon, or can be detrimental to the
project, share your concerns. See the section "How to oppose a collaborator
nomination" below.
Our goal is to keep gate-keeping at a minimal, but it cannot be zero since being
a collaborator requires trust (collaborators can start CI jobs, use their veto,
push commits, etc.), so what's the minimal amount is subjective, and there will
be cases where collaborators disagree on whether a nomination should move
forward.
Refrain from discussing or debating aspects of the nomination process
itself directly within a nomination private discussion or public issue.
Such discussions can derail and frustrate the nomination causing unnecessary
friction. Move such discussions to a separate issue or discussion thread.
##### How to oppose a collaborator nomination
An important rule of thumb is that the nomination process is intended to be
biased strongly towards implicit approval of the nomination. This means
discussion and review around the proposal should be more geared towards "I have
reasons to say no..." as opposed to "Give me reasons to say yes...".
Given that there is no "Request for changes" feature in discussions and issues,
try to be explicit when your comment is expressing a blocking concern.
Similarly, once the blocking concern has been addressed, explicitly say so.
Explicit opposition would typically be signaled as some form of clear
and unambiguous comment like, "I don't believe this nomination should pass".
Asking clarifying questions or expressing general concerns is not the same as
explicit opposition; however, a best effort should be made to answer such
questions or addressing those concerns before advancing the nomination.
Opposition does not need to be public. Ideally, the comment showing opposition,
and any discussion thereof, should be done in the private discussion _before_
the public issue is opened. Opposition _should_ be paired with clear suggestions
for positive, concrete, and unambiguous next steps that the nominee can take to
overcome the objection and allow it to move forward. While such suggestions are
technically optional, they are _strongly encouraged_ to prevent the nomination
from stalling indefinitely or objections from being overridden by the TSC.
Remember that all private discussions about a nomination will be visible to
the nominee once they are onboarded.
### Onboarding
After the nomination passes, a TSC member onboards the new collaborator. See
[the onboarding guide](./onboarding.md) for details of the onboarding
process.
## Consensus seeking process
The TSC follows a [Consensus Seeking][] decision-making model per the
[TSC Charter][].
[Consensus Seeking]: https://en.wikipedia.org/wiki/Consensus-seeking_decision-making
[TSC Charter]: https://github.com/nodejs/TSC/blob/HEAD/TSC-Charter.md
[discussion in the nodejs/collaborators]: https://github.com/nodejs/collaborators/discussions/categories/collaborator-nominations
[nodejs/help]: https://github.com/nodejs/help
[nodejs/node]: https://github.com/nodejs/node

2666
LICENSE Normal file

File diff suppressed because it is too large Load Diff

1654
Makefile Normal file

File diff suppressed because it is too large Load Diff

265
SECURITY.md Normal file
View File

@ -0,0 +1,265 @@
# Security
## Reporting a bug in Node.js
Report security bugs in Node.js via [HackerOne](https://hackerone.com/nodejs).
Normally, your report will be acknowledged within 5 days, and you'll receive
a more detailed response to your report within 10 days indicating the
next steps in handling your submission. These timelines may extend when
our triage volunteers are away on holiday, particularly at the end of the
year.
After the initial reply to your report, the security team will endeavor to keep
you informed of the progress being made towards a fix and full announcement,
and may ask for additional information or guidance surrounding the reported
issue.
### Node.js bug bounty program
The Node.js project engages in an official bug bounty program for security
researchers and responsible public disclosures. The program is managed through
the HackerOne platform. See <https://hackerone.com/nodejs> for further details.
## Reporting a bug in a third-party module
Security bugs in third-party modules should be reported to their respective
maintainers.
## Disclosure policy
Here is the security disclosure policy for Node.js
* The security report is received and is assigned a primary handler. This
person will coordinate the fix and release process. The problem is validated
against all supported Node.js versions. Once confirmed, a list of all affected
versions is determined. Code is audited to find any potential similar
problems. Fixes are prepared for all supported releases.
These fixes are not committed to the public repository but rather held locally
pending the announcement.
* A suggested embargo date for this vulnerability is chosen and a CVE (Common
Vulnerabilities and Exposures (CVE®)) is requested for the vulnerability.
* On the embargo date, a copy of the announcement is sent to the Node.js
security mailing list. The changes are pushed to the public repository and new
builds are deployed to nodejs.org. Within 6 hours of the mailing list being
notified, a copy of the advisory will be published on the Node.js blog.
* Typically, the embargo date will be set 72 hours from the time the CVE is
issued. However, this may vary depending on the severity of the bug or
difficulty in applying a fix.
* This process can take some time, especially when we need to coordinate with
maintainers of other projects. We will try to handle the bug as quickly as
possible; however, we must follow the release process above to ensure that we
handle disclosure consistently.
## Code of Conduct and Vulnerability Reporting Guidelines
When reporting security vulnerabilities, reporters must adhere to the following guidelines:
1. **Code of Conduct Compliance**: All security reports must comply with our
[Code of Conduct](CODE_OF_CONDUCT.md). Reports that violate our code of conduct
will not be considered and may result in being banned from future participation.
2. **No Harmful Actions**: Security research and vulnerability reporting must not:
* Cause damage to running systems or production environments.
* Disrupt Node.js development or infrastructure.
* Affect other users' applications or systems.
* Include actual exploits that could harm users.
* Involve social engineering or phishing attempts.
3. **Responsible Testing**: When testing potential vulnerabilities:
* Use isolated, controlled environments.
* Do not test on production systems without prior authorization. Contact
the Node.js Technical Steering Committee (<tsc@iojs.org>) for permission or open
a HackerOne report.
* Do not attempt to access or modify other users' data.
* Immediately stop testing if unauthorized access is gained accidentally.
4. **Report Quality**
* Provide clear, detailed steps to reproduce the vulnerability.
* Include only the minimum proof of concept required to demonstrate the issue.
* Remove any malicious payloads or components that could cause harm.
Failure to follow these guidelines may result in:
* Rejection of the vulnerability report.
* Forfeiture of any potential bug bounty.
* Temporary or permanent ban from the bug bounty program.
* Legal action in cases of malicious intent.
## The Node.js threat model
In the Node.js threat model, there are trusted elements such as the
underlying operating system. Vulnerabilities that require the compromise
of these trusted elements are outside the scope of the Node.js threat
model.
For a vulnerability to be eligible for a bug bounty, it must be a
vulnerability in the context of the Node.js threat model. In other
words, it cannot assume that a trusted element (such as the operating
system) has been compromised.
Being able to cause the following through control of the elements that Node.js
does not trust is considered a vulnerability:
* Disclosure or loss of integrity or confidentiality of data protected through
the correct use of Node.js APIs.
* The unavailability of the runtime, including the unbounded degradation of its
performance.
If Node.js loads configuration files or runs code by default (without a
specific request from the user), and this is not documented, it is considered a
vulnerability.
Vulnerabilities related to this case may be fixed by a documentation update.
**Node.js does NOT trust**:
* Data received from the remote end of inbound network connections
that are accepted through the use of Node.js APIs and
which is transformed/validated by Node.js before being passed
to the application. This includes:
* HTTP APIs (all flavors) server APIs.
* The data received from the remote end of outbound network connections
that are created through the use of Node.js APIs and
which is transformed/validated by Node.js before being passed
to the application EXCEPT with respect to payload length. Node.js trusts
that applications make connections/requests which will avoid payload
sizes that will result in a Denial of Service.
* HTTP APIs (all flavors) client APIs.
* DNS APIs.
* Consumers of data protected through the use of Node.js APIs (for example,
people who have access to data encrypted through the Node.js crypto APIs).
* The file content or other I/O that is opened for reading or writing by the
use of Node.js APIs (ex: stdin, stdout, stderr).
In other words, if the data passing through Node.js to/from the application
can trigger actions other than those documented for the APIs, there is likely
a security vulnerability. Examples of unwanted actions are polluting globals,
causing an unrecoverable crash, or any other unexpected side effects that can
lead to a loss of confidentiality, integrity, or availability.
For example, if trusted input (like secure application code) is correct,
then untrusted input must not lead to arbitrary JavaScript code execution.
**Node.js trusts everything else**. Examples include:
* The developers and infrastructure that runs it.
* The operating system that Node.js is running under and its configuration,
along with anything under control of the operating system.
* The code it is asked to run, including JavaScript, WASM and native code, even
if said code is dynamically loaded, e.g., all dependencies installed from the
npm registry.
The code run inherits all the privileges of the execution user.
* Inputs provided to it by the code it is asked to run, as it is the
responsibility of the application to perform the required input validations,
e.g. the input to `JSON.parse()`.
* Any connection used for inspector (debugger protocol) regardless of being
opened by command line options or Node.js APIs, and regardless of the remote
end being on the local machine or remote.
* The file system when requiring a module.
See <https://nodejs.org/api/modules.html#all-together>.
* The `node:wasi` module does not currently provide the comprehensive file
system security properties provided by some WASI runtimes.
Any unexpected behavior from the data manipulation from Node.js Internal
functions may be considered a vulnerability if they are exploitable via
untrusted resources.
In addition to addressing vulnerabilities based on the above, the project works
to avoid APIs and internal implementations that make it "easy" for application
code to use the APIs incorrectly in a way that results in vulnerabilities within
the application code itself. While we dont consider those vulnerabilities in
Node.js itself and will not necessarily issue a CVE, we do want them to be
reported privately to Node.js first.
We often choose to work to improve our APIs based on those reports and issue
fixes either in regular or security releases depending on how much of a risk to
the community they pose.
### Examples of vulnerabilities
#### Improper Certificate Validation (CWE-295)
* Node.js provides APIs to validate handling of Subject Alternative Names (SANs)
in certificates used to connect to a TLS/SSL endpoint. If certificates can be
crafted which result in incorrect validation by the Node.js APIs that is
considered a vulnerability.
#### Inconsistent Interpretation of HTTP Requests (CWE-444)
* Node.js provides APIs to accept http connections. Those APIs parse the
headers received for a connection and pass them on to the application.
Bugs in parsing those headers which can result in request smuggling are
considered vulnerabilities.
#### Missing Cryptographic Step (CWE-325)
* Node.js provides APIs to encrypt data. Bugs that would allow an attacker
to get the original data without requiring the decryption key are
considered vulnerabilities.
#### External Control of System or Configuration Setting (CWE-15)
* If Node.js automatically loads a configuration file which is not documented
and modification of that configuration can affect the confidentiality of
data protected using the Node.js APIs this is considered a vulnerability.
### Examples of non-vulnerabilities
#### Malicious Third-Party Modules (CWE-1357)
* Code is trusted by Node.js. Therefore any scenario that requires a malicious
third-party module cannot result in a vulnerability in Node.js.
#### Prototype Pollution Attacks (CWE-1321)
* Node.js trusts the inputs provided to it by application code.
It is up to the application to sanitize appropriately. Therefore any scenario
that requires control over user input is not considered a vulnerability.
#### Uncontrolled Search Path Element (CWE-427)
* Node.js trusts the file system in the environment accessible to it.
Therefore, it is not a vulnerability if it accesses/loads files from any path
that is accessible to it.
#### External Control of System or Configuration Setting (CWE-15)
* If Node.js automatically loads a configuration file which is documented
no scenario that requires modification of that configuration file is
considered a vulnerability.
#### Uncontrolled Resource Consumption (CWE-400) on outbound connections
* If Node.js is asked to connect to a remote site and return an
artifact, it is not considered a vulnerability if the size of
that artifact is large enough to impact performance or
cause the runtime to run out of resources.
#### Vulnerabilities affecting software downloaded by Corepack
* Corepack defaults to downloading the latest version of the software requested
by the user, or a specific version requested by the user. For this reason,
Node.js releases won't be affected by such vulnerabilities. Users are
responsible for keeping the software they use through Corepack up-to-date.
## Assessing experimental features reports
Experimental features are eligible to reports as any other stable feature of
Node.js. They will also be susceptible to receiving the same severity score
as any other stable feature.
## Receiving security updates
Security notifications will be distributed via the following methods.
* <https://groups.google.com/group/nodejs-sec>
* <https://nodejs.org/en/blog/vulnerability>
## Comments on this policy
If you have suggestions on how this process could be improved, please visit
the [nodejs/security-wg](https://github.com/nodejs/security-wg)
repository.

35
android-configure Executable file
View File

@ -0,0 +1,35 @@
#!/bin/sh
# Locate an acceptable Python interpreter and then re-execute the script.
# Note that the mix of single and double quotes is intentional,
# as is the fact that the ] goes on a new line.
_=[ 'exec' '/bin/sh' '-c' '''
command -v python3.13 >/dev/null && exec python3.13 "$0" "$@"
command -v python3.12 >/dev/null && exec python3.12 "$0" "$@"
command -v python3.11 >/dev/null && exec python3.11 "$0" "$@"
command -v python3.10 >/dev/null && exec python3.10 "$0" "$@"
command -v python3.9 >/dev/null && exec python3.9 "$0" "$@"
command -v python3 >/dev/null && exec python3 "$0" "$@"
exec python "$0" "$@"
''' "$0" "$@"
]
del _
import sys
try:
from shutil import which
except ImportError:
from distutils.spawn import find_executable as which
print('Node.js android configure: Found Python {}.{}.{}...'.format(*sys.version_info))
acceptable_pythons = ((3, 13), (3, 12), (3, 11), (3, 10), (3, 9))
if sys.version_info[:2] in acceptable_pythons:
import android_configure
else:
python_cmds = ['python{}.{}'.format(*vers) for vers in acceptable_pythons]
sys.stderr.write('Please use {}.\n'.format(' or '.join(python_cmds)))
for python_cmd in python_cmds:
python_cmd_path = which(python_cmd)
if python_cmd_path and 'pyenv/shims' not in python_cmd_path:
sys.stderr.write('\t{} {}\n'.format(python_cmd_path, ' '.join(sys.argv[:1])))
sys.exit(1)

View File

@ -0,0 +1,26 @@
--- trap-handler.h 2022-08-11 09:01:23.384000000 +0800
+++ fixed-trap-handler.h 2022-08-11 09:09:15.352000000 +0800
@@ -17,23 +17,7 @@
namespace internal {
namespace trap_handler {
-// X64 on Linux, Windows, MacOS, FreeBSD.
-#if V8_HOST_ARCH_X64 && V8_TARGET_ARCH_X64 && \
- ((V8_OS_LINUX && !V8_OS_ANDROID) || V8_OS_WIN || V8_OS_DARWIN || \
- V8_OS_FREEBSD)
-#define V8_TRAP_HANDLER_SUPPORTED true
-// Arm64 (non-simulator) on Mac.
-#elif V8_TARGET_ARCH_ARM64 && V8_HOST_ARCH_ARM64 && V8_OS_DARWIN
-#define V8_TRAP_HANDLER_SUPPORTED true
-// Arm64 simulator on x64 on Linux, Mac, or Windows.
-#elif V8_TARGET_ARCH_ARM64 && V8_HOST_ARCH_X64 && \
- (V8_OS_LINUX || V8_OS_DARWIN)
-#define V8_TRAP_HANDLER_VIA_SIMULATOR
-#define V8_TRAP_HANDLER_SUPPORTED true
-// Everything else is unsupported.
-#else
#define V8_TRAP_HANDLER_SUPPORTED false
-#endif
// Setup for shared library export.
#if defined(BUILDING_V8_SHARED) && defined(V8_OS_WIN)

77
android_configure.py Normal file
View File

@ -0,0 +1,77 @@
import platform
import sys
import os
# TODO: In next version, it will be a JSON file listing all the patches, and then it will iterate through to apply them.
def patch_android():
print("- Patches List -")
print("[1] [deps/v8/src/trap-handler/trap-handler.h] related to https://github.com/nodejs/node/issues/36287")
if platform.system() == "Linux":
os.system('patch -f ./deps/v8/src/trap-handler/trap-handler.h < ./android-patches/trap-handler.h.patch')
print("\033[92mInfo: \033[0m" + "Tried to patch.")
if platform.system() != "Linux" and platform.system() != "Darwin":
print("android-configure is currently only supported on Linux and Darwin.")
sys.exit(1)
if len(sys.argv) == 2 and sys.argv[1] == "patch":
patch_android()
sys.exit(0)
if len(sys.argv) != 4:
print("Usage: ./android-configure [patch] <path to the Android NDK> <Android SDK version> <target architecture>")
sys.exit(1)
if not os.path.exists(sys.argv[1]) or not os.listdir(sys.argv[1]):
print("\033[91mError: \033[0m" + "Invalid path to the Android NDK")
sys.exit(1)
if int(sys.argv[2]) < 24:
print("\033[91mError: \033[0m" + "Android SDK version must be at least 24 (Android 7.0)")
sys.exit(1)
android_ndk_path = sys.argv[1]
android_sdk_version = sys.argv[2]
arch = sys.argv[3]
if arch == "arm":
DEST_CPU = "arm"
TOOLCHAIN_PREFIX = "armv7a-linux-androideabi"
elif arch in ("aarch64", "arm64"):
DEST_CPU = "arm64"
TOOLCHAIN_PREFIX = "aarch64-linux-android"
arch = "arm64"
elif arch == "x86":
DEST_CPU = "ia32"
TOOLCHAIN_PREFIX = "i686-linux-android"
elif arch == "x86_64":
DEST_CPU = "x64"
TOOLCHAIN_PREFIX = "x86_64-linux-android"
arch = "x64"
else:
print("\033[91mError: \033[0m" + "Invalid target architecture, must be one of: arm, arm64, aarch64, x86, x86_64")
sys.exit(1)
print("\033[92mInfo: \033[0m" + "Configuring for " + DEST_CPU + "...")
if platform.system() == "Darwin":
host_os = "darwin"
toolchain_path = android_ndk_path + "/toolchains/llvm/prebuilt/darwin-x86_64"
elif platform.system() == "Linux":
host_os = "linux"
toolchain_path = android_ndk_path + "/toolchains/llvm/prebuilt/linux-x86_64"
os.environ['PATH'] += os.pathsep + toolchain_path + "/bin"
os.environ['CC'] = toolchain_path + "/bin/" + TOOLCHAIN_PREFIX + android_sdk_version + "-" + "clang"
os.environ['CXX'] = toolchain_path + "/bin/" + TOOLCHAIN_PREFIX + android_sdk_version + "-" + "clang++"
GYP_DEFINES = "target_arch=" + arch
GYP_DEFINES += " v8_target_arch=" + arch
GYP_DEFINES += " android_target_arch=" + arch
GYP_DEFINES += " host_os=" + host_os + " OS=android"
GYP_DEFINES += " android_ndk_path=" + android_ndk_path
os.environ['GYP_DEFINES'] = GYP_DEFINES
if os.path.exists("./configure"):
os.system("./configure --dest-cpu=" + DEST_CPU + " --dest-os=android --openssl-no-asm --cross-compiling")

81
benchmark/README.md Normal file
View File

@ -0,0 +1,81 @@
# Node.js Core Benchmarks
This folder contains code and data used to measure performance
of different Node.js implementations and different ways of
writing JavaScript run by the built-in JavaScript engine.
For a detailed guide on how to write and run benchmarks in this
directory, see [the guide on benchmarks](../doc/contributing/writing-and-running-benchmarks.md).
## Table of Contents
* [File tree structure](#file-tree-structure)
* [Common API](#common-api)
## File tree structure
### Directories
Benchmarks testing the performance of a single node submodule are placed into a
directory with the corresponding name, so that they can be executed by submodule
or individually.
Benchmarks that span multiple submodules may either be placed into the `misc`
directory or into a directory named after the feature they benchmark.
E.g. benchmarks for various new ECMAScript features and their pre-ES2015
counterparts are placed in a directory named `es`.
Fixtures that are not specific to a certain benchmark but can be reused
throughout the benchmark suite should be placed in the `fixtures` directory.
### Other Top-level files
The top-level files include common dependencies of the benchmarks
and the tools for launching benchmarks and visualizing their output.
The actual benchmark scripts should be placed in their corresponding
directories.
* `_benchmark_progress.js`: implements the progress bar displayed
when running `compare.js`
* `_cli.js`: parses the command line arguments passed to `compare.js`,
`run.js` and `scatter.js`
* `_cli.R`: parses the command line arguments passed to `compare.R`
* `_http-benchmarkers.js`: selects and runs external tools for benchmarking
the `http` subsystem.
* `bar.R`: R script for visualizing the output of benchmarks with bar plots.
* `common.js`: see [Common API](#common-api).
* `compare.js`: command line tool for comparing performance between different
Node.js binaries.
* `compare.R`: R script for statistically analyzing the output of
`compare.js`
* `run.js`: command line tool for running individual benchmark suite(s).
* `scatter.js`: command line tool for comparing the performance
between different parameters in benchmark configurations,
for example to analyze the time complexity.
* `scatter.R`: R script for visualizing the output of `scatter.js` with
scatter plots.
## Common API
The common.js module is used by benchmarks for consistency across repeated
tasks. It has a number of helpful functions and properties to help with
writing benchmarks.
### `createBenchmark(fn, configs[, options])`
See [the guide on writing benchmarks](../doc/contributing/writing-and-running-benchmarks.md#basics-of-a-benchmark).
### `default_http_benchmarker`
The default benchmarker used to run HTTP benchmarks.
See [the guide on writing HTTP benchmarks](../doc/contributing/writing-and-running-benchmarks.md#creating-an-http-benchmark).
### `PORT`
The default port used to run HTTP benchmarks.
See [the guide on writing HTTP benchmarks](../doc/contributing/writing-and-running-benchmarks.md#creating-an-http-benchmark).
### `sendResult(data)`
Used in special benchmarks that can't use `createBenchmark` and the object
it returns to accomplish what they need. This function reports timing
data to the parent process (usually created by running `compare.js`, `run.js` or
`scatter.js`).

View File

@ -0,0 +1,119 @@
'use strict';
const readline = require('readline');
function pad(input, minLength, fill) {
const result = String(input);
const padding = fill.repeat(Math.max(0, minLength - result.length));
return `${padding}${result}`;
}
function fraction(numerator, denominator) {
const fdenominator = String(denominator);
const fnumerator = pad(numerator, fdenominator.length, ' ');
return `${fnumerator}/${fdenominator}`;
}
function getTime(diff) {
const time = Math.ceil(diff[0] + diff[1] / 1e9);
const hours = pad(Math.floor(time / 3600), 2, '0');
const minutes = pad(Math.floor((time % 3600) / 60), 2, '0');
const seconds = pad((time % 3600) % 60, 2, '0');
return `${hours}:${minutes}:${seconds}`;
}
// A run is an item in the job queue: { binary, filename, iter }
// A config is an item in the subqueue: { binary, filename, iter, configs }
class BenchmarkProgress {
constructor(queue, benchmarks) {
this.queue = queue; // Scheduled runs.
this.benchmarks = benchmarks; // Filenames of scheduled benchmarks.
this.completedRuns = 0; // Number of completed runs.
this.scheduledRuns = queue.length; // Number of scheduled runs.
// Time when starting to run benchmarks.
this.startTime = process.hrtime();
// Number of times each file will be run (roughly).
this.runsPerFile = queue.length / benchmarks.length;
this.currentFile = ''; // Filename of current benchmark.
// Number of configurations already run for the current file.
this.completedConfig = 0;
// Total number of configurations for the current file
this.scheduledConfig = 0;
}
startQueue(index) {
this.kStartOfQueue = index;
this.currentFile = this.queue[index].filename;
this.interval = setInterval(() => {
if (this.completedRuns === this.scheduledRuns) {
clearInterval(this.interval);
} else {
this.updateProgress();
}
}, 1000);
}
startSubqueue(data, index) {
// This subqueue is generated by a new benchmark
if (data.name !== this.currentFile || index === this.kStartOfQueue) {
this.currentFile = data.name;
this.scheduledConfig = data.queueLength;
}
this.completedConfig = 0;
this.updateProgress();
}
completeConfig() {
this.completedConfig++;
this.updateProgress();
}
completeRun() {
this.completedRuns++;
this.updateProgress();
}
getProgress() {
// Get time as soon as possible.
const diff = process.hrtime(this.startTime);
const completedRuns = this.completedRuns;
const scheduledRuns = this.scheduledRuns;
const finished = completedRuns === scheduledRuns;
// Calculate numbers for fractions.
const runsPerFile = this.runsPerFile;
const completedFiles = Math.floor(completedRuns / runsPerFile);
const scheduledFiles = this.benchmarks.length;
const completedRunsForFile =
finished ? runsPerFile : completedRuns % runsPerFile;
const completedConfig = this.completedConfig;
const scheduledConfig = this.scheduledConfig;
// Calculate the percentage.
let runRate = 0; // Rate of current incomplete run.
if (completedConfig !== scheduledConfig) {
runRate = completedConfig / scheduledConfig;
}
const completedRate = ((completedRuns + runRate) / scheduledRuns);
const percent = pad(Math.floor(completedRate * 100), 3, ' ');
const caption = finished ? 'Done\n' : this.currentFile;
return `[${getTime(diff)}|% ${percent}| ` +
`${fraction(completedFiles, scheduledFiles)} files | ` +
`${fraction(completedRunsForFile, runsPerFile)} runs | ` +
`${fraction(completedConfig, scheduledConfig)} configs]: ` +
`${caption} `;
}
updateProgress() {
if (!process.stderr.isTTY || process.stdout.isTTY) {
return;
}
readline.clearLine(process.stderr);
readline.cursorTo(process.stderr, 0);
process.stderr.write(this.getProgress());
}
}
module.exports = BenchmarkProgress;

24
benchmark/_cli.R Normal file
View File

@ -0,0 +1,24 @@
args = commandArgs(TRUE);
args.options = list();
temp.option.key = NULL;
for (arg in args) {
# Optional arguments declaration
if (substring(arg, 1, 1) == '-') {
temp.option.key = substring(arg, 2);
if (substring(arg, 2, 2) == '-') {
temp.option.key = substring(arg, 3);
}
args.options[[temp.option.key]] = TRUE;
}
# Optional arguments value
else if (!is.null(temp.option.key)) {
args.options[[temp.option.key]] = arg;
temp.option.key = NULL;
}
}

149
benchmark/_cli.js Normal file
View File

@ -0,0 +1,149 @@
'use strict';
const fs = require('fs');
const path = require('path');
// Create an object of all benchmark scripts
const benchmarks = {};
fs.readdirSync(__dirname)
.filter((name) => {
return name !== 'fixtures' &&
fs.statSync(path.resolve(__dirname, name)).isDirectory();
})
.forEach((category) => {
benchmarks[category] = fs.readdirSync(path.resolve(__dirname, category))
.filter((filename) => filename[0] !== '.' && filename[0] !== '_');
});
function CLI(usage, settings) {
if (process.argv.length < 3) {
this.abort(usage); // Abort will exit the process
}
this.usage = usage;
this.optional = {};
this.items = [];
this.test = false;
for (const argName of settings.arrayArgs) {
this.optional[argName] = [];
}
let currentOptional = null;
let mode = 'both'; // Possible states are: [both, option, item]
for (const arg of process.argv.slice(2)) {
if (arg === '--help') this.abort(usage);
if (arg === '--') {
// Only items can follow --
mode = 'item';
} else if (mode === 'both' && arg[0] === '-') {
// Optional arguments declaration
if (arg[1] === '-') {
currentOptional = arg.slice(2);
} else {
currentOptional = arg.slice(1);
}
if (settings.boolArgs && settings.boolArgs.includes(currentOptional)) {
this.optional[currentOptional] = true;
mode = 'both';
} else {
// Expect the next value to be option related (either -- or the value)
mode = 'option';
}
} else if (mode === 'option') {
// Optional arguments value
if (settings.arrayArgs.includes(currentOptional)) {
this.optional[currentOptional].push(arg);
} else {
this.optional[currentOptional] = arg;
}
// The next value can be either an option or an item
mode = 'both';
} else if (arg === 'test') {
this.test = true;
} else if (['both', 'item'].includes(mode)) {
// item arguments
this.items.push(arg);
// The next value must be an item
mode = 'item';
} else {
// Bad case, abort
this.abort(usage);
}
}
}
module.exports = CLI;
CLI.prototype.abort = function(msg) {
console.error(msg);
process.exit(1);
};
CLI.prototype.benchmarks = function() {
const paths = [];
if (this.items.includes('all')) {
this.items = Object.keys(benchmarks);
}
for (const category of this.items) {
if (benchmarks[category] === undefined) {
console.error(`The "${category}" category does not exist.`);
process.exit(1);
}
for (const scripts of benchmarks[category]) {
if (this.shouldSkip(scripts)) continue;
paths.push(path.join(category, scripts));
}
}
return paths;
};
CLI.prototype.shouldSkip = function(scripts) {
const filters = this.optional.filter || [];
const excludes = this.optional.exclude || [];
let skip = filters.length > 0;
for (const filter of filters) {
if (scripts.lastIndexOf(filter) !== -1) {
skip = false;
}
}
for (const exclude of excludes) {
if (scripts.lastIndexOf(exclude) !== -1) {
skip = true;
}
}
return skip;
};
/**
* Extracts the CPU core setting from the CLI arguments.
* @returns {string|null} The CPU core setting if found, otherwise null.
*/
CLI.prototype.getCpuCoreSetting = function() {
const cpuCoreSetting = this.optional.set.find((s) => s.startsWith('CPUSET='));
if (!cpuCoreSetting) return null;
const value = cpuCoreSetting.split('=')[1];
// Validate the CPUSET value to match patterns like "0", "0-2", "0,1,2", "0,2-4,6" or "0,0,1-2"
const isValid = /^(\d+(-\d+)?)(,\d+(-\d+)?)*$/.test(value);
if (!isValid) {
throw new Error(`
Invalid CPUSET format: "${value}". Please use a single core number (e.g., "0"),
a range of cores (e.g., "0-3"), or a list of cores/ranges
(e.g., "0,2,4" or "0-2,4").\n\n${this.usage}
`);
}
return value;
};

View File

@ -0,0 +1,266 @@
'use strict';
const child_process = require('child_process');
const path = require('path');
const fs = require('fs');
const requirementsURL =
'https://github.com/nodejs/node/blob/HEAD/doc/contributing/writing-and-running-benchmarks.md#http-benchmark-requirements';
// The port used by servers and wrk
exports.PORT = Number(process.env.PORT) || 12346;
class AutocannonBenchmarker {
constructor() {
const shell = (process.platform === 'win32');
this.name = 'autocannon';
this.opts = { shell };
this.executable = shell ? 'autocannon.cmd' : 'autocannon';
const result = child_process.spawnSync(this.executable, ['-h'], this.opts);
if (shell) {
this.present = (result.status === 0);
} else {
this.present = !(result.error && result.error.code === 'ENOENT');
}
}
create(options) {
const args = [
'-d', options.duration,
'-c', options.connections,
'-j',
'-n',
];
for (const field in options.headers) {
if (this.opts.shell) {
args.push('-H', `'${field}=${options.headers[field]}'`);
} else {
args.push('-H', `${field}=${options.headers[field]}`);
}
}
const scheme = options.scheme || 'http';
args.push(`${scheme}://127.0.0.1:${options.port}${options.path}`);
const child = child_process.spawn(this.executable, args, this.opts);
return child;
}
processResults(output) {
let result;
try {
result = JSON.parse(output);
} catch {
return undefined;
}
if (!result || !result.requests || !result.requests.average) {
return undefined;
}
return result.requests.average;
}
}
class WrkBenchmarker {
constructor() {
this.name = 'wrk';
this.executable = 'wrk';
const result = child_process.spawnSync(this.executable, ['-h']);
this.present = !(result.error && result.error.code === 'ENOENT');
}
create(options) {
const duration = typeof options.duration === 'number' ?
Math.max(options.duration, 1) :
options.duration;
const scheme = options.scheme || 'http';
const args = [
'-d', duration,
'-c', options.connections,
'-t', Math.min(options.connections, require('os').availableParallelism() || 8),
`${scheme}://127.0.0.1:${options.port}${options.path}`,
];
for (const field in options.headers) {
args.push('-H', `${field}: ${options.headers[field]}`);
}
const child = child_process.spawn(this.executable, args);
return child;
}
processResults(output) {
const throughputRe = /Requests\/sec:[ \t]+([0-9.]+)/;
const match = output.match(throughputRe);
const throughput = match && +match[1];
if (!isFinite(throughput)) {
return undefined;
}
return throughput;
}
}
/**
* Simple, single-threaded benchmarker for testing if the benchmark
* works
*/
class TestDoubleBenchmarker {
constructor(type) {
// `type` is the type of benchmarker. Possible values are 'http', 'https',
// and 'http2'.
this.name = `test-double-${type}`;
this.executable = path.resolve(__dirname, '_test-double-benchmarker.js');
this.present = fs.existsSync(this.executable);
this.type = type;
}
create(options) {
process.env.duration ||= options.duration || 5;
const scheme = options.scheme || 'http';
const env = {
test_url: `${scheme}://127.0.0.1:${options.port}${options.path}`,
...process.env,
};
const child = child_process.fork(this.executable,
[this.type],
{ silent: true, env });
return child;
}
processResults(output) {
let result;
try {
result = JSON.parse(output);
} catch {
return undefined;
}
return result.throughput;
}
}
/**
* HTTP/2 Benchmarker
*/
class H2LoadBenchmarker {
constructor() {
this.name = 'h2load';
this.executable = 'h2load';
const result = child_process.spawnSync(this.executable, ['-h']);
this.present = !(result.error && result.error.code === 'ENOENT');
}
create(options) {
const args = [];
if (typeof options.requests === 'number')
args.push('-n', options.requests);
if (typeof options.clients === 'number')
args.push('-c', options.clients);
if (typeof options.threads === 'number')
args.push('-t', options.threads);
if (typeof options.maxConcurrentStreams === 'number')
args.push('-m', options.maxConcurrentStreams);
if (typeof options.initialWindowSize === 'number')
args.push('-w', options.initialWindowSize);
if (typeof options.sessionInitialWindowSize === 'number')
args.push('-W', options.sessionInitialWindowSize);
if (typeof options.rate === 'number')
args.push('-r', options.rate);
if (typeof options.ratePeriod === 'number')
args.push(`--rate-period=${options.ratePeriod}`);
if (typeof options.duration === 'number')
args.push('-T', options.duration);
if (typeof options.timeout === 'number')
args.push('-N', options.timeout);
if (typeof options.headerTableSize === 'number')
args.push(`--header-table-size=${options.headerTableSize}`);
if (typeof options.encoderHeaderTableSize === 'number') {
args.push(
`--encoder-header-table-size=${options.encoderHeaderTableSize}`);
}
const scheme = options.scheme || 'http';
const host = options.host || '127.0.0.1';
args.push(`${scheme}://${host}:${options.port}${options.path}`);
const child = child_process.spawn(this.executable, args);
return child;
}
processResults(output) {
const rex = /(\d+\.\d+) req\/s/;
return rex.exec(output)[1];
}
}
const http_benchmarkers = [
new WrkBenchmarker(),
new AutocannonBenchmarker(),
new TestDoubleBenchmarker('http'),
new TestDoubleBenchmarker('https'),
new TestDoubleBenchmarker('http2'),
new H2LoadBenchmarker(),
];
const benchmarkers = {};
http_benchmarkers.forEach((benchmarker) => {
benchmarkers[benchmarker.name] = benchmarker;
if (!exports.default_http_benchmarker && benchmarker.present) {
exports.default_http_benchmarker = benchmarker.name;
}
});
exports.run = function(options, callback) {
options = {
port: exports.PORT,
path: '/',
connections: 100,
duration: 5,
benchmarker: exports.default_http_benchmarker,
...options,
};
if (!options.benchmarker) {
callback(new Error('Could not locate required http benchmarker. See ' +
`${requirementsURL} for further instructions.`));
return;
}
const benchmarker = benchmarkers[options.benchmarker];
if (!benchmarker) {
callback(new Error(`Requested benchmarker '${options.benchmarker}' ` +
'is not supported'));
return;
}
if (!benchmarker.present) {
callback(new Error(`Requested benchmarker '${options.benchmarker}' ` +
'is not installed'));
return;
}
const benchmarker_start = process.hrtime.bigint();
const child = benchmarker.create(options);
child.stderr.pipe(process.stderr);
let stdout = '';
child.stdout.setEncoding('utf8');
child.stdout.on('data', (chunk) => stdout += chunk);
child.once('close', (code) => {
const benchmark_end = process.hrtime.bigint();
if (code) {
let error_message = `${options.benchmarker} failed with ${code}.`;
if (stdout !== '') {
error_message += ` Output: ${stdout}`;
}
callback(new Error(error_message), code);
return;
}
const result = benchmarker.processResults(stdout);
if (result === undefined) {
callback(new Error(
`${options.benchmarker} produced strange output: ${stdout}`), code);
return;
}
const elapsed = benchmark_end - benchmarker_start;
callback(null, code, options.benchmarker, result, elapsed);
});
};

View File

@ -0,0 +1,54 @@
'use strict';
const myModule = process.argv[2];
if (!['http', 'https', 'http2'].includes(myModule)) {
throw new Error(`Invalid module for benchmark test double: ${myModule}`);
}
let options;
if (myModule === 'https') {
options = { rejectUnauthorized: false };
}
const http = require(myModule);
const duration = +process.env.duration;
const url = process.env.test_url;
const start = process.hrtime();
let throughput = 0;
function request(res, client) {
res.resume();
res.on('error', () => {});
res.on('end', () => {
throughput++;
const [sec, nanosec] = process.hrtime(start);
const ms = sec * 1000 + nanosec / 1e6;
if (ms < duration * 1000) {
run();
} else {
console.log(JSON.stringify({ throughput }));
if (client) {
client.destroy();
process.exit(0);
}
}
});
}
function run() {
if (http.get) { // HTTP or HTTPS
if (options) {
http.get(url, options, request);
} else {
http.get(url, request);
}
} else { // HTTP/2
const client = http.connect(url);
client.on('error', () => {});
request(client.request(), client);
}
}
run();

View File

@ -0,0 +1,29 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [5e6],
kind: ['default-reason', 'same-reason'],
});
function main({ n, kind }) {
switch (kind) {
case 'default-reason':
bench.start();
for (let i = 0; i < n; ++i)
AbortSignal.abort();
bench.end(n);
break;
case 'same-reason': {
const reason = new Error('same reason');
bench.start();
for (let i = 0; i < n; ++i)
AbortSignal.abort(reason);
bench.end(n);
break;
}
default:
throw new Error('Invalid kind');
}
}

View File

@ -0,0 +1,37 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [200],
size: [2, 75],
});
const baseObject = {
a: 1,
b: {
c: 2,
d: [3, 4, 5],
e: 'fghi',
j: {
k: 6,
},
},
};
function createObjects(size) {
return Array.from({ length: size }, () => baseObject);
}
function main({ n, size }) {
bench.start();
for (let i = 0; i < n; ++i) {
new assert.AssertionError({
actual: {},
expected: createObjects(size),
operator: 'partialDeepStrictEqual',
stackStartFunction: () => {},
});
}
bench.end(n);
}

View File

@ -0,0 +1,53 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [2e4],
len: [1e2, 1e3],
strict: [0, 1],
arrayBuffer: [0, 1],
method: ['deepEqual', 'notDeepEqual', 'unequal_length', 'partial'],
}, {
combinationFilter: (p) => {
return p.strict === 1 || p.method === 'deepEqual';
},
});
function main({ len, n, method, strict, arrayBuffer }) {
let actual = Buffer.alloc(len);
let expected = Buffer.alloc(len + Number(method === 'unequal_length'));
if (method === 'unequal_length') {
method = 'notDeepEqual';
}
if (method === 'partial') {
method = 'partialDeepStrictEqual';
} else if (strict) {
method = method.replace('eep', 'eepStrict');
}
for (let i = 0; i < len; i++) {
actual.writeInt8(i % 128, i);
expected.writeInt8(i % 128, i);
}
if (method.includes('not')) {
const position = Math.floor(len / 2);
expected[position] = expected[position] + 1;
}
const fn = assert[method];
if (arrayBuffer) {
actual = actual.buffer;
expected = expected.buffer;
}
bench.start();
for (let i = 0; i < n; ++i) {
fn(actual, expected);
}
bench.end(n);
}

View File

@ -0,0 +1,80 @@
'use strict';
const common = require('../common.js');
const { deepEqual, deepStrictEqual, notDeepEqual, notDeepStrictEqual } =
require('assert');
const bench = common.createBenchmark(main, {
n: [2e3],
len: [5e2],
strict: [0, 1],
method: [
'deepEqual_primitiveOnly',
'deepEqual_objectOnly',
'deepEqual_mixed',
'notDeepEqual_primitiveOnly',
'notDeepEqual_objectOnly',
'notDeepEqual_mixed',
],
});
function benchmark(method, n, values, values2) {
const actual = new Map(values);
// Prevent reference equal elements
const deepCopy = JSON.parse(JSON.stringify(values2 ? values2 : values));
const expected = new Map(deepCopy);
bench.start();
for (let i = 0; i < n; ++i) {
method(actual, expected);
}
bench.end(n);
}
function main({ n, len, method, strict }) {
const array = Array.from({ length: len }, () => '');
switch (method) {
case 'deepEqual_primitiveOnly': {
const values = array.map((_, i) => [`str_${i}`, 123]);
benchmark(strict ? deepStrictEqual : deepEqual, n, values);
break;
}
case 'deepEqual_objectOnly': {
const values = array.map((_, i) => [[`str_${i}`, 1], 123]);
benchmark(strict ? deepStrictEqual : deepEqual, n, values);
break;
}
case 'deepEqual_mixed': {
const values = array.map(
(_, i) => [i % 2 ? [`str_${i}`, 1] : `str_${i}`, 123],
);
benchmark(strict ? deepStrictEqual : deepEqual, n, values);
break;
}
case 'notDeepEqual_primitiveOnly': {
const values = array.map((_, i) => [`str_${i}`, 123]);
const values2 = values.slice(0);
values2[Math.floor(len / 2)] = ['w00t', 123];
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2);
break;
}
case 'notDeepEqual_objectOnly': {
const values = array.map((_, i) => [[`str_${i}`, 1], 123]);
const values2 = values.slice(0);
values2[Math.floor(len / 2)] = [['w00t'], 123];
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2);
break;
}
case 'notDeepEqual_mixed': {
const values = array.map(
(_, i) => [i % 2 ? [`str_${i}`, 1] : `str_${i}`, 123],
);
const values2 = values.slice(0);
values2[0] = ['w00t', 123];
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2);
break;
}
default:
throw new Error(`Unsupported method ${method}`);
}
}

View File

@ -0,0 +1,43 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [50, 2e2],
size: [1e2, 1e4],
method: ['deepEqual', 'notDeepEqual', 'deepStrictEqual', 'notDeepStrictEqual'],
}, {
combinationFilter: (p) => {
return p.size === 1e4 && p.n === 50 ||
p.size === 1e3 && p.n === 2e2 ||
p.size === 1e2 && p.n === 2e3 ||
p.size === 1;
},
});
function createObj(size, add = '') {
return Array.from({ length: size }, (n) => ({
foo: 'yarp',
nope: {
bar: `123${add}`,
a: [1, 2, 3],
baz: n,
c: {},
b: [],
},
}));
}
function main({ size, n, method }) {
const fn = assert[method];
const actual = createObj(size);
const expected = method.includes('not') ? createObj(size, '4') : createObj(size);
bench.start();
for (let i = 0; i < n; ++i) {
fn(actual, expected);
}
bench.end(n);
}

View File

@ -0,0 +1,80 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const circular = {};
circular.circular = circular;
const circular2 = {};
circular2.circular = circular2;
const notCircular = {};
notCircular.circular = {};
const primValues = {
'null_prototype': { __proto__: null },
'string': 'abcdef',
'number': 1_000,
'boolean': true,
'object': { property: 'abcdef' },
'array': [1, 2, 3],
'set_object': new Set([[1]]),
'set_simple': new Set([1, 2, 3]),
'circular': circular,
'empty_object': {},
'regexp': /abc/i,
'date': new Date(),
};
const primValues2 = {
'null_prototype': { __proto__: null },
'object': { property: 'abcdef' },
'array': [1, 2, 3],
'set_object': new Set([[1]]),
'set_simple': new Set([1, 3, 2]),
'circular': circular2,
'empty_object': {},
'regexp': /abc/i,
'date': new Date(primValues.date),
};
const primValuesUnequal = {
'null_prototype': { __proto__: { __proto__: null } },
'string': 'abcdez',
'number': 1_001,
'boolean': false,
'object': { property2: 'abcdef' },
'array': [1, 3, 2],
'set_object': new Set([[2]]),
'set_simple': new Set([1, 4, 2]),
'circular': notCircular,
'empty_object': [],
'regexp': /abc/g,
'date': new Date(primValues.date.getTime() + 1),
};
const bench = common.createBenchmark(main, {
primitive: Object.keys(primValues),
n: [1e5],
strict: [0, 1],
method: ['deepEqual', 'notDeepEqual'],
}, {
combinationFilter: (p) => {
return p.strict === 1 || p.method === 'deepEqual';
},
});
function main({ n, primitive, method, strict }) {
const prim = primValues[primitive];
const actual = primValues2[primitive] ?? prim;
const expected = method.includes('not') ? primValuesUnequal[primitive] : prim;
if (strict) {
method = method.replace('eep', 'eepStrict');
}
const fn = assert[method];
bench.start();
for (let i = 0; i < n; ++i) {
fn(actual, expected);
}
bench.end(n);
}

View File

@ -0,0 +1,99 @@
'use strict';
const common = require('../common.js');
const { deepEqual, deepStrictEqual, notDeepEqual, notDeepStrictEqual } =
require('assert');
const bench = common.createBenchmark(main, {
n: [1e3],
len: [2, 1e2],
strict: [0, 1],
order: ['insert', 'random', 'reversed'],
method: [
'deepEqual_primitiveOnly',
'deepEqual_objectOnly',
'deepEqual_mixed',
'notDeepEqual_primitiveOnly',
'notDeepEqual_objectOnly',
'notDeepEqual_mixed',
],
}, {
combinationFilter(p) {
return p.order !== 'random' || p.strict === 1 && p.method !== 'notDeepEqual_objectOnly';
},
});
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
function benchmark(method, n, values, values2, order) {
const actual = new Set(values);
// Prevent reference equal elements
let deepCopy = JSON.parse(JSON.stringify(values2));
if (order === 'reversed') {
deepCopy = deepCopy.reverse();
} else if (order === 'random') {
shuffleArray(deepCopy);
}
const expected = new Set(deepCopy);
bench.start();
for (let i = 0; i < n; ++i) {
method(actual, expected);
}
bench.end(n);
}
function main({ n, len, method, strict, order }) {
const array = Array.from({ length: len }, () => '');
switch (method) {
case 'deepEqual_primitiveOnly': {
const values = array.map((_, i) => `str_${i}`);
benchmark(strict ? deepStrictEqual : deepEqual, n, values, values, order);
break;
}
case 'deepEqual_objectOnly': {
const values = array.map((_, i) => [`str_${i}`, null]);
benchmark(strict ? deepStrictEqual : deepEqual, n, values, values, order);
break;
}
case 'deepEqual_mixed': {
const values = array.map((_, i) => {
return i % 2 ? [`str_${i}`, null] : `str_${i}`;
});
benchmark(strict ? deepStrictEqual : deepEqual, n, values, values, order);
break;
}
case 'notDeepEqual_primitiveOnly': {
const values = array.map((_, i) => `str_${i}`);
const values2 = values.slice(0);
values2[Math.floor(len / 2)] = 'w00t';
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2, order);
break;
}
case 'notDeepEqual_objectOnly': {
const values = array.map((_, i) => [`str_${i}`, null]);
const values2 = values.slice(0);
values2[Math.floor(len / 2)] = ['w00t'];
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2, order);
break;
}
case 'notDeepEqual_mixed': {
const values = array.map((_, i) => {
return i % 2 ? [`str_${i}`, null] : `str_${i}`;
});
const values2 = values.slice();
values2[0] = 'w00t';
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2, order);
break;
}
default:
throw new Error(`Unsupported method "${method}"`);
}
}

View File

@ -0,0 +1,71 @@
'use strict';
const common = require('../common.js');
const { deepEqual, deepStrictEqual, notDeepEqual, notDeepStrictEqual } =
require('assert');
const bench = common.createBenchmark(main, {
n: [1e3],
len: [1e4],
strict: [1],
method: [
'deepEqual_Array',
'notDeepEqual_Array',
'deepEqual_sparseArray',
'notDeepEqual_sparseArray',
'deepEqual_Set',
'notDeepEqual_Set',
],
});
function run(fn, n, actual, expected) {
bench.start();
for (let i = 0; i < n; ++i) {
fn(actual, expected);
}
bench.end(n);
}
function main({ n, len, method, strict }) {
let actual = Array.from({ length: len }, (_, i) => i);
// Contain one undefined value to trigger a specific code path
actual[0] = undefined;
let expected = actual.slice(0);
if (method.includes('not')) {
expected[len - 1] += 1;
}
switch (method) {
case 'deepEqual_sparseArray':
case 'notDeepEqual_sparseArray':
actual = new Array(len);
for (let i = 0; i < len; i += 2) {
actual[i] = i;
}
expected = actual.slice(0);
if (method.includes('not')) {
expected[len - 2] += 1;
run(strict ? notDeepStrictEqual : notDeepEqual, n, actual, expected);
} else {
run(strict ? deepStrictEqual : deepEqual, n, actual, expected);
}
break;
case 'deepEqual_Array':
run(strict ? deepStrictEqual : deepEqual, n, actual, expected);
break;
case 'notDeepEqual_Array':
run(strict ? notDeepStrictEqual : notDeepEqual, n, actual, expected);
break;
case 'deepEqual_Set':
run(strict ? deepStrictEqual : deepEqual,
n, new Set(actual), new Set(expected));
break;
case 'notDeepEqual_Set':
run(strict ? notDeepStrictEqual : notDeepEqual,
n, new Set(actual), new Set(expected));
break;
default:
throw new Error(`Unsupported method "${method}"`);
}
}

View File

@ -0,0 +1,51 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
type: [
'Int8Array',
'Uint8Array',
'Float32Array',
'Uint32Array',
],
n: [25000],
strict: [0, 1],
method: [
'deepEqual',
'notDeepEqual',
],
len: [1e2, 5e3],
}, {
combinationFilter(p) {
return p.strict === 1 ||
p.type !== 'Float32Array' ||
p.len === 1e2;
},
});
function main({ type, n, len, method, strict }) {
const clazz = global[type];
const actual = new clazz(len);
const expected = new clazz(len);
if (strict) {
method = method.replace('eep', 'eepStrict');
}
const fn = assert[method];
if (method.includes('not')) {
expected[Math.floor(len / 2)] = 123;
}
bench.start();
for (let i = 0; i < n; ++i) {
actual[0] = i;
expected[0] = i;
const pos = Math.ceil(len / 2) + 1;
actual[pos] = i;
expected[pos] = i;
fn(actual, expected);
}
bench.end(n);
}

28
benchmark/assert/match.js Normal file
View File

@ -0,0 +1,28 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [2e7],
method: ['match', 'doesNotMatch'],
}, {
combinationFilter(p) {
// These benchmarks purposefully do not run by default. They do not provide
// might insight, due to only being a small wrapper around a native regexp
// call.
return p.n === 1;
},
});
function main({ n, method }) {
const fn = assert[method];
const actual = 'Example of string that will match';
const expected = method === 'match' ? /will match/ : /will not match/;
bench.start();
for (let i = 0; i < n; ++i) {
fn(actual, expected);
}
bench.end(n);
}

View File

@ -0,0 +1,170 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [125],
size: [500],
extraProps: [0, 1],
datasetName: [
'objects',
'sets',
'setsWithObjects',
'maps',
'circularRefs',
'typedArrays',
'arrayBuffers',
'dataViewArrayBuffers',
'array',
],
});
function createArray(length, extraProps) {
if (extraProps) {
return Array.from({ length: length * 4 }, (_, i) => i);
}
return Array.from({ length }, (_, i) => i * 4);
}
function createObjects(length, extraProps, depth = 0) {
return Array.from({ length }, (_, i) => ({
foo: 'yarp',
nope: {
bar: '123',
...(extraProps ? { a: [1, 2, i] } : {}),
c: {},
b: !depth ? createObjects(2, extraProps, depth + 1) : [],
},
}));
}
function createSetsWithObjects(length, extraProps, depth = 0) {
return Array.from({ length }, (_, i) => new Set([
...(extraProps ? [{}] : []),
{
simple: 'object',
number: i,
},
['array', 'with', 'values'],
new Set([[], {}, { nested: i }]),
]));
}
function createSets(length, extraProps, depth = 0) {
return Array.from({ length }, (_, i) => new Set([
'yarp',
...(extraProps ? ['123', 1, 2] : []),
i + 3,
null,
{
simple: 'object',
number: i,
},
['array', 'with', 'values'],
!depth ? new Set([1, { nested: i }]) : new Set(),
!depth ? createSets(2, extraProps, depth + 1) : null,
]));
}
function createMaps(length, extraProps, depth = 0) {
return Array.from({ length }, (_, i) => new Map([
...(extraProps ? [['primitiveKey', 'primitiveValue']] : []),
[42, 'numberKey'],
['objectValue', { a: 1, b: i }],
['arrayValue', [1, 2, i]],
['nestedMap', new Map([['a', i], ['b', { deep: true }]])],
[{ objectKey: true }, 'value from object key'],
[[1, i, 3], 'value from array key'],
[!depth ? createMaps(2, extraProps, depth + 1) : null, 'recursive value' + i],
]));
}
function createCircularRefs(length, extraProps) {
return Array.from({ length }, (_, i) => {
const circularSet = new Set();
const circularMap = new Map();
const circularObj = { name: 'circular object' };
circularSet.add('some value' + i);
circularSet.add(circularSet);
circularMap.set('self', circularMap);
circularMap.set('value', 'regular value');
circularObj.self = circularObj;
const objA = { name: 'A' };
const objB = { name: 'B' };
objA.ref = objB;
objB.ref = objA;
circularSet.add(objA);
circularMap.set('objB', objB);
return {
circularSet,
circularMap,
...extraProps ? { extra: i } : {},
circularObj,
objA,
objB,
};
});
}
function createTypedArrays(length, extraParts) {
const extra = extraParts ? [9, 8, 7] : [];
return Array.from({ length }, (_, i) => {
return {
uint8: new Uint8Array(new ArrayBuffer(32), 4, 4),
int16: new Int16Array([1, 2, ...extra, 3]),
uint32: new Uint32Array([i + 1, i + 2, ...extra, i + 3]),
float64: new Float64Array([1.1, 2.2, ...extra, i + 3.3]),
bigUint64: new BigUint64Array([1n, 2n, 3n]),
};
});
}
function createArrayBuffers(length, extra) {
return Array.from({ length }, (_, n) => {
const buffer = Buffer.alloc(n + (extra ? 1 : 0));
for (let i = 0; i < n; i++) {
buffer.writeInt8(i % 128, i);
}
return buffer.buffer;
});
}
function createDataViewArrayBuffers(length, extra) {
return createArrayBuffers(length, extra).map((buffer) => new DataView(buffer));
}
const datasetMappings = {
objects: createObjects,
sets: createSets,
setsWithObjects: createSetsWithObjects,
maps: createMaps,
circularRefs: createCircularRefs,
typedArrays: createTypedArrays,
arrayBuffers: createArrayBuffers,
dataViewArrayBuffers: createDataViewArrayBuffers,
array: createArray,
};
function getDatasets(datasetName, size, extra) {
return {
actual: datasetMappings[datasetName](size, true),
expected: datasetMappings[datasetName](size, !extra),
};
}
function main({ size, n, datasetName, extraProps }) {
const { actual, expected } = getDatasets(datasetName, size, extraProps);
bench.start();
for (let i = 0; i < n; ++i) {
assert.partialDeepStrictEqual(actual, expected);
}
bench.end(n);
}

View File

@ -0,0 +1,34 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [2e5],
method: ['rejects', 'doesNotReject'],
}, {
combinationFilter(p) {
// These benchmarks purposefully do not run by default. They do not provide
// much insight, due to only being a small wrapper around a native promise
// with a few extra checks.
return p.n === 1;
},
});
async function main({ n, method }) {
const fn = assert[method];
const shouldReject = method === 'rejects';
bench.start();
for (let i = 0; i < n; ++i) {
await fn(async () => {
const err = new Error(`assert.${method}`);
if (shouldReject) {
throw err;
} else {
return err;
}
});
}
bench.end(n);
}

View File

@ -0,0 +1,49 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [2e5],
type: ['string', 'object', 'number'],
method: ['strictEqual', 'notStrictEqual'],
}, {
combinationFilter(p) {
// These benchmarks purposefully do not run by default. They do not provide
// much insight, due to only being a small wrapper around `Object.is()`.
return p.n === 1;
},
});
function main({ type, n, method }) {
const fn = assert[method];
let actual, expected;
switch (type) {
case 'string':
actual = expected = 'Hello World';
if (method === 'notStrictEqual') {
expected += 'bar';
}
break;
case 'object':
actual = expected = { a: 'Hello', b: 'World' };
if (method === 'notStrictEqual') {
expected = { a: 'Hello', b: 'World' };
}
break;
case 'number':
actual = expected = 1e9;
if (method === 'notStrictEqual') {
expected += 1;
}
break;
default:
throw new Error('Unexpected type');
}
bench.start();
for (let i = 0; i < n; ++i) {
fn(actual, expected);
}
bench.end(n);
}

View File

@ -0,0 +1,33 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [2e5],
method: ['throws', 'doesNotThrow'],
}, {
combinationFilter(p) {
// These benchmarks purposefully do not run by default. They do not provide
// much insight, due to only being a small wrapper around a try / catch.
return p.n === 1;
},
});
function main({ n, method }) {
const fn = assert[method];
const shouldThrow = method === 'throws';
bench.start();
for (let i = 0; i < n; ++i) {
fn(() => {
const err = new Error(`assert.${method}`);
if (shouldThrow) {
throw err;
} else {
return err;
}
});
}
bench.end(n);
}

View File

@ -0,0 +1,48 @@
'use strict';
const common = require('../common.js');
const { AsyncLocalStorage, AsyncResource } = require('async_hooks');
/**
* This benchmark verifies the performance of
* `AsyncLocalStorage.getStore()` on propagation through async
* resource scopes.
*
* - AsyncLocalStorage.run()
* - AsyncResource.runInAsyncScope
* - AsyncResource.runInAsyncScope
* ...
* - AsyncResource.runInAsyncScope
* - AsyncLocalStorage.getStore()
*/
const bench = common.createBenchmark(main, {
resourceCount: [10, 100, 1000],
n: [5e5],
});
function runBenchmark(store, n) {
for (let i = 0; i < n; i++) {
store.getStore();
}
}
function runInAsyncScopes(resourceCount, cb, i = 0) {
if (i === resourceCount) {
cb();
} else {
const resource = new AsyncResource('noop');
resource.runInAsyncScope(() => {
runInAsyncScopes(resourceCount, cb, i + 1);
});
}
}
function main({ n, resourceCount }) {
const store = new AsyncLocalStorage();
store.run({}, () => {
runInAsyncScopes(resourceCount, () => {
bench.start();
runBenchmark(store, n);
bench.end(n);
});
});
}

View File

@ -0,0 +1,46 @@
'use strict';
const common = require('../common.js');
const { AsyncLocalStorage } = require('async_hooks');
/**
* This benchmark verifies the performance of
* `AsyncLocalStorage.getStore()` on multiple `AsyncLocalStorage` instances
* nested `AsyncLocalStorage.run()`s.
*
* - AsyncLocalStorage1.run()
* - AsyncLocalStorage2.run()
* ...
* - AsyncLocalStorageN.run()
* - AsyncLocalStorage1.getStore()
*/
const bench = common.createBenchmark(main, {
storageCount: [1, 10, 100],
n: [1e4],
});
function runBenchmark(store, n) {
for (let idx = 0; idx < n; idx++) {
store.getStore();
}
}
function runStores(stores, value, cb, idx = 0) {
if (idx === stores.length) {
cb();
} else {
stores[idx].run(value, () => {
runStores(stores, value, cb, idx + 1);
});
}
}
function main({ n, storageCount }) {
const stores = new Array(storageCount).fill(0).map(() => new AsyncLocalStorage());
const contextValue = {};
runStores(stores, contextValue, () => {
bench.start();
runBenchmark(stores[0], n);
bench.end(n);
});
}

View File

@ -0,0 +1,46 @@
'use strict';
const common = require('../common.js');
const { AsyncLocalStorage, AsyncResource } = require('async_hooks');
/**
* This benchmark verifies the performance degradation of
* async resource propagation on the increasing number of
* active `AsyncLocalStorage`s.
*
* - AsyncLocalStorage.run() * storageCount
* - new AsyncResource()
* - new AsyncResource()
* ...
* - N new Asyncresource()
*/
const bench = common.createBenchmark(main, {
storageCount: [0, 1, 10, 100],
n: [1e3],
});
function runStores(stores, value, cb, idx = 0) {
if (idx === stores.length) {
cb();
} else {
stores[idx].run(value, () => {
runStores(stores, value, cb, idx + 1);
});
}
}
function runBenchmark(n) {
for (let i = 0; i < n; i++) {
new AsyncResource('noop');
}
}
function main({ n, storageCount }) {
const stores = new Array(storageCount).fill(0).map(() => new AsyncLocalStorage());
const contextValue = {};
runStores(stores, contextValue, () => {
bench.start();
runBenchmark(n);
bench.end(n);
});
}

View File

@ -0,0 +1,48 @@
'use strict';
const common = require('../common.js');
const { AsyncLocalStorage } = require('async_hooks');
/**
* This benchmark verifies the performance degradation of
* async resource propagation on the increasing number of
* active `AsyncLocalStorage`s.
*
* - AsyncLocalStorage.run()
* - Promise
* - Promise
* ...
* - Promise
*/
const bench = common.createBenchmark(main, {
storageCount: [0, 1, 10, 100],
n: [1e5],
});
function runStores(stores, value, cb, idx = 0) {
if (idx === stores.length) {
cb();
} else {
stores[idx].run(value, () => {
runStores(stores, value, cb, idx + 1);
});
}
}
async function runBenchmark(n) {
for (let i = 0; i < n; i++) {
// Avoid creating additional ticks.
await undefined;
}
}
function main({ n, storageCount }) {
const stores = new Array(storageCount).fill(0).map(() => new AsyncLocalStorage());
const contextValue = {};
runStores(stores, contextValue, () => {
bench.start();
runBenchmark(n).then(() => {
bench.end(n);
});
});
}

View File

@ -0,0 +1,21 @@
'use strict';
const common = require('../common.js');
const { AsyncLocalStorage } = require('async_hooks');
const bench = common.createBenchmark(main, {
n: [1e7],
});
async function run(store, n) {
for (let i = 0; i < n; i++) {
await new Promise((resolve) => store.run(i, resolve));
}
}
function main({ n }) {
const store = new AsyncLocalStorage();
bench.start();
run(store, n).then(() => {
bench.end(n);
});
}

View File

@ -0,0 +1,186 @@
'use strict';
const { promisify } = require('util');
const { readFile } = require('fs');
const sleep = promisify(setTimeout);
const read = promisify(readFile);
const common = require('../common.js');
const {
createHook,
executionAsyncResource,
executionAsyncId,
AsyncLocalStorage,
} = require('async_hooks');
const { createServer } = require('http');
const bench = common.createBenchmark(main, {
type: ['async-resource', 'destroy', 'async-local-storage'],
asyncMethod: ['callbacks', 'async'],
path: '/',
connections: 500,
duration: 5,
n: [1e6],
});
function buildCurrentResource(getServe) {
const server = createServer(getServe(getCLS, setCLS));
const hook = createHook({ init });
const cls = Symbol('cls');
hook.enable();
return {
server,
close,
};
function getCLS() {
const resource = executionAsyncResource();
if (!resource[cls]) {
return null;
}
return resource[cls].state;
}
function setCLS(state) {
const resource = executionAsyncResource();
if (!resource[cls]) {
resource[cls] = { state };
} else {
resource[cls].state = state;
}
}
function init(asyncId, type, triggerAsyncId, resource) {
const cr = executionAsyncResource();
if (cr !== null) {
resource[cls] = cr[cls];
}
}
function close() {
hook.disable();
server.close();
}
}
function buildDestroy(getServe) {
const transactions = new Map();
const server = createServer(getServe(getCLS, setCLS));
const hook = createHook({ init, destroy });
hook.enable();
return {
server,
close,
};
function getCLS() {
const asyncId = executionAsyncId();
return transactions.has(asyncId) ? transactions.get(asyncId) : null;
}
function setCLS(value) {
const asyncId = executionAsyncId();
transactions.set(asyncId, value);
}
function init(asyncId, type, triggerAsyncId, resource) {
transactions.set(asyncId, getCLS());
}
function destroy(asyncId) {
transactions.delete(asyncId);
}
function close() {
hook.disable();
server.close();
}
}
function buildAsyncLocalStorage(getServe) {
const asyncLocalStorage = new AsyncLocalStorage();
const server = createServer((req, res) => {
asyncLocalStorage.run({}, () => {
getServe(getCLS, setCLS)(req, res);
});
});
return {
server,
close,
};
function getCLS() {
const store = asyncLocalStorage.getStore();
if (store === undefined) {
return null;
}
return store.state;
}
function setCLS(state) {
const store = asyncLocalStorage.getStore();
if (store === undefined) {
return;
}
store.state = state;
}
function close() {
asyncLocalStorage.disable();
server.close();
}
}
function getServeAwait(getCLS, setCLS) {
return async function serve(req, res) {
setCLS(Math.random());
await sleep(10);
await read(__filename);
if (res.destroyed) return;
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({ cls: getCLS() }));
};
}
function getServeCallbacks(getCLS, setCLS) {
return function serve(req, res) {
setCLS(Math.random());
setTimeout(() => {
readFile(__filename, () => {
if (res.destroyed) return;
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({ cls: getCLS() }));
});
}, 10);
};
}
const types = {
'async-resource': buildCurrentResource,
'destroy': buildDestroy,
'async-local-storage': buildAsyncLocalStorage,
};
const asyncMethods = {
'callbacks': getServeCallbacks,
'async': getServeAwait,
};
function main({ type, asyncMethod, connections, duration, path }) {
const { server, close } = types[type](asyncMethods[asyncMethod]);
server
.listen(common.PORT)
.on('listening', () => {
bench.http({
path,
connections,
duration,
}, () => {
close();
});
});
}

View File

@ -0,0 +1,52 @@
'use strict';
const common = require('../common.js');
const { createHook, AsyncResource } = require('async_hooks');
const bench = common.createBenchmark(main, {
n: [1e6],
method: [
'trackingEnabled',
'trackingEnabledWithDestroyHook',
'trackingDisabled',
],
}, {
flags: ['--expose-gc'],
});
function endAfterGC(n) {
setImmediate(() => {
global.gc();
setImmediate(() => {
bench.end(n);
});
});
}
function main({ n, method }) {
switch (method) {
case 'trackingEnabled':
bench.start();
for (let i = 0; i < n; i++) {
new AsyncResource('foobar');
}
endAfterGC(n);
break;
case 'trackingEnabledWithDestroyHook':
createHook({ destroy: () => {} }).enable();
bench.start();
for (let i = 0; i < n; i++) {
new AsyncResource('foobar');
}
endAfterGC(n);
break;
case 'trackingDisabled':
bench.start();
for (let i = 0; i < n; i++) {
new AsyncResource('foobar', { requireManualDestroy: true });
}
endAfterGC(n);
break;
default:
throw new Error(`Unsupported method "${method}"`);
}
}

View File

@ -0,0 +1,42 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
asyncHooks: ['init', 'before', 'after', 'all', 'disabled', 'none'],
connections: [50, 500],
duration: 5,
});
function main({ asyncHooks, connections, duration }) {
if (asyncHooks !== 'none') {
let hooks = {
init() {},
before() {},
after() {},
destroy() {},
promiseResolve() {},
};
if (asyncHooks !== 'all' || asyncHooks !== 'disabled') {
hooks = {
[asyncHooks]: () => {},
};
}
const hook = require('async_hooks').createHook(hooks);
if (asyncHooks !== 'disabled') {
hook.enable();
}
}
const server = require('../fixtures/simple-http-server.js')
.listen(common.PORT)
.on('listening', () => {
const path = '/buffer/4/4/normal/1';
bench.http({
connections,
path,
duration,
}, () => {
server.close();
});
});
}

View File

@ -0,0 +1,56 @@
'use strict';
const common = require('../common.js');
const { createHook } = require('async_hooks');
let hook;
const tests = {
disabled() {
hook = createHook({
promiseResolve() {},
});
},
enabled() {
hook = createHook({
promiseResolve() {},
}).enable();
},
enabledWithDestroy() {
hook = createHook({
promiseResolve() {},
destroy() {},
}).enable();
},
enabledWithInitOnly() {
hook = createHook({
init() {},
}).enable();
},
};
const bench = common.createBenchmark(main, {
n: [1e6],
asyncHooks: [
'enabled',
'enabledWithDestroy',
'enabledWithInitOnly',
'disabled',
],
});
const err = new Error('foobar');
async function run(n) {
for (let i = 0; i < n; i++) {
await new Promise((resolve) => resolve())
.then(() => { throw err; })
.catch((e) => e);
}
}
function main({ n, asyncHooks }) {
if (hook) hook.disable();
tests[asyncHooks]();
bench.start();
run(n).then(() => {
bench.end(n);
});
}

36
benchmark/bar.R Normal file
View File

@ -0,0 +1,36 @@
#!/usr/bin/env Rscript
library(ggplot2);
library(plyr);
# get __dirname and load ./_cli.R
args = commandArgs(trailingOnly = F);
dirname = dirname(sub("--file=", "", args[grep("--file", args)]));
source(paste0(dirname, '/_cli.R'), chdir=T);
if (!is.null(args.options$help) ||
(!is.null(args.options$plot) && args.options$plot == TRUE)) {
stop("usage: cat file.csv | Rscript bar.R
--help show this message
--plot filename save plot to filename");
}
plot.filename = args.options$plot;
dat = read.csv(
file('stdin'),
colClasses=c('character', 'character', 'character', 'numeric', 'numeric')
);
dat = data.frame(dat);
dat$nameTwoLines = paste0(dat$filename, '\n', dat$configuration);
dat$name = paste0(dat$filename, ' ', dat$configuration);
# Create a box plot
if (!is.null(plot.filename)) {
p = ggplot(data=dat, aes(x=nameTwoLines, y=rate, fill=binary));
p = p + geom_bar(stat="summary", position=position_dodge());
p = p + ylab("rate of operations (higher is better)");
p = p + xlab("benchmark");
p = p + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5));
ggsave(plot.filename, p);
}

30
benchmark/blob/blob.js Normal file
View File

@ -0,0 +1,30 @@
'use strict';
const common = require('../common.js');
const { Blob } = require('buffer');
const bench = common.createBenchmark(main, {
bytes: [128, 1024, 8192],
n: [1e3],
operation: ['text', 'arrayBuffer'],
});
async function run(n, bytes, operation) {
const buff = Buffer.allocUnsafe(bytes);
const source = new Blob(buff);
bench.start();
for (let i = 0; i < n; i++) {
switch (operation) {
case 'text':
await source.text();
break;
case 'arrayBuffer':
await source.arrayBuffer();
break;
}
}
bench.end(n);
}
function main(conf) {
run(conf.n, conf.bytes, conf.operation).catch(console.log);
}

25
benchmark/blob/clone.js Normal file
View File

@ -0,0 +1,25 @@
'use strict';
const common = require('../common.js');
const {
Blob,
} = require('node:buffer');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [50e3],
bytes: [128, 1024, 1024 ** 2],
});
let _cloneResult;
function main({ n, bytes }) {
const buff = Buffer.allocUnsafe(bytes);
const blob = new Blob(buff);
bench.start();
for (let i = 0; i < n; ++i)
_cloneResult = structuredClone(blob);
bench.end(n);
// Avoid V8 deadcode (elimination)
assert.ok(_cloneResult);
}

View File

@ -0,0 +1,48 @@
'use strict';
const common = require('../common.js');
const {
Blob,
} = require('node:buffer');
const assert = require('assert');
const options = {
flags: ['--expose-internals'],
};
const bench = common.createBenchmark(main, {
n: [50e3],
kind: [
'Blob',
'createBlob',
],
}, options);
let _blob;
let _createBlob;
function main({ n, kind }) {
switch (kind) {
case 'Blob': {
bench.start();
for (let i = 0; i < n; ++i)
_blob = new Blob(['hello']);
bench.end(n);
// Avoid V8 deadcode (elimination)
assert.ok(_blob);
break;
} case 'createBlob': {
const { createBlob } = require('internal/blob');
const reusableBlob = new Blob(['hello']);
bench.start();
for (let i = 0; i < n; ++i)
_createBlob = createBlob(reusableBlob, reusableBlob.size);
bench.end(n);
// Avoid V8 deadcode (elimination)
assert.ok(_createBlob);
break;
} default:
throw new Error('Invalid kind');
}
}

34
benchmark/blob/file.js Normal file
View File

@ -0,0 +1,34 @@
'use strict';
const common = require('../common.js');
const { File } = require('buffer');
const bench = common.createBenchmark(main, {
bytes: [128, 1024],
n: [1e3],
operation: ['text', 'arrayBuffer'],
});
const options = {
lastModified: Date.now() - 1e6,
};
async function run(n, bytes, operation) {
const buff = Buffer.allocUnsafe(bytes);
const source = new File(buff, 'dummy.txt', options);
bench.start();
for (let i = 0; i < n; i++) {
switch (operation) {
case 'text':
await source.text();
break;
case 'arrayBuffer':
await source.arrayBuffer();
break;
}
}
bench.end(n);
}
function main(conf) {
run(conf.n, conf.bytes, conf.operation).catch(console.log);
}

View File

@ -0,0 +1,22 @@
'use strict';
const common = require('../common.js');
const { Blob, resolveObjectURL } = require('node:buffer');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [50e3],
});
let _resolveObjectURL;
function main({ n }) {
const blob = new Blob(['hello']);
const id = URL.createObjectURL(blob);
bench.start();
for (let i = 0; i < n; ++i)
_resolveObjectURL = resolveObjectURL(id);
bench.end(n);
// Avoid V8 deadcode (elimination)
assert.ok(_resolveObjectURL);
}

27
benchmark/blob/slice.js Normal file
View File

@ -0,0 +1,27 @@
'use strict';
const common = require('../common.js');
const { Blob } = require('node:buffer');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [50e3],
dataSize: [
5,
30 * 1024,
],
});
let _sliceResult;
function main({ n, dataSize }) {
const data = 'a'.repeat(dataSize);
const reusableBlob = new Blob([data]);
bench.start();
for (let i = 0; i < n; ++i)
_sliceResult = reusableBlob.slice(0, Math.floor(data.length / 2));
bench.end(n);
// Avoid V8 deadcode (elimination)
assert.ok(_sliceResult);
}

View File

@ -0,0 +1,20 @@
'use strict';
const common = require('../common.js');
const assert = require('node:assert');
const bench = common.createBenchmark(main, {
size: [16, 32, 64, 128],
n: [1e6],
});
function main({ n, size }) {
const input = btoa('A'.repeat(size));
let out = 0;
bench.start();
for (let i = 0; i < n; i++) {
out += atob(input).length;
}
bench.end(n);
assert.ok(out > 0);
}

View File

@ -0,0 +1,25 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
charsPerLine: [76],
linesCount: [8 << 16],
n: [32],
});
function main({ charsPerLine, linesCount, n }) {
const bytesCount = charsPerLine * linesCount / 4 * 3;
const line = `${'abcd'.repeat(charsPerLine / 4)}\n`;
const data = line.repeat(linesCount);
// eslint-disable-next-line node-core/no-unescaped-regexp-dot
data.match(/./); // Flatten the string
const buffer = Buffer.alloc(bytesCount, line, 'base64');
bench.start();
for (let i = 0; i < n; i++) {
buffer.base64Write(data, 0, bytesCount);
}
bench.end(n);
}

View File

@ -0,0 +1,29 @@
'use strict';
const assert = require('assert');
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [32],
size: [8 << 20],
});
function main({ n, size }) {
const s = 'abcd'.repeat(size);
const encodedSize = s.length * 3 / 4;
// eslint-disable-next-line node-core/no-unescaped-regexp-dot
s.match(/./); // Flatten string.
assert.strictEqual(s.length % 4, 0);
const b = Buffer.allocUnsafe(encodedSize);
b.write(s, 0, encodedSize, 'base64');
let tmp;
bench.start();
for (let i = 0; i < n; i += 1)
tmp = b.base64Write(s, 0, s.length);
bench.end(n);
assert.strictEqual(tmp, encodedSize);
}

View File

@ -0,0 +1,50 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
len: [64 * 1024 * 1024],
n: [32],
}, {
test: { len: 256 },
});
function main({ n, len }) {
const b = Buffer.allocUnsafe(len);
let s = '';
let i;
for (i = 0; i < 256; ++i) s += String.fromCharCode(i);
for (i = 0; i < len; i += 256) b.write(s, i, 256, 'ascii');
let tmp;
bench.start();
for (i = 0; i < n; ++i)
tmp = b.toString('base64');
bench.end(n);
assert.strictEqual(typeof tmp, 'string');
}

View File

@ -0,0 +1,29 @@
'use strict';
const assert = require('assert');
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [32],
size: [8 << 20],
});
function main({ n, size }) {
const s = 'abcd'.repeat(size);
const encodedSize = s.length * 3 / 4;
// eslint-disable-next-line node-core/no-unescaped-regexp-dot
s.match(/./); // Flatten string.
assert.strictEqual(s.length % 4, 0);
const b = Buffer.allocUnsafe(encodedSize);
b.write(s, 0, encodedSize, 'base64url');
let tmp;
bench.start();
for (let i = 0; i < n; i += 1)
tmp = b.base64Write(s, 0, s.length);
bench.end(n);
assert.strictEqual(tmp, encodedSize);
}

View File

@ -0,0 +1,29 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
len: [64 * 1024 * 1024],
n: [32],
}, {
test: { len: 256 },
});
function main({ n, len }) {
const b = Buffer.allocUnsafe(len);
let s = '';
let i;
for (i = 0; i < 256; ++i) s += String.fromCharCode(i);
for (i = 0; i < len; i += 256) b.write(s, i, 256, 'ascii');
let tmp;
bench.start();
for (i = 0; i < n; ++i)
tmp = b.toString('base64url');
bench.end(n);
assert.strictEqual(typeof tmp, 'string');
}

View File

@ -0,0 +1,20 @@
'use strict';
const common = require('../common.js');
const assert = require('node:assert');
const bench = common.createBenchmark(main, {
size: [16, 32, 64, 128, 256, 1024],
n: [1e6],
});
function main({ n, size }) {
const input = 'A'.repeat(size);
let out = 0;
bench.start();
for (let i = 0; i < n; i++) {
out += btoa(input).length;
}
bench.end(n);
assert.ok(out > 0);
}

View File

@ -0,0 +1,22 @@
'use strict';
const common = require('../common');
const bench = common.createBenchmark(main, {
len: [2, 16, 256], // x16
n: [4e6],
});
function main({ n, len }) {
const data = Buffer.alloc(len * 16, 'a');
const expected = Buffer.byteLength(data, 'buffer');
let changed = false;
bench.start();
for (let i = 0; i < n; i++) {
const actual = Buffer.byteLength(data, 'buffer');
if (expected !== actual) { changed = true; }
}
bench.end(n);
if (changed) {
throw new Error('Result changed during iteration');
}
}

View File

@ -0,0 +1,43 @@
'use strict';
const common = require('../common');
const bench = common.createBenchmark(main, {
type: ['one_byte', 'two_bytes', 'three_bytes',
'four_bytes', 'latin1'],
encoding: ['utf8', 'base64'],
repeat: [1, 2, 16, 256], // x16
n: [4e6],
});
// 16 chars each
const chars = {
one_byte: 'hello brendan!!!',
two_bytes: 'ΰαβγδεζηθικλμνξο',
three_bytes: '挰挱挲挳挴挵挶挷挸挹挺挻挼挽挾挿',
four_bytes: '𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢',
latin1: 'Un homme sage est supérieur à toutes ' +
'les insultes qui peuvent lui être adressées, et la meilleure réponse est la patience et la modération.',
};
function getInput(type, repeat, encoding) {
const original = (repeat === 1) ? chars[type] : chars[type].repeat(repeat);
if (encoding === 'base64') {
Buffer.from(original, 'utf8').toString('base64');
}
return original;
}
function main({ n, repeat, encoding, type }) {
const data = getInput(type, repeat, encoding);
const expected = Buffer.byteLength(data, encoding);
let changed = false;
bench.start();
for (let i = 0; i < n; i++) {
const actual = Buffer.byteLength(data, encoding);
if (expected !== actual) { changed = true; }
}
bench.end(n);
if (changed) {
throw new Error('Result changed during iteration');
}
}

View File

@ -0,0 +1,59 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
size: [16, 512, 4096, 16386],
args: [1, 2, 5],
n: [1e6],
});
function main({ n, size, args }) {
const b0 = Buffer.alloc(size, 'a');
const b1 = Buffer.alloc(size, 'a');
const b0Len = b0.length;
const b1Len = b1.length;
b1[size - 1] = 'b'.charCodeAt(0);
switch (args) {
case 2:
b0.compare(b1, 0);
bench.start();
for (let i = 0; i < n; i++) {
b0.compare(b1, 0);
}
bench.end(n);
break;
case 3:
b0.compare(b1, 0, b1Len);
bench.start();
for (let i = 0; i < n; i++) {
b0.compare(b1, 0, b1Len);
}
bench.end(n);
break;
case 4:
b0.compare(b1, 0, b1Len, 0);
bench.start();
for (let i = 0; i < n; i++) {
b0.compare(b1, 0, b1Len, 0);
}
bench.end(n);
break;
case 5:
b0.compare(b1, 0, b1Len, 0, b0Len);
bench.start();
for (let i = 0; i < n; i++) {
b0.compare(b1, 0, b1Len, 0, b0Len);
}
bench.end(n);
break;
default:
b0.compare(b1);
bench.start();
for (let i = 0; i < n; i++) {
b0.compare(b1);
}
bench.end(n);
}
}

View File

@ -0,0 +1,28 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
method: ['offset', 'slice'],
size: [16, 512, 4096, 16386],
n: [1e6],
});
function compareUsingSlice(b0, b1, len, iter) {
for (let i = 0; i < iter; i++)
Buffer.compare(b0.slice(1, len), b1.slice(1, len));
}
function compareUsingOffset(b0, b1, len, iter) {
for (let i = 0; i < iter; i++)
b0.compare(b1, 1, len, 1, len);
}
function main({ n, size, method }) {
const fn = method === 'slice' ? compareUsingSlice : compareUsingOffset;
bench.start();
fn(Buffer.alloc(size, 'a'),
Buffer.alloc(size, 'b'),
size >> 1,
n);
bench.end(n);
}

View File

@ -0,0 +1,41 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
size: [16, 512, 4096, 16386],
n: [1e6],
});
function main({ n, size }) {
const b0 = Buffer.alloc(size, 'a');
const b1 = Buffer.alloc(size, 'a');
b1[size - 1] = 'b'.charCodeAt(0);
bench.start();
for (let i = 0; i < n; i++) {
Buffer.compare(b0, b1);
}
bench.end(n);
}

View File

@ -0,0 +1,23 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
extraSize: [1, 256, 4 * 256],
n: [8e5],
});
function main({ n, extraSize }) {
const pieces = 4;
const pieceSize = 256;
const list = Array.from({ length: pieces })
.fill(Buffer.allocUnsafe(pieceSize));
const totalLength = (pieces * pieceSize) + extraSize;
bench.start();
for (let i = 0; i < n; i++) {
Buffer.concat(list, totalLength);
}
bench.end(n);
}

View File

@ -0,0 +1,22 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
pieces: [4, 16],
pieceSize: [1, 16, 256],
withTotalLength: [0, 1],
n: [8e5],
});
function main({ n, pieces, pieceSize, withTotalLength }) {
const list = Array.from({ length: pieces })
.fill(Buffer.allocUnsafe(pieceSize));
const totalLength = withTotalLength ? pieces * pieceSize : undefined;
bench.start();
for (let i = 0; i < n; i++) {
Buffer.concat(list, totalLength);
}
bench.end(n);
}

View File

@ -0,0 +1,19 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
bytes: [8, 128, 1024],
partial: ['true', 'false'],
n: [6e6],
});
function main({ n, bytes, partial }) {
const source = Buffer.allocUnsafe(bytes);
const target = Buffer.allocUnsafe(bytes);
const sourceStart = (partial === 'true' ? Math.floor(bytes / 2) : 0);
bench.start();
for (let i = 0; i < n; i++) {
source.copy(target, 0, sourceStart);
}
bench.end(n);
}

View File

@ -0,0 +1,44 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
type: [
'fast-alloc',
'fast-alloc-fill',
'fast-allocUnsafe',
'slow-allocUnsafe',
],
len: [10, 1024, 4096, 8192],
n: [6e5],
});
function main({ len, n, type }) {
let fn, i;
switch (type) {
case 'fast-alloc':
fn = Buffer.alloc;
break;
case 'fast-alloc-fill':
bench.start();
for (i = 0; i < n; i++) {
Buffer.alloc(len, 0);
}
bench.end(n);
return;
case 'fast-allocUnsafe':
fn = Buffer.allocUnsafe;
break;
case 'slow-allocUnsafe':
fn = Buffer.allocUnsafeSlow;
break;
default:
assert.fail('Should not get here');
}
bench.start();
for (i = 0; i < n; i++) {
fn(len);
}
bench.end(n);
}

View File

@ -0,0 +1,22 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
size: [0, 512, 16386],
difflen: ['true', 'false'],
n: [1e6],
});
function main({ n, size, difflen }) {
const b0 = Buffer.alloc(size, 'a');
const b1 = Buffer.alloc(size + (difflen === 'true' ? 1 : 0), 'a');
if (b1.length > 0)
b1[b1.length - 1] = 'b'.charCodeAt(0);
bench.start();
for (let i = 0; i < n; i++) {
b0.equals(b1);
}
bench.end(n);
}

View File

@ -0,0 +1,31 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
type: [
'fill(0)',
'fill("")',
'fill(100)',
'fill(400)',
'fill("t")',
'fill("test")',
'fill("t", "utf8")',
'fill("t", 0, "utf8")',
'fill("t", 0)',
'fill(Buffer.alloc(1), 0)',
],
size: [2 ** 13, 2 ** 16],
n: [2e4],
});
function main({ n, type, size }) {
const buffer = Buffer.allocUnsafe(size);
const testFunction = new Function('b', `
for (var i = 0; i < ${n}; i++) {
b.${type};
}
`);
bench.start();
testFunction(buffer);
bench.end(n);
}

View File

@ -0,0 +1,121 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
source: [
'array',
'arraybuffer',
'arraybuffer-middle',
'buffer',
'string',
'string-utf8',
'string-base64',
'object',
'uint8array',
'uint16array',
],
len: [100, 2048],
n: [8e5],
});
function main({ len, n, source }) {
let i = 0;
switch (source) {
case 'array': {
const array = new Array(len).fill(42);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(array);
}
bench.end(n);
break;
}
case 'arraybuffer': {
const arrayBuf = new ArrayBuffer(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(arrayBuf);
}
bench.end(n);
break;
}
case 'arraybuffer-middle': {
const arrayBuf = new ArrayBuffer(len);
const offset = ~~(len / 4);
const length = ~~(len / 2);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(arrayBuf, offset, length);
}
bench.end(n);
break;
}
case 'buffer': {
const buffer = Buffer.allocUnsafe(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(buffer);
}
bench.end(n);
break;
}
case 'uint8array': {
const uint8array = new Uint8Array(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(uint8array);
}
bench.end(n);
break;
}
case 'uint16array': {
const uint16array = new Uint16Array(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(uint16array);
}
bench.end(n);
break;
}
case 'string': {
const str = 'a'.repeat(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(str);
}
bench.end(n);
break;
}
case 'string-utf8': {
const str = 'a'.repeat(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(str, 'utf8');
}
bench.end(n);
break;
}
case 'string-base64': {
const str = 'a'.repeat(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(str, 'base64');
}
bench.end(n);
break;
}
case 'object': {
const obj = { length: null }; // Results in a new, empty Buffer
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(obj);
}
bench.end(n);
break;
}
default:
assert.fail('Should not get here');
}
}

View File

@ -0,0 +1,29 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
len: [64, 1024],
n: [1e6],
});
function main({ len, n }) {
const buf = Buffer.alloc(len);
for (let i = 0; i < buf.length; i++)
buf[i] = i & 0xff;
const plain = buf;
bench.start();
let tmp;
for (let i = 0; i < n; i += 1)
tmp = plain.toString('hex');
bench.end(n);
assert.strictEqual(typeof tmp, 'string');
}

View File

@ -0,0 +1,28 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
len: [64, 1024],
n: [1e6],
});
function main({ len, n }) {
const buf = Buffer.alloc(len);
for (let i = 0; i < buf.length; i++)
buf[i] = i & 0xff;
const hex = buf.toString('hex');
let tmp;
bench.start();
for (let i = 0; i < n; i += 1)
tmp = Buffer.from(hex, 'hex');
bench.end(n);
assert.strictEqual(typeof tmp, 'object');
}

View File

@ -0,0 +1,23 @@
'use strict';
const common = require('../common.js');
const fs = require('fs');
const path = require('path');
const bench = common.createBenchmark(main, {
value: ['@'.charCodeAt(0)],
n: [1e6],
});
function main({ n, value }) {
const aliceBuffer = fs.readFileSync(
path.resolve(__dirname, '../fixtures/alice.html'),
);
let count = 0;
bench.start();
for (let i = 0; i < n; i++) {
count += aliceBuffer.indexOf(value, 0, undefined);
}
bench.end(n);
return count;
}

View File

@ -0,0 +1,54 @@
'use strict';
const common = require('../common.js');
const fs = require('fs');
const path = require('path');
const searchStrings = [
'@',
'SQ',
'--l',
'Alice',
'Gryphon',
'Ou est ma chatte?',
'found it very',
'neighbouring pool',
'aaaaaaaaaaaaaaaaa',
'venture to go near the house till she had brought herself down to',
'</i> to the Caterpillar',
];
const bench = common.createBenchmark(main, {
search: searchStrings,
encoding: ['undefined', 'utf8', 'ucs2'],
type: ['buffer', 'string'],
n: [5e4],
}, {
combinationFilter: (p) => {
return (p.type === 'buffer' && p.encoding === 'undefined') ||
(p.type !== 'buffer' && p.encoding !== 'undefined');
},
});
function main({ n, search, encoding, type }) {
let aliceBuffer = fs.readFileSync(
path.resolve(__dirname, '../fixtures/alice.html'),
);
if (encoding === 'undefined') {
encoding = undefined;
}
if (encoding === 'ucs2') {
aliceBuffer = Buffer.from(aliceBuffer.toString(), encoding);
}
if (type === 'buffer') {
search = Buffer.from(Buffer.from(search).toString(), encoding);
}
bench.start();
for (let i = 0; i < n; i++) {
aliceBuffer.indexOf(search, 0, encoding);
}
bench.end(n);
}

View File

@ -0,0 +1,23 @@
'use strict';
const common = require('../common.js');
const buffer = require('node:buffer');
const assert = require('node:assert');
const bench = common.createBenchmark(main, {
n: [2e7],
length: ['short', 'long'],
input: ['hello world'],
});
function main({ n, input }) {
const normalizedInput = input === 'short' ? input : input.repeat(200);
const encoder = new TextEncoder();
const buff = encoder.encode(normalizedInput);
bench.start();
for (let i = 0; i < n; ++i) {
assert.ok(buffer.isAscii(buff));
}
bench.end(n);
}

View File

@ -0,0 +1,23 @@
'use strict';
const common = require('../common.js');
const buffer = require('node:buffer');
const assert = require('node:assert');
const bench = common.createBenchmark(main, {
n: [2e7],
length: ['short', 'long'],
input: ['regular string', '∀x∈: ⌈x⌉ = x⌋'],
});
function main({ n, input, length }) {
const normalizedInput = length === 'short' ? input : input.repeat(300);
const encoder = new TextEncoder();
const buff = encoder.encode(normalizedInput);
bench.start();
for (let i = 0; i < n; ++i) {
assert.ok(buffer.isUtf8(buff));
}
bench.end(n);
}

View File

@ -0,0 +1,58 @@
'use strict';
const SlowBuffer = require('buffer').SlowBuffer;
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
size: [512, 4096, 16386],
type: ['fast'],
method: ['for', 'forOf', 'iterator'],
n: [1e3],
});
const methods = {
'for': benchFor,
'forOf': benchForOf,
'iterator': benchIterator,
};
function main({ size, type, method, n }) {
const buffer = type === 'fast' ?
Buffer.alloc(size) :
SlowBuffer(size).fill(0);
const fn = methods[method];
bench.start();
fn(buffer, n);
bench.end(n);
}
function benchFor(buffer, n) {
for (let k = 0; k < n; k++) {
for (let i = 0; i < buffer.length; i++) {
assert.strictEqual(buffer[i], 0);
}
}
}
function benchForOf(buffer, n) {
for (let k = 0; k < n; k++) {
for (const b of buffer) {
assert.strictEqual(b, 0);
}
}
}
function benchIterator(buffer, n) {
for (let k = 0; k < n; k++) {
const iter = buffer[Symbol.iterator]();
let cur = iter.next();
while (!cur.done) {
assert.strictEqual(cur.value, 0);
cur = iter.next();
}
}
}

View File

@ -0,0 +1,38 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
encoding: [
'ascii',
'base64',
'BASE64',
'binary',
'hex',
'HEX',
'latin1',
'LATIN1',
'UCS-2',
'UCS2',
'utf-16le',
'UTF-16LE',
'utf-8',
'utf16le',
'UTF16LE',
'utf8',
'UTF8',
],
n: [1e6],
}, {
flags: ['--expose-internals'],
});
function main({ encoding, n }) {
const { normalizeEncoding } = require('internal/util');
bench.start();
for (let i = 0; i < n; i++) {
normalizeEncoding(encoding);
}
bench.end(n);
}

View File

@ -0,0 +1,38 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
type: ['Double', 'Float'],
endian: ['LE'],
value: ['zero', 'big', 'small', 'inf', 'nan'],
n: [1e6],
});
function main({ n, type, endian, value }) {
const buff = Buffer.alloc(8);
const fn = `read${type}${endian}`;
const values = {
Double: {
zero: 0,
big: 2 ** 1023,
small: 2 ** -1074,
inf: Infinity,
nan: NaN,
},
Float: {
zero: 0,
big: 2 ** 127,
small: 2 ** -149,
inf: Infinity,
nan: NaN,
},
};
buff[`write${type}${endian}`](values[type][value], 0);
bench.start();
for (let i = 0; i !== n; i++) {
buff[fn](0);
}
bench.end(n);
}

View File

@ -0,0 +1,30 @@
'use strict';
const common = require('../common.js');
const types = [
'IntBE',
'IntLE',
'UIntBE',
'UIntLE',
];
const bench = common.createBenchmark(main, {
buffer: ['fast'],
type: types,
n: [1e6],
byteLength: [1, 2, 3, 4, 5, 6],
});
function main({ n, buf, type, byteLength }) {
const buff = buf === 'fast' ?
Buffer.alloc(8) :
require('buffer').SlowBuffer(8);
const fn = `read${type}`;
buff.writeDoubleLE(0, 0);
bench.start();
for (let i = 0; i !== n; i++) {
buff[fn](0, byteLength);
}
bench.end(n);
}

View File

@ -0,0 +1,40 @@
'use strict';
const common = require('../common.js');
const types = [
'BigUInt64LE',
'BigUInt64BE',
'BigInt64LE',
'BigInt64BE',
'UInt8',
'UInt16LE',
'UInt16BE',
'UInt32LE',
'UInt32BE',
'Int8',
'Int16LE',
'Int16BE',
'Int32LE',
'Int32BE',
];
const bench = common.createBenchmark(main, {
buffer: ['fast'],
type: types,
n: [1e6],
});
function main({ n, buf, type }) {
const buff = buf === 'fast' ?
Buffer.alloc(8) :
require('buffer').SlowBuffer(8);
const fn = `read${type}`;
buff.writeDoubleLE(0, 0);
bench.start();
for (let i = 0; i !== n; i++) {
buff[fn](0);
}
bench.end(n);
}

View File

@ -0,0 +1,24 @@
'use strict';
const common = require('../common.js');
const SlowBuffer = require('buffer').SlowBuffer;
const bench = common.createBenchmark(main, {
type: ['fast', 'slow', 'subarray'],
n: [1e6],
});
const buf = Buffer.allocUnsafe(1024);
const slowBuf = new SlowBuffer(1024);
function main({ n, type }) {
const b = type === 'slow' ? slowBuf : buf;
const fn = type === 'subarray' ?
() => b.subarray(10, 256) :
() => b.slice(10, 256);
bench.start();
for (let i = 0; i < n; i++) {
fn();
}
bench.end(n);
}

View File

@ -0,0 +1,85 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
aligned: ['true', 'false'],
method: ['swap16', 'swap32', 'swap64'/* , 'htons', 'htonl', 'htonll' */],
len: [64, 256, 768, 1024, 2056, 8192],
n: [1e6],
}, {
test: { len: 16 },
});
// The htons and htonl methods below are used to benchmark the
// performance difference between doing the byteswap in pure
// javascript regardless of Buffer size as opposed to dropping
// down to the native layer for larger Buffer sizes. Commented
// out by default because they are slow for big buffers. If
// re-evaluating the crossover point, uncomment those methods
// and comment out their implementations in lib/buffer.js so
// C++ version will always be used.
function swap(b, n, m) {
const i = b[n];
b[n] = b[m];
b[m] = i;
}
Buffer.prototype.htons = function htons() {
if (this.length % 2 !== 0)
throw new RangeError();
for (let i = 0; i < this.length; i += 2) {
swap(this, i, i + 1);
}
return this;
};
Buffer.prototype.htonl = function htonl() {
if (this.length % 4 !== 0)
throw new RangeError();
for (let i = 0; i < this.length; i += 4) {
swap(this, i, i + 3);
swap(this, i + 1, i + 2);
}
return this;
};
Buffer.prototype.htonll = function htonll() {
if (this.length % 8 !== 0)
throw new RangeError();
for (let i = 0; i < this.length; i += 8) {
swap(this, i, i + 7);
swap(this, i + 1, i + 6);
swap(this, i + 2, i + 5);
swap(this, i + 3, i + 4);
}
return this;
};
function createBuffer(len, aligned) {
len += aligned ? 0 : 1;
const buf = Buffer.allocUnsafe(len);
for (let i = 1; i <= len; i++)
buf[i - 1] = i;
return aligned ? buf : buf.slice(1);
}
function genMethod(method) {
const fnString = `
return function ${method}(n, buf) {
for (let i = 0; i <= n; i++)
buf.${method}();
}`;
return (new Function(fnString))();
}
function main({ method, len, n, aligned = 'true' }) {
const buf = createBuffer(len, aligned === 'true');
const bufferSwap = genMethod(method);
bufferSwap(n, buf);
bench.start();
bufferSwap(n, buf);
bench.end(n);
}

View File

@ -0,0 +1,17 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [1e4],
len: [0, 256, 4 * 1024],
});
function main({ n, len }) {
const buf = Buffer.allocUnsafe(len);
bench.start();
for (let i = 0; i < n; ++i)
buf.toJSON();
bench.end(n);
}

View File

@ -0,0 +1,49 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
encoding: ['', 'utf8', 'ascii', 'latin1', 'hex', 'UCS-2'],
args: [0, 1, 3],
len: [1, 64, 1024],
n: [1e6],
}, {
combinationFilter: (p) => {
return (p.args === 0 && p.encoding === '') ||
(p.args !== 0 && p.encoding !== '');
},
});
function main({ encoding, args, len, n }) {
const buf = Buffer.alloc(len, 42);
if (encoding.length === 0)
encoding = undefined;
switch (args) {
case 1:
bench.start();
for (let i = 0; i < n; i += 1)
buf.toString(encoding);
bench.end(n);
break;
case 2:
bench.start();
for (let i = 0; i < n; i += 1)
buf.toString(encoding, 0);
bench.end(n);
break;
case 3:
bench.start();
for (let i = 0; i < n; i += 1)
buf.toString(encoding, 0, len);
bench.end(n);
break;
default:
bench.start();
for (let i = 0; i < n; i += 1)
buf.toString();
bench.end(n);
break;
}
}

View File

@ -0,0 +1,35 @@
'use strict';
const common = require('../common.js');
const assert = require('node:assert');
const buffer = require('node:buffer');
const hasIntl = !!process.config.variables.v8_enable_i18n_support;
const encodings = ['latin1', 'ascii', 'ucs2', 'utf8'];
if (!hasIntl) {
console.log('Skipping: `transcode` is only available on platforms that support i18n`');
process.exit(0);
}
const bench = common.createBenchmark(main, {
fromEncoding: encodings,
toEncoding: encodings,
length: [1, 10, 1000],
n: [1e5],
}, {
combinationFilter(p) {
return !(p.fromEncoding === 'ucs2' && p.toEncoding === 'utf8');
},
});
function main({ n, fromEncoding, toEncoding, length }) {
const input = Buffer.from('a'.repeat(length));
let out = 0;
bench.start();
for (let i = 0; i < n; i++) {
const dest = buffer.transcode(input, fromEncoding, toEncoding);
out += dest.buffer.byteLength;
}
bench.end(n);
assert.ok(out >= 0);
}

View File

@ -0,0 +1,20 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
encoding: [
'utf8', 'ascii', 'latin1',
],
len: [1, 8, 16, 32],
n: [1e6],
});
function main({ len, n, encoding }) {
const buf = Buffer.allocUnsafe(len);
const string = Buffer.from('a'.repeat(len)).toString();
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, 0, encoding);
}
bench.end(n);
}

View File

@ -0,0 +1,68 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
encoding: [
'', 'utf8', 'ascii', 'hex', 'utf16le', 'latin1',
],
args: [ '', 'offset', 'offset+length' ],
len: [2048],
n: [1e6],
});
function main({ len, n, encoding, args }) {
let string;
let start = 0;
const buf = Buffer.allocUnsafe(len);
switch (args) {
case 'offset':
string = 'a'.repeat(Math.floor(len / 2));
start = len - string.length;
if (encoding) {
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, start, encoding);
}
bench.end(n);
} else {
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, start);
}
bench.end(n);
}
break;
case 'offset+length':
string = 'a'.repeat(len);
if (encoding) {
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, 0, buf.length, encoding);
}
bench.end(n);
} else {
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, 0, buf.length);
}
bench.end(n);
}
break;
default:
string = 'a'.repeat(len);
if (encoding) {
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, encoding);
}
bench.end(n);
} else {
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string);
}
bench.end(n);
}
}
}

View File

@ -0,0 +1,123 @@
'use strict';
const common = require('../common.js');
const types = [
'BigUInt64LE',
'BigUInt64BE',
'BigInt64LE',
'BigInt64BE',
'UInt8',
'UInt16LE',
'UInt16BE',
'UInt32LE',
'UInt32BE',
'UIntLE',
'UIntBE',
'Int8',
'Int16LE',
'Int16BE',
'Int32LE',
'Int32BE',
'IntLE',
'IntBE',
'FloatLE',
'FloatBE',
'DoubleLE',
'DoubleBE',
];
const bench = common.createBenchmark(main, {
buffer: ['fast'],
type: types,
n: [1e6],
});
const INT8 = 0x7f;
const INT16 = 0x7fff;
const INT32 = 0x7fffffff;
const INT48 = 0x7fffffffffff;
const INT64 = 0x7fffffffffffffffn;
const UINT8 = 0xff;
const UINT16 = 0xffff;
const UINT32 = 0xffffffff;
const UINT64 = 0xffffffffffffffffn;
const mod = {
writeBigInt64BE: INT64,
writeBigInt64LE: INT64,
writeBigUInt64BE: UINT64,
writeBigUInt64LE: UINT64,
writeInt8: INT8,
writeInt16BE: INT16,
writeInt16LE: INT16,
writeInt32BE: INT32,
writeInt32LE: INT32,
writeUInt8: UINT8,
writeUInt16BE: UINT16,
writeUInt16LE: UINT16,
writeUInt32BE: UINT32,
writeUInt32LE: UINT32,
writeUIntLE: INT8,
writeUIntBE: INT16,
writeIntLE: INT32,
writeIntBE: INT48,
};
const byteLength = {
writeUIntLE: 1,
writeUIntBE: 2,
writeIntLE: 4,
writeIntBE: 6,
};
function main({ n, buf, type }) {
const buff = buf === 'fast' ?
Buffer.alloc(8) :
require('buffer').SlowBuffer(8);
const fn = `write${type}`;
if (!/\d/.test(fn))
benchSpecialInt(buff, fn, n);
else if (/BigU?Int/.test(fn))
benchBigInt(buff, fn, BigInt(n));
else if (/Int/.test(fn))
benchInt(buff, fn, n);
else
benchFloat(buff, fn, n);
}
function benchBigInt(buff, fn, n) {
const m = mod[fn];
bench.start();
for (let i = 0n; i !== n; i++) {
buff[fn](i & m, 0);
}
bench.end(Number(n));
}
function benchInt(buff, fn, n) {
const m = mod[fn];
bench.start();
for (let i = 0; i !== n; i++) {
buff[fn](i & m, 0);
}
bench.end(n);
}
function benchSpecialInt(buff, fn, n) {
const m = mod[fn];
const byte = byteLength[fn];
bench.start();
for (let i = 0; i !== n; i++) {
buff[fn](i & m, 0, byte);
}
bench.end(n);
}
function benchFloat(buff, fn, n) {
bench.start();
for (let i = 0; i !== n; i++) {
buff[fn](i, 0);
}
bench.end(n);
}

View File

@ -0,0 +1,19 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [1e6],
type: ['buffer', 'string'],
});
const zeroBuffer = Buffer.alloc(0);
const zeroString = '';
function main({ n, type }) {
const data = type === 'buffer' ? zeroBuffer : zeroString;
bench.start();
for (let i = 0; i < n; i++) Buffer.from(data);
bench.end(n);
}

View File

@ -0,0 +1,71 @@
'use strict';
const common = require('../common.js');
const types = [
'Uint8',
'Uint16LE',
'Uint16BE',
'Uint32LE',
'Uint32BE',
'Int8',
'Int16LE',
'Int16BE',
'Int32LE',
'Int32BE',
'Float32LE',
'Float32BE',
'Float64LE',
'Float64BE',
];
const bench = common.createBenchmark(main, {
type: types,
n: [1e6],
});
const INT8 = 0x7f;
const INT16 = 0x7fff;
const INT32 = 0x7fffffff;
const UINT8 = INT8 * 2;
const UINT16 = INT16 * 2;
const UINT32 = INT32 * 2;
const mod = {
setInt8: INT8,
setInt16: INT16,
setInt32: INT32,
setUint8: UINT8,
setUint16: UINT16,
setUint32: UINT32,
};
function main({ n, type }) {
const ab = new ArrayBuffer(8);
const dv = new DataView(ab, 0, 8);
const le = /LE$/.test(type);
const fn = `set${type.replace(/[LB]E$/, '')}`;
if (/int/i.test(fn))
benchInt(dv, fn, n, le);
else
benchFloat(dv, fn, n, le);
}
function benchInt(dv, fn, len, le) {
const m = mod[fn];
const method = dv[fn];
bench.start();
for (let i = 0; i < len; i++) {
method.call(dv, 0, i % m, le);
}
bench.end(len);
}
function benchFloat(dv, fn, len, le) {
const method = dv[fn];
bench.start();
for (let i = 0; i < len; i++) {
method.call(dv, 0, i * 0.1, le);
}
bench.end(len);
}

View File

@ -0,0 +1,40 @@
'use strict';
const common = require('../common.js');
const { exec, execSync } = require('child_process');
const isWindows = process.platform === 'win32';
const messagesLength = [64, 256, 1024, 4096];
// Windows does not support command lines longer than 8191 characters
if (!isWindows) messagesLength.push(32768);
const bench = common.createBenchmark(childProcessExecStdout, {
len: messagesLength,
dur: [5],
});
function childProcessExecStdout({ dur, len }) {
bench.start();
const maxDuration = dur * 1000;
const cmd = `yes "${'.'.repeat(len)}"`;
const child = exec(cmd, { 'stdio': ['ignore', 'pipe', 'ignore'] });
let bytes = 0;
child.stdout.on('data', (msg) => {
bytes += msg.length;
});
setTimeout(() => {
bench.end(bytes);
if (isWindows) {
// Sometimes there's a yes.exe process left hanging around on Windows.
try {
execSync(`taskkill /f /t /pid ${child.pid}`);
} catch {
// This is a best effort kill. stderr is piped to parent for tracing.
}
} else {
child.kill();
}
}, maxDuration);
}

View File

@ -0,0 +1,124 @@
'use strict';
const common = require('../common.js');
const cp = require('child_process');
const command = 'echo';
const args = ['hello'];
const options = {};
const cb = () => {};
const configs = {
n: [1e3],
methodName: [
'exec', 'execSync',
'execFile', 'execFileSync',
'spawn', 'spawnSync',
],
params: [1, 2, 3, 4],
};
const bench = common.createBenchmark(main, configs);
function main({ n, methodName, params }) {
const method = cp[methodName];
switch (methodName) {
case 'exec':
switch (params) {
case 1:
bench.start();
for (let i = 0; i < n; i++) method(command).kill();
bench.end(n);
break;
case 2:
bench.start();
for (let i = 0; i < n; i++) method(command, options).kill();
bench.end(n);
break;
case 3:
bench.start();
for (let i = 0; i < n; i++) method(command, options, cb).kill();
bench.end(n);
break;
}
break;
case 'execSync':
switch (params) {
case 1:
bench.start();
for (let i = 0; i < n; i++) method(command);
bench.end(n);
break;
case 2:
bench.start();
for (let i = 0; i < n; i++) method(command, options);
bench.end(n);
break;
}
break;
case 'execFile':
switch (params) {
case 1:
bench.start();
for (let i = 0; i < n; i++) method(command).kill();
bench.end(n);
break;
case 2:
bench.start();
for (let i = 0; i < n; i++) method(command, args).kill();
bench.end(n);
break;
case 3:
bench.start();
for (let i = 0; i < n; i++) method(command, args, options).kill();
bench.end(n);
break;
case 4:
bench.start();
for (let i = 0; i < n; i++) method(command, args, options, cb).kill();
bench.end(n);
break;
}
break;
case 'execFileSync':
case 'spawnSync':
switch (params) {
case 1:
bench.start();
for (let i = 0; i < n; i++) method(command);
bench.end(n);
break;
case 2:
bench.start();
for (let i = 0; i < n; i++) method(command, args);
bench.end(n);
break;
case 3:
bench.start();
for (let i = 0; i < n; i++) method(command, args, options);
bench.end(n);
break;
}
break;
case 'spawn':
switch (params) {
case 1:
bench.start();
for (let i = 0; i < n; i++) method(command).kill();
bench.end(n);
break;
case 2:
bench.start();
for (let i = 0; i < n; i++) method(command, args).kill();
bench.end(n);
break;
case 3:
bench.start();
for (let i = 0; i < n; i++) method(command, args, options).kill();
bench.end(n);
break;
}
break;
}
}

View File

@ -0,0 +1,37 @@
'use strict';
if (process.argv[2] === 'child') {
const len = +process.argv[3];
const msg = '.'.repeat(len);
const send = () => {
while (process.send(msg));
// Wait: backlog of unsent messages exceeds threshold
setImmediate(send);
};
send();
} else {
const common = require('../common.js');
const bench = common.createBenchmark(main, {
len: [
64, 256, 1024, 4096, 16384, 65536,
65536 << 4, 65536 << 6 - 1,
],
dur: [5],
});
const spawn = require('child_process').spawn;
function main({ dur, len }) {
bench.start();
const options = { 'stdio': ['ignore', 1, 2, 'ipc'] };
const child = spawn(process.argv[0],
[process.argv[1], 'child', len], options);
let bytes = 0;
child.on('message', (msg) => { bytes += msg.length; });
setTimeout(() => {
child.kill();
bench.end(bytes);
}, dur * 1000);
}
}

View File

@ -0,0 +1,42 @@
'use strict';
const common = require('../common.js');
// This benchmark uses `yes` to a create noisy child_processes with varying
// output message lengths, and tries to read 8GB of output
const os = require('os');
const child_process = require('child_process');
const messagesLength = [64, 256, 1024, 4096];
// Windows does not support that long arguments
if (os.platform() !== 'win32')
messagesLength.push(32768);
const bench = common.createBenchmark(main, {
len: messagesLength,
dur: [5],
});
function main({ dur, len }) {
bench.start();
const msg = `"${'.'.repeat(len)}"`;
const options = { 'stdio': ['ignore', 'pipe', 'ignore'] };
const child = child_process.spawn('yes', [msg], options);
let bytes = 0;
child.stdout.on('data', (msg) => {
bytes += msg.length;
});
setTimeout(() => {
if (process.platform === 'win32') {
// Sometimes there's a yes.exe process left hanging around on Windows...
child_process.execSync(`taskkill /f /t /pid ${child.pid}`);
} else {
child.kill();
}
const gbits = (bytes * 8) / (1024 * 1024 * 1024);
bench.end(gbits);
}, dur * 1000);
}

View File

@ -0,0 +1,24 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [1000],
});
const spawn = require('child_process').spawn;
function main({ n }) {
bench.start();
go(n, n);
}
function go(n, left) {
if (--left === 0)
return bench.end(n);
const child = spawn('echo', ['hello']);
child.on('exit', (code) => {
if (code)
process.exit(code);
else
go(n, left);
});
}

75
benchmark/cluster/echo.js Normal file
View File

@ -0,0 +1,75 @@
'use strict';
const cluster = require('cluster');
if (cluster.isPrimary) {
const common = require('../common.js');
const bench = common.createBenchmark(main, {
workers: [1],
payload: ['string', 'object'],
sendsPerBroadcast: [1, 10],
serialization: ['json', 'advanced'],
n: [1e5],
});
function main({
n,
workers,
sendsPerBroadcast,
payload,
serialization,
}) {
const expectedPerBroadcast = sendsPerBroadcast * workers;
let readies = 0;
let broadcasts = 0;
let msgCount = 0;
let data;
cluster.settings.serialization = serialization;
switch (payload) {
case 'string':
data = 'hello world!';
break;
case 'object':
data = { action: 'pewpewpew', powerLevel: 9001 };
break;
default:
throw new Error('Unsupported payload type');
}
for (let i = 0; i < workers; ++i)
cluster.fork().on('online', onOnline).on('message', onMessage);
function onOnline() {
if (++readies === workers) {
bench.start();
broadcast();
}
}
function broadcast() {
if (broadcasts++ === n) {
bench.end(n);
for (const id in cluster.workers)
cluster.workers[id].disconnect();
return;
}
for (const id in cluster.workers) {
const worker = cluster.workers[id];
for (let i = 0; i < sendsPerBroadcast; ++i)
worker.send(data);
}
}
function onMessage() {
if (++msgCount === expectedPerBroadcast) {
msgCount = 0;
broadcast();
}
}
}
} else {
process.on('message', (msg) => {
process.send(msg);
});
}

443
benchmark/common.js Normal file
View File

@ -0,0 +1,443 @@
'use strict';
const child_process = require('child_process');
const http_benchmarkers = require('./_http-benchmarkers.js');
function allow() {
return true;
}
class Benchmark {
constructor(fn, configs, options = {}) {
// Used to make sure a benchmark only start a timer once
this._started = false;
// Indicate that the benchmark ended
this._ended = false;
// Holds process.hrtime value
this._time = 0n;
// Use the file name as the name of the benchmark
this.name = require.main.filename.slice(__dirname.length + 1);
// Execution arguments i.e. flags used to run the jobs
this.flags = process.env.NODE_BENCHMARK_FLAGS?.split(/\s+/) ?? [];
// Parse job-specific configuration from the command line arguments
const argv = process.argv.slice(2);
const parsed_args = this._parseArgs(argv, configs, options);
this.options = parsed_args.cli;
this.extra_options = parsed_args.extra;
this.combinationFilter = typeof options.combinationFilter === 'function' ? options.combinationFilter : allow;
if (options.byGroups) {
this.queue = [];
const groupNames = process.env.NODE_RUN_BENCHMARK_GROUPS?.split(',') ?? Object.keys(configs);
for (const groupName of groupNames) {
const config = { ...configs[groupName][0], group: groupName };
const parsed_args = this._parseArgs(argv, config, options);
this.options = parsed_args.cli;
this.extra_options = parsed_args.extra;
this.queue = this.queue.concat(this._queue(this.options));
}
} else {
this.queue = this._queue(this.options);
}
if (options.flags) {
this.flags = this.flags.concat(options.flags);
}
if (this.queue.length === 0)
return;
// The configuration of the current job, head of the queue
this.config = this.queue[0];
process.nextTick(() => {
if (process.env.NODE_RUN_BENCHMARK_FN !== undefined) {
fn(this.config);
} else {
// _run will use fork() to create a new process for each configuration
// combination.
this._run();
}
});
}
_parseArgs(argv, configs, options) {
const cliOptions = {};
// Check for the test mode first.
const testIndex = argv.indexOf('--test');
if (testIndex !== -1) {
for (const [key, rawValue] of Object.entries(configs)) {
let value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
// Set numbers to one by default to reduce the runtime.
if (typeof value === 'number') {
if (key === 'dur' || key === 'duration') {
value = 0.05;
} else if (value > 1) {
value = 1;
}
}
cliOptions[key] = [value];
}
// Override specific test options.
if (options.test) {
for (const [key, value] of Object.entries(options.test)) {
cliOptions[key] = Array.isArray(value) ? value : [value];
}
}
argv.splice(testIndex, 1);
} else {
// Accept single values instead of arrays.
for (const [key, value] of Object.entries(configs)) {
if (!Array.isArray(value))
configs[key] = [value];
}
}
const extraOptions = {};
const validArgRE = /^(.+?)=([\s\S]*)$/;
// Parse configuration arguments
for (const arg of argv) {
const match = arg.match(validArgRE);
if (!match) {
console.error(`bad argument: ${arg}`);
process.exit(1);
}
const [, key, value] = match;
if (configs[key] !== undefined) {
cliOptions[key] ||= [];
cliOptions[key].push(
// Infer the type from the config object and parse accordingly
typeof configs[key][0] === 'number' ? +value : value,
);
} else {
extraOptions[key] = value;
}
}
return { cli: { ...configs, ...cliOptions }, extra: extraOptions };
}
_queue(options) {
const queue = [];
const keys = Object.keys(options);
const { combinationFilter } = this;
// Perform a depth-first walk through all options to generate a
// configuration list that contains all combinations.
function recursive(keyIndex, prevConfig) {
const key = keys[keyIndex];
const values = options[key];
for (const value of values) {
if (typeof value !== 'number' && typeof value !== 'string') {
throw new TypeError(
`configuration "${key}" had type ${typeof value}`);
}
if (typeof value !== typeof values[0]) {
// This is a requirement for being able to consistently and
// predictably parse CLI provided configuration values.
throw new TypeError(`configuration "${key}" has mixed types`);
}
const currConfig = { [key]: value, ...prevConfig };
if (keyIndex + 1 < keys.length) {
recursive(keyIndex + 1, currConfig);
} else {
// Check if we should allow the current combination
const allowed = combinationFilter({ ...currConfig });
if (typeof allowed !== 'boolean') {
throw new TypeError(
'Combination filter must always return a boolean',
);
}
if (allowed)
queue.push(currConfig);
}
}
}
if (keys.length > 0) {
recursive(0, {});
} else {
queue.push({});
}
return queue;
}
http(options, cb) {
const http_options = { ...options };
http_options.benchmarker ||= this.config.benchmarker ||
this.extra_options.benchmarker ||
http_benchmarkers.default_http_benchmarker;
http_benchmarkers.run(
http_options, (error, code, used_benchmarker, result, elapsed) => {
if (cb) {
cb(code);
}
if (error) {
console.error(error);
process.exit(code || 1);
}
this.config.benchmarker = used_benchmarker;
this.report(result, elapsed);
},
);
}
_run() {
// If forked, report to the parent.
if (process.send) {
process.send({
type: 'config',
name: this.name,
queueLength: this.queue.length,
});
}
const recursive = (queueIndex) => {
const config = this.queue[queueIndex];
// Set NODE_RUN_BENCHMARK_FN to indicate that the child shouldn't
// construct a configuration queue, but just execute the benchmark
// function.
const childEnv = { ...process.env };
childEnv.NODE_RUN_BENCHMARK_FN = '';
// Create configuration arguments
const childArgs = [];
for (const [key, value] of Object.entries(config)) {
childArgs.push(`${key}=${value}`);
}
for (const [key, value] of Object.entries(this.extra_options)) {
childArgs.push(`${key}=${value}`);
}
const child = child_process.fork(require.main.filename, childArgs, {
env: childEnv,
execArgv: this.flags.concat(process.execArgv),
});
child.on('message', sendResult);
child.on('close', (code) => {
if (code) {
process.exit(code);
}
if (queueIndex + 1 < this.queue.length) {
recursive(queueIndex + 1);
}
});
};
recursive(0);
}
start() {
if (this._started) {
throw new Error('Called start more than once in a single benchmark');
}
this._started = true;
this._time = process.hrtime.bigint();
}
end(operations) {
// Get elapsed time now and do error checking later for accuracy.
const time = process.hrtime.bigint();
if (!this._started) {
throw new Error('called end without start');
}
if (this._ended) {
throw new Error('called end multiple times');
}
if (typeof operations !== 'number') {
throw new Error('called end() without specifying operation count');
}
if (!process.env.NODEJS_BENCHMARK_ZERO_ALLOWED && operations <= 0) {
throw new Error('called end() with operation count <= 0');
}
this._ended = true;
if (time === this._time) {
if (!process.env.NODEJS_BENCHMARK_ZERO_ALLOWED)
throw new Error('insufficient clock precision for short benchmark');
// Avoid dividing by zero
this.report(operations && Number.MAX_VALUE, 0n);
return;
}
const elapsed = time - this._time;
const rate = operations / (Number(elapsed) / 1e9);
this.report(rate, elapsed);
}
report(rate, elapsed) {
sendResult({
name: this.name,
conf: this.config,
rate,
time: nanoSecondsToString(elapsed),
type: 'report',
});
}
}
function nanoSecondsToString(bigint) {
const str = bigint.toString();
const decimalPointIndex = str.length - 9;
if (decimalPointIndex <= 0) {
return `0.${'0'.repeat(-decimalPointIndex)}${str}`;
}
return `${str.slice(0, decimalPointIndex)}.${str.slice(decimalPointIndex)}`;
}
function formatResult(data) {
// Construct configuration string, " A=a, B=b, ..."
let conf = '';
for (const key of Object.keys(data.conf)) {
conf += ` ${key}=${JSON.stringify(data.conf[key])}`;
}
let rate = data.rate.toString().split('.');
rate[0] = rate[0].replace(/(\d)(?=(?:\d\d\d)+(?!\d))/g, '$1,');
rate = (rate[1] ? rate.join('.') : rate[0]);
return `${data.name}${conf}: ${rate}\n`;
}
function sendResult(data) {
if (process.send) {
// If forked, report by process send
process.send(data, () => {
if (process.env.NODE_RUN_BENCHMARK_FN !== undefined) {
// If, for any reason, the process is unable to self close within
// a second after completing, forcefully close it.
require('timers').setTimeout(() => {
process.exit(0);
}, 5000).unref();
}
});
} else {
// Otherwise report by stdout
process.stdout.write(formatResult(data));
}
}
const urls = {
long: 'http://nodejs.org:89/docs/latest/api/foo/bar/qua/13949281/0f28b/' +
'/5d49/b3020/url.html#test?payload1=true&payload2=false&test=1' +
'&benchmark=3&foo=38.38.011.293&bar=1234834910480&test=19299&3992&' +
'key=f5c65e1e98fe07e648249ad41e1cfdb0',
short: 'https://nodejs.org/en/blog/',
idn: 'http://你好你好.在线',
auth: 'https://user:pass@example.com/path?search=1',
file: 'file:///foo/bar/test/node.js',
ws: 'ws://localhost:9229/f46db715-70df-43ad-a359-7f9949f39868',
javascript: 'javascript:alert("node is awesome");',
percent: 'https://%E4%BD%A0/foo',
dot: 'https://example.org/./a/../b/./c',
};
const searchParams = {
noencode: 'foo=bar&baz=quux&xyzzy=thud',
multicharsep: 'foo=bar&&&&&&&&&&baz=quux&&&&&&&&&&xyzzy=thud',
encodefake: 'foo=%©ar&baz=%A©uux&xyzzy=%©ud',
encodemany: '%66%6F%6F=bar&%62%61%7A=quux&xyzzy=%74h%75d',
encodelast: 'foo=bar&baz=quux&xyzzy=thu%64',
multivalue: 'foo=bar&foo=baz&foo=quux&quuy=quuz',
multivaluemany: 'foo=bar&foo=baz&foo=quux&quuy=quuz&foo=abc&foo=def&' +
'foo=ghi&foo=jkl&foo=mno&foo=pqr&foo=stu&foo=vwxyz',
manypairs: 'a&b&c&d&e&f&g&h&i&j&k&l&m&n&o&p&q&r&s&t&u&v&w&x&y&z',
manyblankpairs: '&&&&&&&&&&&&&&&&&&&&&&&&',
altspaces: 'foo+bar=baz+quux&xyzzy+thud=quuy+quuz&abc=def+ghi',
};
function getUrlData(withBase) {
const data = require('../test/fixtures/wpt/url/resources/urltestdata.json');
const result = [];
for (const item of data) {
if (item.failure || !item.input) continue;
if (withBase) {
// item.base might be null. It should be converted into `undefined`.
result.push([item.input, item.base ?? undefined]);
} else if (item.base !== null) {
result.push(item.base);
}
}
return result;
}
/**
* Generate an array of data for URL benchmarks to use.
* The size of the resulting data set is the original data size * 2 ** `e`.
* The 'wpt' type contains about 400 data points when `withBase` is true,
* and 200 data points when `withBase` is false.
* Other types contain 200 data points with or without base.
* @param {string} type Type of the data, 'wpt' or a key of `urls`
* @param {number} e The repetition of the data, as exponent of 2
* @param {boolean} withBase Whether to include a base URL
* @param {boolean} asUrl Whether to return the results as URL objects
* @return {string[] | string[][] | URL[]}
*/
function bakeUrlData(type, e = 0, withBase = false, asUrl = false) {
let result = [];
if (type === 'wpt') {
result = getUrlData(withBase);
} else if (urls[type]) {
const input = urls[type];
const item = withBase ? [input, 'about:blank'] : input;
// Roughly the size of WPT URL test data
result = new Array(200).fill(item);
} else {
throw new Error(`Unknown url data type ${type}`);
}
if (typeof e !== 'number') {
throw new Error(`e must be a number, received ${e}`);
}
for (let i = 0; i < e; ++i) {
result = result.concat(result);
}
if (asUrl) {
if (withBase) {
result = result.map(([input, base]) => new URL(input, base));
} else {
result = result.map((input) => new URL(input));
}
}
return result;
}
module.exports = {
Benchmark,
PORT: http_benchmarkers.PORT,
bakeUrlData,
binding(bindingName) {
try {
const { internalBinding } = require('internal/test/binding');
return internalBinding(bindingName);
} catch {
return process.binding(bindingName);
}
},
buildType: process.features.debug ? 'Debug' : 'Release',
createBenchmark(fn, configs, options) {
return new Benchmark(fn, configs, options);
},
sendResult,
searchParams,
urlDataTypes: Object.keys(urls).concat(['wpt']),
urls,
};

120
benchmark/compare.R Normal file
View File

@ -0,0 +1,120 @@
#!/usr/bin/env Rscript
library(ggplot2);
library(plyr);
# get __dirname and load ./_cli.R
args = commandArgs(trailingOnly = F);
dirname = dirname(sub("--file=", "", args[grep("--file", args)]));
source(paste0(dirname, '/_cli.R'), chdir=T);
if (!is.null(args.options$help) ||
(!is.null(args.options$plot) && args.options$plot == TRUE)) {
stop("usage: cat file.csv | Rscript compare.R
--help show this message
--plot filename save plot to filename");
}
plot.filename = args.options$plot;
dat = read.csv(
file('stdin'),
colClasses=c('character', 'character', 'character', 'numeric', 'numeric')
);
dat = data.frame(dat);
dat$nameTwoLines = paste0(dat$filename, '\n', dat$configuration);
dat$name = paste0(dat$filename, ' ', dat$configuration);
# Create a box plot
if (!is.null(plot.filename)) {
p = ggplot(data=dat);
p = p + geom_boxplot(aes(x=nameTwoLines, y=rate, fill=binary));
p = p + ylab("rate of operations (higher is better)");
p = p + xlab("benchmark");
p = p + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5));
ggsave(plot.filename, p);
}
# Computes the shared standard error, as used in Welch's t-test.
welch.sd = function (old.rate, new.rate) {
old.se.squared = var(old.rate) / length(old.rate)
new.se.squared = var(new.rate) / length(new.rate)
return(sqrt(old.se.squared + new.se.squared))
}
# Calculate the improvement confidence interval. The improvement is calculated
# by dividing by old.mu and not new.mu, because old.mu is what the mean
# improvement is calculated relative to.
confidence.interval = function (shared.se, old.mu, w, risk) {
interval = qt(1 - (risk / 2), w$parameter) * shared.se;
return(sprintf("±%.2f%%", (interval / old.mu) * 100))
}
# Calculate the statistics table.
statistics = ddply(dat, "name", function(subdat) {
old.rate = subset(subdat, binary == "old")$rate;
new.rate = subset(subdat, binary == "new")$rate;
# Calculate improvement for the "new" binary compared with the "old" binary
old.mu = mean(old.rate);
new.mu = mean(new.rate);
improvement = sprintf("%.2f %%", ((new.mu - old.mu) / old.mu * 100));
r = list(
confidence = "NA",
improvement = improvement,
"accuracy (*)" = "NA",
"(**)" = "NA",
"(***)" = "NA"
);
# Check if there is enough data to calculate the p-value.
if (length(old.rate) > 1 && length(new.rate) > 1) {
# Perform a statistical test to see if there actually is a difference in
# performance.
w = t.test(rate ~ binary, data=subdat);
shared.se = welch.sd(old.rate, new.rate)
# Add user-friendly stars to the table. There should be at least one star
# before you can say that there is an improvement.
confidence = '';
if (w$p.value < 0.001) {
confidence = '***';
} else if (w$p.value < 0.01) {
confidence = '**';
} else if (w$p.value < 0.05) {
confidence = '*';
}
r = list(
confidence = confidence,
improvement = improvement,
"accuracy (*)" = confidence.interval(shared.se, old.mu, w, 0.05),
"(**)" = confidence.interval(shared.se, old.mu, w, 0.01),
"(***)" = confidence.interval(shared.se, old.mu, w, 0.001)
);
}
return(data.frame(r, check.names=FALSE));
});
# Set the benchmark names as the row.names to left align them in the print.
row.names(statistics) = statistics$name;
statistics$name = NULL;
options(width = 200);
print(statistics);
cat("\n")
cat(sprintf(
"Be aware that when doing many comparisons the risk of a false-positive
result increases. In this case, there are %d comparisons, you can thus
expect the following amount of false-positive results:
%.2f false positives, when considering a 5%% risk acceptance (*, **, ***),
%.2f false positives, when considering a 1%% risk acceptance (**, ***),
%.2f false positives, when considering a 0.1%% risk acceptance (***)
",
nrow(statistics),
nrow(statistics) * 0.05,
nrow(statistics) * 0.01,
nrow(statistics) * 0.001))

130
benchmark/compare.js Normal file
View File

@ -0,0 +1,130 @@
'use strict';
const { spawn, fork } = require('node:child_process');
const { inspect } = require('util');
const path = require('path');
const CLI = require('./_cli.js');
const BenchmarkProgress = require('./_benchmark_progress.js');
//
// Parse arguments
//
const cli = new CLI(`usage: ./node compare.js [options] [--] <category> ...
Run each benchmark in the <category> directory many times using two different
node versions. More than one <category> directory can be specified.
The output is formatted as csv, which can be processed using for
example 'compare.R'.
--new ./new-node-binary new node binary (required)
--old ./old-node-binary old node binary (required)
--runs 30 number of samples
--filter pattern includes only benchmark scripts matching
<pattern> (can be repeated)
--exclude pattern excludes scripts matching <pattern> (can be
repeated)
--set variable=value set benchmark variable (can be repeated)
--no-progress don't show benchmark progress indicator
Examples:
--set CPUSET=0 Runs benchmarks on CPU core 0.
--set CPUSET=0-2 Specifies that benchmarks should run on CPU cores 0 to 2.
Note: The CPUSET format should match the specifications of the 'taskset' command
`, { arrayArgs: ['set', 'filter', 'exclude'], boolArgs: ['no-progress'] });
if (!cli.optional.new || !cli.optional.old) {
cli.abort(cli.usage);
}
const binaries = ['old', 'new'];
const runs = cli.optional.runs ? parseInt(cli.optional.runs, 10) : 30;
const benchmarks = cli.benchmarks();
if (benchmarks.length === 0) {
console.error('No benchmarks found');
process.exitCode = 1;
return;
}
// Create queue from the benchmarks list such both node versions are tested
// `runs` amount of times each.
// Note: BenchmarkProgress relies on this order to estimate
// how much runs remaining for a file. All benchmarks generated from
// the same file must be run consecutively.
const queue = [];
for (const filename of benchmarks) {
for (let iter = 0; iter < runs; iter++) {
for (const binary of binaries) {
queue.push({ binary, filename, iter });
}
}
}
// queue.length = binary.length * runs * benchmarks.length
// Print csv header
console.log('"binary","filename","configuration","rate","time"');
const kStartOfQueue = 0;
const showProgress = !cli.optional['no-progress'];
let progress;
if (showProgress) {
progress = new BenchmarkProgress(queue, benchmarks);
progress.startQueue(kStartOfQueue);
}
(function recursive(i) {
const job = queue[i];
const resolvedPath = path.resolve(__dirname, job.filename);
const cpuCore = cli.getCpuCoreSetting();
let child;
if (cpuCore !== null) {
const spawnArgs = ['-c', cpuCore, cli.optional[job.binary], resolvedPath, ...cli.optional.set];
child = spawn('taskset', spawnArgs, {
env: process.env,
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
});
} else {
child = fork(resolvedPath, cli.optional.set, {
execPath: cli.optional[job.binary],
});
}
child.on('message', (data) => {
if (data.type === 'report') {
// Construct configuration string, " A=a, B=b, ..."
let conf = '';
for (const key of Object.keys(data.conf)) {
conf += ` ${key}=${inspect(data.conf[key])}`;
}
conf = conf.slice(1);
// Escape quotes (") for correct csv formatting
conf = conf.replace(/"/g, '""');
console.log(`"${job.binary}","${job.filename}","${conf}",` +
`${data.rate},${data.time}`);
if (showProgress) {
// One item in the subqueue has been completed.
progress.completeConfig(data);
}
} else if (showProgress && data.type === 'config') {
// The child has computed the configurations, ready to run subqueue.
progress.startSubqueue(data, i);
}
});
child.once('close', (code) => {
if (code) {
process.exit(code);
}
if (showProgress) {
progress.completeRun(job);
}
// If there are more benchmarks execute the next
if (i + 1 < queue.length) {
recursive(i + 1);
}
});
})(kStartOfQueue);

24
benchmark/cpu.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
CPUPATH=/sys/devices/system/cpu
MAXID=$(cat $CPUPATH/present | awk -F- '{print $NF}')
set_governor() {
echo "Setting CPU frequency governor to \"$1\""
i=0
while [ "$i" -le "$MAXID" ]; do
echo "$1" > "$CPUPATH/cpu$i/cpufreq/scaling_governor"
i=$((i + 1))
done
}
case "$1" in
fast | performance)
set_governor "performance"
;;
*)
echo "Usage: $0 fast"
exit 1
;;
esac

Some files were not shown because too many files have changed in this diff Show More