Skip to content

Commit

Permalink
Support percent values in maxProcesses
Browse files Browse the repository at this point in the history
  • Loading branch information
lynxtaa committed Oct 26, 2022
1 parent 875d2df commit 1b96674
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 5 deletions.
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -146,8 +146,9 @@ concurrently [options] <command ...>
General
-m, --max-processes How many processes should run at once.
Could be an exact number or a percent of CPUs available.
New processes only spawn after all restart tries
of a process. [number]
of a process. [string]
-n, --names List of custom names to be used in prefix
template.
Example names: "main,browser,server" [string]
Expand Down
5 changes: 3 additions & 2 deletions bin/concurrently.ts
Expand Up @@ -30,8 +30,9 @@ const args = yargs(argsBeforeSep)
alias: 'm',
describe:
'How many processes should run at once.\n' +
'New processes only spawn after all restart tries of a process.',
type: 'number',
'New processes only spawn after all restart tries of a process.\n' +
'Could be an exact number or a percent of CPUs available (for example "50%")',
type: 'string',
},
names: {
alias: 'n',
Expand Down
32 changes: 32 additions & 0 deletions src/concurrently.spec.ts
@@ -1,4 +1,5 @@
import { createMockInstance } from 'jest-create-mock-instance';
import os from 'os';
import { Writable } from 'stream';

import { ChildProcess, KillProcess, SpawnCommand } from './command';
Expand All @@ -16,6 +17,8 @@ const create = (commands: ConcurrentlyCommandInput[], options: Partial<Concurren
concurrently(commands, Object.assign(options, { controllers, spawn, kill }));

beforeEach(() => {
jest.resetAllMocks();

processes = [];
spawn = jest.fn(() => {
const process = createFakeProcess(processes.length);
Expand Down Expand Up @@ -79,6 +82,35 @@ it('spawns commands up to configured limit at once', () => {
expect(spawn).toHaveBeenCalledTimes(4);
});

it('spawns commands up to percent based limit at once', () => {
const cpusSpy = jest.spyOn(os, 'cpus');
cpusSpy.mockReturnValue(
new Array(4).fill({
model: 'Intel',
speed: 0,
times: { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 },
})
);

create(['foo', 'bar', 'baz', 'qux'], { maxProcesses: '50%' });
expect(spawn).toHaveBeenCalledTimes(2);
expect(spawn).toHaveBeenCalledWith('foo', expect.objectContaining({}));
expect(spawn).toHaveBeenCalledWith('bar', expect.objectContaining({}));

// Test out of order completion picking up new processes in-order
processes[1].emit('close', 1, null);
expect(spawn).toHaveBeenCalledTimes(3);
expect(spawn).toHaveBeenCalledWith('baz', expect.objectContaining({}));

processes[0].emit('close', null, 'SIGINT');
expect(spawn).toHaveBeenCalledTimes(4);
expect(spawn).toHaveBeenCalledWith('qux', expect.objectContaining({}));

// Shouldn't attempt to spawn anything else.
processes[2].emit('close', 1, null);
expect(spawn).toHaveBeenCalledTimes(4);
});

it('runs controllers with the commands', () => {
create(['echo', '"echo wrapped"']);

Expand Down
15 changes: 13 additions & 2 deletions src/concurrently.ts
@@ -1,5 +1,6 @@
import assert from 'assert';
import _ from 'lodash';
import { cpus } from 'os';
import spawn from 'spawn-command';
import { Writable } from 'stream';
import treeKill from 'tree-kill';
Expand Down Expand Up @@ -67,11 +68,12 @@ export type ConcurrentlyOptions = {

/**
* Maximum number of commands to run at once.
* Could be an exact number or a percent of CPUs available.
*
* If undefined, then all processes will start in parallel.
* Setting this value to 1 will achieve sequential running.
*/
maxProcesses?: number;
maxProcesses?: number | string;

/**
* Whether commands should be spawned in raw mode.
Expand Down Expand Up @@ -187,7 +189,12 @@ export function concurrently(
}

const commandsLeft = commands.slice();
const maxProcesses = Math.max(1, Number(options.maxProcesses) || commandsLeft.length);
const maxProcesses = Math.max(
1,
(typeof options.maxProcesses === 'string' && options.maxProcesses.endsWith('%')
? Math.floor(cpus().length * percentToNumber(options.maxProcesses))
: Number(options.maxProcesses)) || commandsLeft.length
);
for (let i = 0; i < maxProcesses; i++) {
maybeRunMore(commandsLeft);
}
Expand Down Expand Up @@ -245,3 +252,7 @@ function maybeRunMore(commandsLeft: Command[]) {
maybeRunMore(commandsLeft);
});
}

function percentToNumber(percent: string): number {
return Number(percent.replace('%', '')) / 100;
}

0 comments on commit 1b96674

Please sign in to comment.