chore: 清理macOS同步产生的重复文件
详细说明: - 删除了352个带数字后缀的重复文件 - 更新.gitignore防止未来产生此类文件 - 这些文件是由iCloud或其他同步服务冲突产生的 - 不影响项目功能,仅清理冗余文件
This commit is contained in:
@@ -1,63 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Script to initialize the benchmark environment on a VM
|
||||
#
|
||||
|
||||
set -euo pipefail;
|
||||
|
||||
CURRENT_USER=$(whoami)
|
||||
|
||||
# Mount the data disk
|
||||
# First wait for the disk to become available
|
||||
WAIT_TIME=0
|
||||
MAX_WAIT_TIME=60
|
||||
|
||||
while [ ! -e /dev/sdc ]; do
|
||||
if [ $WAIT_TIME -ge $MAX_WAIT_TIME ]; then
|
||||
echo "Error: /dev/sdc did not become available within $MAX_WAIT_TIME seconds."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Waiting for /dev/sdc to be available... ($WAIT_TIME/$MAX_WAIT_TIME)"
|
||||
sleep 1
|
||||
WAIT_TIME=$((WAIT_TIME + 1))
|
||||
done
|
||||
|
||||
# Then mount it
|
||||
if [ -d "/n8n" ]; then
|
||||
echo "Data disk already mounted. Clearing it..."
|
||||
sudo rm -rf /n8n/*
|
||||
sudo rm -rf /n8n/.[!.]*
|
||||
else
|
||||
sudo mkdir -p /n8n
|
||||
sudo parted /dev/sdc --script mklabel gpt mkpart xfspart xfs 0% 100%
|
||||
sudo mkfs.xfs /dev/sdc1
|
||||
sudo partprobe /dev/sdc1
|
||||
sudo mount /dev/sdc1 /n8n
|
||||
sudo chown -R "$CURRENT_USER":"$CURRENT_USER" /n8n
|
||||
fi
|
||||
|
||||
### Remove unneeded dependencies
|
||||
# TTY
|
||||
sudo systemctl disable getty@tty1.service
|
||||
sudo systemctl disable serial-getty@ttyS0.service
|
||||
# Snap
|
||||
sudo systemctl disable snapd.service
|
||||
# Unattended upgrades
|
||||
sudo systemctl disable unattended-upgrades.service
|
||||
# Cron
|
||||
sudo systemctl disable cron.service
|
||||
|
||||
# Include nodejs v20 repository
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh
|
||||
sudo -E bash nodesource_setup.sh
|
||||
|
||||
# Install docker, docker compose and nodejs
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update -yq
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq docker.io docker-compose nodejs
|
||||
|
||||
# Add the current user to the docker group
|
||||
sudo usermod -aG docker "$CURRENT_USER"
|
||||
|
||||
# Install zx
|
||||
npm install zx
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env zx
|
||||
/**
|
||||
* Script that deletes all resources created by the benchmark environment.
|
||||
*
|
||||
* This scripts tries to delete resources created by Terraform. If Terraform
|
||||
* state file is not found, it will try to delete resources using Azure CLI.
|
||||
* The terraform state is not persisted, so we want to support both cases.
|
||||
*/
|
||||
// @ts-check
|
||||
import { $, minimist } from 'zx';
|
||||
import { TerraformClient } from './clients/terraform-client.mjs';
|
||||
|
||||
const RESOURCE_GROUP_NAME = 'n8n-benchmarking';
|
||||
|
||||
const args = minimist(process.argv.slice(3), {
|
||||
boolean: ['debug'],
|
||||
});
|
||||
|
||||
const isVerbose = !!args.debug;
|
||||
|
||||
async function main() {
|
||||
const terraformClient = new TerraformClient({ isVerbose });
|
||||
|
||||
if (terraformClient.hasTerraformState()) {
|
||||
await terraformClient.destroyEnvironment();
|
||||
} else {
|
||||
await destroyUsingAz();
|
||||
}
|
||||
}
|
||||
|
||||
async function destroyUsingAz() {
|
||||
const resourcesResult =
|
||||
await $`az resource list --resource-group ${RESOURCE_GROUP_NAME} --query "[?tags.Id == 'N8nBenchmark'].{id:id, createdAt:tags.CreatedAt}" -o json`;
|
||||
|
||||
const resources = JSON.parse(resourcesResult.stdout);
|
||||
|
||||
const resourcesToDelete = resources.map((resource) => resource.id);
|
||||
|
||||
if (resourcesToDelete.length === 0) {
|
||||
console.log('No resources found in the resource group.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await deleteResources(resourcesToDelete);
|
||||
}
|
||||
|
||||
async function deleteResources(resourceIds) {
|
||||
// We don't know the order in which resource should be deleted.
|
||||
// Here's a poor person's approach to try deletion until all complete
|
||||
const MAX_ITERATIONS = 100;
|
||||
let i = 0;
|
||||
const toDelete = [...resourceIds];
|
||||
|
||||
console.log(`Deleting ${resourceIds.length} resources...`);
|
||||
while (toDelete.length > 0) {
|
||||
const resourceId = toDelete.shift();
|
||||
const deleted = await deleteById(resourceId);
|
||||
if (!deleted) {
|
||||
toDelete.push(resourceId);
|
||||
}
|
||||
|
||||
if (i++ > MAX_ITERATIONS) {
|
||||
console.log(
|
||||
`Max iterations reached. Exiting. Could not delete ${toDelete.length} resources.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteById(id) {
|
||||
try {
|
||||
await $`az resource delete --ids ${id}`;
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('An error occurred destroying cloud env:');
|
||||
console.error(error);
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env zx
|
||||
/**
|
||||
* Provisions the cloud benchmark environment
|
||||
*
|
||||
* NOTE: Must be run in the root of the package.
|
||||
*/
|
||||
// @ts-check
|
||||
import { which, minimist } from 'zx';
|
||||
import { TerraformClient } from './clients/terraform-client.mjs';
|
||||
|
||||
const args = minimist(process.argv.slice(3), {
|
||||
boolean: ['debug'],
|
||||
});
|
||||
|
||||
const isVerbose = !!args.debug;
|
||||
|
||||
export async function provision() {
|
||||
await ensureDependencies();
|
||||
|
||||
const terraformClient = new TerraformClient({
|
||||
isVerbose,
|
||||
});
|
||||
|
||||
await terraformClient.provisionEnvironment();
|
||||
}
|
||||
|
||||
async function ensureDependencies() {
|
||||
await which('terraform');
|
||||
}
|
||||
|
||||
provision().catch((error) => {
|
||||
console.error('An error occurred while provisioning cloud env:');
|
||||
console.error(error);
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,186 +0,0 @@
|
||||
#!/usr/bin/env zx
|
||||
/**
|
||||
* Script to run benchmarks either on the cloud benchmark environment or locally.
|
||||
* The cloud environment needs to be provisioned using Terraform before running the benchmarks.
|
||||
*
|
||||
* NOTE: Must be run in the root of the package.
|
||||
*/
|
||||
// @ts-check
|
||||
import fs from 'fs';
|
||||
import minimist from 'minimist';
|
||||
import path from 'path';
|
||||
import { runInCloud } from './run-in-cloud.mjs';
|
||||
import { runLocally } from './run-locally.mjs';
|
||||
|
||||
const paths = {
|
||||
n8nSetupsDir: path.join(path.resolve('scripts'), 'n8n-setups'),
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const config = await parseAndValidateConfig();
|
||||
|
||||
const n8nSetupsToUse =
|
||||
config.n8nSetupToUse === 'all' ? readAvailableN8nSetups() : [config.n8nSetupToUse];
|
||||
|
||||
console.log('Using n8n tag', config.n8nTag);
|
||||
console.log('Using benchmark cli tag', config.benchmarkTag);
|
||||
console.log('Using environment', config.env);
|
||||
console.log('Using n8n setups', n8nSetupsToUse.join(', '));
|
||||
console.log('');
|
||||
|
||||
if (config.env === 'cloud') {
|
||||
await runInCloud({
|
||||
benchmarkTag: config.benchmarkTag,
|
||||
isVerbose: config.isVerbose,
|
||||
k6ApiToken: config.k6ApiToken,
|
||||
resultWebhookUrl: config.resultWebhookUrl,
|
||||
resultWebhookAuthHeader: config.resultWebhookAuthHeader,
|
||||
n8nLicenseCert: config.n8nLicenseCert,
|
||||
n8nTag: config.n8nTag,
|
||||
n8nSetupsToUse,
|
||||
vus: config.vus,
|
||||
duration: config.duration,
|
||||
});
|
||||
} else if (config.env === 'local') {
|
||||
await runLocally({
|
||||
benchmarkTag: config.benchmarkTag,
|
||||
isVerbose: config.isVerbose,
|
||||
k6ApiToken: config.k6ApiToken,
|
||||
resultWebhookUrl: config.resultWebhookUrl,
|
||||
resultWebhookAuthHeader: config.resultWebhookAuthHeader,
|
||||
n8nLicenseCert: config.n8nLicenseCert,
|
||||
n8nTag: config.n8nTag,
|
||||
runDir: config.runDir,
|
||||
n8nSetupsToUse,
|
||||
vus: config.vus,
|
||||
duration: config.duration,
|
||||
});
|
||||
} else {
|
||||
console.error('Invalid env:', config.env);
|
||||
printUsage();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function readAvailableN8nSetups() {
|
||||
const setups = fs.readdirSync(paths.n8nSetupsDir);
|
||||
|
||||
return setups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} Config
|
||||
* @property {boolean} isVerbose
|
||||
* @property {'cloud' | 'local'} env
|
||||
* @property {string} n8nSetupToUse
|
||||
* @property {string} n8nTag
|
||||
* @property {string} benchmarkTag
|
||||
* @property {string} [k6ApiToken]
|
||||
* @property {string} [resultWebhookUrl]
|
||||
* @property {string} [resultWebhookAuthHeader]
|
||||
* @property {string} [n8nLicenseCert]
|
||||
* @property {string} [runDir]
|
||||
* @property {string} [vus]
|
||||
* @property {string} [duration]
|
||||
*
|
||||
* @returns {Promise<Config>}
|
||||
*/
|
||||
async function parseAndValidateConfig() {
|
||||
const args = minimist(process.argv.slice(3), {
|
||||
boolean: ['debug', 'help'],
|
||||
});
|
||||
|
||||
if (args.help) {
|
||||
printUsage();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const n8nSetupToUse = await getAndValidateN8nSetup(args);
|
||||
const isVerbose = args.debug || false;
|
||||
const n8nTag = args.n8nTag || process.env.N8N_DOCKER_TAG || 'latest';
|
||||
const benchmarkTag = args.benchmarkTag || process.env.BENCHMARK_DOCKER_TAG || 'latest';
|
||||
const k6ApiToken = args.k6ApiToken || process.env.K6_API_TOKEN || undefined;
|
||||
const resultWebhookUrl =
|
||||
args.resultWebhookUrl || process.env.BENCHMARK_RESULT_WEBHOOK_URL || undefined;
|
||||
const resultWebhookAuthHeader =
|
||||
args.resultWebhookAuthHeader || process.env.BENCHMARK_RESULT_WEBHOOK_AUTH_HEADER || undefined;
|
||||
const n8nLicenseCert = args.n8nLicenseCert || process.env.N8N_LICENSE_CERT || undefined;
|
||||
const runDir = args.runDir || undefined;
|
||||
const env = args.env || 'local';
|
||||
const vus = args.vus;
|
||||
const duration = args.duration;
|
||||
|
||||
if (!env) {
|
||||
printUsage();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return {
|
||||
isVerbose,
|
||||
env,
|
||||
n8nSetupToUse,
|
||||
n8nTag,
|
||||
benchmarkTag,
|
||||
k6ApiToken,
|
||||
resultWebhookUrl,
|
||||
resultWebhookAuthHeader,
|
||||
n8nLicenseCert,
|
||||
runDir,
|
||||
vus,
|
||||
duration,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {minimist.ParsedArgs} args
|
||||
*/
|
||||
async function getAndValidateN8nSetup(args) {
|
||||
// Last parameter is the n8n setup to use
|
||||
const n8nSetupToUse = args._[args._.length - 1];
|
||||
if (!n8nSetupToUse || n8nSetupToUse === 'all') {
|
||||
return 'all';
|
||||
}
|
||||
|
||||
const availableSetups = readAvailableN8nSetups();
|
||||
|
||||
if (!availableSetups.includes(n8nSetupToUse)) {
|
||||
printUsage();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return n8nSetupToUse;
|
||||
}
|
||||
|
||||
function printUsage() {
|
||||
const availableSetups = readAvailableN8nSetups();
|
||||
|
||||
console.log(`Usage: zx scripts/${path.basename(__filename)} [n8n setup name]`);
|
||||
console.log(` eg: zx scripts/${path.basename(__filename)}`);
|
||||
console.log('');
|
||||
console.log('Options:');
|
||||
console.log(
|
||||
` [n8n setup name] Against which n8n setup to run the benchmarks. One of: ${['all', ...availableSetups].join(', ')}. Default is all`,
|
||||
);
|
||||
console.log(
|
||||
' --env Env where to run the benchmarks. Either cloud or local. Default is local.',
|
||||
);
|
||||
console.log(' --debug Enable verbose output');
|
||||
console.log(' --n8nTag Docker tag for n8n image. Default is latest');
|
||||
console.log(' --benchmarkTag Docker tag for benchmark cli image. Default is latest');
|
||||
console.log(' --vus How many concurrent requests to make');
|
||||
console.log(' --duration Test duration, e.g. 1m or 30s');
|
||||
console.log(
|
||||
' --k6ApiToken API token for k6 cloud. Default is read from K6_API_TOKEN env var. If omitted, k6 cloud will not be used',
|
||||
);
|
||||
console.log(
|
||||
' --runDir Directory to share with the n8n container for storing data. Needed only for local runs.',
|
||||
);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('An error occurred while running the benchmarks:');
|
||||
console.error(error);
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,158 +0,0 @@
|
||||
#!/usr/bin/env zx
|
||||
/**
|
||||
* This script runs the benchmarks for the given n8n setup.
|
||||
*/
|
||||
// @ts-check
|
||||
import path from 'path';
|
||||
import { $, argv, fs } from 'zx';
|
||||
import { DockerComposeClient } from './clients/docker-compose-client.mjs';
|
||||
import { flagsObjectToCliArgs } from './utils/flags.mjs';
|
||||
|
||||
const paths = {
|
||||
n8nSetupsDir: path.join(__dirname, 'n8n-setups'),
|
||||
mockApiDataPath: path.join(__dirname, 'mock-api'),
|
||||
};
|
||||
|
||||
const N8N_ENCRYPTION_KEY = 'very-secret-encryption-key';
|
||||
|
||||
async function main() {
|
||||
const [n8nSetupToUse] = argv._;
|
||||
validateN8nSetup(n8nSetupToUse);
|
||||
|
||||
const composeFilePath = path.join(paths.n8nSetupsDir, n8nSetupToUse);
|
||||
const setupScriptPath = path.join(paths.n8nSetupsDir, n8nSetupToUse, 'setup.mjs');
|
||||
const n8nTag = argv.n8nDockerTag || process.env.N8N_DOCKER_TAG || 'latest';
|
||||
const benchmarkTag = argv.benchmarkDockerTag || process.env.BENCHMARK_DOCKER_TAG || 'latest';
|
||||
const k6ApiToken = argv.k6ApiToken || process.env.K6_API_TOKEN || undefined;
|
||||
const resultWebhookUrl =
|
||||
argv.resultWebhookUrl || process.env.BENCHMARK_RESULT_WEBHOOK_URL || undefined;
|
||||
const resultWebhookAuthHeader =
|
||||
argv.resultWebhookAuthHeader || process.env.BENCHMARK_RESULT_WEBHOOK_AUTH_HEADER || undefined;
|
||||
const baseRunDir = argv.runDir || process.env.RUN_DIR || '/n8n';
|
||||
const n8nLicenseCert = argv.n8nLicenseCert || process.env.N8N_LICENSE_CERT || undefined;
|
||||
const n8nLicenseActivationKey = process.env.N8N_LICENSE_ACTIVATION_KEY || undefined;
|
||||
const n8nLicenseTenantId = argv.n8nLicenseTenantId || process.env.N8N_LICENSE_TENANT_ID || '1';
|
||||
const envTag = argv.env || 'local';
|
||||
const vus = argv.vus;
|
||||
const duration = argv.duration;
|
||||
|
||||
const hasN8nLicense = !!n8nLicenseCert || !!n8nLicenseActivationKey;
|
||||
if (n8nSetupToUse === 'scaling-multi-main' && !hasN8nLicense) {
|
||||
console.error(
|
||||
'n8n license is required to run the multi-main scaling setup. Please provide N8N_LICENSE_CERT or N8N_LICENSE_ACTIVATION_KEY (and N8N_LICENSE_TENANT_ID if needed)',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(baseRunDir)) {
|
||||
console.error(
|
||||
`The run directory "${baseRunDir}" does not exist. Please specify a valid directory using --runDir`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const runDir = path.join(baseRunDir, n8nSetupToUse);
|
||||
fs.emptyDirSync(runDir);
|
||||
|
||||
const dockerComposeClient = new DockerComposeClient({
|
||||
$: $({
|
||||
cwd: composeFilePath,
|
||||
verbose: true,
|
||||
env: {
|
||||
PATH: process.env.PATH,
|
||||
N8N_VERSION: n8nTag,
|
||||
N8N_LICENSE_CERT: n8nLicenseCert,
|
||||
N8N_LICENSE_ACTIVATION_KEY: n8nLicenseActivationKey,
|
||||
N8N_LICENSE_TENANT_ID: n8nLicenseTenantId,
|
||||
N8N_ENCRYPTION_KEY,
|
||||
BENCHMARK_VERSION: benchmarkTag,
|
||||
K6_API_TOKEN: k6ApiToken,
|
||||
BENCHMARK_RESULT_WEBHOOK_URL: resultWebhookUrl,
|
||||
BENCHMARK_RESULT_WEBHOOK_AUTH_HEADER: resultWebhookAuthHeader,
|
||||
RUN_DIR: runDir,
|
||||
MOCK_API_DATA_PATH: paths.mockApiDataPath,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Run the setup script if it exists
|
||||
if (fs.existsSync(setupScriptPath)) {
|
||||
const setupScript = await import(setupScriptPath);
|
||||
await setupScript.setup({ runDir });
|
||||
}
|
||||
|
||||
try {
|
||||
await dockerComposeClient.$('up', '-d', '--remove-orphans', 'n8n');
|
||||
|
||||
const tags = Object.entries({
|
||||
Env: envTag,
|
||||
N8nVersion: n8nTag,
|
||||
N8nSetup: n8nSetupToUse,
|
||||
})
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(',');
|
||||
|
||||
const cliArgs = flagsObjectToCliArgs({
|
||||
scenarioNamePrefix: n8nSetupToUse,
|
||||
vus,
|
||||
duration,
|
||||
tags,
|
||||
});
|
||||
|
||||
await dockerComposeClient.$('run', 'benchmark', 'run', ...cliArgs);
|
||||
} catch (error) {
|
||||
console.error('An error occurred while running the benchmarks:');
|
||||
console.error(error.message);
|
||||
console.error('');
|
||||
await printContainerStatus(dockerComposeClient);
|
||||
} finally {
|
||||
await dumpLogs(dockerComposeClient);
|
||||
await dockerComposeClient.$('down');
|
||||
}
|
||||
}
|
||||
|
||||
async function printContainerStatus(dockerComposeClient) {
|
||||
console.error('Container statuses:');
|
||||
await dockerComposeClient.$('ps', '-a');
|
||||
}
|
||||
|
||||
async function dumpLogs(dockerComposeClient) {
|
||||
console.info('Container logs:');
|
||||
await dockerComposeClient.$('logs');
|
||||
}
|
||||
|
||||
function printUsage() {
|
||||
const availableSetups = getAllN8nSetups();
|
||||
console.log('Usage: zx runForN8nSetup.mjs --runDir /path/for/n8n/data <n8n setup to use>');
|
||||
console.log(` eg: zx runForN8nSetup.mjs --runDir /path/for/n8n/data ${availableSetups[0]}`);
|
||||
console.log('');
|
||||
console.log('Flags:');
|
||||
console.log(
|
||||
' --runDir <path> Directory to share with the n8n container for storing data. Default is /n8n',
|
||||
);
|
||||
console.log(' --n8nDockerTag <tag> Docker tag for n8n image. Default is latest');
|
||||
console.log(
|
||||
' --benchmarkDockerTag <tag> Docker tag for benchmark cli image. Default is latest',
|
||||
);
|
||||
console.log(' --k6ApiToken <token> K6 API token to upload the results');
|
||||
console.log('');
|
||||
console.log('Available setups:');
|
||||
console.log(availableSetups.join(', '));
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function getAllN8nSetups() {
|
||||
return fs.readdirSync(paths.n8nSetupsDir);
|
||||
}
|
||||
|
||||
function validateN8nSetup(givenSetup) {
|
||||
const availableSetups = getAllN8nSetups();
|
||||
if (!availableSetups.includes(givenSetup)) {
|
||||
printUsage();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,154 +0,0 @@
|
||||
#!/usr/bin/env zx
|
||||
/**
|
||||
* Script to run benchmarks on the cloud benchmark environment.
|
||||
* This script will:
|
||||
* 1. Provision a benchmark environment using Terraform.
|
||||
* 2. Run the benchmarks on the VM.
|
||||
* 3. Destroy the cloud environment.
|
||||
*
|
||||
* NOTE: Must be run in the root of the package.
|
||||
*/
|
||||
// @ts-check
|
||||
import { sleep, which, $, tmpdir } from 'zx';
|
||||
import path from 'path';
|
||||
import { SshClient } from './clients/ssh-client.mjs';
|
||||
import { TerraformClient } from './clients/terraform-client.mjs';
|
||||
import { flagsObjectToCliArgs } from './utils/flags.mjs';
|
||||
|
||||
/**
|
||||
* @typedef {Object} BenchmarkEnv
|
||||
* @property {string} vmName
|
||||
* @property {string} ip
|
||||
* @property {string} sshUsername
|
||||
* @property {string} sshPrivateKeyPath
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Config
|
||||
* @property {boolean} isVerbose
|
||||
* @property {string[]} n8nSetupsToUse
|
||||
* @property {string} n8nTag
|
||||
* @property {string} benchmarkTag
|
||||
* @property {string} [k6ApiToken]
|
||||
* @property {string} [resultWebhookUrl]
|
||||
* @property {string} [resultWebhookAuthHeader]
|
||||
* @property {string} [n8nLicenseCert]
|
||||
* @property {string} [vus]
|
||||
* @property {string} [duration]
|
||||
*
|
||||
* @param {Config} config
|
||||
*/
|
||||
export async function runInCloud(config) {
|
||||
await ensureDependencies();
|
||||
|
||||
const terraformClient = new TerraformClient({
|
||||
isVerbose: config.isVerbose,
|
||||
});
|
||||
|
||||
const benchmarkEnv = await terraformClient.getTerraformOutputs();
|
||||
|
||||
await runBenchmarksOnVm(config, benchmarkEnv);
|
||||
}
|
||||
|
||||
async function ensureDependencies() {
|
||||
await which('terraform');
|
||||
await which('az');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Config} config
|
||||
* @param {BenchmarkEnv} benchmarkEnv
|
||||
*/
|
||||
async function runBenchmarksOnVm(config, benchmarkEnv) {
|
||||
console.log(`Setting up the environment...`);
|
||||
|
||||
const sshClient = new SshClient({
|
||||
ip: benchmarkEnv.ip,
|
||||
username: benchmarkEnv.sshUsername,
|
||||
privateKeyPath: benchmarkEnv.sshPrivateKeyPath,
|
||||
verbose: config.isVerbose,
|
||||
});
|
||||
|
||||
await ensureVmIsReachable(sshClient);
|
||||
|
||||
const scriptsDir = await transferScriptsToVm(sshClient, config);
|
||||
|
||||
// Bootstrap the environment with dependencies
|
||||
console.log('Running bootstrap script...');
|
||||
const bootstrapScriptPath = path.join(scriptsDir, 'bootstrap.sh');
|
||||
await sshClient.ssh(`chmod a+x ${bootstrapScriptPath} && ${bootstrapScriptPath}`);
|
||||
|
||||
// Give some time for the VM to be ready
|
||||
await sleep(1000);
|
||||
|
||||
for (const n8nSetup of config.n8nSetupsToUse) {
|
||||
await runBenchmarkForN8nSetup({
|
||||
config,
|
||||
sshClient,
|
||||
scriptsDir,
|
||||
n8nSetup,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ config: Config; sshClient: any; scriptsDir: string; n8nSetup: string; }} opts
|
||||
*/
|
||||
async function runBenchmarkForN8nSetup({ config, sshClient, scriptsDir, n8nSetup }) {
|
||||
console.log(`Running benchmarks for ${n8nSetup}...`);
|
||||
const runScriptPath = path.join(scriptsDir, 'run-for-n8n-setup.mjs');
|
||||
|
||||
const cliArgs = flagsObjectToCliArgs({
|
||||
n8nDockerTag: config.n8nTag,
|
||||
benchmarkDockerTag: config.benchmarkTag,
|
||||
k6ApiToken: config.k6ApiToken,
|
||||
resultWebhookUrl: config.resultWebhookUrl,
|
||||
resultWebhookAuthHeader: config.resultWebhookAuthHeader,
|
||||
n8nLicenseCert: config.n8nLicenseCert,
|
||||
vus: config.vus,
|
||||
duration: config.duration,
|
||||
env: 'cloud',
|
||||
});
|
||||
|
||||
const flagsString = cliArgs.join(' ');
|
||||
|
||||
await sshClient.ssh(`npx zx ${runScriptPath} ${flagsString} ${n8nSetup}`, {
|
||||
// Test run should always log its output
|
||||
verbose: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureVmIsReachable(sshClient) {
|
||||
try {
|
||||
await sshClient.ssh('echo "VM is reachable"');
|
||||
} catch (error) {
|
||||
console.error(`VM is not reachable: ${error.message}`);
|
||||
console.error(
|
||||
`Did you provision the cloud environment first with 'pnpm provision-cloud-env'? You can also run the benchmarks locally with 'pnpm run benchmark-locally'.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns Path where the scripts are located on the VM
|
||||
*/
|
||||
async function transferScriptsToVm(sshClient, config) {
|
||||
const cwd = process.cwd();
|
||||
const scriptsDir = path.resolve(cwd, './scripts');
|
||||
const tarFilename = 'scripts.tar.gz';
|
||||
const scriptsTarPath = path.join(tmpdir('n8n-benchmark'), tarFilename);
|
||||
|
||||
const $$ = $({ verbose: config.isVerbose });
|
||||
|
||||
// Compress the scripts folder
|
||||
await $$`tar -czf ${scriptsTarPath} ${scriptsDir} -C ${cwd} ./scripts`;
|
||||
|
||||
// Transfer the scripts to the VM
|
||||
await sshClient.scp(scriptsTarPath, `~/${tarFilename}`);
|
||||
|
||||
// Extract the scripts on the VM
|
||||
await sshClient.ssh(`tar -xzf ~/${tarFilename}`);
|
||||
|
||||
return '~/scripts';
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env zx
|
||||
/**
|
||||
* Script to run benchmarks on the cloud benchmark environment.
|
||||
* This script will:
|
||||
* 1. Provision a benchmark environment using Terraform.
|
||||
* 2. Run the benchmarks on the VM.
|
||||
* 3. Destroy the cloud environment.
|
||||
*
|
||||
* NOTE: Must be run in the root of the package.
|
||||
*/
|
||||
// @ts-check
|
||||
import { $ } from 'zx';
|
||||
import path from 'path';
|
||||
import { flagsObjectToCliArgs } from './utils/flags.mjs';
|
||||
|
||||
/**
|
||||
* @typedef {Object} BenchmarkEnv
|
||||
* @property {string} vmName
|
||||
*/
|
||||
|
||||
const paths = {
|
||||
scriptsDir: path.join(path.resolve('scripts')),
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} Config
|
||||
* @property {boolean} isVerbose
|
||||
* @property {string[]} n8nSetupsToUse
|
||||
* @property {string} n8nTag
|
||||
* @property {string} benchmarkTag
|
||||
* @property {string} [runDir]
|
||||
* @property {string} [k6ApiToken]
|
||||
* @property {string} [resultWebhookUrl]
|
||||
* @property {string} [resultWebhookAuthHeader]
|
||||
* @property {string} [n8nLicenseCert]
|
||||
* @property {string} [vus]
|
||||
* @property {string} [duration]
|
||||
*
|
||||
* @param {Config} config
|
||||
*/
|
||||
export async function runLocally(config) {
|
||||
const runScriptPath = path.join(paths.scriptsDir, 'run-for-n8n-setup.mjs');
|
||||
|
||||
const cliArgs = flagsObjectToCliArgs({
|
||||
n8nDockerTag: config.n8nTag,
|
||||
benchmarkDockerTag: config.benchmarkTag,
|
||||
runDir: config.runDir,
|
||||
vus: config.vus,
|
||||
duration: config.duration,
|
||||
env: 'local',
|
||||
});
|
||||
|
||||
try {
|
||||
for (const n8nSetup of config.n8nSetupsToUse) {
|
||||
console.log(`Running benchmarks for n8n setup: ${n8nSetup}`);
|
||||
|
||||
await $({
|
||||
env: {
|
||||
...process.env,
|
||||
K6_API_TOKEN: config.k6ApiToken,
|
||||
BENCHMARK_RESULT_WEBHOOK_URL: config.resultWebhookUrl,
|
||||
BENCHMARK_RESULT_WEBHOOK_AUTH_HEADER: config.resultWebhookAuthHeader,
|
||||
N8N_LICENSE_CERT: config.n8nLicenseCert,
|
||||
},
|
||||
})`npx ${runScriptPath} ${cliArgs} ${n8nSetup}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('An error occurred while running the benchmarks:');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user