@orkastery/cli 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/dist/adapters/codex-controller-sensor.js +21 -2
- package/dist/adapters/codex-events.js +15 -6
- package/dist/adapters/grok-events.js +43 -2
- package/dist/adapters/grok.js +28 -5
- package/dist/agendador-cpu.js +120 -30
- package/dist/cenarios-sensores.js +5 -4
- package/dist/check-agil.js +87 -11
- package/dist/check-paralelo.js +385 -13
- package/dist/check-selecao.js +149 -0
- package/dist/codex-task-complete.js +95 -0
- package/dist/junit.js +546 -0
- package/dist/locale-verify.js +37 -0
- package/dist/manifest.js +237 -13
- package/dist/retry.js +35 -0
- package/dist/security-scan/agil.js +28 -12
- package/dist/security-scan/executor.js +1 -0
- package/dist/security-scan/incremental.js +289 -0
- package/dist/security-scan/trabalhador.js +53 -5
- package/dist/session-watcher-claude.js +38 -22
- package/dist/session-watcher-grok.js +10 -0
- package/dist/session-watcher.js +47 -18
- package/dist/ship.js +9 -3
- package/dist/verify.js +215 -41
- package/package.json +1 -1
package/dist/check-agil.js
CHANGED
|
@@ -388,6 +388,15 @@ function caiuDeVerdade(r) {
|
|
|
388
388
|
return !r.ok && r.executado !== false && (r.causa === undefined || r.causa === 'exit') && Number.isInteger(r.code)
|
|
389
389
|
&& r.code >= 1 && r.code <= MAIOR_CODIGO_DA_LANE && !(r.relogioSobSteal?.length);
|
|
390
390
|
}
|
|
391
|
+
/** O agregado so afirma contagens quando todas as lanes trouxeram JUnit valido. */
|
|
392
|
+
function junitDasLanes(lanes) {
|
|
393
|
+
if (!lanes.length || lanes.some((l) => l.resultado.junit?.estado !== 'lido'))
|
|
394
|
+
return undefined;
|
|
395
|
+
const relatorios = lanes.map((l) => l.resultado.junit);
|
|
396
|
+
const somar = (campo) => relatorios.reduce((total, r) => total + (r[campo] ?? 0), 0);
|
|
397
|
+
return { caminho: relatorios.map((r) => r.caminho).join(';'), estado: 'lido', testes: somar('testes'),
|
|
398
|
+
falhas: somar('falhas'), erros: somar('erros'), pulados: somar('pulados'), duracaoMs: somar('duracaoMs') };
|
|
399
|
+
}
|
|
391
400
|
/**
|
|
392
401
|
* O `test` das lanes (D4): ok so com todas ok; a evidencia de quem caiu; a parede e a da lane mais longa. A falha de
|
|
393
402
|
* verdade de uma lane manda, como num comando so do Classic: o estouro ou o relogio sob steal de outra lane nao a esconde.
|
|
@@ -414,9 +423,22 @@ function testDasLanes(lanes) {
|
|
|
414
423
|
const causas = lanes.map((l) => l.resultado.causa).filter((c) => !!c);
|
|
415
424
|
const causa = deVerdade.length ? deVerdade[0].resultado.causa ?? 'exit' : PIOR_CAUSA.find((c) => causas.includes(c));
|
|
416
425
|
const soRelogio = caidas.length > 0 && caidas.every((l) => (l.resultado.relogioSobSteal?.length ?? 0) > 0);
|
|
417
|
-
const
|
|
426
|
+
const nomesExtraidos = juntar((r) => r.testeQueCaiu);
|
|
427
|
+
const testes = lanes.some((l) => l.resultado.junit?.estado === 'lido') ? nomesExtraidos : nomesExtraidos.slice(0, 10);
|
|
418
428
|
const falhas = juntar((r) => r.falhasDeTeste);
|
|
429
|
+
const parciais = lanes.filter((l) => !!l.resultado.parcial);
|
|
430
|
+
const filtrados = juntar((r) => r.parcial?.testes);
|
|
431
|
+
const junit = junitDasLanes(lanes);
|
|
419
432
|
const relogio = soRelogio ? juntar((r) => r.relogioSobSteal) : [];
|
|
433
|
+
const retestes = lanes.map((l) => l.resultado.retesteDeRelogio).filter((r) => !!r);
|
|
434
|
+
const estadoDoReteste = ['reprovou', 'sem-veredito', 'sem-filtro', 'passou']
|
|
435
|
+
.find((estado) => retestes.some((r) => r.estado === estado));
|
|
436
|
+
const reteste = estadoDoReteste ? {
|
|
437
|
+
testes: [...new Set(retestes.flatMap((r) => r.testes))],
|
|
438
|
+
estado: estadoDoReteste,
|
|
439
|
+
...(retestes.find((r) => r.estado === estadoDoReteste)?.code !== undefined
|
|
440
|
+
? { code: retestes.find((r) => r.estado === estadoDoReteste).code } : {}),
|
|
441
|
+
} : undefined;
|
|
420
442
|
const trechos = caidas.map((l) => l.resultado.trecho).filter((t) => !!t);
|
|
421
443
|
return {
|
|
422
444
|
nome: 'test',
|
|
@@ -430,12 +452,22 @@ function testDasLanes(lanes) {
|
|
|
430
452
|
executado: !tempoComFalha && lanes.every((l) => l.resultado.executado !== false),
|
|
431
453
|
...(testes.length ? { testeQueCaiu: testes } : {}),
|
|
432
454
|
...(falhas.length ? { falhasDeTeste: falhas } : {}),
|
|
455
|
+
...(junit ? { junit } : {}),
|
|
456
|
+
...(parciais.length ? { parcial: { testes: [...new Set(filtrados)] } } : {}),
|
|
433
457
|
...(trechos.length ? { trecho: trechos.join('\n').slice(0, 1024) } : {}),
|
|
434
458
|
...(lanes.some((l) => l.resultado.stealPct !== undefined) ? { stealPct: lanes.find((l) => l.resultado.stealPct !== undefined).resultado.stealPct } : {}),
|
|
435
459
|
...(relogio.length ? { relogioSobSteal: relogio } : {}),
|
|
460
|
+
...(reteste ? { retesteDeRelogio: reteste } : {}),
|
|
436
461
|
lanes: lanes.map((l) => ({ lane: l.lane, comando: l.resultado.comando, ok: l.resultado.ok, code: l.resultado.code,
|
|
437
462
|
...(l.resultado.causa ? { causa: l.resultado.causa } : {}), duracaoMs: l.resultado.duracaoMs ?? 0,
|
|
438
|
-
...(l.resultado.testeQueCaiu?.length ? { testeQueCaiu: l.resultado.testeQueCaiu } : {})
|
|
463
|
+
...(l.resultado.testeQueCaiu?.length ? { testeQueCaiu: l.resultado.testeQueCaiu } : {}),
|
|
464
|
+
...(l.resultado.junit ? { junit: l.resultado.junit } : {}),
|
|
465
|
+
...(l.resultado.parcial ? { parcial: l.resultado.parcial } : {}),
|
|
466
|
+
...(l.resultado.comandoExecutado ? { comandoExecutado: l.resultado.comandoExecutado } : {}),
|
|
467
|
+
...(l.resultado.selecao ? { selecao: l.resultado.selecao } : {}),
|
|
468
|
+
...(l.resultado.relogioSobSteal?.length ? { relogioSobSteal: l.resultado.relogioSobSteal } : {}),
|
|
469
|
+
...(l.resultado.retesteDeRelogio ? { retesteDeRelogio: l.resultado.retesteDeRelogio } : {}),
|
|
470
|
+
...(l.resultado.stealPct !== undefined ? { stealPct: l.resultado.stealPct } : {}) })),
|
|
439
471
|
};
|
|
440
472
|
}
|
|
441
473
|
/**
|
|
@@ -466,7 +498,7 @@ function porQueDasLanes(c) {
|
|
|
466
498
|
* Os comandos do manifesto no Agile (D4): `build` reaproveitado do preparo com a guarda valendo; o resto num grupo
|
|
467
499
|
* paralelo, com as lanes no lugar do `verify.test`, so com a guarda valendo; sem ela, tudo em serie (o Classic).
|
|
468
500
|
*/
|
|
469
|
-
function comandosNoAgile(manifesto, cwd, lista, executor, grupo, guarda, preparo) {
|
|
501
|
+
function comandosNoAgile(manifesto, cwd, lista, executor, grupo, guarda, preparo, selecao = { comandos: {}, lanes: {} }, passagemParcial, selecaoPorMudanca = {}) {
|
|
470
502
|
const pecas = [];
|
|
471
503
|
const passosDoPreparo = manifesto.verify.preparo ? passosDoComando(manifesto.verify.preparo) : [];
|
|
472
504
|
const lanesDeclaradas = Object.entries(manifesto.check?.agile?.test ?? {});
|
|
@@ -480,7 +512,12 @@ function comandosNoAgile(manifesto, cwd, lista, executor, grupo, guarda, preparo
|
|
|
480
512
|
pecas.push({ peca: 'grupo', estado: 'fallback', motivo, fallback: 'comandos em serie' });
|
|
481
513
|
if (lanesDeclaradas.length > 0)
|
|
482
514
|
pecas.push({ peca: 'lanes', estado: 'fallback', motivo, fallback: 'verify.test' });
|
|
483
|
-
|
|
515
|
+
const executar = (c) => {
|
|
516
|
+
const parcial = c.filtro && selecao.comandos[c.nome]
|
|
517
|
+
? passagemParcial?.(c.nome, c.comando, cwd, c.filtro, selecao.comandos[c.nome]) : null;
|
|
518
|
+
return parcial && !parcial.ok ? parcial : executor(c.nome, c.comando, cwd);
|
|
519
|
+
};
|
|
520
|
+
return { comandos: lista.map(executar), pecas, paralelo: false, lanes: [] };
|
|
484
521
|
}
|
|
485
522
|
// Com a guarda: o build primeiro (do preparo, quando e so passos dele), e o resto junto.
|
|
486
523
|
const build = lista.find((c) => c.nome === 'build');
|
|
@@ -499,17 +536,55 @@ function comandosNoAgile(manifesto, cwd, lista, executor, grupo, guarda, preparo
|
|
|
499
536
|
for (const c of resto) {
|
|
500
537
|
if (c.nome === 'test' && lanesDeclaradas.length > 0) {
|
|
501
538
|
for (const [lane, comando] of lanesDeclaradas) {
|
|
539
|
+
const junit = manifesto.check?.agile?.junit?.[lane];
|
|
540
|
+
const filtro = manifesto.check?.agile?.filtro?.[lane];
|
|
541
|
+
const recursos = manifesto.check?.agile?.recursos?.[lane];
|
|
542
|
+
const testesFiltrados = selecao.lanes[lane];
|
|
543
|
+
const porMudanca = selecaoPorMudanca[lane];
|
|
544
|
+
const efetivo = porMudanca?.comando ?? comando;
|
|
545
|
+
const dadosDaSelecao = porMudanca ? { comandoIntegral: comando, selecaoPorMudanca: porMudanca } : {};
|
|
502
546
|
if (depoisDaVarredura.has(lane))
|
|
503
|
-
encadeados.push({ nome: `test:${lane}`, comando
|
|
547
|
+
encadeados.push({ nome: `test:${lane}`, comando: efetivo, ...dadosDaSelecao,
|
|
548
|
+
...(junit ? { junit } : {}), ...(filtro ? { filtro } : {}), ...(testesFiltrados ? { testesFiltrados } : {}),
|
|
549
|
+
...(recursos?.length ? { recursos } : {}),
|
|
550
|
+
apos: ['security_scan'], papelCpu: 'integracao' });
|
|
504
551
|
else
|
|
505
|
-
itens.push({ nome: `test:${lane}`, comando
|
|
552
|
+
itens.push({ nome: `test:${lane}`, comando: efetivo, ...dadosDaSelecao, ...(junit ? { junit } : {}),
|
|
553
|
+
...(filtro ? { filtro } : {}), ...(testesFiltrados ? { testesFiltrados } : {}),
|
|
554
|
+
...(recursos?.length ? { recursos } : {}),
|
|
555
|
+
papelCpu: lane === 'hermetica' ? 'hermetica' : 'outro' });
|
|
506
556
|
}
|
|
507
557
|
}
|
|
508
558
|
else
|
|
509
|
-
itens.push({ nome: c.nome, comando: c.comando,
|
|
559
|
+
itens.push({ nome: c.nome, comando: c.comando, ...(c.junit ? { junit: c.junit } : {}),
|
|
560
|
+
...(c.filtro ? { filtro: c.filtro } : {}), ...(selecao.comandos[c.nome] ? { testesFiltrados: selecao.comandos[c.nome] } : {}),
|
|
561
|
+
papelCpu: c.nome === 'security_scan' ? 'varredura' : 'outro' });
|
|
510
562
|
}
|
|
511
563
|
itens.push(...encadeados);
|
|
512
|
-
const
|
|
564
|
+
const parciaisVermelhas = new Map();
|
|
565
|
+
for (const item of itens) {
|
|
566
|
+
if (!item.filtro || !item.testesFiltrados?.length || !passagemParcial)
|
|
567
|
+
continue;
|
|
568
|
+
const parcial = passagemParcial(item.nome, item.comando, cwd, item.filtro, item.testesFiltrados);
|
|
569
|
+
if (parcial && !parcial.ok)
|
|
570
|
+
parciaisVermelhas.set(item.nome, parcial);
|
|
571
|
+
}
|
|
572
|
+
const inteiros = itens.filter((i) => !parciaisVermelhas.has(i.nome));
|
|
573
|
+
const resultadosInteiros = inteiros.length > 1 ? grupo(inteiros, cwd) : inteiros.map((i) => executor(i.nome, i.comando, cwd));
|
|
574
|
+
const porItemInteiro = new Map(inteiros.map((item, i) => [item.nome, resultadosInteiros[i]]));
|
|
575
|
+
const resultados = itens.map((item) => {
|
|
576
|
+
const parcial = parciaisVermelhas.get(item.nome);
|
|
577
|
+
if (parcial)
|
|
578
|
+
return parcial;
|
|
579
|
+
const resultado = porItemInteiro.get(item.nome);
|
|
580
|
+
if (!item.selecaoPorMudanca || !item.comandoIntegral)
|
|
581
|
+
return resultado;
|
|
582
|
+
const { comando: _comando, ...registro } = item.selecaoPorMudanca;
|
|
583
|
+
return item.selecaoPorMudanca.modo === 'selecionada'
|
|
584
|
+
? { ...resultado, comando: item.comandoIntegral, comandoExecutado: item.comando,
|
|
585
|
+
parcial: { testes: [] }, selecao: registro }
|
|
586
|
+
: { ...resultado, comando: item.comandoIntegral, selecao: registro };
|
|
587
|
+
});
|
|
513
588
|
pecas.push(...(grupo.pecas ?? []));
|
|
514
589
|
const lanes = [];
|
|
515
590
|
itens.forEach((item, i) => {
|
|
@@ -590,7 +665,8 @@ exports.RelatorioDoCheck = RelatorioDoCheck;
|
|
|
590
665
|
*/
|
|
591
666
|
function pecasDoRelatorio(linhas) {
|
|
592
667
|
const vistas = new Set();
|
|
593
|
-
return linhas.filter((l) => l.tipo === 'peca' && typeof l.peca === 'string'
|
|
668
|
+
return linhas.filter((l) => l.tipo === 'peca' && typeof l.peca === 'string'
|
|
669
|
+
&& (l.estado === 'agile' || l.estado === 'degradado' || l.estado === 'fallback'))
|
|
594
670
|
.map((l) => ({ peca: l.peca, estado: l.estado,
|
|
595
671
|
...(typeof l.motivo === 'string' ? { motivo: l.motivo } : {}), ...(typeof l.fallback === 'string' ? { fallback: l.fallback } : {}) }))
|
|
596
672
|
.filter((p) => { const chave = JSON.stringify(p); if (vistas.has(chave))
|
|
@@ -627,8 +703,8 @@ function resultadosDasLanes(linhas) {
|
|
|
627
703
|
/** Hash do ambiente funcional; transporte do CHECK, npm e bookkeeping do shell nao mudam o teste. */
|
|
628
704
|
function assinaturaDoAmbiente(env) {
|
|
629
705
|
const ignorada = (nome) => nome.startsWith('npm_') || nome.startsWith('NPM_')
|
|
630
|
-
|| ['ORK_CHECK_RELATORIO', 'ORK_CHECK_CPUS_DA_LANE', '
|
|
631
|
-
'ORK_CHECK_CPUS_DOCKER',
|
|
706
|
+
|| ['ORK_CHECK_RELATORIO', 'ORK_CHECK_CPUS_DA_LANE', 'ORK_CHECK_CPUS_DA_LANE_APOS_VARREDURA',
|
|
707
|
+
'ORK_CHECK_CPUS_DA_LANE_APOS_RESERVA', 'ORK_CHECK_RESERVA_DEVOLVIDA', 'ORK_CHECK_CONTAINERS_DA_LANE', 'ORK_CHECK_CPUS_DOCKER',
|
|
632
708
|
'NODE_TEST_CONTEXT', 'NODE_COMPILE_CACHE', 'PWD', 'OLDPWD', 'SHLVL', '_', 'INIT_CWD'].includes(nome);
|
|
633
709
|
const pares = Object.entries(env).filter(([nome, valor]) => valor !== undefined && !ignorada(nome))
|
|
634
710
|
.sort(([a], [b]) => a.localeCompare(b));
|
package/dist/check-paralelo.js
CHANGED
|
@@ -34,6 +34,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.argvDoItem = argvDoItem;
|
|
37
|
+
exports.workersDaLane = workersDaLane;
|
|
38
|
+
exports.chaveDoPerfilIntegracao = chaveDoPerfilIntegracao;
|
|
39
|
+
exports.picoDaArvoreParaPerfil = picoDaArvoreParaPerfil;
|
|
40
|
+
exports.picoCpuPorFonte = picoCpuPorFonte;
|
|
37
41
|
exports.esperaDoGrupo = esperaDoGrupo;
|
|
38
42
|
exports.rodarEmParalelo = rodarEmParalelo;
|
|
39
43
|
/**
|
|
@@ -48,12 +52,17 @@ exports.rodarEmParalelo = rodarEmParalelo;
|
|
|
48
52
|
* de integracao depois da varredura), e `maxParalelo` limita quantos rodam ao mesmo tempo.
|
|
49
53
|
*/
|
|
50
54
|
const node_child_process_1 = require("node:child_process");
|
|
55
|
+
const node_crypto_1 = require("node:crypto");
|
|
51
56
|
const fs = __importStar(require("node:fs"));
|
|
52
57
|
const os = __importStar(require("node:os"));
|
|
53
58
|
const path = __importStar(require("node:path"));
|
|
54
59
|
const agendador_cpu_1 = require("./agendador-cpu");
|
|
60
|
+
const integracoes_locais_1 = require("./integracoes-locais");
|
|
55
61
|
const TIMEOUT_BIN = '/usr/bin/timeout';
|
|
56
62
|
const NICE_BIN = '/usr/bin/nice';
|
|
63
|
+
const TIME_BIN = '/usr/bin/time';
|
|
64
|
+
const CONTRATO_PERFIL_INTEGRACAO = 'ork.check.cpu-integracao/v2';
|
|
65
|
+
const INTERVALO_CPU_MS = 100;
|
|
57
66
|
/** O argv de um item: `nice` (se pedido e existir), `timeout(1)` (se existir) e o `argv` do item ou `bash -c`. */
|
|
58
67
|
function argvDoItem(item, comGrupo = fs.existsSync(TIMEOUT_BIN), comNice = fs.existsSync(NICE_BIN)) {
|
|
59
68
|
const programa = item.argv?.length ? item.argv : ['bash', '-c', item.comando];
|
|
@@ -77,6 +86,299 @@ function vagas(max) {
|
|
|
77
86
|
livres++; },
|
|
78
87
|
};
|
|
79
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Recursos exclusivos sem dependencia artificial. Cada pedido adquire o conjunto inteiro ou espera, o que evita
|
|
91
|
+
* deadlock entre listas cruzadas; pedidos disjuntos continuam livres para avancar.
|
|
92
|
+
*/
|
|
93
|
+
function recursosExclusivos() {
|
|
94
|
+
const ocupados = new Set();
|
|
95
|
+
const fila = [];
|
|
96
|
+
const despachar = () => {
|
|
97
|
+
for (let i = 0; i < fila.length;) {
|
|
98
|
+
const pedido = fila[i];
|
|
99
|
+
if (pedido.nomes.some((nome) => ocupados.has(nome))) {
|
|
100
|
+
i++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
fila.splice(i, 1);
|
|
104
|
+
pedido.nomes.forEach((nome) => ocupados.add(nome));
|
|
105
|
+
pedido.pronto(() => {
|
|
106
|
+
pedido.nomes.forEach((nome) => ocupados.delete(nome));
|
|
107
|
+
despachar();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
tomar: (nomes) => new Promise((pronto) => {
|
|
113
|
+
fila.push({ nomes: [...new Set(nomes)], pronto });
|
|
114
|
+
despachar();
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** A reserva que vira ORK_WORKERS quando a lane e efetivamente lancada. */
|
|
119
|
+
function workersDaLane(item, plano, cpus, totalDeLanes) {
|
|
120
|
+
if (!item.nome.startsWith('test:'))
|
|
121
|
+
return null;
|
|
122
|
+
const capacidade = (0, agendador_cpu_1.workersDaReserva)(cpus);
|
|
123
|
+
if (!plano)
|
|
124
|
+
return Math.max(1, Math.floor(capacidade / Math.max(1, Math.min(capacidade, totalDeLanes))));
|
|
125
|
+
if (item.papelCpu === 'hermetica')
|
|
126
|
+
return (0, agendador_cpu_1.workersDaReserva)(plano.cpusHermetica);
|
|
127
|
+
if (item.papelCpu === 'integracao')
|
|
128
|
+
return (0, agendador_cpu_1.workersDaReserva)(plano.cpusIntegracao);
|
|
129
|
+
// Outros fecham a cadeia depois que as reservas da varredura e da integracao voltaram.
|
|
130
|
+
return capacidade;
|
|
131
|
+
}
|
|
132
|
+
function arquivoDoPerfilIntegracao() {
|
|
133
|
+
const base = process.env.XDG_CACHE_HOME && path.isAbsolute(process.env.XDG_CACHE_HOME)
|
|
134
|
+
? process.env.XDG_CACHE_HOME : path.join(os.homedir(), '.cache');
|
|
135
|
+
return path.join(base, 'orkastery', 'check', 'cpu-integracao.json');
|
|
136
|
+
}
|
|
137
|
+
/** Os arquivos da lane de integracao, pela mesma regra do `test-lanes.cjs`: a lista do ambiente ou a do nucleo. */
|
|
138
|
+
function arquivosDaIntegracao(env = process.env) {
|
|
139
|
+
const lista = env.ORK_LANES_INTEGRACAO !== undefined
|
|
140
|
+
? env.ORK_LANES_INTEGRACAO.split(',').map((n) => n.trim()).filter(Boolean) : [...integracoes_locais_1.TESTES_DE_INTEGRACAO_LOCAL];
|
|
141
|
+
return [...new Set(lista)].sort();
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* A medida so atravessa rodadas quando comando, capacidade e entradas que definem a lane continuam iguais: o script das
|
|
145
|
+
* lanes, a lista de integracao e o conteudo compilado de cada teste dela. Teste novo, mudado ou ausente troca a chave.
|
|
146
|
+
*/
|
|
147
|
+
function chaveDoPerfilIntegracao(itens, cpus, arquivos = arquivosDaIntegracao()) {
|
|
148
|
+
const integracao = itens.filter((i) => i.papelCpu === 'integracao');
|
|
149
|
+
if (!integracao.length)
|
|
150
|
+
return null;
|
|
151
|
+
const h = (0, node_crypto_1.createHash)('sha256').update(JSON.stringify({ cpus, node: process.version,
|
|
152
|
+
comandos: integracao.map((i) => i.comando), arquivos }));
|
|
153
|
+
const cwd = integracao[0].cwd;
|
|
154
|
+
const relativos = ['core/dist/integracoes-locais.js', 'core/scripts/test-lanes.cjs',
|
|
155
|
+
...arquivos.map((nome) => path.join('core', 'dist-test', 'test', nome))];
|
|
156
|
+
for (const relativo of relativos) {
|
|
157
|
+
try {
|
|
158
|
+
h.update(relativo).update(fs.readFileSync(path.join(cwd, relativo)));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
h.update(`${relativo}:ausente`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return h.digest('hex');
|
|
165
|
+
}
|
|
166
|
+
function lerPerfilIntegracao(chave, cpus) {
|
|
167
|
+
if (!chave)
|
|
168
|
+
return null;
|
|
169
|
+
try {
|
|
170
|
+
const p = JSON.parse(fs.readFileSync(arquivoDoPerfilIntegracao(), 'utf8'));
|
|
171
|
+
return p?.contrato === CONTRATO_PERFIL_INTEGRACAO && p.chave === chave && p.cpus === cpus && p.completa
|
|
172
|
+
&& Number.isFinite(p.picoCpusDaArvore) && p.picoCpusDaArvore > 0 && p.picoCpusDaArvore <= cpus ? p : null;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Somente uma arvore completa e fisicamente possivel pode ensinar a reserva; lacunas de conteiner ficam na evidencia. */
|
|
179
|
+
function picoDaArvoreParaPerfil(medicao, cpus) {
|
|
180
|
+
const pico = medicao.picoCpusDaArvore;
|
|
181
|
+
return medicao.arvoreCompleta && !medicao.anomaliaDaArvore && pico !== null && pico > 0 && pico <= cpus ? pico : null;
|
|
182
|
+
}
|
|
183
|
+
function gravarPerfilIntegracao(chave, cpus, medicao) {
|
|
184
|
+
const picoCpusDaArvore = picoDaArvoreParaPerfil(medicao, cpus);
|
|
185
|
+
if (!chave || picoCpusDaArvore === null)
|
|
186
|
+
return;
|
|
187
|
+
const arquivo = arquivoDoPerfilIntegracao();
|
|
188
|
+
try {
|
|
189
|
+
fs.mkdirSync(path.dirname(arquivo), { recursive: true });
|
|
190
|
+
const temporario = `${arquivo}.${process.pid}.tmp`;
|
|
191
|
+
const perfil = { contrato: CONTRATO_PERFIL_INTEGRACAO, chave, cpus,
|
|
192
|
+
picoCpusDaArvore, completa: true, medidoEm: new Date().toISOString() };
|
|
193
|
+
fs.writeFileSync(temporario, JSON.stringify(perfil) + '\n', { mode: 0o600 });
|
|
194
|
+
fs.renameSync(temporario, arquivo);
|
|
195
|
+
}
|
|
196
|
+
catch { /* cache de desempenho ausente conserva a reserva; nunca derruba o CHECK */ }
|
|
197
|
+
}
|
|
198
|
+
const CLK_TCK = (() => {
|
|
199
|
+
const r = (0, node_child_process_1.spawnSync)('getconf', ['CLK_TCK'], { encoding: 'utf8', timeout: 1000 });
|
|
200
|
+
const n = Number(r.stdout?.trim());
|
|
201
|
+
return Number.isFinite(n) && n > 0 ? n : 100;
|
|
202
|
+
})();
|
|
203
|
+
function processosCpu() {
|
|
204
|
+
const tabela = new Map();
|
|
205
|
+
let nomes = [];
|
|
206
|
+
try {
|
|
207
|
+
nomes = fs.readdirSync('/proc');
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return tabela;
|
|
211
|
+
}
|
|
212
|
+
for (const nome of nomes) {
|
|
213
|
+
if (!/^\d+$/.test(nome))
|
|
214
|
+
continue;
|
|
215
|
+
try {
|
|
216
|
+
const stat = fs.readFileSync(`/proc/${nome}/stat`, 'utf8');
|
|
217
|
+
const depois = stat.slice(stat.lastIndexOf(')') + 2).trim().split(/\s+/);
|
|
218
|
+
tabela.set(Number(nome), { ppid: Number(depois[1]), cpuMs: (Number(depois[11]) + Number(depois[12])) * 1000 / CLK_TCK,
|
|
219
|
+
inicio: depois[19] });
|
|
220
|
+
}
|
|
221
|
+
catch { /* acabou entre a lista e a leitura */ }
|
|
222
|
+
}
|
|
223
|
+
return tabela;
|
|
224
|
+
}
|
|
225
|
+
function arvoreDeProcessos(raiz, tabela) {
|
|
226
|
+
const filhos = new Map();
|
|
227
|
+
for (const [pid, p] of tabela) {
|
|
228
|
+
const lista = filhos.get(p.ppid) ?? [];
|
|
229
|
+
lista.push(pid);
|
|
230
|
+
filhos.set(p.ppid, lista);
|
|
231
|
+
}
|
|
232
|
+
const vistos = new Set(), pilha = [raiz];
|
|
233
|
+
while (pilha.length) {
|
|
234
|
+
const pid = pilha.pop();
|
|
235
|
+
if (vistos.has(pid))
|
|
236
|
+
continue;
|
|
237
|
+
vistos.add(pid);
|
|
238
|
+
pilha.push(...(filhos.get(pid) ?? []));
|
|
239
|
+
}
|
|
240
|
+
return [...vistos];
|
|
241
|
+
}
|
|
242
|
+
function nomesDosContainers(arquivo) {
|
|
243
|
+
try {
|
|
244
|
+
return [...new Set(fs.readFileSync(arquivo, 'utf8').split('\n').filter(Boolean).flatMap((linha) => {
|
|
245
|
+
try {
|
|
246
|
+
const r = JSON.parse(linha);
|
|
247
|
+
return typeof r.nome === 'string' ? [r.nome] : [];
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
}))];
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function arquivoCpuDoContainer(nome) {
|
|
259
|
+
const r = (0, node_child_process_1.spawnSync)('docker', ['inspect', '--format', '{{.Id}} {{.State.Pid}}', nome], { encoding: 'utf8', timeout: 1000 });
|
|
260
|
+
const [id = '', pidTexto = ''] = r.stdout?.trim().split(/\s+/) ?? [];
|
|
261
|
+
const pid = Number(pidTexto);
|
|
262
|
+
if (r.status !== 0)
|
|
263
|
+
return { fonte: null, motivo: 'docker inspect nao encontrou o conteiner' };
|
|
264
|
+
if (!/^[a-f0-9]{12,64}$/.test(id) || !Number.isInteger(pid) || pid <= 0) {
|
|
265
|
+
return { fonte: null, motivo: 'PID ou identificador do conteiner indisponivel' };
|
|
266
|
+
}
|
|
267
|
+
let linhas = '';
|
|
268
|
+
try {
|
|
269
|
+
linhas = fs.readFileSync(`/proc/${pid}/cgroup`, 'utf8');
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return { fonte: null, motivo: 'cgroup do PID do conteiner indisponivel' };
|
|
273
|
+
}
|
|
274
|
+
const unificado = /^0::(.+)$/m.exec(linhas)?.[1];
|
|
275
|
+
const candidatos = [
|
|
276
|
+
...(unificado ? [{ arquivo: path.join('/sys/fs/cgroup', unificado, 'cpu.stat'), divisor: 1000 }] : []),
|
|
277
|
+
{ arquivo: path.join('/sys/fs/cgroup/system.slice', `docker-${id}.scope`, 'cpu.stat'), divisor: 1000 },
|
|
278
|
+
{ arquivo: path.join('/sys/fs/cgroup/docker', id, 'cpu.stat'), divisor: 1000 },
|
|
279
|
+
];
|
|
280
|
+
const legado = /^(?:\d+):[^\n]*(?:cpuacct|cpu)[^\n]*:(.+)$/m.exec(linhas)?.[1];
|
|
281
|
+
if (legado)
|
|
282
|
+
candidatos.push({ arquivo: path.join('/sys/fs/cgroup/cpuacct', legado, 'cpuacct.usage'), divisor: 1_000_000 });
|
|
283
|
+
const fonte = candidatos.find((x) => fs.existsSync(x.arquivo)) ?? null;
|
|
284
|
+
return fonte ? { fonte, motivo: null }
|
|
285
|
+
: { fonte: null, motivo: 'cpu.stat ausente no cgroup v2, no escopo do systemd e no caminho legado' };
|
|
286
|
+
}
|
|
287
|
+
function lerCpuDoCgroup(fonte) {
|
|
288
|
+
try {
|
|
289
|
+
const texto = fs.readFileSync(fonte.arquivo, 'utf8');
|
|
290
|
+
const bruto = fonte.arquivo.endsWith('cpu.stat') ? Number(/^usage_usec\s+(\d+)$/m.exec(texto)?.[1]) : Number(texto.trim());
|
|
291
|
+
return Number.isFinite(bruto) && bruto >= 0 ? bruto / fonte.divisor : null;
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Calcula o pico por fonte. A primeira leitura vira baseline; uma fonte que nasce depois dela contribui com o acumulado
|
|
299
|
+
* desde seu nascimento, sem despejar na janela o tempo anterior de processos ou conteineres que ja existiam.
|
|
300
|
+
*/
|
|
301
|
+
function picoCpuPorFonte(amostras, cpus) {
|
|
302
|
+
let pico = 0;
|
|
303
|
+
for (let i = 1; i < amostras.length; i++) {
|
|
304
|
+
const anterior = amostras[i - 1], atual = amostras[i];
|
|
305
|
+
if (!(atual.em > anterior.em))
|
|
306
|
+
continue;
|
|
307
|
+
let delta = 0;
|
|
308
|
+
for (const [fonte, acumulado] of Object.entries(atual.fontes)) {
|
|
309
|
+
const base = anterior.fontes[fonte];
|
|
310
|
+
delta += Math.max(0, acumulado - (base ?? 0));
|
|
311
|
+
}
|
|
312
|
+
pico = Math.max(pico, delta / (atual.em - anterior.em));
|
|
313
|
+
}
|
|
314
|
+
if (pico > cpus + 1e-9) {
|
|
315
|
+
return { picoCpus: null, anomalia: `pico medido de ${pico.toFixed(2)} CPU excede a capacidade de ${cpus} CPU` };
|
|
316
|
+
}
|
|
317
|
+
return { picoCpus: pico > 0 ? Math.ceil(pico * 100) / 100 : null, anomalia: null };
|
|
318
|
+
}
|
|
319
|
+
/** CPU acumulada e pico por janela da integracao; nomes registrados mas nunca observados tornam a medida incompleta. */
|
|
320
|
+
function sondarCpuDaIntegracao(pid, registro, cpus) {
|
|
321
|
+
const fontes = new Map();
|
|
322
|
+
const motivos = new Map();
|
|
323
|
+
const amostras = [];
|
|
324
|
+
const amostrasDaArvore = [];
|
|
325
|
+
const primeiraCpuDosContainers = new Map();
|
|
326
|
+
const ultimaCpuDosContainers = new Map();
|
|
327
|
+
const amostrar = () => {
|
|
328
|
+
const tabela = processosCpu();
|
|
329
|
+
const arvore = {};
|
|
330
|
+
for (const p of arvoreDeProcessos(pid, tabela)) {
|
|
331
|
+
const x = tabela.get(p);
|
|
332
|
+
if (x)
|
|
333
|
+
arvore[`processo:${p}:${x.inicio}`] = x.cpuMs;
|
|
334
|
+
}
|
|
335
|
+
const atuais = { ...arvore };
|
|
336
|
+
for (const nome of nomesDosContainers(registro)) {
|
|
337
|
+
let fonte = fontes.get(nome);
|
|
338
|
+
if (!fonte) {
|
|
339
|
+
const busca = arquivoCpuDoContainer(nome);
|
|
340
|
+
if (busca.fonte) {
|
|
341
|
+
fonte = busca.fonte;
|
|
342
|
+
fontes.set(nome, fonte);
|
|
343
|
+
motivos.delete(nome);
|
|
344
|
+
}
|
|
345
|
+
else if (busca.motivo)
|
|
346
|
+
motivos.set(nome, busca.motivo);
|
|
347
|
+
}
|
|
348
|
+
const cpu = fonte ? lerCpuDoCgroup(fonte) : null;
|
|
349
|
+
if (cpu !== null) {
|
|
350
|
+
atuais[`conteiner:${nome}`] = cpu;
|
|
351
|
+
if (!primeiraCpuDosContainers.has(nome))
|
|
352
|
+
primeiraCpuDosContainers.set(nome, amostras.length === 0 ? cpu : 0);
|
|
353
|
+
ultimaCpuDosContainers.set(nome, cpu);
|
|
354
|
+
motivos.delete(nome);
|
|
355
|
+
}
|
|
356
|
+
else if (fonte)
|
|
357
|
+
motivos.set(nome, 'contador de CPU do cgroup ilegivel');
|
|
358
|
+
}
|
|
359
|
+
const em = Date.now();
|
|
360
|
+
amostrasDaArvore.push({ em, fontes: arvore });
|
|
361
|
+
amostras.push({ em, fontes: atuais });
|
|
362
|
+
};
|
|
363
|
+
return {
|
|
364
|
+
amostrar,
|
|
365
|
+
fechar: (arvoreMs, paredeMs) => {
|
|
366
|
+
amostrar();
|
|
367
|
+
const nomes = nomesDosContainers(registro);
|
|
368
|
+
const containersMs = nomes.reduce((total, nome) => total + Math.max(0, (ultimaCpuDosContainers.get(nome) ?? 0) - (primeiraCpuDosContainers.get(nome) ?? 0)), 0);
|
|
369
|
+
const ausentes = nomes.filter((nome) => !ultimaCpuDosContainers.has(nome));
|
|
370
|
+
const pico = picoCpuPorFonte(amostras, cpus);
|
|
371
|
+
const picoDaArvore = picoCpuPorFonte(amostrasDaArvore, cpus);
|
|
372
|
+
const arvoreCompleta = arvoreMs > 0 && picoDaArvore.picoCpus !== null && !picoDaArvore.anomalia;
|
|
373
|
+
return { arvoreMs, containersMs, totalMs: arvoreMs + containersMs, paredeMs,
|
|
374
|
+
picoCpus: pico.picoCpus, picoCpusDaArvore: picoDaArvore.picoCpus,
|
|
375
|
+
completa: arvoreCompleta && ausentes.length === 0 && !pico.anomalia, arvoreCompleta,
|
|
376
|
+
containers: nomes, ausentes, motivosDosAusentes: ausentes.map((container) => ({ container,
|
|
377
|
+
motivo: motivos.get(container) ?? 'conteiner terminou antes de uma amostra completa' })),
|
|
378
|
+
anomalia: pico.anomalia, anomaliaDaArvore: picoDaArvore.anomalia };
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
}
|
|
80
382
|
/**
|
|
81
383
|
* O lado do auxiliar: lanca cada item quando os de `apos` terminam e ha vaga, espera todos e grava `resultado.json` ao
|
|
82
384
|
* lado das saidas.
|
|
@@ -84,10 +386,21 @@ function vagas(max) {
|
|
|
84
386
|
async function rodarEspecificacao(esp) {
|
|
85
387
|
const comGrupo = fs.existsSync(TIMEOUT_BIN);
|
|
86
388
|
const usaAgendador = esp.itens.some((i) => i.papelCpu === 'hermetica');
|
|
87
|
-
const
|
|
389
|
+
const cpus = esp.cpus ?? os.availableParallelism();
|
|
390
|
+
const chaveDoPerfil = usaAgendador ? chaveDoPerfilIntegracao(esp.itens, cpus) : null;
|
|
391
|
+
const perfil = esp.medicaoCpu?.cpusIntegracao === undefined ? lerPerfilIntegracao(chaveDoPerfil, cpus) : null;
|
|
392
|
+
const medicao = esp.medicaoCpu ?? await (0, agendador_cpu_1.medirCpuDoGrupo)();
|
|
393
|
+
if (perfil)
|
|
394
|
+
Object.assign(medicao, { cpusIntegracao: perfil.picoCpusDaArvore, fonteCpusIntegracao: 'medida' });
|
|
395
|
+
else if (medicao.cpusIntegracao === undefined)
|
|
396
|
+
Object.assign(medicao, { cpusIntegracao: null, fonteCpusIntegracao: 'conservadora' });
|
|
397
|
+
const plano = usaAgendador ? (0, agendador_cpu_1.planejarCpuDoGrupo)(cpus, medicao) : undefined;
|
|
88
398
|
const sinalDaReserva = plano && !plano.saturado ? path.join(esp.dir, 'reserva-devolvida.json') : undefined;
|
|
89
399
|
const itens = plano ? aplicarPlano(esp.itens, plano, sinalDaReserva) : esp.itens;
|
|
90
|
-
const
|
|
400
|
+
const tetoInformado = esp.maxParalelo && esp.maxParalelo > 0 ? esp.maxParalelo : itens.length;
|
|
401
|
+
const limite = vagas(Math.max(1, Math.min(tetoInformado, (0, agendador_cpu_1.workersDaReserva)(cpus))));
|
|
402
|
+
const exclusivos = recursosExclusivos();
|
|
403
|
+
const totalDeLanes = itens.filter((item) => item.nome.startsWith('test:')).length;
|
|
91
404
|
const porNome = new Map();
|
|
92
405
|
const indice = new Map(itens.map((item, i) => [item.nome, i]));
|
|
93
406
|
const vezDoItem = (item, i) => {
|
|
@@ -102,12 +415,14 @@ async function rodarEspecificacao(esp) {
|
|
|
102
415
|
if (j !== undefined)
|
|
103
416
|
await vezDoItem(itens[j], j);
|
|
104
417
|
}
|
|
418
|
+
const soltarRecursos = await exclusivos.tomar(item.recursos ?? []);
|
|
105
419
|
await limite.tomar();
|
|
106
420
|
try {
|
|
107
421
|
return await lancar(item, i);
|
|
108
422
|
}
|
|
109
423
|
finally {
|
|
110
424
|
limite.soltar();
|
|
425
|
+
soltarRecursos();
|
|
111
426
|
}
|
|
112
427
|
});
|
|
113
428
|
porNome.set(item.nome, vez);
|
|
@@ -119,29 +434,85 @@ async function rodarEspecificacao(esp) {
|
|
|
119
434
|
if (!sinalDaReserva || !reservados.some((item) => item.papelCpu === 'varredura')
|
|
120
435
|
|| !reservados.some((item) => item.papelCpu === 'integracao'))
|
|
121
436
|
return;
|
|
122
|
-
|
|
123
|
-
|
|
437
|
+
const varreduras = reservados.filter((item) => item.papelCpu === 'varredura');
|
|
438
|
+
const integracoes = reservados.filter((item) => item.papelCpu === 'integracao');
|
|
439
|
+
const degrau = async (origem, pecas, vagas, antes) => {
|
|
440
|
+
await Promise.all(pecas.map((item) => vezDoItem(item, indice.get(item.nome))));
|
|
441
|
+
fs.appendFileSync(sinalDaReserva, JSON.stringify({ contrato: 'ork.check.reserva-devolvida/v2', origem,
|
|
442
|
+
em: new Date().toISOString(), vagasGanhas: vagas - antes, vagas }) + '\n', { mode: 0o600 });
|
|
443
|
+
};
|
|
444
|
+
if (!plano.integracaoEmParalelo) {
|
|
445
|
+
await degrau('varredura', varreduras, plano.cpusHermeticaAposVarredura, plano.cpusHermetica);
|
|
446
|
+
await degrau('integracao', integracoes, plano.cpusHermeticaAposReserva, plano.cpusHermeticaAposVarredura);
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
// As promessas escrevem em série no event loop; o primeiro término recebe somente a reserva da própria peça.
|
|
450
|
+
let vagas = plano.cpusHermetica;
|
|
451
|
+
const aoTerminar = async (origem, pecas, libera) => {
|
|
452
|
+
await Promise.all(pecas.map((item) => vezDoItem(item, indice.get(item.nome))));
|
|
453
|
+
const antes = vagas;
|
|
454
|
+
vagas = Math.min(plano.cpus, vagas + libera);
|
|
455
|
+
fs.appendFileSync(sinalDaReserva, JSON.stringify({ contrato: 'ork.check.reserva-devolvida/v2', origem,
|
|
456
|
+
em: new Date().toISOString(), vagasGanhas: vagas - antes, vagas }) + '\n', { mode: 0o600 });
|
|
457
|
+
};
|
|
458
|
+
await Promise.all([aoTerminar('varredura', varreduras, plano.cpusDaFatia),
|
|
459
|
+
aoTerminar('integracao', integracoes, plano.cpusIntegracao)]);
|
|
460
|
+
}
|
|
124
461
|
};
|
|
125
462
|
const [, terminados] = await Promise.all([devolverReserva(), todos]);
|
|
463
|
+
const medidaDaIntegracao = terminados.find((t) => t.medicaoCpu)?.medicaoCpu;
|
|
464
|
+
if (plano && medidaDaIntegracao) {
|
|
465
|
+
plano.medicaoCpuIntegracao = medidaDaIntegracao;
|
|
466
|
+
gravarPerfilIntegracao(chaveDoPerfil, cpus, medidaDaIntegracao);
|
|
467
|
+
}
|
|
126
468
|
fs.writeFileSync(path.join(esp.dir, 'resultado.json'), JSON.stringify({ terminados, ...(plano ? { agendamentoCpu: plano } : {}) }), { mode: 0o600 });
|
|
127
469
|
function lancar(item, i) {
|
|
128
470
|
return new Promise((resolve) => {
|
|
129
471
|
const saida = fs.openSync(path.join(esp.dir, `${i}.out`), 'w', 0o600);
|
|
130
472
|
const erro = fs.openSync(path.join(esp.dir, `${i}.err`), 'w', 0o600);
|
|
131
473
|
const inicio = Date.now();
|
|
132
|
-
const
|
|
474
|
+
const medirIntegracao = item.papelCpu === 'integracao';
|
|
475
|
+
const registro = path.join(esp.dir, `${i}.containers.jsonl`);
|
|
476
|
+
const tempo = path.join(esp.dir, `${i}.tempo`);
|
|
477
|
+
if (medirIntegracao)
|
|
478
|
+
fs.writeFileSync(registro, '', { mode: 0o600 });
|
|
479
|
+
const base = argvDoItem(item, comGrupo);
|
|
480
|
+
const medido = medirIntegracao && fs.existsSync(TIME_BIN)
|
|
481
|
+
? [TIME_BIN, '-f', '%U %S', '-o', tempo, ...base] : base;
|
|
482
|
+
const [bin, ...args] = medido;
|
|
133
483
|
let fim = null;
|
|
484
|
+
let sonda = null;
|
|
485
|
+
let relogio = null;
|
|
134
486
|
const encerrar = (t) => {
|
|
135
487
|
if (fim)
|
|
136
488
|
return;
|
|
137
|
-
|
|
489
|
+
if (relogio)
|
|
490
|
+
clearInterval(relogio);
|
|
491
|
+
let arvoreMs = 0;
|
|
492
|
+
if (medirIntegracao) {
|
|
493
|
+
try {
|
|
494
|
+
const [user, sys] = fs.readFileSync(tempo, 'utf8').trim().split(/\s+/).map(Number);
|
|
495
|
+
arvoreMs = (user + sys) * 1000;
|
|
496
|
+
}
|
|
497
|
+
catch { /* medida incompleta abaixo */ }
|
|
498
|
+
}
|
|
499
|
+
const medicaoCpu = medirIntegracao && sonda ? sonda.fechar(arvoreMs, t.duracaoMs) : undefined;
|
|
500
|
+
fim = { ...t, ...(medicaoCpu ? { medicaoCpu } : {}) };
|
|
138
501
|
fs.closeSync(saida);
|
|
139
502
|
fs.closeSync(erro);
|
|
140
|
-
resolve(
|
|
503
|
+
resolve(fim);
|
|
141
504
|
};
|
|
142
505
|
// Sem o `timeout(1)`, o proprio auxiliar mata pelo prazo, como o `spawnSync` do `exec` faria.
|
|
143
|
-
const
|
|
506
|
+
const workers = workersDaLane(item, plano, cpus, totalDeLanes);
|
|
507
|
+
const ambiente = { ...process.env, ...item.ambiente, ...(workers === null ? {} : { [agendador_cpu_1.ENV_WORKERS]: String(workers) }),
|
|
508
|
+
...(medirIntegracao ? { [agendador_cpu_1.ENV_CONTAINERS_DA_LANE]: registro } : {}) };
|
|
509
|
+
const filho = (0, node_child_process_1.spawn)(bin, args, { cwd: item.cwd, env: ambiente, stdio: ['ignore', saida, erro],
|
|
144
510
|
...(comGrupo ? {} : { timeout: item.prazoMs, killSignal: 'SIGTERM' }) });
|
|
511
|
+
if (medirIntegracao) {
|
|
512
|
+
sonda = sondarCpuDaIntegracao(filho.pid, registro, cpus);
|
|
513
|
+
sonda.amostrar();
|
|
514
|
+
relogio = setInterval(sonda.amostrar, INTERVALO_CPU_MS);
|
|
515
|
+
}
|
|
145
516
|
filho.on('error', (e) => encerrar({ nome: item.nome, code: null, signal: null,
|
|
146
517
|
error: e.code ?? e.message, duracaoMs: Date.now() - inicio, comGrupo }));
|
|
147
518
|
filho.on('close', (code, signal) => encerrar({ nome: item.nome, code, signal,
|
|
@@ -170,24 +541,25 @@ function aplicarPlano(originais, plano, sinalDaReserva) {
|
|
|
170
541
|
const cpusContainer = String(plano.cpusPorContainer);
|
|
171
542
|
ambiente(hermetica, { [agendador_cpu_1.ENV_CPUS_DA_LANE]: String(plano.cpusHermetica), [agendador_cpu_1.ENV_CPUS_DOCKER]: cpusContainer,
|
|
172
543
|
...(sinalDaReserva && varredura && integracoes.length > 0 ? {
|
|
544
|
+
[agendador_cpu_1.ENV_CPUS_DA_LANE_APOS_VARREDURA]: String(plano.cpusHermeticaAposVarredura),
|
|
173
545
|
[agendador_cpu_1.ENV_CPUS_DA_LANE_APOS_RESERVA]: String(plano.cpusHermeticaAposReserva), [agendador_cpu_1.ENV_SINAL_DA_RESERVA]: sinalDaReserva,
|
|
174
546
|
} : {}) });
|
|
175
547
|
ambiente(varredura, {
|
|
176
548
|
ORK_CHECK_VARREDURA_PARALELA: String(plano.ferramentasDaVarredura),
|
|
177
549
|
[agendador_cpu_1.ENV_CPUS_DOCKER]: cpusContainer,
|
|
178
550
|
});
|
|
179
|
-
let
|
|
551
|
+
let anteriorIntegracao;
|
|
180
552
|
for (const item of integracoes) {
|
|
181
553
|
ambiente(item, { [agendador_cpu_1.ENV_CPUS_DA_LANE]: String(plano.cpusIntegracao), [agendador_cpu_1.ENV_CPUS_DOCKER]: cpusContainer });
|
|
182
|
-
depender(item, [
|
|
183
|
-
|
|
554
|
+
depender(item, [anteriorIntegracao ?? (plano.integracaoEmParalelo ? undefined : varredura?.nome)]);
|
|
555
|
+
anteriorIntegracao = item.nome;
|
|
184
556
|
}
|
|
185
557
|
if (plano.saturado)
|
|
186
558
|
depender(varredura, [hermetica?.nome]);
|
|
187
559
|
// Comandos sem papel conhecido nao disputam CPU com as tres pecas: fecham a cadeia, um por vez.
|
|
188
560
|
for (const item of outros) {
|
|
189
|
-
depender(item, [hermetica?.nome,
|
|
190
|
-
|
|
561
|
+
depender(item, [hermetica?.nome, varredura?.nome, anteriorIntegracao]);
|
|
562
|
+
anteriorIntegracao = item.nome;
|
|
191
563
|
}
|
|
192
564
|
return itens;
|
|
193
565
|
}
|