@atom8n/n8n-benchmark 2.0.0

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.
Files changed (158) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/Dockerfile +63 -0
  3. package/README.md +122 -0
  4. package/bin/n8n-benchmark +13 -0
  5. package/biome.jsonc +7 -0
  6. package/dist/build.tsbuildinfo +1 -0
  7. package/dist/commands/list.d.ts +8 -0
  8. package/dist/commands/list.js +23 -0
  9. package/dist/commands/list.js.map +1 -0
  10. package/dist/commands/run.d.ts +24 -0
  11. package/dist/commands/run.js +128 -0
  12. package/dist/commands/run.js.map +1 -0
  13. package/dist/config/common-flags.d.ts +1 -0
  14. package/dist/config/common-flags.js +9 -0
  15. package/dist/config/common-flags.js.map +1 -0
  16. package/dist/n8n-api-client/authenticated-n8n-api-client.d.ts +15 -0
  17. package/dist/n8n-api-client/authenticated-n8n-api-client.js +67 -0
  18. package/dist/n8n-api-client/authenticated-n8n-api-client.js.map +1 -0
  19. package/dist/n8n-api-client/credentials-api-client.d.ts +9 -0
  20. package/dist/n8n-api-client/credentials-api-client.js +24 -0
  21. package/dist/n8n-api-client/credentials-api-client.js.map +1 -0
  22. package/dist/n8n-api-client/data-table-api-client.d.ts +9 -0
  23. package/dist/n8n-api-client/data-table-api-client.js +23 -0
  24. package/dist/n8n-api-client/data-table-api-client.js.map +1 -0
  25. package/dist/n8n-api-client/n8n-api-client.d.ts +13 -0
  26. package/dist/n8n-api-client/n8n-api-client.js +82 -0
  27. package/dist/n8n-api-client/n8n-api-client.js.map +1 -0
  28. package/dist/n8n-api-client/n8n-api-client.types.d.ts +21 -0
  29. package/dist/n8n-api-client/n8n-api-client.types.js +3 -0
  30. package/dist/n8n-api-client/n8n-api-client.types.js.map +1 -0
  31. package/dist/n8n-api-client/project-api-client.d.ts +6 -0
  32. package/dist/n8n-api-client/project-api-client.js +14 -0
  33. package/dist/n8n-api-client/project-api-client.js.map +1 -0
  34. package/dist/n8n-api-client/workflows-api-client.d.ts +11 -0
  35. package/dist/n8n-api-client/workflows-api-client.js +30 -0
  36. package/dist/n8n-api-client/workflows-api-client.js.map +1 -0
  37. package/dist/scenario/scenario-data-loader.d.ts +13 -0
  38. package/dist/scenario/scenario-data-loader.js +84 -0
  39. package/dist/scenario/scenario-data-loader.js.map +1 -0
  40. package/dist/scenario/scenario-loader.d.ts +7 -0
  41. package/dist/scenario/scenario-loader.js +101 -0
  42. package/dist/scenario/scenario-loader.js.map +1 -0
  43. package/dist/test-execution/app-metrics-poller.d.ts +13 -0
  44. package/dist/test-execution/app-metrics-poller.js +54 -0
  45. package/dist/test-execution/app-metrics-poller.js.map +1 -0
  46. package/dist/test-execution/k6-executor.d.ts +33 -0
  47. package/dist/test-execution/k6-executor.js +120 -0
  48. package/dist/test-execution/k6-executor.js.map +1 -0
  49. package/dist/test-execution/k6-summary.d.ts +82 -0
  50. package/dist/test-execution/k6-summary.js +2 -0
  51. package/dist/test-execution/k6-summary.js.map +1 -0
  52. package/dist/test-execution/prometheus-metrics-parser.d.ts +9 -0
  53. package/dist/test-execution/prometheus-metrics-parser.js +44 -0
  54. package/dist/test-execution/prometheus-metrics-parser.js.map +1 -0
  55. package/dist/test-execution/scenario-data-importer.d.ts +21 -0
  56. package/dist/test-execution/scenario-data-importer.js +108 -0
  57. package/dist/test-execution/scenario-data-importer.js.map +1 -0
  58. package/dist/test-execution/scenario-runner.d.ts +18 -0
  59. package/dist/test-execution/scenario-runner.js +46 -0
  60. package/dist/test-execution/scenario-runner.js.map +1 -0
  61. package/dist/test-execution/test-report.d.ts +56 -0
  62. package/dist/test-execution/test-report.js +65 -0
  63. package/dist/test-execution/test-report.js.map +1 -0
  64. package/dist/types/scenario.d.ts +16 -0
  65. package/dist/types/scenario.js +3 -0
  66. package/dist/types/scenario.js.map +1 -0
  67. package/eslint.config.mjs +22 -0
  68. package/infra/.terraform.lock.hcl +60 -0
  69. package/infra/benchmark-env.tf +54 -0
  70. package/infra/modules/benchmark-vm/output.tf +11 -0
  71. package/infra/modules/benchmark-vm/vars.tf +29 -0
  72. package/infra/modules/benchmark-vm/vm.tf +126 -0
  73. package/infra/output.tf +16 -0
  74. package/infra/providers.tf +23 -0
  75. package/infra/vars.tf +34 -0
  76. package/package.json +55 -0
  77. package/scenarios/binary-data/binary-data.json +67 -0
  78. package/scenarios/binary-data/binary-data.manifest.json +7 -0
  79. package/scenarios/binary-data/binary-data.script.js +29 -0
  80. package/scenarios/credential-http-node/credential-bearer.json +8 -0
  81. package/scenarios/credential-http-node/credential-http-node.json +241 -0
  82. package/scenarios/credential-http-node/credential-http-node.manifest.json +10 -0
  83. package/scenarios/credential-http-node/credential-http-node.script.js +30 -0
  84. package/scenarios/data-table-node/data-table-node.json +168 -0
  85. package/scenarios/data-table-node/data-table-node.manifest.json +10 -0
  86. package/scenarios/data-table-node/data-table-node.script.js +38 -0
  87. package/scenarios/data-table-node/data-table.json +25 -0
  88. package/scenarios/http-node/http-node.json +213 -0
  89. package/scenarios/http-node/http-node.manifest.json +7 -0
  90. package/scenarios/http-node/http-node.script.js +30 -0
  91. package/scenarios/js-code-node/js-code-node.json +96 -0
  92. package/scenarios/js-code-node/js-code-node.manifest.json +7 -0
  93. package/scenarios/js-code-node/js-code-node.script.js +29 -0
  94. package/scenarios/multiple-webhooks/multiple-webhooks.manifest.json +20 -0
  95. package/scenarios/multiple-webhooks/multiple-webhooks.script.js +19 -0
  96. package/scenarios/multiple-webhooks/multiple-webhooks1.json +25 -0
  97. package/scenarios/multiple-webhooks/multiple-webhooks10.json +25 -0
  98. package/scenarios/multiple-webhooks/multiple-webhooks2.json +25 -0
  99. package/scenarios/multiple-webhooks/multiple-webhooks3.json +25 -0
  100. package/scenarios/multiple-webhooks/multiple-webhooks4.json +25 -0
  101. package/scenarios/multiple-webhooks/multiple-webhooks5.json +25 -0
  102. package/scenarios/multiple-webhooks/multiple-webhooks6.json +25 -0
  103. package/scenarios/multiple-webhooks/multiple-webhooks7.json +25 -0
  104. package/scenarios/multiple-webhooks/multiple-webhooks8.json +25 -0
  105. package/scenarios/multiple-webhooks/multiple-webhooks9.json +25 -0
  106. package/scenarios/py-code-node/py-code-node.json +98 -0
  107. package/scenarios/py-code-node/py-code-node.manifest.json +7 -0
  108. package/scenarios/py-code-node/py-code-node.script.js +29 -0
  109. package/scenarios/scenario.schema.json +51 -0
  110. package/scenarios/set-node-expressions/set-node-expressions.json +91 -0
  111. package/scenarios/set-node-expressions/set-node-expressions.manifest.json +7 -0
  112. package/scenarios/set-node-expressions/set-node-expressions.script.js +18 -0
  113. package/scenarios/single-webhook/single-webhook.json +25 -0
  114. package/scenarios/single-webhook/single-webhook.manifest.json +7 -0
  115. package/scenarios/single-webhook/single-webhook.script.js +18 -0
  116. package/scripts/bootstrap.sh +63 -0
  117. package/scripts/clients/docker-compose-client.mjs +45 -0
  118. package/scripts/clients/ssh-client.mjs +37 -0
  119. package/scripts/clients/terraform-client.mjs +71 -0
  120. package/scripts/destroy-cloud-env.mjs +86 -0
  121. package/scripts/mock-api/mappings/mockApiData.json +92110 -0
  122. package/scripts/n8n-setups/postgres/docker-compose.yml +76 -0
  123. package/scripts/n8n-setups/postgres/setup.mjs +15 -0
  124. package/scripts/n8n-setups/scaling-multi-main/docker-compose.yml +230 -0
  125. package/scripts/n8n-setups/scaling-multi-main/nginx.conf +24 -0
  126. package/scripts/n8n-setups/scaling-multi-main/setup.mjs +15 -0
  127. package/scripts/n8n-setups/scaling-single-main/docker-compose.yml +174 -0
  128. package/scripts/n8n-setups/scaling-single-main/setup.mjs +15 -0
  129. package/scripts/n8n-setups/sqlite/docker-compose.yml +55 -0
  130. package/scripts/n8n-setups/sqlite/setup.mjs +15 -0
  131. package/scripts/provision-cloud-env.mjs +36 -0
  132. package/scripts/run-for-n8n-setup.mjs +175 -0
  133. package/scripts/run-in-cloud.mjs +167 -0
  134. package/scripts/run-locally.mjs +73 -0
  135. package/scripts/run.mjs +192 -0
  136. package/scripts/utils/flags.mjs +20 -0
  137. package/src/commands/list.ts +26 -0
  138. package/src/commands/run.ts +140 -0
  139. package/src/config/common-flags.ts +6 -0
  140. package/src/n8n-api-client/authenticated-n8n-api-client.ts +88 -0
  141. package/src/n8n-api-client/credentials-api-client.ts +28 -0
  142. package/src/n8n-api-client/data-table-api-client.ts +30 -0
  143. package/src/n8n-api-client/n8n-api-client.ts +85 -0
  144. package/src/n8n-api-client/n8n-api-client.types.ts +27 -0
  145. package/src/n8n-api-client/project-api-client.ts +11 -0
  146. package/src/n8n-api-client/workflows-api-client.ts +38 -0
  147. package/src/scenario/scenario-data-loader.ts +75 -0
  148. package/src/scenario/scenario-loader.ts +90 -0
  149. package/src/test-execution/app-metrics-poller.ts +81 -0
  150. package/src/test-execution/k6-executor.ts +192 -0
  151. package/src/test-execution/k6-summary.ts +255 -0
  152. package/src/test-execution/prometheus-metrics-parser.ts +63 -0
  153. package/src/test-execution/scenario-data-importer.ts +165 -0
  154. package/src/test-execution/scenario-runner.ts +76 -0
  155. package/src/test-execution/test-report.ts +152 -0
  156. package/src/types/scenario.ts +33 -0
  157. package/tsconfig.build.json +9 -0
  158. package/tsconfig.json +14 -0
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAppMetricsReport = buildAppMetricsReport;
4
+ exports.buildTestReport = buildTestReport;
5
+ const nanoid_1 = require("nanoid");
6
+ const prometheus_metrics_parser_1 = require("../test-execution/prometheus-metrics-parser");
7
+ function k6CheckToCheck(check) {
8
+ return {
9
+ name: check.name,
10
+ passes: check.passes,
11
+ fails: check.fails,
12
+ };
13
+ }
14
+ function k6CounterToCounter(counter) {
15
+ return {
16
+ type: 'counter',
17
+ count: counter.values.count,
18
+ rate: counter.values.rate,
19
+ };
20
+ }
21
+ function k6TrendToTrend(trend) {
22
+ return {
23
+ type: 'trend',
24
+ 'p(90)': trend.values['p(90)'],
25
+ avg: trend.values.avg,
26
+ min: trend.values.min,
27
+ med: trend.values.med,
28
+ max: trend.values.max,
29
+ 'p(95)': trend.values['p(95)'],
30
+ };
31
+ }
32
+ function buildAppMetricsReport(metricsData) {
33
+ const heapSizeTotal = prometheus_metrics_parser_1.PrometheusMetricsParser.calculateMetricStats(metricsData, 'n8n_nodejs_heap_size_total_bytes');
34
+ const heapSizeUsed = prometheus_metrics_parser_1.PrometheusMetricsParser.calculateMetricStats(metricsData, 'n8n_nodejs_heap_size_used_bytes');
35
+ const externalMemory = prometheus_metrics_parser_1.PrometheusMetricsParser.calculateMetricStats(metricsData, 'n8n_nodejs_external_memory_bytes');
36
+ const eventLoopLag = prometheus_metrics_parser_1.PrometheusMetricsParser.calculateMetricStats(metricsData, 'n8n_nodejs_eventloop_lag_seconds');
37
+ return {
38
+ ...(heapSizeTotal && { heapSizeTotal }),
39
+ ...(heapSizeUsed && { heapSizeUsed }),
40
+ ...(externalMemory && { externalMemory }),
41
+ ...(eventLoopLag && { eventLoopLag }),
42
+ };
43
+ }
44
+ function buildTestReport(scenario, endOfTestSummary, tags, appMetricsData) {
45
+ const appMetrics = appMetricsData ? buildAppMetricsReport(appMetricsData) : undefined;
46
+ return {
47
+ runId: (0, nanoid_1.nanoid)(),
48
+ ts: new Date().toISOString(),
49
+ scenarioName: scenario.name,
50
+ tags,
51
+ checks: endOfTestSummary.root_group.checks.map(k6CheckToCheck),
52
+ metrics: {
53
+ dataReceived: k6CounterToCounter(endOfTestSummary.metrics.data_received),
54
+ dataSent: k6CounterToCounter(endOfTestSummary.metrics.data_sent),
55
+ httpRequests: k6CounterToCounter(endOfTestSummary.metrics.http_reqs),
56
+ httpRequestDuration: k6TrendToTrend(endOfTestSummary.metrics.http_req_duration),
57
+ httpRequestSending: k6TrendToTrend(endOfTestSummary.metrics.http_req_sending),
58
+ httpRequestReceiving: k6TrendToTrend(endOfTestSummary.metrics.http_req_receiving),
59
+ httpRequestWaiting: k6TrendToTrend(endOfTestSummary.metrics.http_req_waiting),
60
+ iterations: k6CounterToCounter(endOfTestSummary.metrics.iterations),
61
+ },
62
+ ...(appMetrics && { appMetrics }),
63
+ };
64
+ }
65
+ //# sourceMappingURL=test-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-report.js","sourceRoot":"","sources":["../../src/test-execution/test-report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export type ScenarioData = {
2
+ workflowFiles?: string[];
3
+ credentialFiles?: string[];
4
+ dataTableFile?: string;
5
+ };
6
+ export type ScenarioManifest = {
7
+ name: string;
8
+ description: string;
9
+ scriptPath: string;
10
+ scenarioData: ScenarioData;
11
+ };
12
+ export type Scenario = ScenarioManifest & {
13
+ id: string;
14
+ scenarioDirPath: string;
15
+ dataTableId?: string;
16
+ };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=scenario.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenario.js","sourceRoot":"","sources":["../../src/types/scenario.ts"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
1
+ import { defineConfig, globalIgnores } from 'eslint/config';
2
+ import { nodeConfig } from '@n8n/eslint-config/node';
3
+
4
+ export default defineConfig(
5
+ nodeConfig,
6
+ globalIgnores(['scenarios/**', 'scripts/**']),
7
+ {
8
+ rules: {
9
+ 'unicorn/filename-case': ['error', { case: 'kebabCase' }],
10
+ 'n8n-local-rules/no-plain-errors': 'off',
11
+ complexity: 'error',
12
+ '@typescript-eslint/naming-convention': 'warn',
13
+ 'no-empty': 'warn',
14
+ },
15
+ },
16
+ {
17
+ files: ['./src/commands/*.ts'],
18
+ rules: {
19
+ 'import-x/no-default-export': 'off',
20
+ },
21
+ },
22
+ );
@@ -0,0 +1,60 @@
1
+ # This file is maintained automatically by "terraform init".
2
+ # Manual edits may be lost in future updates.
3
+
4
+ provider "registry.terraform.io/hashicorp/azurerm" {
5
+ version = "3.115.0"
6
+ constraints = "~> 3.115.0"
7
+ hashes = [
8
+ "h1:O7C3Xb+MSOc9C/eAJ5C/CiJ4vuvUsYxxIzr9ZurmHNI=",
9
+ "zh:0ea93abd53cb872691bad6d5625bda88b5d9619ea813c208b36e0ee236308589",
10
+ "zh:26703cb9c2c38bc43e97bc83af03559d065750856ea85834b71fbcb2ef9d935c",
11
+ "zh:316255a3391c49fe9bd7c5b6aa53b56dd490e1083d19b722e7b8f956a2dfe004",
12
+ "zh:431637ae90c592126fb1ec813fee6390604275438a0d5e15904c65b0a6a0f826",
13
+ "zh:4cee0fa2e84f89853723c0bc72b7debf8ea2ffffc7ae34ff28d8a69269d3a879",
14
+ "zh:64a3a3c78ea877515365ed336bd0f3abbe71db7c99b3d2837915fbca168d429c",
15
+ "zh:7380d7b503b5a87fd71a31360c3eeab504f78e4f314824e3ceda724d9dc74cf0",
16
+ "zh:974213e05708037a6d2d8c58cc84981819138f44fe40e344034eb80e16ca6012",
17
+ "zh:9a91614de0476074e9c62bbf08d3bb9c64adbd1d3a4a2b5a3e8e41d9d6d5672f",
18
+ "zh:a438471c85b8788ab21bdef4cd5ca391a46cbae33bd0262668a80f5e6c4610e1",
19
+ "zh:bf823f2c941b336a1208f015466212b1a8fdf6da28abacf59bea708377709d9e",
20
+ "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
21
+ ]
22
+ }
23
+
24
+ provider "registry.terraform.io/hashicorp/random" {
25
+ version = "3.6.2"
26
+ hashes = [
27
+ "h1:VavG5unYCa3SYISMKF9pzc3718M0bhPlcbUZZGl7wuo=",
28
+ "zh:0ef01a4f81147b32c1bea3429974d4d104bbc4be2ba3cfa667031a8183ef88ec",
29
+ "zh:1bcd2d8161e89e39886119965ef0f37fcce2da9c1aca34263dd3002ba05fcb53",
30
+ "zh:37c75d15e9514556a5f4ed02e1548aaa95c0ecd6ff9af1119ac905144c70c114",
31
+ "zh:4210550a767226976bc7e57d988b9ce48f4411fa8a60cd74a6b246baf7589dad",
32
+ "zh:562007382520cd4baa7320f35e1370ffe84e46ed4e2071fdc7e4b1a9b1f8ae9b",
33
+ "zh:5efb9da90f665e43f22c2e13e0ce48e86cae2d960aaf1abf721b497f32025916",
34
+ "zh:6f71257a6b1218d02a573fc9bff0657410404fb2ef23bc66ae8cd968f98d5ff6",
35
+ "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
36
+ "zh:9647e18f221380a85f2f0ab387c68fdafd58af6193a932417299cdcae4710150",
37
+ "zh:bb6297ce412c3c2fa9fec726114e5e0508dd2638cad6a0cb433194930c97a544",
38
+ "zh:f83e925ed73ff8a5ef6e3608ad9225baa5376446349572c2449c0c0b3cf184b7",
39
+ "zh:fbef0781cb64de76b1df1ca11078aecba7800d82fd4a956302734999cfd9a4af",
40
+ ]
41
+ }
42
+
43
+ provider "registry.terraform.io/hashicorp/tls" {
44
+ version = "4.0.5"
45
+ hashes = [
46
+ "h1:zeG5RmggBZW/8JWIVrdaeSJa0OG62uFX5HY1eE8SjzY=",
47
+ "zh:01cfb11cb74654c003f6d4e32bbef8f5969ee2856394a96d127da4949c65153e",
48
+ "zh:0472ea1574026aa1e8ca82bb6df2c40cd0478e9336b7a8a64e652119a2fa4f32",
49
+ "zh:1a8ddba2b1550c5d02003ea5d6cdda2eef6870ece86c5619f33edd699c9dc14b",
50
+ "zh:1e3bb505c000adb12cdf60af5b08f0ed68bc3955b0d4d4a126db5ca4d429eb4a",
51
+ "zh:6636401b2463c25e03e68a6b786acf91a311c78444b1dc4f97c539f9f78de22a",
52
+ "zh:76858f9d8b460e7b2a338c477671d07286b0d287fd2d2e3214030ae8f61dd56e",
53
+ "zh:a13b69fb43cb8746793b3069c4d897bb18f454290b496f19d03c3387d1c9a2dc",
54
+ "zh:a90ca81bb9bb509063b736842250ecff0f886a91baae8de65c8430168001dad9",
55
+ "zh:c4de401395936e41234f1956ebadbd2ed9f414e6908f27d578614aaa529870d4",
56
+ "zh:c657e121af8fde19964482997f0de2d5173217274f6997e16389e7707ed8ece8",
57
+ "zh:d68b07a67fbd604c38ec9733069fbf23441436fecf554de6c75c032f82e1ef19",
58
+ "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
59
+ ]
60
+ }
@@ -0,0 +1,54 @@
1
+
2
+ data "azurerm_resource_group" "main" {
3
+ name = var.resource_group_name
4
+ }
5
+
6
+ # Random prefix for the resources
7
+ resource "random_string" "prefix" {
8
+ length = 8
9
+ special = false
10
+ }
11
+
12
+ # SSH key pair
13
+ resource "tls_private_key" "ssh_key" {
14
+ algorithm = "RSA"
15
+ rsa_bits = 4096
16
+ }
17
+
18
+ # Dedicated Host Group & Hosts
19
+
20
+ resource "azurerm_dedicated_host_group" "main" {
21
+ name = "${random_string.prefix.result}-hostgroup"
22
+ location = var.location
23
+ resource_group_name = data.azurerm_resource_group.main.name
24
+ platform_fault_domain_count = 1
25
+ automatic_placement_enabled = false
26
+ zone = 1
27
+
28
+ tags = local.common_tags
29
+ }
30
+
31
+ resource "azurerm_dedicated_host" "hosts" {
32
+ name = "${random_string.prefix.result}-host"
33
+ location = var.location
34
+ dedicated_host_group_id = azurerm_dedicated_host_group.main.id
35
+ sku_name = var.host_size_family
36
+ platform_fault_domain = 0
37
+
38
+ tags = local.common_tags
39
+ }
40
+
41
+ # VM
42
+
43
+ module "test_vm" {
44
+ source = "./modules/benchmark-vm"
45
+
46
+ location = var.location
47
+ resource_group_name = data.azurerm_resource_group.main.name
48
+ prefix = random_string.prefix.result
49
+ dedicated_host_id = azurerm_dedicated_host.hosts.id
50
+ ssh_public_key = tls_private_key.ssh_key.public_key_openssh
51
+ vm_size = var.vm_size
52
+
53
+ tags = local.common_tags
54
+ }
@@ -0,0 +1,11 @@
1
+ output "vm_name" {
2
+ value = azurerm_linux_virtual_machine.main.name
3
+ }
4
+
5
+ output "ip" {
6
+ value = azurerm_public_ip.main.ip_address
7
+ }
8
+
9
+ output "ssh_username" {
10
+ value = azurerm_linux_virtual_machine.main.admin_username
11
+ }
@@ -0,0 +1,29 @@
1
+ variable "location" {
2
+ description = "Region to deploy resources"
3
+ default = "East US"
4
+ }
5
+
6
+ variable "resource_group_name" {
7
+ description = "Name of the resource group"
8
+ }
9
+
10
+ variable "prefix" {
11
+ description = "Prefix to append to resources"
12
+ }
13
+
14
+ variable "dedicated_host_id" {
15
+ description = "Dedicated Host ID"
16
+ }
17
+
18
+ variable "ssh_public_key" {
19
+ description = "SSH Public Key"
20
+ }
21
+
22
+ variable "vm_size" {
23
+ description = "VM Size"
24
+ }
25
+
26
+ variable "tags" {
27
+ description = "Tags to apply to all resources created by this module"
28
+ type = map(string)
29
+ }
@@ -0,0 +1,126 @@
1
+ # Network
2
+
3
+ resource "azurerm_virtual_network" "main" {
4
+ name = "${var.prefix}-vnet"
5
+ location = var.location
6
+ resource_group_name = var.resource_group_name
7
+ address_space = ["10.0.0.0/16"]
8
+
9
+ tags = var.tags
10
+ }
11
+
12
+ resource "azurerm_subnet" "main" {
13
+ name = "${var.prefix}-subnet"
14
+ resource_group_name = var.resource_group_name
15
+ virtual_network_name = azurerm_virtual_network.main.name
16
+ address_prefixes = ["10.0.0.0/24"]
17
+ }
18
+
19
+ resource "azurerm_network_security_group" "ssh" {
20
+ name = "${var.prefix}-nsg"
21
+ location = var.location
22
+ resource_group_name = var.resource_group_name
23
+
24
+ security_rule {
25
+ name = "AllowSSH"
26
+ priority = 1001
27
+ direction = "Inbound"
28
+ access = "Allow"
29
+ protocol = "Tcp"
30
+ source_port_range = "*"
31
+ destination_port_range = "22"
32
+ source_address_prefix = "*"
33
+ destination_address_prefix = "*"
34
+ }
35
+
36
+ tags = var.tags
37
+ }
38
+
39
+ resource "azurerm_public_ip" "main" {
40
+ name = "${var.prefix}-pip"
41
+ location = var.location
42
+ resource_group_name = var.resource_group_name
43
+ allocation_method = "Static"
44
+ sku = "Standard"
45
+
46
+ tags = var.tags
47
+ }
48
+
49
+ resource "azurerm_network_interface" "main" {
50
+ name = "${var.prefix}-nic"
51
+ location = var.location
52
+ resource_group_name = var.resource_group_name
53
+
54
+ ip_configuration {
55
+ name = "${var.prefix}-ipconfig"
56
+ subnet_id = azurerm_subnet.main.id
57
+ private_ip_address_allocation = "Dynamic"
58
+ public_ip_address_id = azurerm_public_ip.main.id
59
+ }
60
+
61
+ tags = var.tags
62
+ }
63
+
64
+ resource "azurerm_network_interface_security_group_association" "ssh" {
65
+ network_interface_id = azurerm_network_interface.main.id
66
+ network_security_group_id = azurerm_network_security_group.ssh.id
67
+ }
68
+
69
+ # Disk
70
+
71
+ resource "azurerm_managed_disk" "data" {
72
+ name = "${var.prefix}-disk"
73
+ location = var.location
74
+ resource_group_name = var.resource_group_name
75
+ storage_account_type = "PremiumV2_LRS"
76
+ create_option = "Empty"
77
+ disk_size_gb = "32"
78
+ zone = 1
79
+
80
+ tags = var.tags
81
+ }
82
+
83
+ resource "azurerm_virtual_machine_data_disk_attachment" "data" {
84
+ managed_disk_id = azurerm_managed_disk.data.id
85
+ virtual_machine_id = azurerm_linux_virtual_machine.main.id
86
+ lun = "1"
87
+ caching = "None"
88
+ }
89
+
90
+ # VM
91
+
92
+ resource "azurerm_linux_virtual_machine" "main" {
93
+ name = "${var.prefix}-vm"
94
+ location = var.location
95
+ resource_group_name = var.resource_group_name
96
+ network_interface_ids = [azurerm_network_interface.main.id]
97
+ dedicated_host_id = var.dedicated_host_id
98
+ zone = 1
99
+
100
+ size = var.vm_size
101
+
102
+ admin_username = "benchmark"
103
+
104
+ admin_ssh_key {
105
+ username = "benchmark"
106
+ public_key = var.ssh_public_key
107
+ }
108
+
109
+ os_disk {
110
+ caching = "ReadWrite"
111
+ storage_account_type = "Premium_LRS"
112
+ }
113
+
114
+ source_image_reference {
115
+ publisher = "Canonical"
116
+ offer = "0001-com-ubuntu-server-jammy"
117
+ sku = "22_04-lts-gen2"
118
+ version = "latest"
119
+ }
120
+
121
+ identity {
122
+ type = "SystemAssigned"
123
+ }
124
+
125
+ tags = var.tags
126
+ }
@@ -0,0 +1,16 @@
1
+ output "vm_name" {
2
+ value = module.test_vm.vm_name
3
+ }
4
+
5
+ output "ip" {
6
+ value = module.test_vm.ip
7
+ }
8
+
9
+ output "ssh_username" {
10
+ value = module.test_vm.ssh_username
11
+ }
12
+
13
+ output "ssh_private_key" {
14
+ value = tls_private_key.ssh_key.private_key_pem
15
+ sensitive = true
16
+ }
@@ -0,0 +1,23 @@
1
+
2
+ terraform {
3
+ required_providers {
4
+ azurerm = {
5
+ source = "hashicorp/azurerm"
6
+ version = "~> 3.115.0"
7
+ }
8
+
9
+ random = {
10
+ source = "hashicorp/random"
11
+ }
12
+ }
13
+
14
+ required_version = "~> 1.8.5"
15
+ }
16
+
17
+ provider "azurerm" {
18
+ features {}
19
+
20
+ skip_provider_registration = true
21
+ }
22
+
23
+ provider "random" {}
package/infra/vars.tf ADDED
@@ -0,0 +1,34 @@
1
+ variable "location" {
2
+ description = "Region to deploy resources"
3
+ default = "East US"
4
+ }
5
+
6
+ variable "resource_group_name" {
7
+ description = "Name of the resource group"
8
+ default = "n8n-benchmarking"
9
+ }
10
+
11
+ variable "host_size_family" {
12
+ description = "Size Family for the Host Group"
13
+ default = "DCSv2-Type1"
14
+ }
15
+
16
+ variable "vm_size" {
17
+ description = "VM Size"
18
+ # 8 vCPUs, 32 GiB memory
19
+ default = "Standard_DC8_v2"
20
+ }
21
+
22
+ variable "number_of_vms" {
23
+ description = "Number of VMs to create"
24
+ default = 1
25
+ }
26
+
27
+ locals {
28
+ common_tags = {
29
+ Id = "N8nBenchmark"
30
+ Terraform = "true"
31
+ Owner = "Catalysts"
32
+ CreatedAt = timestamp()
33
+ }
34
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@atom8n/n8n-benchmark",
3
+ "version": "2.0.0",
4
+ "description": "Cli for running benchmark tests for n8n",
5
+ "main": "dist/index",
6
+ "scripts": {
7
+ "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
8
+ "format": "biome format --write .",
9
+ "format:check": "biome ci .",
10
+ "lint": "eslint . --quiet",
11
+ "lint:fix": "eslint . --fix",
12
+ "start": "./bin/n8n-benchmark",
13
+ "test": "echo \"WARNING: no test specified\" && exit 0",
14
+ "typecheck": "tsc --noEmit",
15
+ "benchmark": "zx scripts/run.mjs",
16
+ "benchmark-in-cloud": "pnpm benchmark --env cloud",
17
+ "benchmark-locally": "pnpm benchmark --env local",
18
+ "provision-cloud-env": "zx scripts/provision-cloud-env.mjs",
19
+ "destroy-cloud-env": "zx scripts/destroy-cloud-env.mjs",
20
+ "watch": "tsc-watch -p tsconfig.build.json --onCompilationComplete \"tsc-alias -p tsconfig.build.json\""
21
+ },
22
+ "engines": {
23
+ "node": ">=22.16"
24
+ },
25
+ "keywords": [
26
+ "automate",
27
+ "automation",
28
+ "IaaS",
29
+ "iPaaS",
30
+ "n8n",
31
+ "workflow",
32
+ "benchmark",
33
+ "performance"
34
+ ],
35
+ "dependencies": {
36
+ "@oclif/core": "4.0.7",
37
+ "axios": "catalog:",
38
+ "dotenv": "17.2.3",
39
+ "nanoid": "catalog:",
40
+ "zx": "^8.1.4"
41
+ },
42
+ "devDependencies": {
43
+ "@atom8n/typescript-config": "1.3.0",
44
+ "@types/convict": "^6.1.1",
45
+ "@types/k6": "^0.52.0"
46
+ },
47
+ "bin": {
48
+ "n8n-benchmark": "./bin/n8n-benchmark"
49
+ },
50
+ "oclif": {
51
+ "bin": "n8n-benchmark",
52
+ "commands": "./dist/commands",
53
+ "topicSeparator": " "
54
+ }
55
+ }
@@ -0,0 +1,67 @@
1
+ {
2
+ "createdAt": "2024-09-03T11:51:56.540Z",
3
+ "updatedAt": "2024-09-03T12:22:21.000Z",
4
+ "name": "Binary Data",
5
+ "active": true,
6
+ "nodes": [
7
+ {
8
+ "parameters": {
9
+ "httpMethod": "POST",
10
+ "path": "binary-files-benchmark",
11
+ "responseMode": "responseNode",
12
+ "options": {}
13
+ },
14
+ "type": "n8n-nodes-base.webhook",
15
+ "typeVersion": 2,
16
+ "position": [0, 0],
17
+ "id": "bfe19f12-3655-440f-be5c-8d71665c6353",
18
+ "name": "Webhook",
19
+ "webhookId": "109d7b13-93ad-42b0-a9ce-ca49e1817b35"
20
+ },
21
+ {
22
+ "parameters": { "respondWith": "binary", "options": {} },
23
+ "type": "n8n-nodes-base.respondToWebhook",
24
+ "typeVersion": 1.1,
25
+ "position": [740, 0],
26
+ "id": "cd957c9b-6b7a-4423-aac3-6df4d8bb571e",
27
+ "name": "Respond to Webhook"
28
+ },
29
+ {
30
+ "parameters": {
31
+ "operation": "write",
32
+ "fileName": "=file-{{ Date.now() }}-{{ Math.random() }}.js",
33
+ "dataPropertyName": "file",
34
+ "options": {}
35
+ },
36
+ "type": "n8n-nodes-base.readWriteFile",
37
+ "typeVersion": 1,
38
+ "position": [260, 0],
39
+ "id": "f2ce4709-7697-4bc6-8eca-6c222485297a",
40
+ "name": "Write File to Disk"
41
+ },
42
+ {
43
+ "parameters": { "fileSelector": "={{ $json.fileName }}", "options": {} },
44
+ "type": "n8n-nodes-base.readWriteFile",
45
+ "typeVersion": 1,
46
+ "position": [500, 0],
47
+ "id": "198e8a6c-81a3-4b34-b099-501961a02006",
48
+ "name": "Read File from Disk"
49
+ }
50
+ ],
51
+ "connections": {
52
+ "Webhook": { "main": [[{ "node": "Write File to Disk", "type": "main", "index": 0 }]] },
53
+ "Write File to Disk": {
54
+ "main": [[{ "node": "Read File from Disk", "type": "main", "index": 0 }]]
55
+ },
56
+ "Read File from Disk": {
57
+ "main": [[{ "node": "Respond to Webhook", "type": "main", "index": 0 }]]
58
+ }
59
+ },
60
+ "settings": { "executionOrder": "v1" },
61
+ "staticData": null,
62
+ "meta": null,
63
+ "pinData": {},
64
+ "versionId": "8dd197c0-d1ea-43c3-9f88-9d11e7b081a0",
65
+ "triggerCount": 1,
66
+ "tags": []
67
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "../scenario.schema.json",
3
+ "name": "BinaryData",
4
+ "description": "Send a binary file to a webhook, write it to FS, read it from FS and receive it back",
5
+ "scenarioData": { "workflowFiles": ["binary-data.json"] },
6
+ "scriptPath": "binary-data.script.js"
7
+ }
@@ -0,0 +1,29 @@
1
+ import http from 'k6/http';
2
+ import { check } from 'k6';
3
+
4
+ const apiBaseUrl = __ENV.API_BASE_URL;
5
+
6
+ // This creates a 2MB file (16 * 128 * 1024 = 2 * 1024 * 1024 = 2MB)
7
+ const file = Array.from({ length: 128 * 1024 }, () => Math.random().toString().slice(2)).join('');
8
+ const filename = 'test.bin';
9
+
10
+ export default function () {
11
+ const data = {
12
+ filename,
13
+ file: http.file(file, filename, 'application/javascript'),
14
+ };
15
+
16
+ const res = http.post(`${apiBaseUrl}/webhook/binary-files-benchmark`, data);
17
+
18
+ if (res.status !== 200) {
19
+ console.error(
20
+ `Invalid response. Received status ${res.status}. Body: ${JSON.stringify(res.body)}`,
21
+ );
22
+ }
23
+
24
+ check(res, {
25
+ 'is status 200': (r) => r.status === 200,
26
+ 'has correct content type': (r) =>
27
+ r.headers['Content-Type'] === 'application/javascript; charset=utf-8',
28
+ });
29
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "type": "httpBearerAuth",
3
+ "name": "Dummy HTTP credential",
4
+ "data": {
5
+ "token": "dummy token"
6
+ },
7
+ "id": "0fqzOReozl2aQvtl"
8
+ }