Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(concurrency): better default for low CPU count #2546

Merged
merged 2 commits into from Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/api/schema/stryker-core.json
Expand Up @@ -237,7 +237,7 @@
"default": []
},
"concurrency": {
"description": "Set the concurrency of workers. Stryker will always run checkers and test runners in parallel by creating worker processes (note, not `worker_threads`). This defaults to n-1 where n is the number of cpu's available on your machine. This is a sane default for most use cases.",
"description": "Set the concurrency of workers. Stryker will always run checkers and test runners in parallel by creating worker processes (note, not `worker_threads`). This defaults to `n-1` where `n` is the number of logical CPU cores available on your machine, unless `n <= 4`, in that case it uses `n`. This is a sane default for most use cases.",
"type": "number"
},
"maxTestRunnerReuse": {
Expand Down
24 changes: 13 additions & 11 deletions packages/core/README.md
Expand Up @@ -103,6 +103,7 @@ You can *ignore* files by adding an exclamation mark (`!`) at the start of an ex
* [allowConsoleColors](#allowConsoleColors)
* [buildCommand](#buildCommand)
* [cleanTempDir](#cleanTempDir)
* [concurrency](#concurrency)
* [commandRunner](#commandRunner)
* [coverageAnalysis](#coverageAnalysis)
* [dashboard.*](#dashboard)
Expand Down Expand Up @@ -152,6 +153,15 @@ Config file: `cleanTempDir: false`
Choose whether or not to clean the temp dir (which is ".stryker-tmp" inside the current working directory by default) after a successful run.
The temp dir will never be removed when the run failed for some reason (for debugging purposes).

<a name="concurrency"></a>
### `concurrency` [`number`]

Default: `cpuCoreCount <= 4? cpuCoreCount : cpuCoreCount - 1`
Command line: `--concurrency 4`
Config file: `concurrency: 4`

Set the concurrency of workers. Stryker will always run checkers and test runners in parallel by creating worker processes (note, not `worker_threads`). This defaults to `n-1` where `n` is the number of logical CPU cores available on your machine, unless `n <= 4`, in that case it uses `n`. This is a sane default for most use cases.

<a name="commandRunner"></a>
### `commandRunner` [`object`]

Expand Down Expand Up @@ -240,21 +250,13 @@ Config file: `logLevel: 'info'`
Thus, to see logging output from the test runner set the `logLevel` to `all` or `trace`.

<a name="maxConcurrentTestRunners"></a>
### `maxConcurrentTestRunners` [`number`]
### DEPRECATED `maxConcurrentTestRunners` [`number`]

Default: `(number of CPU Cores)`
Default: (see [concurrency](#concurrency))
Command line: `--maxConcurrentTestRunners 3`
Config file: `maxConcurrentTestRunners: 3`

Specifies the maximum number of concurrent test runners to spawn.
Mutation testing is time consuming. By default, Stryker tries to make the most of your CPU, by spawning as many test runners as you have CPU cores.
This setting allows you to override this default behavior.

Reasons you might want to lower this setting:

* Your test runner starts a browser (another CPU-intensive process)
* You're running on a shared server and/or
* Your hard disk cannot handle the I/O of all test runners
DEPRECATED. Please use [concurrency](#concurrency) instead.

<a name="mutate"></a>
### `mutate` [`string[]`]
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/concurrent/concurrency-token-provider.ts
Expand Up @@ -18,7 +18,8 @@ export class ConcurrencyTokenProvider implements Disposable {
public static readonly inject = tokens(commonTokens.options, commonTokens.logger);

constructor(options: Pick<StrykerOptions, 'concurrency' | 'checkers'>, private readonly log: Logger) {
const concurrency = options.concurrency ?? os.cpus().length - 1;
const cpuCount = os.cpus().length;
const concurrency = options.concurrency ?? (cpuCount > 4 ? cpuCount - 1 : cpuCount);
if (options.checkers.length > 0) {
this.concurrencyCheckers = Math.max(Math.ceil(concurrency / 2), 1);
this.checkerToken$ = range(this.concurrencyCheckers);
Expand Down
Expand Up @@ -28,11 +28,18 @@ describe(ConcurrencyTokenProvider.name, () => {
});

describe('testRunnerToken$', () => {
it('should use cpuCount - 1 if concurrency is not set', async () => {
sinon.stub(os, 'cpus').returns([0, 1, 2]);
it('should use cpuCount if concurrency is not set and CPU count <= 4', async () => {
sinon.stub(os, 'cpus').returns([0, 1, 2, 3]);
const sut = createSut();
const actualTokens = await actAllTestRunnerTokens(sut);
expect(actualTokens).deep.eq([0, 1]);
expect(actualTokens).deep.eq([0, 1, 2, 3]);
});

it('should use cpuCount - 1 if concurrency is not set and CPU count > 4', async () => {
sinon.stub(os, 'cpus').returns([0, 1, 2, 3, 4]);
const sut = createSut();
const actualTokens = await actAllTestRunnerTokens(sut);
expect(actualTokens).deep.eq([0, 1, 2, 3]);
});

it('should allow half of the concurrency when there are checkers configured', async () => {
Expand Down