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 2 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
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.
Exact number or a percent of CPUs available (for example "50%").
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' +
'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', () => {
// Mock architecture with 4 cores
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%' });

// Max parallel processes should be 50% of 4
paescuj marked this conversation as resolved.
Show resolved Hide resolved
expect(spawn).toHaveBeenCalledTimes(2);
expect(spawn).toHaveBeenCalledWith('foo', expect.objectContaining({}));
expect(spawn).toHaveBeenCalledWith('bar', expect.objectContaining({}));

// Close first process
paescuj marked this conversation as resolved.
Show resolved Hide resolved
processes[0].emit('close', 1, null);
expect(spawn).toHaveBeenCalledTimes(3);
expect(spawn).toHaveBeenCalledWith('baz', expect.objectContaining({}));

// Close second process
paescuj marked this conversation as resolved.
Show resolved Hide resolved
processes[1].emit('close', 1, null);
expect(spawn).toHaveBeenCalledTimes(4);
expect(spawn).toHaveBeenCalledWith('qux', expect.objectContaining({}));
});

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

Expand Down
11 changes: 9 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.
* Exact number or a percent of CPUs available (for example "50%").
*
* 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 * Number(options.maxProcesses.slice(0, -1))) / 100)
: Number(options.maxProcesses)) || commandsLeft.length
);
for (let i = 0; i < maxProcesses; i++) {
maybeRunMore(commandsLeft);
}
Expand Down