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

Support percent values in maxProcesses #375

Merged
merged 3 commits into from Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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.
lynxtaa marked this conversation as resolved.
Show resolved Hide resolved
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%")',
lynxtaa marked this conversation as resolved.
Show resolved Hide resolved
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(
paescuj marked this conversation as resolved.
Show resolved Hide resolved
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.
lynxtaa marked this conversation as resolved.
Show resolved Hide resolved
*
* 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.round(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;
}
lynxtaa marked this conversation as resolved.
Show resolved Hide resolved