Skip to content

Commit

Permalink
fix(concurrency): better default for low CPU count (#2546)
Browse files Browse the repository at this point in the history
Sets the default `--concurrency` to `n` when `n <= 4`. (where `n` is the number of logical CPU cores).
Also updates the docs (apparently, `--concurrency` was missing from the readme).
  • Loading branch information
nicojs committed Oct 13, 2020
1 parent dbf8625 commit eac9199
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 16 deletions.
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

0 comments on commit eac9199

Please sign in to comment.