Add initial setup guide, test scripts, and configuration files
This commit is contained in:
parent
4e5350faab
commit
0bea9a93d8
3
rinha-test/.gitignore
vendored
Normal file
3
rinha-test/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
partial-results.json
|
||||
report.html
|
||||
docker-compose.logs
|
||||
43
rinha-test/MINIGUIA.md
Normal file
43
rinha-test/MINIGUIA.md
Normal file
@ -0,0 +1,43 @@
|
||||
*Esse guia foi gentilmente elaborado por [leonardosegfault](https://github.com/leonardosegfault). Link do guia original [aqui](https://github.com/zanfranceschi/rinha-de-backend-2025/issues/11).*
|
||||
|
||||
# Mini Guia de Setup
|
||||
|
||||
Assim como eu, acredito que haja muitas pessoas que tiveram ou terão dúvidas de realizar a configuração inicial, então decidi escrever esse minúsculo guia temporário para que você também não tenha que ficar caçando no repositório, como eu fiz.
|
||||
|
||||
O objetivo é ser bastante breve, então eu intencionalmente omiti alguns detalhes pois a própria documentação dos projetos já instruem adequadamente. Se tiver problemas ou soluções específicas, abra uma Issue que o pessoal te ajuda.
|
||||
|
||||
A IA também pode ser sua amiga, então elabore um texto bonitinho para que ela compreenda seu problema e consiga chegar numa solução — apesar de eu recomendar que faça sozinho para exercitar o cérebro.
|
||||
|
||||
### 1. Docker
|
||||
|
||||
Docker será utilizado do início ao final do projeto para que haja um ambiente consistente e isolado. Assim tudo poderá ser configurado e rodado com poucos comandos.
|
||||
|
||||
[Depois de instalado](https://docs.docker.com/get-started/get-docker/) vá até a pasta `payment-processor` e rode `docker compose up` para inicializar os servidores do payment processor.
|
||||
|
||||
Se tudo ocorrer como esperado, o servidor default (http://127.0.0.1:8001/) e de fallback (http://127.0.0.1:8002/) estarão online e com [seus endpoints](https://github.com/zanfranceschi/rinha-de-backend-2025/blob/main/INSTRUCOES.md#detalhes-dos-endpoints) funcionando bonitinho.
|
||||
|
||||
### 2. K6
|
||||
|
||||
Essa ferramenta [será utilizada para testar requisições na sua API local](https://github.com/zanfranceschi/rinha-de-backend-2025/tree/main/rinha-test#instru%C3%A7%C3%B5es-para-execu%C3%A7%C3%A3o-dos-testes-locais). Assim, você poderá validar se seu backend está processando as informações direitinho e tankando o estresse.
|
||||
|
||||
Siga as [instruções para instalar no seu sistema operacional](https://grafana.com/docs/k6/latest/set-up/install-k6/) e depois vá para a pasta `rinha-test` para rodar o teste com `k6 run rinha.js` — mas não agora, tem algumas coisas que ainda serão resolvidas logo abaixo.
|
||||
|
||||
### 3. Seu Backend
|
||||
|
||||
Sinta-se livre para criar o seu backend com os [endpoints](https://github.com/zanfranceschi/rinha-de-backend-2025/blob/main/INSTRUCOES.md#detalhes-dos-endpoints) necessários.
|
||||
|
||||
Se por algum motivo você quiser testar apenas um cenário, comente os demais no arquivo [`rinha.js`](https://github.com/zanfranceschi/rinha-de-backend-2025/blob/2ac3f62f225afd6748e9164be3c4d4ebe5d3474e/rinha-test/rinha.js#L35-L128).
|
||||
|
||||
### 4. Divirta-se
|
||||
|
||||
Desenvolva sua aplicação baseada nas especificações e realize os testes para checar se está tudo nos conformes.
|
||||
|
||||
Você também pode assistir [sua API apanhando ao vivo através do dashboard](https://github.com/zanfranceschi/rinha-de-backend-2025/tree/main/rinha-test#acompanhando-os-testes-via-dashboard-e-report), se quiser:
|
||||
|
||||

|
||||
|
||||
*(demo da minha API capenga)*
|
||||
|
||||
Não se esqueça de fechar a página ou clicar no **Report** para que o teste finalize e exiba os stats no terminal.
|
||||
|
||||
Boa sorte!
|
||||
46
rinha-test/README.md
Normal file
46
rinha-test/README.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Rinha de Backend - 2025
|
||||
|
||||
## Instruções para Execução dos Testes Locais
|
||||
|
||||
A ferramenta de testes para essa edição da Rinha de Backend é o [k6](https://k6.io/).
|
||||
|
||||

|
||||
|
||||
Instale o k6 caso ainda já não o tenha feito. Siga as instruções [aqui](https://grafana.com/docs/k6/latest/set-up/install-k6/).
|
||||
|
||||
### Execução dos Testes
|
||||
|
||||
Antes de executar os testes, você precisa subir os containers do seu backend e dos [Payment Processors](../payment-processor/docker-compose.yml). Após ter feito isso, basta entrar no diretório [rinha-test](./) e executar o seguinte comando:
|
||||
|
||||
```shell
|
||||
k6 run rinha.js
|
||||
```
|
||||
|
||||
Você deverá ver algo como a imagem seguinte.
|
||||

|
||||
|
||||
|
||||
### Acompanhando os Testes via Dashboard e Report
|
||||
|
||||
Se quiser acompanhar os testes via dashboard e obter um relatório HTML do k6, você pode configurar as seguintes variáveis de ambiente. Para mais informações, acesse a [documentação oficial](https://grafana.com/docs/k6/latest/results-output/web-dashboard/).
|
||||
|
||||
|
||||
```shell
|
||||
export K6_WEB_DASHBOARD=true
|
||||
export K6_WEB_DASHBOARD_PORT=5665
|
||||
export K6_WEB_DASHBOARD_PERIOD=2s
|
||||
export K6_WEB_DASHBOARD_OPEN=true
|
||||
export K6_WEB_DASHBOARD_EXPORT='report.html'
|
||||
```
|
||||
|
||||
### Número Máximo de Requisições Simultâneas
|
||||
|
||||
Se quiser alterar o número máximo de requisições simultâneas, você poderá definiar a variável `MAX_REQUESTS` no comando para executar o teste em vez de alterar o script.
|
||||
|
||||
```shell
|
||||
k6 run -e MAX_REQUESTS=550 rinha.js
|
||||
```
|
||||
|
||||
### Contribuição com o Script de Teste
|
||||
|
||||
O script de testes foi feito por mim (Zan), mas como não tenho proficiência em Javascript, muito provavelmente existem muitos pontos de melhoria. Sugestões de melhoria no script de testes são muito bem-vindas! Abra um PR e contribua!
|
||||
45
rinha-test/previa_resultados_json.py
Normal file
45
rinha-test/previa_resultados_json.py
Normal file
@ -0,0 +1,45 @@
|
||||
import json
|
||||
from os import walk
|
||||
from os.path import join, isfile
|
||||
import sys
|
||||
|
||||
summary = []
|
||||
|
||||
for (dirpath, dirnames, filenames) in walk("../participantes/"):
|
||||
for filename in filenames:
|
||||
entry = {}
|
||||
if (filename == "info.json"):
|
||||
info_file = join(dirpath, "info.json")
|
||||
with open(info_file) as f:
|
||||
try:
|
||||
entry.update({
|
||||
"info": json.loads(f.read())
|
||||
})
|
||||
except Exception as ex:
|
||||
entry.update({
|
||||
"info": None
|
||||
})
|
||||
|
||||
partial_result_file = join(dirpath, "partial-results.json")
|
||||
errors_log_file = join(dirpath, "error.logs")
|
||||
entry.update({"erro_na_execucao": isfile(errors_log_file)})
|
||||
|
||||
if (isfile(partial_result_file)):
|
||||
with open(partial_result_file) as f:
|
||||
partial_results = f.read()
|
||||
if (partial_results):
|
||||
entry.update({
|
||||
"resultado_partial": json.loads(partial_results)
|
||||
})
|
||||
else:
|
||||
entry.update({
|
||||
"resultado_partial": None
|
||||
})
|
||||
|
||||
if (entry):
|
||||
summary.append(entry)
|
||||
|
||||
summary_file = sys.argv[1] if len(sys.argv) > 1 else "../previa-resultados+participantes-info.json"
|
||||
|
||||
with open(summary_file, 'w') as pf:
|
||||
pf.write(json.dumps(summary))
|
||||
149
rinha-test/requests.js
Normal file
149
rinha-test/requests.js
Normal file
@ -0,0 +1,149 @@
|
||||
import { Httpx } from 'https://jslib.k6.io/httpx/0.1.0/index.js';
|
||||
import exec from 'k6/execution';
|
||||
|
||||
const initialToken = '123';
|
||||
export const token = __ENV.TOKEN ?? initialToken;
|
||||
|
||||
const paymentProcessorDefaultHttp = new Httpx({
|
||||
baseURL: 'http://localhost:8001',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Rinha-Token': token
|
||||
},
|
||||
timeout: 1500,
|
||||
});
|
||||
|
||||
const paymentProcessorFallbacktHttp = new Httpx({
|
||||
baseURL: 'http://localhost:8002',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Rinha-Token': token
|
||||
},
|
||||
timeout: 1500,
|
||||
});
|
||||
|
||||
const backendHttp = new Httpx({
|
||||
baseURL: "http://localhost:9999",
|
||||
//baseURL: "http://localhost:5123",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout: 1500,
|
||||
});
|
||||
|
||||
const paymentProcessorHttp = {
|
||||
"default": paymentProcessorDefaultHttp,
|
||||
"fallback": paymentProcessorFallbacktHttp,
|
||||
};
|
||||
|
||||
export async function setPPToken(service, token) {
|
||||
|
||||
const httpClient = paymentProcessorHttp[service];
|
||||
const params = { headers: { 'X-Rinha-Token': initialToken } };
|
||||
|
||||
const payload = JSON.stringify({
|
||||
token: token
|
||||
});
|
||||
|
||||
const response = await httpClient.asyncPut('/admin/configurations/token', payload, params);
|
||||
|
||||
if (response.status != 204) {
|
||||
exec.test.abort(`Erro ao definir token para ${service} (HTTP ${response.status}).`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function setPPDelay(service, ms) {
|
||||
|
||||
const httpClient = paymentProcessorHttp[service];
|
||||
|
||||
const payload = JSON.stringify({
|
||||
delay: ms
|
||||
});
|
||||
|
||||
const response = await httpClient.asyncPut('/admin/configurations/delay', payload);
|
||||
|
||||
if (response.status != 200) {
|
||||
exec.test.abort(`Erro ao definir delay para ${service} (HTTP ${response.status}).`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function setPPFailure(service, failure) {
|
||||
|
||||
const httpClient = paymentProcessorHttp[service];
|
||||
|
||||
const payload = JSON.stringify({
|
||||
failure: failure
|
||||
});
|
||||
|
||||
const response = await httpClient.asyncPut('/admin/configurations/failure', payload);
|
||||
|
||||
if (response.status != 200) {
|
||||
exec.test.abort(`Erro ao definir failure para ${service} (HTTP ${response.status}).`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetPPDatabase(service) {
|
||||
|
||||
const httpClient = paymentProcessorHttp[service];
|
||||
const response = await httpClient.asyncPost('/admin/purge-payments');
|
||||
|
||||
if (response.status != 200) {
|
||||
exec.test.abort(`Erro ao resetar database para ${service} (HTTP ${response.status}).`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPPPaymentsSummary(service, from, to) {
|
||||
|
||||
const httpClient = paymentProcessorHttp[service];
|
||||
const response = await httpClient.asyncGet(`/admin/payments-summary?from=${from}&to=${to}`);
|
||||
|
||||
if (response.status == 200) {
|
||||
return JSON.parse(response.body);
|
||||
}
|
||||
|
||||
console.error(`Não foi possível obter resposta de '/admin/payments-summary?from=${from}&to=${to}' para ${service} (HTTP ${response.status})`);
|
||||
|
||||
return {
|
||||
totalAmount: 0,
|
||||
totalRequests: 0,
|
||||
feePerTransaction: 0,
|
||||
totalFee: 0
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetBackendDatabase() {
|
||||
|
||||
try {
|
||||
await backendHttp.asyncPost('/purge-payments');
|
||||
} catch (error) {
|
||||
console.info("Seu backend provavelmente não possui um endpoint para resetar o banco. Isso não é um problem.", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getBackendPaymentsSummary(from, to) {
|
||||
|
||||
const response = await backendHttp.asyncGet(`/payments-summary?from=${from}&to=${to}`);
|
||||
|
||||
if (response.status == 200) {
|
||||
return JSON.parse(response.body);
|
||||
}
|
||||
|
||||
console.error(`Não foi possível obter resposta de '/payments-summary?from=${from}&to=${to}' para o backend (HTTP ${response.status})`);
|
||||
|
||||
return {
|
||||
default: {
|
||||
totalAmount: 0,
|
||||
totalRequests: 0
|
||||
},
|
||||
fallback: {
|
||||
totalAmount: 0,
|
||||
totalRequests: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function requestBackendPayment(payload) {
|
||||
|
||||
const response = await backendHttp.asyncPost('/payments', JSON.stringify(payload));
|
||||
return response;
|
||||
}
|
||||
346
rinha-test/rinha.js
Normal file
346
rinha-test/rinha.js
Normal file
@ -0,0 +1,346 @@
|
||||
import { textSummary } from "https://jslib.k6.io/k6-summary/0.1.0/index.js";
|
||||
import { uuidv4 } from "https://jslib.k6.io/k6-utils/1.4.0/index.js";
|
||||
import { sleep } from "k6";
|
||||
import exec from "k6/execution";
|
||||
import { Counter } from "k6/metrics";
|
||||
import {
|
||||
token,
|
||||
setPPToken,
|
||||
setPPDelay,
|
||||
setPPFailure,
|
||||
resetPPDatabase,
|
||||
getPPPaymentsSummary,
|
||||
resetBackendDatabase,
|
||||
getBackendPaymentsSummary,
|
||||
requestBackendPayment
|
||||
} from "./requests.js";
|
||||
|
||||
// https://mikemcl.github.io/big.js/
|
||||
import Big from "https://cdn.jsdelivr.net/npm/big.js@7.0.1/big.min.js";
|
||||
|
||||
const MAX_REQUESTS = __ENV.MAX_REQUESTS ?? 500;
|
||||
|
||||
export const options = {
|
||||
summaryTrendStats: [
|
||||
"p(99)",
|
||||
"count",
|
||||
],
|
||||
thresholds: {
|
||||
//http_req_failed: [{ threshold: "rate < 0.01", abortOnFail: false }],
|
||||
//payments_inconsistency: ["count == 0"]
|
||||
//http_req_duration: ['p(99) < 50'],
|
||||
//payments_count: ['count > 3500'],
|
||||
},
|
||||
scenarios: {
|
||||
payments: {
|
||||
exec: "payments",
|
||||
executor: "ramping-vus",
|
||||
startVUs: 1,
|
||||
gracefulRampDown: "0s",
|
||||
stages: [{ target: MAX_REQUESTS, duration: "60s" }],
|
||||
},
|
||||
payments_consistency: {
|
||||
exec: "checkPaymentsConsistency",
|
||||
executor: "constant-vus",
|
||||
//startTime: "5s",
|
||||
duration: "60s",
|
||||
vus: "1",
|
||||
},
|
||||
stage_00: {
|
||||
exec: "define_stage",
|
||||
startTime: "1s",
|
||||
executor: "constant-vus",
|
||||
vus: 1,
|
||||
duration: "1s",
|
||||
tags: {
|
||||
defaultDelay: "0",
|
||||
defaultFailure: "false",
|
||||
fallbackDelay: "0",
|
||||
fallbackFailure: "false",
|
||||
},
|
||||
},
|
||||
stage_01: {
|
||||
exec: "define_stage",
|
||||
startTime: "10s",
|
||||
executor: "constant-vus",
|
||||
vus: 1,
|
||||
duration: "1s",
|
||||
tags: {
|
||||
defaultDelay: "100",
|
||||
defaultFailure: "false",
|
||||
fallbackDelay: "0",
|
||||
fallbackFailure: "false",
|
||||
},
|
||||
},
|
||||
stage_02: {
|
||||
exec: "define_stage",
|
||||
startTime: "20s",
|
||||
executor: "constant-vus",
|
||||
vus: 1,
|
||||
duration: "1s",
|
||||
tags: {
|
||||
defaultDelay: "100",
|
||||
defaultFailure: "true",
|
||||
fallbackDelay: "0",
|
||||
fallbackFailure: "false",
|
||||
},
|
||||
},
|
||||
stage_03: {
|
||||
exec: "define_stage",
|
||||
startTime: "30s",
|
||||
executor: "constant-vus",
|
||||
vus: 1,
|
||||
duration: "1s",
|
||||
tags: {
|
||||
defaultDelay: "2000",
|
||||
defaultFailure: "true",
|
||||
fallbackDelay: "1000",
|
||||
fallbackFailure: "true",
|
||||
},
|
||||
},
|
||||
stage_04: {
|
||||
exec: "define_stage",
|
||||
startTime: "40s",
|
||||
executor: "constant-vus",
|
||||
vus: 1,
|
||||
duration: "1s",
|
||||
tags: {
|
||||
defaultDelay: "20",
|
||||
defaultFailure: "false",
|
||||
fallbackDelay: "20",
|
||||
fallbackFailure: "false",
|
||||
},
|
||||
},
|
||||
stage_05: {
|
||||
exec: "define_stage",
|
||||
startTime: "50s",
|
||||
executor: "constant-vus",
|
||||
vus: 1,
|
||||
duration: "1s",
|
||||
tags: {
|
||||
defaultDelay: "0",
|
||||
defaultFailure: "false",
|
||||
fallbackDelay: "5000",
|
||||
fallbackFailure: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const transactionsSuccessCounter = new Counter("transactions_success");
|
||||
const transactionsFailureCounter = new Counter("transactions_failure");
|
||||
const totalTransactionsAmountCounter = new Counter("total_transactions_amount");
|
||||
const paymentsInconsistencyCounter = new Counter("payments_inconsistency");
|
||||
|
||||
const defaultTotalAmountCounter = new Counter("default_total_amount");
|
||||
const defaultTotalRequestsCounter = new Counter("default_total_requests");
|
||||
const fallbackTotalAmountCounter = new Counter("fallback_total_amount");
|
||||
const fallbackTotalRequestsCounter = new Counter("fallback_total_requests");
|
||||
|
||||
const defaultTotalFeeCounter = new Counter("default_total_fee");
|
||||
const fallbackTotalFeeCounter = new Counter("fallback_total_fee");
|
||||
|
||||
export async function setup() {
|
||||
await setPPToken("default", token);
|
||||
await setPPToken("fallback", token);
|
||||
await resetPPDatabase("default");
|
||||
await resetPPDatabase("fallback");
|
||||
await resetBackendDatabase();
|
||||
}
|
||||
|
||||
const paymentRequestFixedAmount = new Big(19.90);
|
||||
|
||||
export async function teardown() {
|
||||
|
||||
const to = new Date();
|
||||
const from = new Date(to.getTime() - 70 * 1000); // 1 minuto e 10 segundos atrás
|
||||
|
||||
console.info(`summaries from ${from.toISOString()} to ${to.toISOString()}`);
|
||||
|
||||
const defaultResponse = await getPPPaymentsSummary("default", from.toISOString(), to.toISOString());
|
||||
const fallbackResponse = await getPPPaymentsSummary("fallback", from.toISOString(), to.toISOString());
|
||||
const backendPaymentsSummary = await getBackendPaymentsSummary(from.toISOString(), to.toISOString());
|
||||
|
||||
const totalTransactionsAmount = new Big(backendPaymentsSummary.default.totalAmount)
|
||||
.plus(backendPaymentsSummary.fallback.totalAmount);
|
||||
|
||||
totalTransactionsAmountCounter.add(totalTransactionsAmount.toNumber());
|
||||
|
||||
defaultTotalAmountCounter.add(backendPaymentsSummary.default.totalAmount);
|
||||
defaultTotalRequestsCounter.add(backendPaymentsSummary.default.totalRequests);
|
||||
fallbackTotalAmountCounter.add(backendPaymentsSummary.fallback.totalAmount);
|
||||
fallbackTotalRequestsCounter.add(backendPaymentsSummary.fallback.totalRequests);
|
||||
|
||||
const defaultTotalFee = new Big(defaultResponse.feePerTransaction).times(backendPaymentsSummary.default.totalAmount);
|
||||
const fallbackTotalFee = new Big(fallbackResponse.feePerTransaction).times(backendPaymentsSummary.fallback.totalAmount);
|
||||
|
||||
defaultTotalFeeCounter.add(defaultTotalFee.toNumber());
|
||||
fallbackTotalFeeCounter.add(fallbackTotalFee.toNumber());
|
||||
}
|
||||
|
||||
export async function payments() {
|
||||
|
||||
const payload = {
|
||||
correlationId: uuidv4(),
|
||||
amount: paymentRequestFixedAmount.toNumber()
|
||||
};
|
||||
|
||||
const response = await requestBackendPayment(payload);
|
||||
|
||||
if ([200, 201, 202, 204].includes(response.status)) {
|
||||
transactionsSuccessCounter.add(1);
|
||||
transactionsFailureCounter.add(0);
|
||||
} else {
|
||||
transactionsSuccessCounter.add(0);
|
||||
transactionsFailureCounter.add(1);
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
export async function checkPaymentsConsistency() {
|
||||
|
||||
const now = new Date();
|
||||
|
||||
const from = new Date(now - 1000 * 15).toISOString();
|
||||
const to = new Date(now - 1500).toISOString();
|
||||
|
||||
const defaultAdminPaymentsSummaryPromise = getPPPaymentsSummary(
|
||||
"default",
|
||||
from,
|
||||
to,
|
||||
);
|
||||
const fallbackAdminPaymentsSummaryPromise = getPPPaymentsSummary(
|
||||
"fallback",
|
||||
from,
|
||||
to,
|
||||
);
|
||||
const backendPaymentsSummaryPromise = getBackendPaymentsSummary(from, to);
|
||||
|
||||
const [defaultAdminPaymentsSummary, fallbackAdminPaymentsSummary, backendPaymentsSummary] = await Promise.all([
|
||||
defaultAdminPaymentsSummaryPromise,
|
||||
fallbackAdminPaymentsSummaryPromise,
|
||||
backendPaymentsSummaryPromise
|
||||
]);
|
||||
|
||||
const inconsistencies =
|
||||
Math.abs(
|
||||
(backendPaymentsSummary.default.totalRequests - defaultAdminPaymentsSummary.totalRequests) +
|
||||
(backendPaymentsSummary.fallback.totalRequests - fallbackAdminPaymentsSummary.totalRequests)
|
||||
);
|
||||
|
||||
paymentsInconsistencyCounter.add(inconsistencies);
|
||||
|
||||
if (inconsistencies > 0) {
|
||||
console.warn(`${inconsistencies} inconsistências encontradas.`);
|
||||
}
|
||||
|
||||
sleep(10);
|
||||
}
|
||||
|
||||
export async function define_stage() {
|
||||
const defaultMs = parseInt(exec.vu.metrics.tags["defaultDelay"]);
|
||||
const fallbackMs = parseInt(exec.vu.metrics.tags["fallbackDelay"]);
|
||||
const defaultFailure = exec.vu.metrics.tags["defaultFailure"] === "true";
|
||||
const fallbackFailure = exec.vu.metrics.tags["fallbackFailure"] === "true";
|
||||
|
||||
await setPPDelay("default", defaultMs);
|
||||
await setPPDelay("fallback", fallbackMs);
|
||||
|
||||
await setPPFailure("default", defaultFailure);
|
||||
await setPPFailure("fallback", fallbackFailure);
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
export function handleSummary(data) {
|
||||
|
||||
const total_transactions_requested = data.metrics.transactions_success.values.count;
|
||||
const actual_total_amount = data.metrics.total_transactions_amount.values.count;
|
||||
|
||||
const default_total_fee = data.metrics.default_total_fee.values.count;
|
||||
const fallback_total_fee = data.metrics.fallback_total_fee.values.count;
|
||||
const total_fee = new Big(default_total_fee).plus(fallback_total_fee).toNumber();
|
||||
|
||||
const p_99 = new Big(data.metrics["http_req_duration{expected_response:true}"].values["p(99)"]).round(2).toNumber();
|
||||
const p_99_bonus = Math.max(new Big((11 - p_99) * 0.02).round(2).toNumber(), 0);
|
||||
const contains_inconsistencies = data.metrics.payments_inconsistency.values.count > 0;
|
||||
|
||||
const inconsistencies_fine = contains_inconsistencies ? 0.35 : 0;
|
||||
|
||||
// caixa dois
|
||||
const lag = data.metrics.transactions_success.values.count - (data.metrics.default_total_requests.values.count + data.metrics.fallback_total_requests.values.count);
|
||||
const slush_fund = lag < 0;
|
||||
|
||||
const liquid_partial_amount = new Big(actual_total_amount).minus(total_fee).toNumber();
|
||||
|
||||
const liquid_amount = new Big(liquid_partial_amount)
|
||||
.plus(new Big(liquid_partial_amount).times(p_99_bonus))
|
||||
.minus(new Big(liquid_partial_amount).times(inconsistencies_fine)).toNumber();
|
||||
|
||||
const name = __ENV.PARTICIPANT ?? "anonymous";
|
||||
|
||||
const custom_data = {
|
||||
participante: name,
|
||||
total_liquido: liquid_amount,
|
||||
total_bruto: actual_total_amount,
|
||||
total_taxas: total_fee,
|
||||
descricao: "'total_liquido' é sua pontuação final. Equivale ao seu lucro. Fórmula: total_liquido + (total_liquido * p99.bonus) - (total_liquido * multa.porcentagem)",
|
||||
p99: {
|
||||
valor: `${p_99}ms`,
|
||||
bonus: `${new Big(p_99_bonus).times(100)}%`,
|
||||
max_requests: MAX_REQUESTS,
|
||||
descricao: "Fórmula para o bônus: max((11 - p99.valor) * 0.02, 0)",
|
||||
},
|
||||
multa: {
|
||||
porcentagem: inconsistencies_fine,
|
||||
total: new Big(liquid_partial_amount).times(inconsistencies_fine).toNumber(),
|
||||
composicao: {
|
||||
num_inconsistencias: data.metrics.payments_inconsistency.values.count,
|
||||
descricao: "Se 'num_inconsistencias' > 0, há multa de 35%.",
|
||||
}
|
||||
},
|
||||
caixa_dois: {
|
||||
detectado: slush_fund,
|
||||
descricao: "Se 'lag' for negativo, significa que seu backend registrou mais pagamentos do que solicitado, automaticamente desclassificando sua submissão!",
|
||||
},
|
||||
lag: {
|
||||
num_pagamentos_total: data.metrics.default_total_requests.values.count + data.metrics.fallback_total_requests.values.count,
|
||||
num_pagamentos_solicitados: data.metrics.transactions_success.values.count,
|
||||
lag: data.metrics.transactions_success.values.count - (data.metrics.default_total_requests.values.count + data.metrics.fallback_total_requests.values.count),
|
||||
descricao: "Lag é a diferença entre a quantidade de solicitações de pagamentos e o que foi realmente computado pelo backend. Mostra a perda de pagamentos possivelmente por estarem enfileirados."
|
||||
},
|
||||
pagamentos_solicitados: {
|
||||
qtd_sucesso: data.metrics.transactions_success.values.count,
|
||||
qtd_falha: data.metrics.transactions_failure.values.count,
|
||||
descricao: "'qtd_sucesso' foram requests bem sucedidos para 'POST /payments' e 'qtd_falha' os requests com erro."
|
||||
},
|
||||
pagamentos_realizados_default: {
|
||||
total_bruto: data.metrics.default_total_amount.values.count,
|
||||
num_pagamentos: data.metrics.default_total_requests.values.count,
|
||||
total_taxas: data.metrics.default_total_fee.values.count,
|
||||
descricao: "Informações do backend sobre solicitações de pagamento para o Payment Processor Default."
|
||||
},
|
||||
pagamentos_realizados_fallback: {
|
||||
total_bruto: data.metrics.fallback_total_amount.values.count,
|
||||
num_pagamentos: data.metrics.fallback_total_requests.values.count,
|
||||
total_taxas: data.metrics.fallback_total_fee.values.count,
|
||||
descricao: "Informações do backend sobre solicitações de pagamento para o Payment Processor Fallback."
|
||||
}
|
||||
};
|
||||
|
||||
const result = {
|
||||
stdout: textSummary(data),
|
||||
};
|
||||
|
||||
const participant = __ENV.PARTICIPANT;
|
||||
let summaryJsonFileName = `../participantes/${participant}/partial-results.json`
|
||||
|
||||
if (participant == undefined) {
|
||||
summaryJsonFileName = `./partial-results.json`
|
||||
}
|
||||
|
||||
result[summaryJsonFileName] = JSON.stringify(custom_data, null, 2);
|
||||
|
||||
return result;
|
||||
}
|
||||
147
rinha-test/run-tests.sh
Executable file
147
rinha-test/run-tests.sh
Executable file
@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export GIT_EDITOR=true
|
||||
|
||||
startContainers() {
|
||||
pushd ../payment-processor > /dev/null
|
||||
docker compose up --build -d 1> /dev/null 2>&1
|
||||
popd > /dev/null
|
||||
pushd ../participantes/$1 > /dev/null
|
||||
services=$(docker compose config --services | wc -l)
|
||||
echo "" > docker-compose.logs
|
||||
nohup docker compose up --build >> docker-compose.logs &
|
||||
popd > /dev/null
|
||||
#expectedServicesUp=$(( services + 4 ))
|
||||
#servicesUp=$(docker ps | grep ' Up ' | wc -l)
|
||||
}
|
||||
|
||||
stopContainers() {
|
||||
pushd ../participantes/$1
|
||||
docker compose down -v --remove-orphans
|
||||
docker compose rm -s -v -f
|
||||
find * ! -group $(whoami) | xargs sudo rm -rf
|
||||
popd > /dev/null
|
||||
pushd ../payment-processor > /dev/null
|
||||
docker compose down --volumes > /dev/null
|
||||
popd > /dev/null
|
||||
}
|
||||
|
||||
MAX_REQUESTS=550
|
||||
|
||||
while true; do
|
||||
|
||||
# docker system prune -a -f --volumes
|
||||
|
||||
for directory in ../participantes/*; do
|
||||
(
|
||||
git pull
|
||||
participant=$(echo $directory | sed -e 's/..\/participantes\///g' -e 's/\///g')
|
||||
echo "========================================"
|
||||
echo " Participant $participant starting..."
|
||||
echo "========================================"
|
||||
|
||||
testedFile="$directory/partial-results.json"
|
||||
|
||||
if ! test -f $testedFile; then
|
||||
touch $testedFile
|
||||
echo "executing test for $participant..."
|
||||
stopContainers $participant
|
||||
startContainers $participant
|
||||
|
||||
success=1
|
||||
max_attempts=15
|
||||
attempt=1
|
||||
while [ $success -ne 0 ] && [ $max_attempts -ge $attempt ]; do
|
||||
curl -f -s --max-time 3 localhost:9999/payments-summary
|
||||
success=$?
|
||||
echo "tried $attempt out of $max_attempts..."
|
||||
sleep 5
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
if [ $success -eq 0 ]; then
|
||||
echo "" > $directory/k6.logs
|
||||
k6 run -e MAX_REQUESTS=$MAX_REQUESTS -e PARTICIPANT=$participant -e TOKEN=$(uuidgen) --log-output=file=$directory/k6.logs rinha.js
|
||||
stopContainers $participant
|
||||
echo "======================================="
|
||||
echo "working on $participant"
|
||||
sed -i '1001,$d' $directory/docker-compose.logs
|
||||
sed -i '1001,$d' $directory/k6.logs
|
||||
echo "log truncated at line 1000" >> $directory/docker-compose.logs
|
||||
echo "log truncated at line 1000" >> $directory/k6.logs
|
||||
else
|
||||
stopContainers $participant
|
||||
echo "[$(date)] Seu backend não respondeu nenhuma das $max_attempts tentativas de GET para http://localhost:9999/payments-summary. Teste abortado." > $directory/error.logs
|
||||
echo "[$(date)] Inspecione o arquivo docker-compose.logs para mais informações." >> $directory/error.logs
|
||||
echo "Could not get a successful response from backend... aborting test for $participant"
|
||||
fi
|
||||
|
||||
git add $directory
|
||||
git commit -m "add $participant's partial result"
|
||||
git push
|
||||
|
||||
echo "================================="
|
||||
echo " Finished testing $participant!"
|
||||
echo "================================="
|
||||
|
||||
sleep 5
|
||||
|
||||
else
|
||||
echo "================================="
|
||||
echo " Skipping $participant"
|
||||
echo "================================="
|
||||
fi
|
||||
)
|
||||
done
|
||||
|
||||
date
|
||||
echo "generating results preview..."
|
||||
|
||||
PREVIA_RESULTADOS=../PREVIA_RESULTADOS.md
|
||||
|
||||
results=$(find ../participantes/*/partial-results.json -size +1b | wc -l)
|
||||
errors=$(find ../participantes/*/partial-results.json -size 0 | wc -l)
|
||||
total=$(find ../participantes/*/partial-results.json | wc -l)
|
||||
|
||||
echo -e "# Prévia do Resultados da Rinha de Backend 2025" > $PREVIA_RESULTADOS
|
||||
echo -e "Atualizado em **$(date)**" >> $PREVIA_RESULTADOS
|
||||
echo -e "$total submissões / $results resultados / $errors submissões com erro" >> $PREVIA_RESULTADOS
|
||||
echo -e "*Testes executados com MAX_REQUESTS=$MAX_REQUESTS*."
|
||||
echo -e "\n" >> $PREVIA_RESULTADOS
|
||||
echo -e "| participante | p99 | bônus por desempenho (%) | multa ($) | lucro | submissão |" >> $PREVIA_RESULTADOS
|
||||
echo -e "| -- | -- | -- | -- | -- | -- |" >> $PREVIA_RESULTADOS
|
||||
|
||||
for partialResult in ../participantes/*/partial-results.json; do
|
||||
(
|
||||
participant=$(echo $partialResult | sed -e 's/..\/participantes\///g' -e 's/\///g' -e 's/partial\-results\.json//g')
|
||||
link="https://github.com/zanfranceschi/rinha-de-backend-2025/tree/main/participantes/$participant"
|
||||
|
||||
if [ -s $partialResult ]; then
|
||||
cat $partialResult | jq -r '(["|", .participante, "|", .p99.valor, "|", .p99.bonus, "|", .multa.total, "|", .total_liquido, "|", "['$participant']('$link')"]) | @tsv' >> $PREVIA_RESULTADOS
|
||||
fi
|
||||
)
|
||||
done
|
||||
|
||||
echo -e "### Submissões com Erro" >> $PREVIA_RESULTADOS
|
||||
echo -e "\n" >> $PREVIA_RESULTADOS
|
||||
echo -e "| participante | submissão |" >> $PREVIA_RESULTADOS
|
||||
echo -e "| -- | -- |" >> $PREVIA_RESULTADOS
|
||||
for errorLog in ../participantes/*/error.logs; do
|
||||
(
|
||||
participant=$(echo $errorLog | sed -e 's/..\/participantes\///g' -e 's/\///g' -e 's/error\.logs//g')
|
||||
link="https://github.com/zanfranceschi/rinha-de-backend-2025/tree/main/participantes/$participant"
|
||||
echo "| $participant | [logs]($link) |" >> $PREVIA_RESULTADOS
|
||||
)
|
||||
done
|
||||
|
||||
PREVIA_RESULTADOS_JSON=../previa-resultados+participantes-info.json
|
||||
python3 previa_resultados_json.py $PREVIA_RESULTADOS_JSON
|
||||
|
||||
git pull
|
||||
git add $PREVIA_RESULTADOS_JSON
|
||||
git add $PREVIA_RESULTADOS
|
||||
git commit -m "previa resultados @ $(date)"
|
||||
git push
|
||||
echo "$(date) - waiting some time until next round..."
|
||||
sleep 300
|
||||
done
|
||||
Loading…
x
Reference in New Issue
Block a user