diff --git a/.gitignore b/.gitignore index 2fd947e..d72b4aa 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,6 @@ __pycache__/ gen/ tree-sitter/ test/ -modules/ +/modules/ cloud-controlplane/ node_modules diff --git a/__tests__/rpcn-connector-docs/docs-data/connect-4.53.0.json b/__tests__/docs-data/connect-4.53.0.json similarity index 100% rename from __tests__/rpcn-connector-docs/docs-data/connect-4.53.0.json rename to __tests__/docs-data/connect-4.53.0.json diff --git a/__tests__/docs-data/mock-master-data.yaml b/__tests__/docs-data/mock-master-data.yaml new file mode 100644 index 0000000..bee1096 --- /dev/null +++ b/__tests__/docs-data/mock-master-data.yaml @@ -0,0 +1,25 @@ +products: + - name: "Basic Tier" + redpandaConfigProfileName: "test-tier-basic" + isPublic: true + cloudProvider: "testcloud" + advertisedMaxIngress: 1000000 + advertisedMaxEgress: 2000000 + advertisedMaxPartitionCount: 100 + advertisedMaxClientCount: 50 + - name: "Advanced Tier" + redpandaConfigProfileName: "test-tier-advanced" + isPublic: true + cloudProvider: "testcloud" + advertisedMaxIngress: 5000000 + advertisedMaxEgress: 10000000 + advertisedMaxPartitionCount: 1000 + advertisedMaxClientCount: 500 + - name: "Internal Tier" + redpandaConfigProfileName: "test-tier-internal" + isPublic: false + cloudProvider: "testcloud" + advertisedMaxIngress: 10000000 + advertisedMaxEgress: 20000000 + advertisedMaxPartitionCount: 2000 + advertisedMaxClientCount: 1000 diff --git a/__tests__/docs-data/mock-tier.yml b/__tests__/docs-data/mock-tier.yml new file mode 100644 index 0000000..5f46fe4 --- /dev/null +++ b/__tests__/docs-data/mock-tier.yml @@ -0,0 +1,118 @@ +config_profiles: + test-tier-basic: + cloud_provider: testcloud + machine_type: t2.micro + nodes_count: 1 + connectors_machine_type: t2.nano + connectors_heap_size: 512M + connectors_tasks_per_pod: 2 + utility_machine_type_name: t2.nano + utility_min_node_count: 1 + utility_max_node_count: 1 + redpanda_reserved_cpu_count: 0 + cluster_config: + topic_partitions_per_shard: '10' + topic_memory_per_partition: '1024' + kafka_connections_max: '100' + max_concurrent_producer_ids: '100' + log_segment_size: '1048576' + log_segment_size_min: '524288' + log_segment_size_max: '1048576' + compacted_log_segment_size: '524288' + max_compacted_log_segment_size: '2097152' + disk_reservation_percent: '5' + retention_local_target_capacity_percent: '50' + cloud_storage_cache_size_percent: '10' + retention_local_target_ms_default: '60000' + cloud_storage_segment_max_upload_interval_sec: '600' + log_segment_ms_min: '10000' + rpc_server_listen_backlog: '50' + rpc_client_connections_per_peer: '5' + kafka_connection_rate_limit: '10' + kafka_throughput_limit_node_in_bps: '1000000' + kafka_throughput_limit_node_out_bps: '2000000' + rps_limit_topic_operations: '5' + rps_limit_acls_and_users_operations: '5' + rps_limit_node_management_operations: '1' + rps_limit_move_operations: '10' + rps_limit_configuration_operations: '1' + kafka_batch_max_bytes: '10240' + kafka_topics_max: '100' + test-tier-basic-v2: + cloud_provider: testcloud + machine_type: t2.small + nodes_count: 1 + connectors_machine_type: t2.nano + connectors_heap_size: 512M + connectors_tasks_per_pod: 2 + utility_machine_type_name: t2.nano + utility_min_node_count: 1 + utility_max_node_count: 1 + redpanda_reserved_cpu_count: 0 + cluster_config: + topic_partitions_per_shard: '20' + topic_memory_per_partition: '2048' + kafka_connections_max: '200' + max_concurrent_producer_ids: '200' + log_segment_size: '2097152' + log_segment_size_min: '1048576' + log_segment_size_max: '2097152' + compacted_log_segment_size: '1048576' + max_compacted_log_segment_size: '4194304' + disk_reservation_percent: '5' + retention_local_target_capacity_percent: '50' + cloud_storage_cache_size_percent: '10' + retention_local_target_ms_default: '60000' + cloud_storage_segment_max_upload_interval_sec: '600' + log_segment_ms_min: '10000' + rpc_server_listen_backlog: '50' + rpc_client_connections_per_peer: '5' + kafka_connection_rate_limit: '10' + kafka_throughput_limit_node_in_bps: '2000000' + kafka_throughput_limit_node_out_bps: '4000000' + rps_limit_topic_operations: '5' + rps_limit_acls_and_users_operations: '5' + rps_limit_node_management_operations: '1' + rps_limit_move_operations: '10' + rps_limit_configuration_operations: '1' + kafka_batch_max_bytes: '10240' + kafka_topics_max: '100' + test-tier-advanced: + cloud_provider: testcloud + machine_type: t2.large + nodes_count: 5 + connectors_machine_type: t2.medium + connectors_heap_size: 2G + connectors_tasks_per_pod: 10 + utility_machine_type_name: t2.medium + utility_min_node_count: 2 + utility_max_node_count: 5 + redpanda_reserved_cpu_count: 1 + cluster_config: + topic_partitions_per_shard: '100' + topic_memory_per_partition: '4096' + kafka_connections_max: '500' + max_concurrent_producer_ids: '500' + log_segment_size: '2097152' + log_segment_size_min: '1048576' + log_segment_size_max: '2097152' + compacted_log_segment_size: '1048576' + max_compacted_log_segment_size: '4194304' + disk_reservation_percent: '10' + retention_local_target_capacity_percent: '80' + cloud_storage_cache_size_percent: '20' + retention_local_target_ms_default: '120000' + cloud_storage_segment_max_upload_interval_sec: '1200' + log_segment_ms_min: '20000' + rpc_server_listen_backlog: '100' + rpc_client_connections_per_peer: '10' + kafka_connection_rate_limit: '20' + kafka_throughput_limit_node_in_bps: '5000000' + kafka_throughput_limit_node_out_bps: '10000000' + rps_limit_topic_operations: '20' + rps_limit_acls_and_users_operations: '20' + rps_limit_node_management_operations: '2' + rps_limit_move_operations: '50' + rps_limit_configuration_operations: '2' + kafka_batch_max_bytes: '20480' + kafka_topics_max: '500' diff --git a/__tests__/tools/cloud-tier-table.spec.js b/__tests__/tools/cloud-tier-table.spec.js new file mode 100644 index 0000000..59a9138 --- /dev/null +++ b/__tests__/tools/cloud-tier-table.spec.js @@ -0,0 +1,70 @@ +const path = require('path'); +const fs = require('fs'); +const { generateCloudTierTable } = require('../../tools/cloud-tier-table/generate-cloud-tier-table.js'); + +describe('generateCloudTierTable', () => { + it('should generate a markdown table from mock YAML using public tiers', async () => { + const input = path.resolve(__dirname, '../docs-data/mock-tier.yml'); + const masterDataPath = path.resolve(__dirname, '../docs-data/mock-master-data.yaml'); + const result = await generateCloudTierTable({ + input, + output: '', + format: 'md', + template: undefined, + masterData: masterDataPath + }); + expect(result).toContain('| Tier | Cloud Provider | Machine Type | Number of Nodes'); + expect(result).toContain('Basic Tier'); + expect(result).toContain('Advanced Tier'); + expect(result).not.toContain('Internal Tier'); // Should not include non-public tiers + }); + + it('should only include public tiers from master-data', async () => { + const input = path.resolve(__dirname, '../docs-data/mock-tier.yml'); + const masterDataPath = path.resolve(__dirname, '../docs-data/mock-master-data.yaml'); + const result = await generateCloudTierTable({ + input, + output: '', + format: 'md', + template: undefined, + masterData: masterDataPath + }); + expect(result).toContain('Basic Tier'); + expect(result).toContain('Advanced Tier'); + expect(result).not.toContain('Internal Tier'); // isPublic: false should be excluded + }); + + it('should use names from master-data instead of config profile names', async () => { + const input = path.resolve(__dirname, '../docs-data/mock-tier.yml'); + const masterDataPath = path.resolve(__dirname, '../docs-data/mock-master-data.yaml'); + const result = await generateCloudTierTable({ + input, + output: '', + format: 'md', + template: undefined, + masterData: masterDataPath + }); + expect(result).toContain('Basic Tier'); // name from master-data + expect(result).toContain('Advanced Tier'); // name from master-data + expect(result).not.toContain('test-tier-basic'); // config profile name should not appear + expect(result).not.toContain('test-tier-advanced'); // config profile name should not appear + }); + + it('should include advertised limits from master-data', async () => { + const input = path.resolve(__dirname, '../docs-data/mock-tier.yml'); + const masterDataPath = path.resolve(__dirname, '../docs-data/mock-master-data.yaml'); + const result = await generateCloudTierTable({ + input, + output: '', + format: 'md', + template: undefined, + masterData: masterDataPath + }); + expect(result).toContain('Max Ingress (bps)'); + expect(result).toContain('Max Egress (bps)'); + expect(result).toContain('Max Partitions'); + expect(result).toContain('Max Client Connections'); + expect(result).toContain('1000000'); // Basic tier ingress + expect(result).toContain('5000000'); // Advanced tier ingress + }); +}); diff --git a/bin/doc-tools.js b/bin/doc-tools.js index 69a2781..b636227 100755 --- a/bin/doc-tools.js +++ b/bin/doc-tools.js @@ -18,6 +18,12 @@ const { printDeltaReport } = require('../tools/redpanda-connect/report-delta'); +// Cloud tier table default URLs +const CLOUD_TIER_DEFAULTS = { + INPUT_URL: 'https://api.github.com/repos/redpanda-data/cloudv2/contents/install-pack', + MASTER_DATA_URL: 'https://api.github.com/repos/redpanda-data/cloudv2-infra/contents/apps/master-data-reconciler/manifests/overlays/production/master-data.yaml?ref=integration' +}; + /** * Searches upward from a starting directory to locate the repository root. * @@ -190,7 +196,6 @@ For more details, see: https://go.dev/doc/install 'version' ); } - /** * Ensures that all required tools for Helm documentation generation are installed. * @@ -1334,11 +1339,217 @@ automation /** * Generate Markdown table of cloud regions and tiers from master-data.yaml */ +automation + .command('cloud-docs') + .description('Generate documentation for Redpanda Cloud infrastructure (regions, tiers, reports)') + .addCommand( + new Command('regions') + .description('Generate tables of available cloud regions by provider/cluster type') + .option('--output ', 'Output file (relative to repo root)', 'cloud-controlplane/x-topics/cloud-regions.md') + .option('--format ', 'Output format: md (Markdown) or adoc (AsciiDoc)', 'md') + .option('--tabs', 'Generate simplified region lists organized by cluster type in separate AsciiDoc files with tabs (creates -byoc.adoc and -dedicated.adoc files with region names only)') + .option('--owner ', 'GitHub repository owner', 'redpanda-data') + .option('--repo ', 'GitHub repository name', 'cloudv2-infra') + .option('--path ', 'Path to YAML file in repository', 'apps/master-data-reconciler/manifests/overlays/production/master-data.yaml') + .option('--ref ', 'Git reference (branch, tag, or commit SHA)', 'integration') + .option('--template ', 'Path to custom Handlebars template (relative to repo root)') + .option('--dry-run', 'Print output to stdout instead of writing file') + .action(async (options) => { + const { generateCloudRegions } = require('../tools/cloud-regions/generate-cloud-regions.js'); + + try { + const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.REDPANDA_GITHUB_TOKEN; + if (!token) { + throw new Error('GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN environment variable is required to fetch from private cloudv2-infra repo.'); + } + const fmt = (options.format || 'md').toLowerCase(); + let templatePath = undefined; + if (options.template) { + const repoRoot = findRepoRoot(); + templatePath = path.resolve(repoRoot, options.template); + if (!fs.existsSync(templatePath)) { + throw new Error(`Custom template not found: ${templatePath}`); + } + } + const out = await generateCloudRegions({ + owner: options.owner, + repo: options.repo, + path: options.path, + ref: options.ref, + format: fmt, + token, + template: templatePath, + tabs: options.tabs, + }); + + if (options.tabs && typeof out === 'object') { + // Handle separate files for BYOC and Dedicated + if (options.dryRun) { + console.log('\n=== BYOC Regions ===\n'); + process.stdout.write(out.byoc || 'No BYOC regions found\n'); + console.log('\n=== Dedicated Regions ===\n'); + process.stdout.write(out.dedicated || 'No Dedicated regions found\n'); + console.log(`\n✅ (dry-run) Separate ${fmt === 'adoc' ? 'AsciiDoc' : 'Markdown'} files printed to stdout.`); + } else { + const repoRoot = findRepoRoot(); + const outputDir = path.dirname(path.resolve(repoRoot, options.output)); + const outputExt = path.extname(options.output); + const outputBase = path.basename(options.output, outputExt); + + fs.mkdirSync(outputDir, { recursive: true }); + + const files = []; + if (out.byoc) { + const byocFile = path.join(outputDir, `${outputBase}-byoc${outputExt}`); + fs.writeFileSync(byocFile, out.byoc, 'utf8'); + files.push(byocFile); + } + if (out.dedicated) { + const dedicatedFile = path.join(outputDir, `${outputBase}-dedicated${outputExt}`); + fs.writeFileSync(dedicatedFile, out.dedicated, 'utf8'); + files.push(dedicatedFile); + } + + console.log(`✅ Wrote ${files.length} files: ${files.join(', ')}`); + } + } else { + if (options.dryRun) { + process.stdout.write(out); + console.log(`\n✅ (dry-run) ${fmt === 'adoc' ? 'AsciiDoc' : 'Markdown'} output printed to stdout.`); + } else { + const repoRoot = findRepoRoot(); + const absOutput = path.resolve(repoRoot, options.output); + fs.mkdirSync(path.dirname(absOutput), { recursive: true }); + fs.writeFileSync(absOutput, out, 'utf8'); + console.log(`✅ Wrote ${absOutput}`); + } + } + } catch (err) { + console.error(`❌ Failed to generate cloud regions: ${err.message}`); + process.exit(1); + } + }) + ) + .addCommand( + new Command('tiers') + .description('Generate detailed tables of cloud tier limits and quotas') + .option('--input ', 'Path or URL to the tier YAML file', CLOUD_TIER_DEFAULTS.INPUT_URL) + .option('--master-data ', 'Path or URL to the master-data YAML file', CLOUD_TIER_DEFAULTS.MASTER_DATA_URL) + .option('--output ', 'Output file (relative to repo root)', 'modules/reference/examples/cloud-limits-by-tier.html') + .option('--format ', 'Output format: md (Markdown), adoc (AsciiDoc), html, or csv', 'html') + .option('--limits ', 'Comma-separated list of limits to include in the table. Available limits include: cloud_provider, machine_type, nodes_count, advertisedMaxIngress, advertisedMaxEgress, advertisedMaxPartitionCount, advertisedMaxClientCount, kafka_connections_max, topic_partitions_per_shard, and more. Example: "cloud_provider,nodes_count,kafka_connections_max"') + .option('--dry-run', 'Print output to stdout instead of writing file') + .option('--template ', 'Path to custom Handlebars template (relative to repo root)') + .action(async (options) => { + const { generateCloudTierTable } = require('../tools/cloud-tier-table/generate-cloud-tier-table.js'); + try { + let templatePath = undefined; + if (options.template) { + const repoRoot = findRepoRoot(); + templatePath = path.resolve(repoRoot, options.template); + if (!fs.existsSync(templatePath)) { + throw new Error(`Custom template not found: ${templatePath}`); + } + } + + // Parse limits if provided + let limits = undefined; + if (options.limits) { + limits = options.limits.split(',').map(limit => limit.trim()).filter(limit => limit.length > 0); + if (limits.length === 0) { + throw new Error('--limits option cannot be empty. Provide comma-separated limit names.'); + } + console.log(`Using custom limits: ${limits.join(', ')}`); + } + + const formatExt = { + md: '.md', + adoc: '.adoc', + html: '.html', + csv: '.csv', + }; + let outputFile = options.output; + const fmt = (options.format || 'md').toLowerCase(); + const ext = formatExt[fmt] || '.md'; + if (!outputFile.endsWith(ext)) { + outputFile = outputFile.replace(/\.(md|adoc|html|csv)$/i, '') + ext; + } + const out = await generateCloudTierTable({ + input: options.input, + masterData: options.masterData, + output: outputFile, + format: options.format, + template: templatePath, + limits: limits, + }); + if (options.dryRun) { + process.stdout.write(out); + console.log(`\n✅ (dry-run) ${fmt} output printed to stdout.`); + } else { + const repoRoot = findRepoRoot(); + const absOutput = path.resolve(repoRoot, outputFile); + fs.mkdirSync(path.dirname(absOutput), { recursive: true }); + fs.writeFileSync(absOutput, out, 'utf8'); + console.log(`✅ Wrote ${absOutput}`); + } + } catch (err) { + console.error(`❌ Failed to generate cloud tier table: ${err.message}`); + process.exit(1); + } + }) + ) + .addCommand( + new Command('discrepancy') + .description('Generate reports analyzing discrepancies between advertised and actual cloud tier limits') + .option('--input ', 'Path or URL to the tier YAML file', CLOUD_TIER_DEFAULTS.INPUT_URL) + .option('--master-data ', 'Path or URL to the master-data YAML file', CLOUD_TIER_DEFAULTS.MASTER_DATA_URL) + .option('--output ', 'Output file (relative to repo root)', 'cloud-tier-discrepancy-report.md') + .option('--format ', 'Output format: md (Markdown) or json', 'md') + .option('--dry-run', 'Print output to stdout instead of writing file') + .action(async (options) => { + const { generateDiscrepancyReport } = require('../tools/cloud-tier-table/generate-discrepancy-report.js'); + try { + const formatExt = { + md: '.md', + json: '.json', + }; + let outputFile = options.output; + const fmt = (options.format || 'md').toLowerCase(); + const ext = formatExt[fmt] || '.md'; + if (!outputFile.endsWith(ext)) { + outputFile = outputFile.replace(/\.(md|json)$/i, '') + ext; + } + + const report = await generateDiscrepancyReport({ + input: options.input, + masterData: options.masterData, + format: options.format === 'json' ? 'json' : 'markdown', + }); + + if (options.dryRun) { + process.stdout.write(report); + console.log(`\n✅ (dry-run) ${fmt} discrepancy report printed to stdout.`); + } else { + const repoRoot = findRepoRoot(); + const fullOutputPath = path.resolve(repoRoot, outputFile); + fs.mkdirSync(path.dirname(fullOutputPath), { recursive: true }); + fs.writeFileSync(fullOutputPath, report); + console.log(`✅ Discrepancy report written to ${outputFile}`); + } + } catch (err) { + console.error(`❌ Failed to generate discrepancy report: ${err.message}`); + process.exit(1); + } + }) + ); + +// Backward compatibility - keep old commands with deprecation warnings automation .command('cloud-regions') - .description('Generate Markdown table of cloud regions and tiers from GitHub YAML file') + .description('⚠️ DEPRECATED: Use "cloud-docs regions" instead. Generate Markdown or AsciiDoc table of cloud regions and tiers from GitHub YAML file') .option('--output ', 'Output file (relative to repo root)', 'cloud-controlplane/x-topics/cloud-regions.md') .option('--format ', 'Output format: md (Markdown) or adoc (AsciiDoc)', 'md') + .option('--tabs', 'Generate simplified region lists organized by cluster type in separate AsciiDoc files with tabs (creates -byoc.adoc and -dedicated.adoc files with region names only)') .option('--owner ', 'GitHub repository owner', 'redpanda-data') .option('--repo ', 'GitHub repository name', 'cloudv2-infra') .option('--path ', 'Path to YAML file in repository', 'apps/master-data-reconciler/manifests/overlays/production/master-data.yaml') @@ -1346,6 +1557,7 @@ automation .option('--template ', 'Path to custom Handlebars template (relative to repo root)') .option('--dry-run', 'Print output to stdout instead of writing file') .action(async (options) => { + console.warn('⚠️ WARNING: "cloud-regions" command is deprecated. Use "doc-tools generate cloud-docs regions" instead.'); const { generateCloudRegions } = require('../tools/cloud-regions/generate-cloud-regions.js'); try { @@ -1370,17 +1582,52 @@ automation format: fmt, token, template: templatePath, + tabs: options.tabs, }); - if (options.dryRun) { - process.stdout.write(out); - console.log(`\n✅ (dry-run) ${fmt === 'adoc' ? 'AsciiDoc' : 'Markdown'} output printed to stdout.`); + + if (options.tabs && typeof out === 'object') { + // Handle separate files for BYOC and Dedicated + if (options.dryRun) { + console.log('\n=== BYOC Regions ===\n'); + process.stdout.write(out.byoc || 'No BYOC regions found\n'); + console.log('\n=== Dedicated Regions ===\n'); + process.stdout.write(out.dedicated || 'No Dedicated regions found\n'); + console.log(`\n✅ (dry-run) Separate ${fmt === 'adoc' ? 'AsciiDoc' : 'Markdown'} files printed to stdout.`); + } else { + const repoRoot = findRepoRoot(); + const outputDir = path.dirname(path.resolve(repoRoot, options.output)); + const outputExt = path.extname(options.output); + const outputBase = path.basename(options.output, outputExt); + + fs.mkdirSync(outputDir, { recursive: true }); + + const files = []; + if (out.byoc) { + const byocFile = path.join(outputDir, `${outputBase}-byoc${outputExt}`); + fs.writeFileSync(byocFile, out.byoc, 'utf8'); + files.push(byocFile); + } + if (out.dedicated) { + const dedicatedFile = path.join(outputDir, `${outputBase}-dedicated${outputExt}`); + fs.writeFileSync(dedicatedFile, out.dedicated, 'utf8'); + files.push(dedicatedFile); + } + + console.log(`✅ Wrote ${files.length} files: ${files.join(', ')}`); + } } else { - // Always resolve output relative to repo root - const repoRoot = findRepoRoot(); - const absOutput = path.resolve(repoRoot, options.output); - fs.mkdirSync(path.dirname(absOutput), { recursive: true }); - fs.writeFileSync(absOutput, out, 'utf8'); - console.log(`✅ Wrote ${absOutput}`); + // Handle single file output + if (options.dryRun) { + process.stdout.write(out); + console.log(`\n✅ (dry-run) ${fmt === 'adoc' ? 'AsciiDoc' : 'Markdown'} output printed to stdout.`); + } else { + // Always resolve output relative to repo root + const repoRoot = findRepoRoot(); + const absOutput = path.resolve(repoRoot, options.output); + fs.mkdirSync(path.dirname(absOutput), { recursive: true }); + fs.writeFileSync(absOutput, out, 'utf8'); + console.log(`✅ Wrote ${absOutput}`); + } } } catch (err) { console.error(`❌ Failed to generate cloud regions: ${err.message}`); diff --git a/package-lock.json b/package-lock.json index ff3b438..b4e88af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@redpanda-data/docs-extensions-and-macros", - "version": "4.10.0", + "version": "4.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@redpanda-data/docs-extensions-and-macros", - "version": "4.10.0", + "version": "4.11.0", "license": "ISC", "dependencies": { "@asciidoctor/tabs": "^1.0.0-beta.6", diff --git a/package.json b/package.json index d996772..3d35ad3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@redpanda-data/docs-extensions-and-macros", - "version": "4.10.0", + "version": "4.11.0", "description": "Antora extensions and macros developed for Redpanda documentation.", "keywords": [ "antora", diff --git a/preview/extensions-and-macros/modules/ROOT/examples/cloud-limits-by-tier.html b/preview/extensions-and-macros/modules/ROOT/examples/cloud-limits-by-tier.html new file mode 100644 index 0000000..23bc775 --- /dev/null +++ b/preview/extensions-and-macros/modules/ROOT/examples/cloud-limits-by-tier.html @@ -0,0 +1,2360 @@ + + + +
+ + + + + + + + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TierCloud ProviderMachine TypeNumber of NodesMax Ingress (bps)Max Egress (bps)Max PartitionsMax Client ConnectionsMax Kafka Topics
tier-1-aws-v2-arm, tier-1-aws-v3-armAWSm7gd.large320000000600000002000900040000
tier-1-aws-v2-x86AWSi3en.large320000000600000002000900040000
tier-2-aws-v2-arm, tier-2-aws-v3-armAWSm7gd.xlarge35000000015000000056002250040000
tier-2-aws-v2-x86AWSi3en.xlarge35000000015000000056002250040000
tier-3-aws-v2-arm, tier-3-aws-v3-armAWSm7gd.xlarge6100000000200000000112004500040000
tier-3-aws-v2-x86AWSi3en.xlarge6100000000200000000112004500040000
tier-4-aws-v2-arm, tier-4-aws-v3-armAWSm7gd.xlarge12200000000400000000226009000040000
tier-4-aws-v2-x86AWSi3en.xlarge12200000000400000000226009000040000
tier-5-aws-v2-arm, tier-5-aws-v3-armAWSm7gd.8xlarge34000000008000000004560018000040000
tier-5-aws-v2-x86AWSi3en.6xlarge34000000008000000004560018000040000
tier-6-aws-v2-arm, tier-6-aws-v3-armAWSm7gd.8xlarge680000000016000000009000018000040000
tier-6-aws-v2-x86AWSi3en.6xlarge680000000016000000009000018000040000
tier-7-aws-v2-arm, tier-7-aws-v3-armAWSm7gd.8xlarge91200000000240000000011250027000040000
tier-7-aws-v2-x86AWSi3en.6xlarge91200000000240000000011250027000040000
tier-8-aws-v2-arm, tier-8-aws-v3-armAWSm7gd.8xlarge121600000000320000000011250036000040000
tier-8-aws-v2-x86AWSi3en.6xlarge121600000000320000000011250036000040000
tier-9-aws-v2-arm, tier-9-aws-v3-armAWSm7gd.8xlarge152000000000400000000011250045000040000
tier-9-aws-v2-x86AWSi3en.6xlarge152000000000400000000011250045000040000
tier-1-gcp-v2-x86GCPn2d-standard-2320000000600000002000900040000
tier-2-gcp-v2-x86GCPn2d-standard-435000000015000000056002250040000
tier-3-gcp-v2-x86GCPn2d-standard-46100000000200000000112004500040000
tier-4-gcp-v2-x86GCPn2d-standard-412200000000400000000226009000040000
tier-5-gcp-v2-x86GCPn2d-standard-1664000000008000000004560018000040000
tier-6-gcp-v2-x86GCPn2d-standard-161280000000016000000009000018000040000
tier-7-gcp-v2-x86GCPn2d-standard-16181200000000240000000011250027000040000
tier-8-gcp-v2-x86GCPn2d-standard-32121600000000320000000011250036000040000
tier-9-gcp-v2-x86GCPn2d-standard-32152000000000400000000011250045000040000
tier-1-azure-v3-x86AzureStandard_D2d_v5320000000600000001000900040000
tier-2-azure-v3-x86AzureStandard_D4d_v535000000015000000028002250040000
tier-3-azure-v3-x86AzureStandard_D4d_v5610000000020000000056004500040000
tier-4-azure-v3-x86AzureStandard_D4d_v512200000000400000000113009000040000
tier-5-azure-v3-x86AzureStandard_D32d_v534000000008000000002280018000040000
+
+
+ + +
+
+
+

tier-1-aws-v2-arm, tier-1-aws-v3-arm

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + m7gd.large +
  • +
  • + nodes_count + 3 +
  • +
  • + advertisedMaxIngress + 20000000 +
  • +
  • + advertisedMaxEgress + 60000000 +
  • +
  • + advertisedMaxPartitionCount + 2000 +
  • +
  • + advertisedMaxClientCount + 9000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-1-aws-v2-x86

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + i3en.large +
  • +
  • + nodes_count + 3 +
  • +
  • + advertisedMaxIngress + 20000000 +
  • +
  • + advertisedMaxEgress + 60000000 +
  • +
  • + advertisedMaxPartitionCount + 2000 +
  • +
  • + advertisedMaxClientCount + 9000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-2-aws-v2-arm, tier-2-aws-v3-arm

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + m7gd.xlarge +
  • +
  • + nodes_count + 3 +
  • +
  • + advertisedMaxIngress + 50000000 +
  • +
  • + advertisedMaxEgress + 150000000 +
  • +
  • + advertisedMaxPartitionCount + 5600 +
  • +
  • + advertisedMaxClientCount + 22500 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-2-aws-v2-x86

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + i3en.xlarge +
  • +
  • + nodes_count + 3 +
  • +
  • + advertisedMaxIngress + 50000000 +
  • +
  • + advertisedMaxEgress + 150000000 +
  • +
  • + advertisedMaxPartitionCount + 5600 +
  • +
  • + advertisedMaxClientCount + 22500 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-3-aws-v2-arm, tier-3-aws-v3-arm

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + m7gd.xlarge +
  • +
  • + nodes_count + 6 +
  • +
  • + advertisedMaxIngress + 100000000 +
  • +
  • + advertisedMaxEgress + 200000000 +
  • +
  • + advertisedMaxPartitionCount + 11200 +
  • +
  • + advertisedMaxClientCount + 45000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-3-aws-v2-x86

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + i3en.xlarge +
  • +
  • + nodes_count + 6 +
  • +
  • + advertisedMaxIngress + 100000000 +
  • +
  • + advertisedMaxEgress + 200000000 +
  • +
  • + advertisedMaxPartitionCount + 11200 +
  • +
  • + advertisedMaxClientCount + 45000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-4-aws-v2-arm, tier-4-aws-v3-arm

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + m7gd.xlarge +
  • +
  • + nodes_count + 12 +
  • +
  • + advertisedMaxIngress + 200000000 +
  • +
  • + advertisedMaxEgress + 400000000 +
  • +
  • + advertisedMaxPartitionCount + 22600 +
  • +
  • + advertisedMaxClientCount + 90000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-4-aws-v2-x86

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + i3en.xlarge +
  • +
  • + nodes_count + 12 +
  • +
  • + advertisedMaxIngress + 200000000 +
  • +
  • + advertisedMaxEgress + 400000000 +
  • +
  • + advertisedMaxPartitionCount + 22600 +
  • +
  • + advertisedMaxClientCount + 90000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-5-aws-v2-arm, tier-5-aws-v3-arm

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + m7gd.8xlarge +
  • +
  • + nodes_count + 3 +
  • +
  • + advertisedMaxIngress + 400000000 +
  • +
  • + advertisedMaxEgress + 800000000 +
  • +
  • + advertisedMaxPartitionCount + 45600 +
  • +
  • + advertisedMaxClientCount + 180000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-5-aws-v2-x86

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + i3en.6xlarge +
  • +
  • + nodes_count + 3 +
  • +
  • + advertisedMaxIngress + 400000000 +
  • +
  • + advertisedMaxEgress + 800000000 +
  • +
  • + advertisedMaxPartitionCount + 45600 +
  • +
  • + advertisedMaxClientCount + 180000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-6-aws-v2-arm, tier-6-aws-v3-arm

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + m7gd.8xlarge +
  • +
  • + nodes_count + 6 +
  • +
  • + advertisedMaxIngress + 800000000 +
  • +
  • + advertisedMaxEgress + 1600000000 +
  • +
  • + advertisedMaxPartitionCount + 90000 +
  • +
  • + advertisedMaxClientCount + 180000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-6-aws-v2-x86

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + i3en.6xlarge +
  • +
  • + nodes_count + 6 +
  • +
  • + advertisedMaxIngress + 800000000 +
  • +
  • + advertisedMaxEgress + 1600000000 +
  • +
  • + advertisedMaxPartitionCount + 90000 +
  • +
  • + advertisedMaxClientCount + 180000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-7-aws-v2-arm, tier-7-aws-v3-arm

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + m7gd.8xlarge +
  • +
  • + nodes_count + 9 +
  • +
  • + advertisedMaxIngress + 1200000000 +
  • +
  • + advertisedMaxEgress + 2400000000 +
  • +
  • + advertisedMaxPartitionCount + 112500 +
  • +
  • + advertisedMaxClientCount + 270000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-7-aws-v2-x86

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + i3en.6xlarge +
  • +
  • + nodes_count + 9 +
  • +
  • + advertisedMaxIngress + 1200000000 +
  • +
  • + advertisedMaxEgress + 2400000000 +
  • +
  • + advertisedMaxPartitionCount + 112500 +
  • +
  • + advertisedMaxClientCount + 270000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-8-aws-v2-arm, tier-8-aws-v3-arm

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + m7gd.8xlarge +
  • +
  • + nodes_count + 12 +
  • +
  • + advertisedMaxIngress + 1600000000 +
  • +
  • + advertisedMaxEgress + 3200000000 +
  • +
  • + advertisedMaxPartitionCount + 112500 +
  • +
  • + advertisedMaxClientCount + 360000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-8-aws-v2-x86

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + i3en.6xlarge +
  • +
  • + nodes_count + 12 +
  • +
  • + advertisedMaxIngress + 1600000000 +
  • +
  • + advertisedMaxEgress + 3200000000 +
  • +
  • + advertisedMaxPartitionCount + 112500 +
  • +
  • + advertisedMaxClientCount + 360000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-9-aws-v2-arm, tier-9-aws-v3-arm

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + m7gd.8xlarge +
  • +
  • + nodes_count + 15 +
  • +
  • + advertisedMaxIngress + 2000000000 +
  • +
  • + advertisedMaxEgress + 4000000000 +
  • +
  • + advertisedMaxPartitionCount + 112500 +
  • +
  • + advertisedMaxClientCount + 450000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-9-aws-v2-x86

+ AWS +
+
    +
  • + cloud_provider + AWS +
  • +
  • + machine_type + i3en.6xlarge +
  • +
  • + nodes_count + 15 +
  • +
  • + advertisedMaxIngress + 2000000000 +
  • +
  • + advertisedMaxEgress + 4000000000 +
  • +
  • + advertisedMaxPartitionCount + 112500 +
  • +
  • + advertisedMaxClientCount + 450000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-1-gcp-v2-x86

+ GCP +
+
    +
  • + cloud_provider + GCP +
  • +
  • + machine_type + n2d-standard-2 +
  • +
  • + nodes_count + 3 +
  • +
  • + advertisedMaxIngress + 20000000 +
  • +
  • + advertisedMaxEgress + 60000000 +
  • +
  • + advertisedMaxPartitionCount + 2000 +
  • +
  • + advertisedMaxClientCount + 9000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-2-gcp-v2-x86

+ GCP +
+
    +
  • + cloud_provider + GCP +
  • +
  • + machine_type + n2d-standard-4 +
  • +
  • + nodes_count + 3 +
  • +
  • + advertisedMaxIngress + 50000000 +
  • +
  • + advertisedMaxEgress + 150000000 +
  • +
  • + advertisedMaxPartitionCount + 5600 +
  • +
  • + advertisedMaxClientCount + 22500 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-3-gcp-v2-x86

+ GCP +
+
    +
  • + cloud_provider + GCP +
  • +
  • + machine_type + n2d-standard-4 +
  • +
  • + nodes_count + 6 +
  • +
  • + advertisedMaxIngress + 100000000 +
  • +
  • + advertisedMaxEgress + 200000000 +
  • +
  • + advertisedMaxPartitionCount + 11200 +
  • +
  • + advertisedMaxClientCount + 45000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-4-gcp-v2-x86

+ GCP +
+
    +
  • + cloud_provider + GCP +
  • +
  • + machine_type + n2d-standard-4 +
  • +
  • + nodes_count + 12 +
  • +
  • + advertisedMaxIngress + 200000000 +
  • +
  • + advertisedMaxEgress + 400000000 +
  • +
  • + advertisedMaxPartitionCount + 22600 +
  • +
  • + advertisedMaxClientCount + 90000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-5-gcp-v2-x86

+ GCP +
+
    +
  • + cloud_provider + GCP +
  • +
  • + machine_type + n2d-standard-16 +
  • +
  • + nodes_count + 6 +
  • +
  • + advertisedMaxIngress + 400000000 +
  • +
  • + advertisedMaxEgress + 800000000 +
  • +
  • + advertisedMaxPartitionCount + 45600 +
  • +
  • + advertisedMaxClientCount + 180000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-6-gcp-v2-x86

+ GCP +
+
    +
  • + cloud_provider + GCP +
  • +
  • + machine_type + n2d-standard-16 +
  • +
  • + nodes_count + 12 +
  • +
  • + advertisedMaxIngress + 800000000 +
  • +
  • + advertisedMaxEgress + 1600000000 +
  • +
  • + advertisedMaxPartitionCount + 90000 +
  • +
  • + advertisedMaxClientCount + 180000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-7-gcp-v2-x86

+ GCP +
+
    +
  • + cloud_provider + GCP +
  • +
  • + machine_type + n2d-standard-16 +
  • +
  • + nodes_count + 18 +
  • +
  • + advertisedMaxIngress + 1200000000 +
  • +
  • + advertisedMaxEgress + 2400000000 +
  • +
  • + advertisedMaxPartitionCount + 112500 +
  • +
  • + advertisedMaxClientCount + 270000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-8-gcp-v2-x86

+ GCP +
+
    +
  • + cloud_provider + GCP +
  • +
  • + machine_type + n2d-standard-32 +
  • +
  • + nodes_count + 12 +
  • +
  • + advertisedMaxIngress + 1600000000 +
  • +
  • + advertisedMaxEgress + 3200000000 +
  • +
  • + advertisedMaxPartitionCount + 112500 +
  • +
  • + advertisedMaxClientCount + 360000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-9-gcp-v2-x86

+ GCP +
+
    +
  • + cloud_provider + GCP +
  • +
  • + machine_type + n2d-standard-32 +
  • +
  • + nodes_count + 15 +
  • +
  • + advertisedMaxIngress + 2000000000 +
  • +
  • + advertisedMaxEgress + 4000000000 +
  • +
  • + advertisedMaxPartitionCount + 112500 +
  • +
  • + advertisedMaxClientCount + 450000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-1-azure-v3-x86

+ Azure +
+
    +
  • + cloud_provider + Azure +
  • +
  • + machine_type + Standard_D2d_v5 +
  • +
  • + nodes_count + 3 +
  • +
  • + advertisedMaxIngress + 20000000 +
  • +
  • + advertisedMaxEgress + 60000000 +
  • +
  • + advertisedMaxPartitionCount + 1000 +
  • +
  • + advertisedMaxClientCount + 9000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-2-azure-v3-x86

+ Azure +
+
    +
  • + cloud_provider + Azure +
  • +
  • + machine_type + Standard_D4d_v5 +
  • +
  • + nodes_count + 3 +
  • +
  • + advertisedMaxIngress + 50000000 +
  • +
  • + advertisedMaxEgress + 150000000 +
  • +
  • + advertisedMaxPartitionCount + 2800 +
  • +
  • + advertisedMaxClientCount + 22500 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-3-azure-v3-x86

+ Azure +
+
    +
  • + cloud_provider + Azure +
  • +
  • + machine_type + Standard_D4d_v5 +
  • +
  • + nodes_count + 6 +
  • +
  • + advertisedMaxIngress + 100000000 +
  • +
  • + advertisedMaxEgress + 200000000 +
  • +
  • + advertisedMaxPartitionCount + 5600 +
  • +
  • + advertisedMaxClientCount + 45000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-4-azure-v3-x86

+ Azure +
+
    +
  • + cloud_provider + Azure +
  • +
  • + machine_type + Standard_D4d_v5 +
  • +
  • + nodes_count + 12 +
  • +
  • + advertisedMaxIngress + 200000000 +
  • +
  • + advertisedMaxEgress + 400000000 +
  • +
  • + advertisedMaxPartitionCount + 11300 +
  • +
  • + advertisedMaxClientCount + 90000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+
+

tier-5-azure-v3-x86

+ Azure +
+
    +
  • + cloud_provider + Azure +
  • +
  • + machine_type + Standard_D32d_v5 +
  • +
  • + nodes_count + 3 +
  • +
  • + advertisedMaxIngress + 400000000 +
  • +
  • + advertisedMaxEgress + 800000000 +
  • +
  • + advertisedMaxPartitionCount + 22800 +
  • +
  • + advertisedMaxClientCount + 180000 +
  • +
  • + kafka_topics_max + 40000 +
  • +
+
+
+ + diff --git a/preview/extensions-and-macros/modules/ROOT/nav.adoc b/preview/extensions-and-macros/modules/ROOT/nav.adoc index ef62578..89c49c7 100644 --- a/preview/extensions-and-macros/modules/ROOT/nav.adoc +++ b/preview/extensions-and-macros/modules/ROOT/nav.adoc @@ -1,3 +1,4 @@ * xref:preview:ROOT:test.adoc[] * xref:preview:ROOT:docker-labs.adoc[] +* xref:preview:ROOT:cloud-limits.adoc[] * xref:preview:reference:glossary.adoc[] \ No newline at end of file diff --git a/preview/extensions-and-macros/modules/ROOT/pages/cloud-limits.adoc b/preview/extensions-and-macros/modules/ROOT/pages/cloud-limits.adoc new file mode 100644 index 0000000..e67ceeb --- /dev/null +++ b/preview/extensions-and-macros/modules/ROOT/pages/cloud-limits.adoc @@ -0,0 +1,6 @@ += Cloud Limits +:page-no-toc: true + +++++ +include::ROOT:example$cloud-limits-by-tier.html[] +++++ \ No newline at end of file diff --git a/preview/extensions-and-macros/modules/ROOT/partials/regions-byoc.adoc b/preview/extensions-and-macros/modules/ROOT/partials/regions-byoc.adoc new file mode 100644 index 0000000..cddf498 --- /dev/null +++ b/preview/extensions-and-macros/modules/ROOT/partials/regions-byoc.adoc @@ -0,0 +1,88 @@ + +//// +This content is auto-generated. Do not edit manually. + +To regenerate this content, run: + npx doc-tools generate cloud-regions --help + +For source code and documentation: +- Source: https://github.com/redpanda-data/docs-extensions-and-macros/tree/main/tools/cloud-regions +- Docs: https://redpandadata.atlassian.net/wiki/spaces/DOC/pages/1185054748/Doc+Tools+CLI +//// + +== BYOC supported regions + +[tabs] +==== +Google Cloud Platform (GCP):: ++ +-- +|=== +| Region + +| asia-east1 +| asia-northeast1 +| asia-south1 +| asia-southeast1 +| australia-southeast1 +| europe-southwest1 +| europe-west1 +| europe-west2 +| europe-west3 +| europe-west4 +| europe-west9 +| northamerica-northeast1 +| southamerica-east1 +| southamerica-west1 +| us-central1 +| us-east1 +| us-east4 +| us-west1 +| us-west2 +|=== +-- +Amazon Web Services (AWS):: ++ +-- +|=== +| Region + +| af-south-1 +| ap-east-1 +| ap-northeast-1 +| ap-south-1 +| ap-southeast-1 +| ap-southeast-2 +| ap-southeast-3 +| ca-central-1 +| eu-central-1 +| eu-north-1 +| eu-south-1 +| eu-west-1 +| eu-west-2 +| eu-west-3 +| me-central-1 +| sa-east-1 +| us-east-1 +| us-east-2 +| us-west-2 +|=== +-- +Azure:: ++ +-- +|=== +| Region + +| centralus +| eastus +| eastus2 +| northeurope +| norwayeast +| uksouth +| westeurope +| westus2 +|=== +-- +==== + diff --git a/preview/extensions-and-macros/modules/ROOT/partials/regions-dedicated.adoc b/preview/extensions-and-macros/modules/ROOT/partials/regions-dedicated.adoc new file mode 100644 index 0000000..f42e74f --- /dev/null +++ b/preview/extensions-and-macros/modules/ROOT/partials/regions-dedicated.adoc @@ -0,0 +1,71 @@ + +//// +This content is auto-generated. Do not edit manually. + +To regenerate this content, run: + npx doc-tools generate cloud-regions --help + +For source code and documentation: +- Source: https://github.com/redpanda-data/docs-extensions-and-macros/tree/main/tools/cloud-regions +- Docs: https://redpandadata.atlassian.net/wiki/spaces/DOC/pages/1185054748/Doc+Tools+CLI +//// + +== Dedicated supported regions + +[tabs] +==== +Google Cloud Platform (GCP):: ++ +-- +|=== +| Region + +| asia-east1 +| asia-northeast1 +| asia-south1 +| asia-southeast1 +| australia-southeast1 +| europe-west1 +| europe-west2 +| europe-west3 +| northamerica-northeast1 +| southamerica-east1 +| us-central1 +| us-east1 +|=== +-- +Amazon Web Services (AWS):: ++ +-- +|=== +| Region + +| ap-northeast-1 +| ap-south-1 +| ap-southeast-1 +| ap-southeast-2 +| ca-central-1 +| eu-central-1 +| eu-west-1 +| eu-west-2 +| eu-west-3 +| us-east-1 +| us-east-2 +| us-west-2 +|=== +-- +Azure:: ++ +-- +|=== +| Region + +| centralus +| eastus +| eastus2 +| norwayeast +| uksouth +|=== +-- +==== + diff --git a/tier-discrepancy-report.json b/tier-discrepancy-report.json new file mode 100644 index 0000000..39f1508 --- /dev/null +++ b/tier-discrepancy-report.json @@ -0,0 +1,1977 @@ +{ + "generatedDate": "2025-10-01T09:22:22.864Z", + "summary": { + "totalTiers": 41, + "totalIssues": 147 + }, + "analyses": [ + { + "tierName": "Tier 1", + "cloudProvider": "AWS", + "machineType": "im4gn.large", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 20000000, + "advertisedFormatted": "19.1 Mbps", + "actual": 13333334, + "actualFormatted": "12.7 Mbps", + "percentageDiff": -33.33333, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 60000000, + "advertisedFormatted": "57.2 Mbps", + "actual": 40000000, + "actualFormatted": "38.1 Mbps", + "percentageDiff": -33.33333333333333, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 2000, + "advertisedFormatted": "2,000", + "actual": 6198, + "actualFormatted": "6,198", + "percentageDiff": 209.90000000000003, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 9000, + "advertisedFormatted": "9,000", + "actual": 3700, + "actualFormatted": "3,700", + "percentageDiff": -58.88888888888889, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 1", + "cloudProvider": "AWS", + "machineType": "m7gd.large", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 20000000, + "advertisedFormatted": "19.1 Mbps", + "actual": 33333334, + "actualFormatted": "31.8 Mbps", + "percentageDiff": 66.66667, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 60000000, + "advertisedFormatted": "57.2 Mbps", + "actual": 100000000, + "actualFormatted": "95.4 Mbps", + "percentageDiff": 66.66666666666666, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 2000, + "advertisedFormatted": "2,000", + "actual": 6198, + "actualFormatted": "6,198", + "percentageDiff": 209.90000000000003, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 9000, + "advertisedFormatted": "9,000", + "actual": 3700, + "actualFormatted": "3,700", + "percentageDiff": -58.88888888888889, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 1 - x86", + "cloudProvider": "AWS", + "machineType": "i3en.large", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 20000000, + "advertisedFormatted": "19.1 Mbps", + "actual": 13333334, + "actualFormatted": "12.7 Mbps", + "percentageDiff": -33.33333, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 60000000, + "advertisedFormatted": "57.2 Mbps", + "actual": 40000000, + "actualFormatted": "38.1 Mbps", + "percentageDiff": -33.33333333333333, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 2000, + "advertisedFormatted": "2,000", + "actual": 6198, + "actualFormatted": "6,198", + "percentageDiff": 209.90000000000003, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 9000, + "advertisedFormatted": "9,000", + "actual": 3700, + "actualFormatted": "3,700", + "percentageDiff": -58.88888888888889, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 2", + "cloudProvider": "AWS", + "machineType": "im4gn.xlarge", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 50000000, + "advertisedFormatted": "47.7 Mbps", + "actual": 33333334, + "actualFormatted": "31.8 Mbps", + "percentageDiff": -33.333332, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 150000000, + "advertisedFormatted": "143.1 Mbps", + "actual": 100000000, + "actualFormatted": "95.4 Mbps", + "percentageDiff": -33.33333333333333, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 5600, + "advertisedFormatted": "5,600", + "actual": 5694, + "actualFormatted": "5,694", + "percentageDiff": 1.6785714285714286, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Client Connections", + "advertised": 22500, + "advertisedFormatted": "22,500", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -59.55555555555555, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 2", + "cloudProvider": "AWS", + "machineType": "m7gd.xlarge", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 50000000, + "advertisedFormatted": "47.7 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": 33.333334, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 150000000, + "advertisedFormatted": "143.1 Mbps", + "actual": 200000000, + "actualFormatted": "190.7 Mbps", + "percentageDiff": 33.33333333333333, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 5600, + "advertisedFormatted": "5,600", + "actual": 5694, + "actualFormatted": "5,694", + "percentageDiff": 1.6785714285714286, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Client Connections", + "advertised": 22500, + "advertisedFormatted": "22,500", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -59.55555555555555, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 2 - x86", + "cloudProvider": "AWS", + "machineType": "i3en.xlarge", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 50000000, + "advertisedFormatted": "47.7 Mbps", + "actual": 33333334, + "actualFormatted": "31.8 Mbps", + "percentageDiff": -33.333332, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 150000000, + "advertisedFormatted": "143.1 Mbps", + "actual": 100000000, + "actualFormatted": "95.4 Mbps", + "percentageDiff": -33.33333333333333, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 5600, + "advertisedFormatted": "5,600", + "actual": 5694, + "actualFormatted": "5,694", + "percentageDiff": 1.6785714285714286, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Client Connections", + "advertised": 22500, + "advertisedFormatted": "22,500", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -59.55555555555555, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 3", + "cloudProvider": "AWS", + "machineType": "im4gn.xlarge", + "nodeCount": "6", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 100000000, + "advertisedFormatted": "95.4 Mbps", + "actual": 33333334, + "actualFormatted": "31.8 Mbps", + "percentageDiff": -66.66666599999999, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 200000000, + "advertisedFormatted": "190.7 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": -66.6666665, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 11200, + "advertisedFormatted": "11,200", + "actual": 11340, + "actualFormatted": "11,340", + "percentageDiff": 1.25, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Client Connections", + "advertised": 45000, + "advertisedFormatted": "45,000", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -79.77777777777779, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 3", + "cloudProvider": "AWS", + "machineType": "m7gd.xlarge", + "nodeCount": "6", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 100000000, + "advertisedFormatted": "95.4 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": -33.333332999999996, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 200000000, + "advertisedFormatted": "190.7 Mbps", + "actual": 200000000, + "actualFormatted": "190.7 Mbps", + "percentageDiff": 0, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Partitions", + "advertised": 11200, + "advertisedFormatted": "11,200", + "actual": 11340, + "actualFormatted": "11,340", + "percentageDiff": 1.25, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Client Connections", + "advertised": 45000, + "advertisedFormatted": "45,000", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -79.77777777777779, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 3 - x86", + "cloudProvider": "AWS", + "machineType": "i3en.xlarge", + "nodeCount": "6", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 100000000, + "advertisedFormatted": "95.4 Mbps", + "actual": 33333334, + "actualFormatted": "31.8 Mbps", + "percentageDiff": -66.66666599999999, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 200000000, + "advertisedFormatted": "190.7 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": -66.6666665, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 11200, + "advertisedFormatted": "11,200", + "actual": 11340, + "actualFormatted": "11,340", + "percentageDiff": 1.25, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Client Connections", + "advertised": 45000, + "advertisedFormatted": "45,000", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -79.77777777777779, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 4", + "cloudProvider": "AWS", + "machineType": "im4gn.xlarge", + "nodeCount": "12", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 200000000, + "advertisedFormatted": "190.7 Mbps", + "actual": 33333334, + "actualFormatted": "31.8 Mbps", + "percentageDiff": -83.333333, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 400000000, + "advertisedFormatted": "381.5 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": -83.33333325, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 22600, + "advertisedFormatted": "22,600", + "actual": 22836, + "actualFormatted": "22,836", + "percentageDiff": 1.0442477876106195, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Client Connections", + "advertised": 90000, + "advertisedFormatted": "90,000", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -89.88888888888889, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 4", + "cloudProvider": "AWS", + "machineType": "m7gd.xlarge", + "nodeCount": "12", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 200000000, + "advertisedFormatted": "190.7 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": -66.6666665, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 400000000, + "advertisedFormatted": "381.5 Mbps", + "actual": 200000000, + "actualFormatted": "190.7 Mbps", + "percentageDiff": -50, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 22600, + "advertisedFormatted": "22,600", + "actual": 22836, + "actualFormatted": "22,836", + "percentageDiff": 1.0442477876106195, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Client Connections", + "advertised": 90000, + "advertisedFormatted": "90,000", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -89.88888888888889, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 4 - x86", + "cloudProvider": "AWS", + "machineType": "i3en.xlarge", + "nodeCount": "12", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 200000000, + "advertisedFormatted": "190.7 Mbps", + "actual": 33333334, + "actualFormatted": "31.8 Mbps", + "percentageDiff": -83.333333, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 400000000, + "advertisedFormatted": "381.5 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": -83.33333325, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 22600, + "advertisedFormatted": "22,600", + "actual": 22836, + "actualFormatted": "22,836", + "percentageDiff": 1.0442477876106195, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Client Connections", + "advertised": 90000, + "advertisedFormatted": "90,000", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -89.88888888888889, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 5", + "cloudProvider": "AWS", + "machineType": "im4gn.8xlarge", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 400000000, + "advertisedFormatted": "381.5 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -33.33333325, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 800000000, + "advertisedFormatted": "762.9 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -33.33333325, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 45600, + "advertisedFormatted": "45,600", + "actual": 4455, + "actualFormatted": "4,455", + "percentageDiff": -90.23026315789474, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 180000, + "advertisedFormatted": "180,000", + "actual": 72100, + "actualFormatted": "72,100", + "percentageDiff": -59.94444444444444, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 5", + "cloudProvider": "AWS", + "machineType": "m7gd.8xlarge", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 400000000, + "advertisedFormatted": "381.5 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": 33.3333335, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 800000000, + "advertisedFormatted": "762.9 Mbps", + "actual": 1600000000, + "actualFormatted": "1525.9 Mbps", + "percentageDiff": 100, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 45600, + "advertisedFormatted": "45,600", + "actual": 4602, + "actualFormatted": "4,602", + "percentageDiff": -89.90789473684211, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 180000, + "advertisedFormatted": "180,000", + "actual": 72100, + "actualFormatted": "72,100", + "percentageDiff": -59.94444444444444, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 5 - x86", + "cloudProvider": "AWS", + "machineType": "i3en.6xlarge", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 400000000, + "advertisedFormatted": "381.5 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -33.33333325, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 800000000, + "advertisedFormatted": "762.9 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -33.33333325, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 45600, + "advertisedFormatted": "45,600", + "actual": 5991, + "actualFormatted": "5,991", + "percentageDiff": -86.86184210526315, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 180000, + "advertisedFormatted": "180,000", + "actual": 72100, + "actualFormatted": "72,100", + "percentageDiff": -59.94444444444444, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 6", + "cloudProvider": "AWS", + "machineType": "im4gn.8xlarge", + "nodeCount": "6", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 800000000, + "advertisedFormatted": "762.9 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -66.666666625, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 1600000000, + "advertisedFormatted": "1525.9 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -66.666666625, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 90000, + "advertisedFormatted": "90,000", + "actual": 8784, + "actualFormatted": "8,784", + "percentageDiff": -90.24, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 180000, + "advertisedFormatted": "180,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -79.94444444444444, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 6", + "cloudProvider": "AWS", + "machineType": "m7gd.8xlarge", + "nodeCount": "6", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 800000000, + "advertisedFormatted": "762.9 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -33.33333325, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 1600000000, + "advertisedFormatted": "1525.9 Mbps", + "actual": 1600000000, + "actualFormatted": "1525.9 Mbps", + "percentageDiff": 0, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Partitions", + "advertised": 90000, + "advertisedFormatted": "90,000", + "actual": 9072, + "actualFormatted": "9,072", + "percentageDiff": -89.92, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 180000, + "advertisedFormatted": "180,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -79.94444444444444, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 6 - x86", + "cloudProvider": "AWS", + "machineType": "i3en.6xlarge", + "nodeCount": "6", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 800000000, + "advertisedFormatted": "762.9 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -66.666666625, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 1600000000, + "advertisedFormatted": "1525.9 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -66.666666625, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 90000, + "advertisedFormatted": "90,000", + "actual": 11814, + "actualFormatted": "11,814", + "percentageDiff": -86.87333333333333, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 180000, + "advertisedFormatted": "180,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -79.94444444444444, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 7", + "cloudProvider": "AWS", + "machineType": "im4gn.8xlarge", + "nodeCount": "9", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 1200000000, + "advertisedFormatted": "1144.4 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -77.77777775, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 2400000000, + "advertisedFormatted": "2288.8 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -77.77777775, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 112500, + "advertisedFormatted": "112,500", + "actual": 10989, + "actualFormatted": "10,989", + "percentageDiff": -90.232, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 270000, + "advertisedFormatted": "270,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -86.62962962962963, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 7", + "cloudProvider": "AWS", + "machineType": "m7gd.8xlarge", + "nodeCount": "9", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 1200000000, + "advertisedFormatted": "1144.4 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -55.5555555, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 2400000000, + "advertisedFormatted": "2288.8 Mbps", + "actual": 1600000000, + "actualFormatted": "1525.9 Mbps", + "percentageDiff": -33.33333333333333, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 112500, + "advertisedFormatted": "112,500", + "actual": 11358, + "actualFormatted": "11,358", + "percentageDiff": -89.904, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 270000, + "advertisedFormatted": "270,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -86.62962962962963, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 7 - x86", + "cloudProvider": "AWS", + "machineType": "i3en.6xlarge", + "nodeCount": "9", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 1200000000, + "advertisedFormatted": "1144.4 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -77.77777775, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 2400000000, + "advertisedFormatted": "2288.8 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -77.77777775, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 112500, + "advertisedFormatted": "112,500", + "actual": 14778, + "actualFormatted": "14,778", + "percentageDiff": -86.86399999999999, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 270000, + "advertisedFormatted": "270,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -86.62962962962963, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 8", + "cloudProvider": "AWS", + "machineType": "im4gn.8xlarge", + "nodeCount": "12", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 1600000000, + "advertisedFormatted": "1525.9 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -83.3333333125, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 3200000000, + "advertisedFormatted": "3051.8 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -83.3333333125, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 112500, + "advertisedFormatted": "112,500", + "actual": 11028, + "actualFormatted": "11,028", + "percentageDiff": -90.19733333333333, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 360000, + "advertisedFormatted": "360,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -89.97222222222221, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 8", + "cloudProvider": "AWS", + "machineType": "m7gd.8xlarge", + "nodeCount": "12", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 1600000000, + "advertisedFormatted": "1525.9 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -66.666666625, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 3200000000, + "advertisedFormatted": "3051.8 Mbps", + "actual": 1600000000, + "actualFormatted": "1525.9 Mbps", + "percentageDiff": -50, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 112500, + "advertisedFormatted": "112,500", + "actual": 11388, + "actualFormatted": "11,388", + "percentageDiff": -89.87733333333333, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 360000, + "advertisedFormatted": "360,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -89.97222222222221, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 8 - x86", + "cloudProvider": "AWS", + "machineType": "i3en.6xlarge", + "nodeCount": "12", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 1600000000, + "advertisedFormatted": "1525.9 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -83.3333333125, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 3200000000, + "advertisedFormatted": "3051.8 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -83.3333333125, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 112500, + "advertisedFormatted": "112,500", + "actual": 14820, + "actualFormatted": "14,820", + "percentageDiff": -86.82666666666667, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 360000, + "advertisedFormatted": "360,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -89.97222222222221, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 9", + "cloudProvider": "AWS", + "machineType": "im4gn.8xlarge", + "nodeCount": "15", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 2000000000, + "advertisedFormatted": "1907.3 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -86.66666665, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 4000000000, + "advertisedFormatted": "3814.7 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -86.66666665, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 112500, + "advertisedFormatted": "112,500", + "actual": 11055, + "actualFormatted": "11,055", + "percentageDiff": -90.17333333333333, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 450000, + "advertisedFormatted": "450,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -91.97777777777777, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 9", + "cloudProvider": "AWS", + "machineType": "m7gd.8xlarge", + "nodeCount": "15", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 2000000000, + "advertisedFormatted": "1907.3 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -73.3333333, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 4000000000, + "advertisedFormatted": "3814.7 Mbps", + "actual": 1600000000, + "actualFormatted": "1525.9 Mbps", + "percentageDiff": -60, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 112500, + "advertisedFormatted": "112,500", + "actual": 11415, + "actualFormatted": "11,415", + "percentageDiff": -89.85333333333332, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 450000, + "advertisedFormatted": "450,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -91.97777777777777, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 9 - x86", + "cloudProvider": "AWS", + "machineType": "i3en.6xlarge", + "nodeCount": "15", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 2000000000, + "advertisedFormatted": "1907.3 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -86.66666665, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 4000000000, + "advertisedFormatted": "3814.7 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -86.66666665, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 112500, + "advertisedFormatted": "112,500", + "actual": 14850, + "actualFormatted": "14,850", + "percentageDiff": -86.8, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 450000, + "advertisedFormatted": "450,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -91.97777777777777, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 1", + "cloudProvider": "GCP", + "machineType": "n2d-standard-2", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 20000000, + "advertisedFormatted": "19.1 Mbps", + "actual": 33333334, + "actualFormatted": "31.8 Mbps", + "percentageDiff": 66.66667, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 60000000, + "advertisedFormatted": "57.2 Mbps", + "actual": 100000000, + "actualFormatted": "95.4 Mbps", + "percentageDiff": 66.66666666666666, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 2000, + "advertisedFormatted": "2,000", + "actual": 6198, + "actualFormatted": "6,198", + "percentageDiff": 209.90000000000003, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 9000, + "advertisedFormatted": "9,000", + "actual": 3700, + "actualFormatted": "3,700", + "percentageDiff": -58.88888888888889, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 2", + "cloudProvider": "GCP", + "machineType": "n2d-standard-4", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 50000000, + "advertisedFormatted": "47.7 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": 33.333334, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 150000000, + "advertisedFormatted": "143.1 Mbps", + "actual": 200000000, + "actualFormatted": "190.7 Mbps", + "percentageDiff": 33.33333333333333, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 5600, + "advertisedFormatted": "5,600", + "actual": 5694, + "actualFormatted": "5,694", + "percentageDiff": 1.6785714285714286, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Client Connections", + "advertised": 22500, + "advertisedFormatted": "22,500", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -59.55555555555555, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 3", + "cloudProvider": "GCP", + "machineType": "n2d-standard-4", + "nodeCount": "6", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 100000000, + "advertisedFormatted": "95.4 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": -33.333332999999996, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 200000000, + "advertisedFormatted": "190.7 Mbps", + "actual": 200000000, + "actualFormatted": "190.7 Mbps", + "percentageDiff": 0, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Partitions", + "advertised": 11200, + "advertisedFormatted": "11,200", + "actual": 11340, + "actualFormatted": "11,340", + "percentageDiff": 1.25, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Client Connections", + "advertised": 45000, + "advertisedFormatted": "45,000", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -79.77777777777779, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 4", + "cloudProvider": "GCP", + "machineType": "n2d-standard-4", + "nodeCount": "12", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 200000000, + "advertisedFormatted": "190.7 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": -66.6666665, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 400000000, + "advertisedFormatted": "381.5 Mbps", + "actual": 200000000, + "actualFormatted": "190.7 Mbps", + "percentageDiff": -50, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 22600, + "advertisedFormatted": "22,600", + "actual": 22836, + "actualFormatted": "22,836", + "percentageDiff": 1.0442477876106195, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Client Connections", + "advertised": 90000, + "advertisedFormatted": "90,000", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -89.88888888888889, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 5", + "cloudProvider": "GCP", + "machineType": "n2d-standard-16", + "nodeCount": "6", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 400000000, + "advertisedFormatted": "381.5 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -33.33333325, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 800000000, + "advertisedFormatted": "762.9 Mbps", + "actual": 800000000, + "actualFormatted": "762.9 Mbps", + "percentageDiff": 0, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Partitions", + "advertised": 45600, + "advertisedFormatted": "45,600", + "actual": 9204, + "actualFormatted": "9,204", + "percentageDiff": -79.8157894736842, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 180000, + "advertisedFormatted": "180,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -79.94444444444444, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 6", + "cloudProvider": "GCP", + "machineType": "n2d-standard-16", + "nodeCount": "12", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 800000000, + "advertisedFormatted": "762.9 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -66.666666625, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 1600000000, + "advertisedFormatted": "1525.9 Mbps", + "actual": 800000000, + "actualFormatted": "762.9 Mbps", + "percentageDiff": -50, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 90000, + "advertisedFormatted": "90,000", + "actual": 18144, + "actualFormatted": "18,144", + "percentageDiff": -79.84, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 180000, + "advertisedFormatted": "180,000", + "actual": 18100, + "actualFormatted": "18,100", + "percentageDiff": -89.94444444444444, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 7", + "cloudProvider": "GCP", + "machineType": "n2d-standard-16", + "nodeCount": "18", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 1200000000, + "advertisedFormatted": "1144.4 Mbps", + "actual": 266666667, + "actualFormatted": "254.3 Mbps", + "percentageDiff": -77.77777775, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 2400000000, + "advertisedFormatted": "2288.8 Mbps", + "actual": 800000000, + "actualFormatted": "762.9 Mbps", + "percentageDiff": -66.66666666666666, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 112500, + "advertisedFormatted": "112,500", + "actual": 22716, + "actualFormatted": "22,716", + "percentageDiff": -79.808, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 270000, + "advertisedFormatted": "270,000", + "actual": 18100, + "actualFormatted": "18,100", + "percentageDiff": -93.2962962962963, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 8", + "cloudProvider": "GCP", + "machineType": "n2d-standard-32", + "nodeCount": "12", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 1600000000, + "advertisedFormatted": "1525.9 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -66.666666625, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 3200000000, + "advertisedFormatted": "3051.8 Mbps", + "actual": 1600000000, + "actualFormatted": "1525.9 Mbps", + "percentageDiff": -50, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 112500, + "advertisedFormatted": "112,500", + "actual": 11028, + "actualFormatted": "11,028", + "percentageDiff": -90.19733333333333, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 360000, + "advertisedFormatted": "360,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -89.97222222222221, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 9", + "cloudProvider": "GCP", + "machineType": "n2d-standard-32", + "nodeCount": "15", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 2000000000, + "advertisedFormatted": "1907.3 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": -73.3333333, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 4000000000, + "advertisedFormatted": "3814.7 Mbps", + "actual": 1600000000, + "actualFormatted": "1525.9 Mbps", + "percentageDiff": -60, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 112500, + "advertisedFormatted": "112,500", + "actual": 11055, + "actualFormatted": "11,055", + "percentageDiff": -90.17333333333333, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 450000, + "advertisedFormatted": "450,000", + "actual": 36100, + "actualFormatted": "36,100", + "percentageDiff": -91.97777777777777, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 1 (x86 - Ddv5)", + "cloudProvider": "Azure", + "machineType": "Standard_D2d_v5", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 20000000, + "advertisedFormatted": "19.1 Mbps", + "actual": 33333334, + "actualFormatted": "31.8 Mbps", + "percentageDiff": 66.66667, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 60000000, + "advertisedFormatted": "57.2 Mbps", + "actual": 100000000, + "actualFormatted": "95.4 Mbps", + "percentageDiff": 66.66666666666666, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 1000, + "advertisedFormatted": "1,000", + "actual": 6198, + "actualFormatted": "6,198", + "percentageDiff": 519.8000000000001, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 9000, + "advertisedFormatted": "9,000", + "actual": 3700, + "actualFormatted": "3,700", + "percentageDiff": -58.88888888888889, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 2 (x86 - Ddv5)", + "cloudProvider": "Azure", + "machineType": "Standard_D4d_v5", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 50000000, + "advertisedFormatted": "47.7 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": 33.333334, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 150000000, + "advertisedFormatted": "143.1 Mbps", + "actual": 200000000, + "actualFormatted": "190.7 Mbps", + "percentageDiff": 33.33333333333333, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 2800, + "advertisedFormatted": "2,800", + "actual": 5694, + "actualFormatted": "5,694", + "percentageDiff": 103.35714285714286, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 22500, + "advertisedFormatted": "22,500", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -59.55555555555555, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 3 (x86 - Ddv5)", + "cloudProvider": "Azure", + "machineType": "Standard_D4d_v5", + "nodeCount": "6", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 100000000, + "advertisedFormatted": "95.4 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": -33.333332999999996, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 200000000, + "advertisedFormatted": "190.7 Mbps", + "actual": 200000000, + "actualFormatted": "190.7 Mbps", + "percentageDiff": 0, + "severity": "minor", + "emoji": "🟢" + }, + { + "metric": "Max Partitions", + "advertised": 5600, + "advertisedFormatted": "5,600", + "actual": 11340, + "actualFormatted": "11,340", + "percentageDiff": 102.49999999999999, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 45000, + "advertisedFormatted": "45,000", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -79.77777777777779, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 4 (x86 - Ddv5)", + "cloudProvider": "Azure", + "machineType": "Standard_D4d_v5", + "nodeCount": "12", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 200000000, + "advertisedFormatted": "190.7 Mbps", + "actual": 66666667, + "actualFormatted": "63.6 Mbps", + "percentageDiff": -66.6666665, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Egress Throughput", + "advertised": 400000000, + "advertisedFormatted": "381.5 Mbps", + "actual": 200000000, + "actualFormatted": "190.7 Mbps", + "percentageDiff": -50, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Max Partitions", + "advertised": 11300, + "advertisedFormatted": "11,300", + "actual": 22836, + "actualFormatted": "22,836", + "percentageDiff": 102.08849557522124, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 90000, + "advertisedFormatted": "90,000", + "actual": 9100, + "actualFormatted": "9,100", + "percentageDiff": -89.88888888888889, + "severity": "critical", + "emoji": "🔴" + } + ] + }, + { + "tierName": "Tier 5 (x86 - Ddv5)", + "cloudProvider": "Azure", + "machineType": "Standard_D32d_v5", + "nodeCount": "3", + "discrepancies": [ + { + "metric": "Ingress Throughput", + "advertised": 400000000, + "advertisedFormatted": "381.5 Mbps", + "actual": 533333334, + "actualFormatted": "508.6 Mbps", + "percentageDiff": 33.3333335, + "severity": "major", + "emoji": "🟠" + }, + { + "metric": "Egress Throughput", + "advertised": 800000000, + "advertisedFormatted": "762.9 Mbps", + "actual": 1600000000, + "actualFormatted": "1525.9 Mbps", + "percentageDiff": 100, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Partitions", + "advertised": 22800, + "advertisedFormatted": "22,800", + "actual": 4602, + "actualFormatted": "4,602", + "percentageDiff": -79.8157894736842, + "severity": "critical", + "emoji": "🔴" + }, + { + "metric": "Max Client Connections", + "advertised": 180000, + "advertisedFormatted": "180,000", + "actual": 72100, + "actualFormatted": "72,100", + "percentageDiff": -59.94444444444444, + "severity": "critical", + "emoji": "🔴" + } + ] + } + ] +} \ No newline at end of file diff --git a/tier-discrepancy-report.md b/tier-discrepancy-report.md new file mode 100644 index 0000000..98a720c --- /dev/null +++ b/tier-discrepancy-report.md @@ -0,0 +1,709 @@ +# Redpanda Cloud Tier Discrepancy Report + +Generated on: 2025-10-01 + +## Executive Summary + +- **Total Tiers Analyzed**: 41 +- **Total Issues Found**: 147 +- **🔴 Critical Issues**: 115 +- **🟠 Major Issues**: 32 +- **🟡 Moderate Issues**: 0 +- **🟢 Minor Issues**: 17 + +## Detailed Analysis + +### AWS + +#### Tier 1 (im4gn.large) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 19.1 Mbps | 12.7 Mbps | -33.3% | 🟠 major | +| Egress Throughput | 57.2 Mbps | 38.1 Mbps | -33.3% | 🟠 major | +| Max Partitions | 2,000 | 6,198 | +209.9% | 🔴 critical | +| Max Client Connections | 9,000 | 3,700 | -58.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% lower than advertised +- Egress Throughput: Config is 33.3% lower than advertised +- Max Partitions: Config is 209.9% higher than advertised +- Max Client Connections: Config is 58.9% lower than advertised + +#### Tier 1 (m7gd.large) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 19.1 Mbps | 31.8 Mbps | +66.7% | 🔴 critical | +| Egress Throughput | 57.2 Mbps | 95.4 Mbps | +66.7% | 🔴 critical | +| Max Partitions | 2,000 | 6,198 | +209.9% | 🔴 critical | +| Max Client Connections | 9,000 | 3,700 | -58.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% higher than advertised +- Egress Throughput: Config is 66.7% higher than advertised +- Max Partitions: Config is 209.9% higher than advertised +- Max Client Connections: Config is 58.9% lower than advertised + +#### Tier 1 - x86 (i3en.large) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 19.1 Mbps | 12.7 Mbps | -33.3% | 🟠 major | +| Egress Throughput | 57.2 Mbps | 38.1 Mbps | -33.3% | 🟠 major | +| Max Partitions | 2,000 | 6,198 | +209.9% | 🔴 critical | +| Max Client Connections | 9,000 | 3,700 | -58.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% lower than advertised +- Egress Throughput: Config is 33.3% lower than advertised +- Max Partitions: Config is 209.9% higher than advertised +- Max Client Connections: Config is 58.9% lower than advertised + +#### Tier 2 (im4gn.xlarge) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 47.7 Mbps | 31.8 Mbps | -33.3% | 🟠 major | +| Egress Throughput | 143.1 Mbps | 95.4 Mbps | -33.3% | 🟠 major | +| Max Partitions | 5,600 | 5,694 | +1.7% | 🟢 minor | +| Max Client Connections | 22,500 | 9,100 | -59.6% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% lower than advertised +- Egress Throughput: Config is 33.3% lower than advertised +- Max Client Connections: Config is 59.6% lower than advertised + +#### Tier 2 (m7gd.xlarge) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 47.7 Mbps | 63.6 Mbps | +33.3% | 🟠 major | +| Egress Throughput | 143.1 Mbps | 190.7 Mbps | +33.3% | 🟠 major | +| Max Partitions | 5,600 | 5,694 | +1.7% | 🟢 minor | +| Max Client Connections | 22,500 | 9,100 | -59.6% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% higher than advertised +- Egress Throughput: Config is 33.3% higher than advertised +- Max Client Connections: Config is 59.6% lower than advertised + +#### Tier 2 - x86 (i3en.xlarge) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 47.7 Mbps | 31.8 Mbps | -33.3% | 🟠 major | +| Egress Throughput | 143.1 Mbps | 95.4 Mbps | -33.3% | 🟠 major | +| Max Partitions | 5,600 | 5,694 | +1.7% | 🟢 minor | +| Max Client Connections | 22,500 | 9,100 | -59.6% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% lower than advertised +- Egress Throughput: Config is 33.3% lower than advertised +- Max Client Connections: Config is 59.6% lower than advertised + +#### Tier 3 (im4gn.xlarge) + +**Configuration**: 6 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 95.4 Mbps | 31.8 Mbps | -66.7% | 🔴 critical | +| Egress Throughput | 190.7 Mbps | 63.6 Mbps | -66.7% | 🔴 critical | +| Max Partitions | 11,200 | 11,340 | +1.3% | 🟢 minor | +| Max Client Connections | 45,000 | 9,100 | -79.8% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% lower than advertised +- Egress Throughput: Config is 66.7% lower than advertised +- Max Client Connections: Config is 79.8% lower than advertised + +#### Tier 3 (m7gd.xlarge) + +**Configuration**: 6 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 95.4 Mbps | 63.6 Mbps | -33.3% | 🟠 major | +| Egress Throughput | 190.7 Mbps | 190.7 Mbps | 0.0% | 🟢 minor | +| Max Partitions | 11,200 | 11,340 | +1.3% | 🟢 minor | +| Max Client Connections | 45,000 | 9,100 | -79.8% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% lower than advertised +- Max Client Connections: Config is 79.8% lower than advertised + +#### Tier 3 - x86 (i3en.xlarge) + +**Configuration**: 6 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 95.4 Mbps | 31.8 Mbps | -66.7% | 🔴 critical | +| Egress Throughput | 190.7 Mbps | 63.6 Mbps | -66.7% | 🔴 critical | +| Max Partitions | 11,200 | 11,340 | +1.3% | 🟢 minor | +| Max Client Connections | 45,000 | 9,100 | -79.8% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% lower than advertised +- Egress Throughput: Config is 66.7% lower than advertised +- Max Client Connections: Config is 79.8% lower than advertised + +#### Tier 4 (im4gn.xlarge) + +**Configuration**: 12 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 190.7 Mbps | 31.8 Mbps | -83.3% | 🔴 critical | +| Egress Throughput | 381.5 Mbps | 63.6 Mbps | -83.3% | 🔴 critical | +| Max Partitions | 22,600 | 22,836 | +1.0% | 🟢 minor | +| Max Client Connections | 90,000 | 9,100 | -89.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 83.3% lower than advertised +- Egress Throughput: Config is 83.3% lower than advertised +- Max Client Connections: Config is 89.9% lower than advertised + +#### Tier 4 (m7gd.xlarge) + +**Configuration**: 12 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 190.7 Mbps | 63.6 Mbps | -66.7% | 🔴 critical | +| Egress Throughput | 381.5 Mbps | 190.7 Mbps | -50.0% | 🟠 major | +| Max Partitions | 22,600 | 22,836 | +1.0% | 🟢 minor | +| Max Client Connections | 90,000 | 9,100 | -89.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% lower than advertised +- Egress Throughput: Config is 50.0% lower than advertised +- Max Client Connections: Config is 89.9% lower than advertised + +#### Tier 4 - x86 (i3en.xlarge) + +**Configuration**: 12 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 190.7 Mbps | 31.8 Mbps | -83.3% | 🔴 critical | +| Egress Throughput | 381.5 Mbps | 63.6 Mbps | -83.3% | 🔴 critical | +| Max Partitions | 22,600 | 22,836 | +1.0% | 🟢 minor | +| Max Client Connections | 90,000 | 9,100 | -89.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 83.3% lower than advertised +- Egress Throughput: Config is 83.3% lower than advertised +- Max Client Connections: Config is 89.9% lower than advertised + +#### Tier 5 (im4gn.8xlarge) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 381.5 Mbps | 254.3 Mbps | -33.3% | 🟠 major | +| Egress Throughput | 762.9 Mbps | 508.6 Mbps | -33.3% | 🟠 major | +| Max Partitions | 45,600 | 4,455 | -90.2% | 🔴 critical | +| Max Client Connections | 180,000 | 72,100 | -59.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% lower than advertised +- Egress Throughput: Config is 33.3% lower than advertised +- Max Partitions: Config is 90.2% lower than advertised +- Max Client Connections: Config is 59.9% lower than advertised + +#### Tier 5 (m7gd.8xlarge) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 381.5 Mbps | 508.6 Mbps | +33.3% | 🟠 major | +| Egress Throughput | 762.9 Mbps | 1525.9 Mbps | +100.0% | 🔴 critical | +| Max Partitions | 45,600 | 4,602 | -89.9% | 🔴 critical | +| Max Client Connections | 180,000 | 72,100 | -59.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% higher than advertised +- Egress Throughput: Config is 100.0% higher than advertised +- Max Partitions: Config is 89.9% lower than advertised +- Max Client Connections: Config is 59.9% lower than advertised + +#### Tier 5 - x86 (i3en.6xlarge) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 381.5 Mbps | 254.3 Mbps | -33.3% | 🟠 major | +| Egress Throughput | 762.9 Mbps | 508.6 Mbps | -33.3% | 🟠 major | +| Max Partitions | 45,600 | 5,991 | -86.9% | 🔴 critical | +| Max Client Connections | 180,000 | 72,100 | -59.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% lower than advertised +- Egress Throughput: Config is 33.3% lower than advertised +- Max Partitions: Config is 86.9% lower than advertised +- Max Client Connections: Config is 59.9% lower than advertised + +#### Tier 6 (im4gn.8xlarge) + +**Configuration**: 6 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 762.9 Mbps | 254.3 Mbps | -66.7% | 🔴 critical | +| Egress Throughput | 1525.9 Mbps | 508.6 Mbps | -66.7% | 🔴 critical | +| Max Partitions | 90,000 | 8,784 | -90.2% | 🔴 critical | +| Max Client Connections | 180,000 | 36,100 | -79.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% lower than advertised +- Egress Throughput: Config is 66.7% lower than advertised +- Max Partitions: Config is 90.2% lower than advertised +- Max Client Connections: Config is 79.9% lower than advertised + +#### Tier 6 (m7gd.8xlarge) + +**Configuration**: 6 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 762.9 Mbps | 508.6 Mbps | -33.3% | 🟠 major | +| Egress Throughput | 1525.9 Mbps | 1525.9 Mbps | 0.0% | 🟢 minor | +| Max Partitions | 90,000 | 9,072 | -89.9% | 🔴 critical | +| Max Client Connections | 180,000 | 36,100 | -79.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% lower than advertised +- Max Partitions: Config is 89.9% lower than advertised +- Max Client Connections: Config is 79.9% lower than advertised + +#### Tier 6 - x86 (i3en.6xlarge) + +**Configuration**: 6 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 762.9 Mbps | 254.3 Mbps | -66.7% | 🔴 critical | +| Egress Throughput | 1525.9 Mbps | 508.6 Mbps | -66.7% | 🔴 critical | +| Max Partitions | 90,000 | 11,814 | -86.9% | 🔴 critical | +| Max Client Connections | 180,000 | 36,100 | -79.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% lower than advertised +- Egress Throughput: Config is 66.7% lower than advertised +- Max Partitions: Config is 86.9% lower than advertised +- Max Client Connections: Config is 79.9% lower than advertised + +#### Tier 7 (im4gn.8xlarge) + +**Configuration**: 9 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 1144.4 Mbps | 254.3 Mbps | -77.8% | 🔴 critical | +| Egress Throughput | 2288.8 Mbps | 508.6 Mbps | -77.8% | 🔴 critical | +| Max Partitions | 112,500 | 10,989 | -90.2% | 🔴 critical | +| Max Client Connections | 270,000 | 36,100 | -86.6% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 77.8% lower than advertised +- Egress Throughput: Config is 77.8% lower than advertised +- Max Partitions: Config is 90.2% lower than advertised +- Max Client Connections: Config is 86.6% lower than advertised + +#### Tier 7 (m7gd.8xlarge) + +**Configuration**: 9 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 1144.4 Mbps | 508.6 Mbps | -55.6% | 🔴 critical | +| Egress Throughput | 2288.8 Mbps | 1525.9 Mbps | -33.3% | 🟠 major | +| Max Partitions | 112,500 | 11,358 | -89.9% | 🔴 critical | +| Max Client Connections | 270,000 | 36,100 | -86.6% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 55.6% lower than advertised +- Egress Throughput: Config is 33.3% lower than advertised +- Max Partitions: Config is 89.9% lower than advertised +- Max Client Connections: Config is 86.6% lower than advertised + +#### Tier 7 - x86 (i3en.6xlarge) + +**Configuration**: 9 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 1144.4 Mbps | 254.3 Mbps | -77.8% | 🔴 critical | +| Egress Throughput | 2288.8 Mbps | 508.6 Mbps | -77.8% | 🔴 critical | +| Max Partitions | 112,500 | 14,778 | -86.9% | 🔴 critical | +| Max Client Connections | 270,000 | 36,100 | -86.6% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 77.8% lower than advertised +- Egress Throughput: Config is 77.8% lower than advertised +- Max Partitions: Config is 86.9% lower than advertised +- Max Client Connections: Config is 86.6% lower than advertised + +#### Tier 8 (im4gn.8xlarge) + +**Configuration**: 12 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 1525.9 Mbps | 254.3 Mbps | -83.3% | 🔴 critical | +| Egress Throughput | 3051.8 Mbps | 508.6 Mbps | -83.3% | 🔴 critical | +| Max Partitions | 112,500 | 11,028 | -90.2% | 🔴 critical | +| Max Client Connections | 360,000 | 36,100 | -90.0% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 83.3% lower than advertised +- Egress Throughput: Config is 83.3% lower than advertised +- Max Partitions: Config is 90.2% lower than advertised +- Max Client Connections: Config is 90.0% lower than advertised + +#### Tier 8 (m7gd.8xlarge) + +**Configuration**: 12 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 1525.9 Mbps | 508.6 Mbps | -66.7% | 🔴 critical | +| Egress Throughput | 3051.8 Mbps | 1525.9 Mbps | -50.0% | 🟠 major | +| Max Partitions | 112,500 | 11,388 | -89.9% | 🔴 critical | +| Max Client Connections | 360,000 | 36,100 | -90.0% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% lower than advertised +- Egress Throughput: Config is 50.0% lower than advertised +- Max Partitions: Config is 89.9% lower than advertised +- Max Client Connections: Config is 90.0% lower than advertised + +#### Tier 8 - x86 (i3en.6xlarge) + +**Configuration**: 12 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 1525.9 Mbps | 254.3 Mbps | -83.3% | 🔴 critical | +| Egress Throughput | 3051.8 Mbps | 508.6 Mbps | -83.3% | 🔴 critical | +| Max Partitions | 112,500 | 14,820 | -86.8% | 🔴 critical | +| Max Client Connections | 360,000 | 36,100 | -90.0% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 83.3% lower than advertised +- Egress Throughput: Config is 83.3% lower than advertised +- Max Partitions: Config is 86.8% lower than advertised +- Max Client Connections: Config is 90.0% lower than advertised + +#### Tier 9 (im4gn.8xlarge) + +**Configuration**: 15 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 1907.3 Mbps | 254.3 Mbps | -86.7% | 🔴 critical | +| Egress Throughput | 3814.7 Mbps | 508.6 Mbps | -86.7% | 🔴 critical | +| Max Partitions | 112,500 | 11,055 | -90.2% | 🔴 critical | +| Max Client Connections | 450,000 | 36,100 | -92.0% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 86.7% lower than advertised +- Egress Throughput: Config is 86.7% lower than advertised +- Max Partitions: Config is 90.2% lower than advertised +- Max Client Connections: Config is 92.0% lower than advertised + +#### Tier 9 (m7gd.8xlarge) + +**Configuration**: 15 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 1907.3 Mbps | 508.6 Mbps | -73.3% | 🔴 critical | +| Egress Throughput | 3814.7 Mbps | 1525.9 Mbps | -60.0% | 🔴 critical | +| Max Partitions | 112,500 | 11,415 | -89.9% | 🔴 critical | +| Max Client Connections | 450,000 | 36,100 | -92.0% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 73.3% lower than advertised +- Egress Throughput: Config is 60.0% lower than advertised +- Max Partitions: Config is 89.9% lower than advertised +- Max Client Connections: Config is 92.0% lower than advertised + +#### Tier 9 - x86 (i3en.6xlarge) + +**Configuration**: 15 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 1907.3 Mbps | 254.3 Mbps | -86.7% | 🔴 critical | +| Egress Throughput | 3814.7 Mbps | 508.6 Mbps | -86.7% | 🔴 critical | +| Max Partitions | 112,500 | 14,850 | -86.8% | 🔴 critical | +| Max Client Connections | 450,000 | 36,100 | -92.0% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 86.7% lower than advertised +- Egress Throughput: Config is 86.7% lower than advertised +- Max Partitions: Config is 86.8% lower than advertised +- Max Client Connections: Config is 92.0% lower than advertised + +### Azure + +#### Tier 1 (x86 - Ddv5) (Standard_D2d_v5) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 19.1 Mbps | 31.8 Mbps | +66.7% | 🔴 critical | +| Egress Throughput | 57.2 Mbps | 95.4 Mbps | +66.7% | 🔴 critical | +| Max Partitions | 1,000 | 6,198 | +519.8% | 🔴 critical | +| Max Client Connections | 9,000 | 3,700 | -58.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% higher than advertised +- Egress Throughput: Config is 66.7% higher than advertised +- Max Partitions: Config is 519.8% higher than advertised +- Max Client Connections: Config is 58.9% lower than advertised + +#### Tier 2 (x86 - Ddv5) (Standard_D4d_v5) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 47.7 Mbps | 63.6 Mbps | +33.3% | 🟠 major | +| Egress Throughput | 143.1 Mbps | 190.7 Mbps | +33.3% | 🟠 major | +| Max Partitions | 2,800 | 5,694 | +103.4% | 🔴 critical | +| Max Client Connections | 22,500 | 9,100 | -59.6% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% higher than advertised +- Egress Throughput: Config is 33.3% higher than advertised +- Max Partitions: Config is 103.4% higher than advertised +- Max Client Connections: Config is 59.6% lower than advertised + +#### Tier 3 (x86 - Ddv5) (Standard_D4d_v5) + +**Configuration**: 6 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 95.4 Mbps | 63.6 Mbps | -33.3% | 🟠 major | +| Egress Throughput | 190.7 Mbps | 190.7 Mbps | 0.0% | 🟢 minor | +| Max Partitions | 5,600 | 11,340 | +102.5% | 🔴 critical | +| Max Client Connections | 45,000 | 9,100 | -79.8% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% lower than advertised +- Max Partitions: Config is 102.5% higher than advertised +- Max Client Connections: Config is 79.8% lower than advertised + +#### Tier 4 (x86 - Ddv5) (Standard_D4d_v5) + +**Configuration**: 12 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 190.7 Mbps | 63.6 Mbps | -66.7% | 🔴 critical | +| Egress Throughput | 381.5 Mbps | 190.7 Mbps | -50.0% | 🟠 major | +| Max Partitions | 11,300 | 22,836 | +102.1% | 🔴 critical | +| Max Client Connections | 90,000 | 9,100 | -89.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% lower than advertised +- Egress Throughput: Config is 50.0% lower than advertised +- Max Partitions: Config is 102.1% higher than advertised +- Max Client Connections: Config is 89.9% lower than advertised + +#### Tier 5 (x86 - Ddv5) (Standard_D32d_v5) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 381.5 Mbps | 508.6 Mbps | +33.3% | 🟠 major | +| Egress Throughput | 762.9 Mbps | 1525.9 Mbps | +100.0% | 🔴 critical | +| Max Partitions | 22,800 | 4,602 | -79.8% | 🔴 critical | +| Max Client Connections | 180,000 | 72,100 | -59.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% higher than advertised +- Egress Throughput: Config is 100.0% higher than advertised +- Max Partitions: Config is 79.8% lower than advertised +- Max Client Connections: Config is 59.9% lower than advertised + +### GCP + +#### Tier 1 (n2d-standard-2) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 19.1 Mbps | 31.8 Mbps | +66.7% | 🔴 critical | +| Egress Throughput | 57.2 Mbps | 95.4 Mbps | +66.7% | 🔴 critical | +| Max Partitions | 2,000 | 6,198 | +209.9% | 🔴 critical | +| Max Client Connections | 9,000 | 3,700 | -58.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% higher than advertised +- Egress Throughput: Config is 66.7% higher than advertised +- Max Partitions: Config is 209.9% higher than advertised +- Max Client Connections: Config is 58.9% lower than advertised + +#### Tier 2 (n2d-standard-4) + +**Configuration**: 3 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 47.7 Mbps | 63.6 Mbps | +33.3% | 🟠 major | +| Egress Throughput | 143.1 Mbps | 190.7 Mbps | +33.3% | 🟠 major | +| Max Partitions | 5,600 | 5,694 | +1.7% | 🟢 minor | +| Max Client Connections | 22,500 | 9,100 | -59.6% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% higher than advertised +- Egress Throughput: Config is 33.3% higher than advertised +- Max Client Connections: Config is 59.6% lower than advertised + +#### Tier 3 (n2d-standard-4) + +**Configuration**: 6 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 95.4 Mbps | 63.6 Mbps | -33.3% | 🟠 major | +| Egress Throughput | 190.7 Mbps | 190.7 Mbps | 0.0% | 🟢 minor | +| Max Partitions | 11,200 | 11,340 | +1.3% | 🟢 minor | +| Max Client Connections | 45,000 | 9,100 | -79.8% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% lower than advertised +- Max Client Connections: Config is 79.8% lower than advertised + +#### Tier 4 (n2d-standard-4) + +**Configuration**: 12 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 190.7 Mbps | 63.6 Mbps | -66.7% | 🔴 critical | +| Egress Throughput | 381.5 Mbps | 190.7 Mbps | -50.0% | 🟠 major | +| Max Partitions | 22,600 | 22,836 | +1.0% | 🟢 minor | +| Max Client Connections | 90,000 | 9,100 | -89.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% lower than advertised +- Egress Throughput: Config is 50.0% lower than advertised +- Max Client Connections: Config is 89.9% lower than advertised + +#### Tier 5 (n2d-standard-16) + +**Configuration**: 6 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 381.5 Mbps | 254.3 Mbps | -33.3% | 🟠 major | +| Egress Throughput | 762.9 Mbps | 762.9 Mbps | 0.0% | 🟢 minor | +| Max Partitions | 45,600 | 9,204 | -79.8% | 🔴 critical | +| Max Client Connections | 180,000 | 36,100 | -79.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 33.3% lower than advertised +- Max Partitions: Config is 79.8% lower than advertised +- Max Client Connections: Config is 79.9% lower than advertised + +#### Tier 6 (n2d-standard-16) + +**Configuration**: 12 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 762.9 Mbps | 254.3 Mbps | -66.7% | 🔴 critical | +| Egress Throughput | 1525.9 Mbps | 762.9 Mbps | -50.0% | 🟠 major | +| Max Partitions | 90,000 | 18,144 | -79.8% | 🔴 critical | +| Max Client Connections | 180,000 | 18,100 | -89.9% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% lower than advertised +- Egress Throughput: Config is 50.0% lower than advertised +- Max Partitions: Config is 79.8% lower than advertised +- Max Client Connections: Config is 89.9% lower than advertised + +#### Tier 7 (n2d-standard-16) + +**Configuration**: 18 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 1144.4 Mbps | 254.3 Mbps | -77.8% | 🔴 critical | +| Egress Throughput | 2288.8 Mbps | 762.9 Mbps | -66.7% | 🔴 critical | +| Max Partitions | 112,500 | 22,716 | -79.8% | 🔴 critical | +| Max Client Connections | 270,000 | 18,100 | -93.3% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 77.8% lower than advertised +- Egress Throughput: Config is 66.7% lower than advertised +- Max Partitions: Config is 79.8% lower than advertised +- Max Client Connections: Config is 93.3% lower than advertised + +#### Tier 8 (n2d-standard-32) + +**Configuration**: 12 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 1525.9 Mbps | 508.6 Mbps | -66.7% | 🔴 critical | +| Egress Throughput | 3051.8 Mbps | 1525.9 Mbps | -50.0% | 🟠 major | +| Max Partitions | 112,500 | 11,028 | -90.2% | 🔴 critical | +| Max Client Connections | 360,000 | 36,100 | -90.0% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 66.7% lower than advertised +- Egress Throughput: Config is 50.0% lower than advertised +- Max Partitions: Config is 90.2% lower than advertised +- Max Client Connections: Config is 90.0% lower than advertised + +#### Tier 9 (n2d-standard-32) + +**Configuration**: 15 nodes + +| Metric | Advertised | Actual | Difference | Status | +|--------|------------|--------|------------|--------| +| Ingress Throughput | 1907.3 Mbps | 508.6 Mbps | -73.3% | 🔴 critical | +| Egress Throughput | 3814.7 Mbps | 1525.9 Mbps | -60.0% | 🔴 critical | +| Max Partitions | 112,500 | 11,055 | -90.2% | 🔴 critical | +| Max Client Connections | 450,000 | 36,100 | -92.0% | 🔴 critical | + +**⚠️ Major Issues:** +- Ingress Throughput: Config is 73.3% lower than advertised +- Egress Throughput: Config is 60.0% lower than advertised +- Max Partitions: Config is 90.2% lower than advertised +- Max Client Connections: Config is 92.0% lower than advertised + +## Recommendations + +1. **🔴 Critical/Major Issues**: Immediate review required for tiers with >25% discrepancies +2. **📊 Throughput Alignment**: Standardize ingress/egress limits across machine types within tiers +3. **👥 Client Connection Review**: Many config limits are significantly lower than advertised +4. **📈 Partition Capacity**: Some tiers exceed advertised partition limits in config +5. **🔄 Regular Audits**: Implement automated checks to prevent future discrepancies + diff --git a/tools/cloud-regions/cloud-regions-table-adoc-tabs.hbs b/tools/cloud-regions/cloud-regions-table-adoc-tabs.hbs new file mode 100644 index 0000000..6bb2324 --- /dev/null +++ b/tools/cloud-regions/cloud-regions-table-adoc-tabs.hbs @@ -0,0 +1,34 @@ +{{!-- AsciiDoc Cloud Regions Table Template with Tabs --}} + +//// +This content is auto-generated. Do not edit manually. + +To regenerate this content, run: + npx doc-tools generate cloud-regions --help + +For source code and documentation: +- Source: https://github.com/redpanda-data/docs-extensions-and-macros/tree/main/tools/cloud-regions +- Docs: https://redpandadata.atlassian.net/wiki/spaces/DOC/pages/1185054748/Doc+Tools+CLI +//// + +{{#each clusterTypes}} +== {{name}} supported regions + +[tabs] +==== +{{#each providers}} +{{name}}:: ++ +-- +|=== +| Region + +{{#each regions}} +| {{name}} +{{/each}} +|=== +-- +{{/each}} +==== + +{{/each}} \ No newline at end of file diff --git a/tools/cloud-regions/cloud-regions-table-adoc.hbs b/tools/cloud-regions/cloud-regions-table-adoc.hbs index 81860b0..4eaf82c 100644 --- a/tools/cloud-regions/cloud-regions-table-adoc.hbs +++ b/tools/cloud-regions/cloud-regions-table-adoc.hbs @@ -28,9 +28,10 @@ _Last Updated: {{lastUpdated}}_ |Region |Zones |Throughput Tiers + {{#each regions}} -|{{name}} -|{{zones}} +|{{name}} +|{{zones}} |{{#each tiers}}* {{this}} {{/each}} {{/each}} diff --git a/tools/cloud-regions/generate-cloud-regions.js b/tools/cloud-regions/generate-cloud-regions.js index 9b822f3..dd7957e 100644 --- a/tools/cloud-regions/generate-cloud-regions.js +++ b/tools/cloud-regions/generate-cloud-regions.js @@ -193,10 +193,113 @@ function processCloudRegions(yamlText) { } /** - * Fetches, processes, and renders cloud region and tier data from a GitHub YAML file. + * Parse cloud-regions YAML and group available regions by cluster type (BYOC or Dedicated) and provider for tabbed output. + * @param {string} yamlText - YAML document containing `regions` and optional `products`; regions must include `cloudProvider`, `name`, and `redpandaProductAvailability` entries. + * @returns {{clusterTypes: Array<{name: string, providers: Array<{name: string, regions: Array<{name: string}>}>}>}} An object with a `clusterTypes` array; each entry lists a cluster type (`BYOC` or `Dedicated`) and its providers (only providers with at least one region), each containing sorted region names. + * @throws {Error} If the YAML is malformed or does not contain a top-level `regions` array. + */ +function processCloudRegionsForTabs(yamlText) { + let data; + try { + data = jsYaml.load(yamlText); + } catch (e) { + console.error('[cloud-regions] ERROR: Malformed YAML.'); + throw new Error('Malformed YAML: ' + e.message); + } + if (!data || !Array.isArray(data.regions)) { + console.error('[cloud-regions] ERROR: YAML missing top-level regions array.'); + throw new Error('YAML does not contain a top-level regions array.'); + } + + // Build a set of public product names + const publicProductNames = new Set(); + if (Array.isArray(data.products)) { + for (const product of data.products) { + if (product.isPublic && product.name) { + publicProductNames.add(product.name); + } + } + } else { + console.warn('[cloud-regions] WARN: No products array found in YAML.'); + } + + // Group regions by cluster type, then by provider + const clusterTypeData = { + 'BYOC': { GCP: [], AWS: [], Azure: [] }, + 'Dedicated': { GCP: [], AWS: [], Azure: [] } + }; + + for (const region of data.regions) { + const providerKey = providerMap[region.cloudProvider]; + if (!providerKey) { + console.warn(`[cloud-regions] WARN: Unknown cloudProvider '${region.cloudProvider}' in region '${region.name}'. Skipping.`); + continue; + } + + // Check which cluster types this region supports + const supportedClusterTypes = new Set(); + if (region.redpandaProductAvailability && typeof region.redpandaProductAvailability === 'object') { + for (const t of Object.values(region.redpandaProductAvailability)) { + if (!t.redpandaProductName || !publicProductNames.has(t.redpandaProductName)) { + continue; + } + if (Array.isArray(t.clusterTypes)) { + for (const ct of t.clusterTypes) { + const displayType = displayClusterType(ct); + if (displayType === 'BYOC' || displayType === 'Dedicated') { + supportedClusterTypes.add(displayType); + } + } + } + } + } + + // Add region to appropriate cluster type groups + for (const clusterType of supportedClusterTypes) { + if (clusterTypeData[clusterType] && clusterTypeData[clusterType][providerKey]) { + clusterTypeData[clusterType][providerKey].push({ + name: region.name + }); + } + } + } + + // Convert to the format expected by the template + const clusterTypes = []; + + for (const [clusterTypeName, providerData] of Object.entries(clusterTypeData)) { + const providers = []; + + for (const providerName of providerOrder) { + const regions = providerData[providerName]; + if (regions && regions.length > 0) { + // Sort regions alphabetically + regions.sort((a, b) => a.name.localeCompare(b.name)); + providers.push({ + name: providerName === 'GCP' ? 'Google Cloud Platform (GCP)' : + providerName === 'AWS' ? 'Amazon Web Services (AWS)' : + providerName, + regions: regions + }); + } + } + + if (providers.length > 0) { + clusterTypes.push({ + name: clusterTypeName, + providers: providers + }); + } + } + + return { clusterTypes }; +} + +/** + * Generate rendered cloud region and tier output from a GitHub-hosted YAML file. * - * Retrieves YAML data from GitHub using the GitHub API (to avoid caching issues), - * parses and filters it to include only public cloud regions and tiers, and renders the result in the requested format. + * Fetches the YAML from the specified repository path, parses and filters it to include only public providers/regions/tiers, + * and renders the result in the requested format. When `options.tabs` is true, returns separate rendered outputs per cluster type. * * @param {Object} options - Options for generating cloud regions. * @param {string} options.owner - GitHub repository owner. @@ -205,11 +308,12 @@ function processCloudRegions(yamlText) { * @param {string} [options.ref='main'] - Git reference (branch, tag, or commit SHA). * @param {string} [options.format='md'] - The output format (e.g., 'md' for Markdown). * @param {string} [options.token] - Optional GitHub token for authentication. - * @param {string} [options.template] - Optional path to custom Handlebars template. - * @returns {string} The rendered cloud regions output. - * @throws {Error} If fetching, processing, or rendering fails, or if no valid providers or regions are found. + * @param {string} [options.template] - Optional path to a custom Handlebars template. + * @param {boolean} [options.tabs=false] - When true, produce separate rendered outputs organized by cluster type (keys are cluster type names lowercased). + * @returns {string|Object} Rendered output as a string, or when `options.tabs` is true an object mapping lowercase cluster type names to rendered strings. + * @throws {Error} If fetching, parsing, processing, or rendering fails, or if no valid providers/regions remain after filtering. */ -async function generateCloudRegions({ owner, repo, path, ref = 'main', format = 'md', token, template }) { +async function generateCloudRegions({ owner, repo, path, ref = 'main', format = 'md', token, template, tabs = false }) { let yamlText; try { yamlText = await fetchYaml({ owner, repo, path, ref, token }); @@ -217,20 +321,43 @@ async function generateCloudRegions({ owner, repo, path, ref = 'main', format = console.error(`[cloud-regions] ERROR: Failed to fetch YAML: ${err.message}`); throw err; } - let providers; + + let data; try { - providers = processCloudRegions(yamlText); + if (tabs) { + data = processCloudRegionsForTabs(yamlText); + } else { + data = { providers: processCloudRegions(yamlText) }; + } } catch (err) { console.error(`[cloud-regions] ERROR: Failed to process cloud regions: ${err.message}`); throw err; } - if (providers.length === 0) { + + if ((tabs && data.clusterTypes.length === 0) || (!tabs && data.providers.length === 0)) { console.error('[cloud-regions] ERROR: No providers/regions found in YAML after filtering.'); throw new Error('No providers/regions found in YAML after filtering.'); } + const lastUpdated = new Date().toISOString(); try { - return renderCloudRegions({ providers, format, lastUpdated, template }); + if (tabs) { + // Generate separate files for each cluster type + const results = {}; + for (const clusterType of data.clusterTypes) { + const singleClusterData = { + clusterTypes: [clusterType], + format, + lastUpdated, + template, + tabs + }; + results[clusterType.name.toLowerCase()] = renderCloudRegions(singleClusterData); + } + return results; + } else { + return renderCloudRegions({ ...data, format, lastUpdated, template, tabs }); + } } catch (err) { console.error(`[cloud-regions] ERROR: Failed to render cloud regions: ${err.message}`); throw err; @@ -240,4 +367,4 @@ async function generateCloudRegions({ owner, repo, path, ref = 'main', format = module.exports = { generateCloudRegions, processCloudRegions, -}; +}; \ No newline at end of file diff --git a/tools/cloud-regions/render-cloud-regions.js b/tools/cloud-regions/render-cloud-regions.js index 46d9e73..ca01db3 100644 --- a/tools/cloud-regions/render-cloud-regions.js +++ b/tools/cloud-regions/render-cloud-regions.js @@ -9,37 +9,68 @@ const handlebars = require('handlebars'); * Sorts regions alphabetically within each provider and renders the data using a template file corresponding to the specified format ('md' or 'adoc'). Optionally includes a last updated timestamp. * * @param {Object} opts - Options for rendering. - * @param {Array} opts.providers - List of cloud provider objects, each with a name and an array of regions. + * @param {Array} [opts.providers] - List of cloud provider objects, each with a name and an array of regions (for non-tabs format). + * @param {Array} [opts.clusterTypes] - List of cluster type objects, each with providers (for tabs format). * @param {string} opts.format - Output format, either 'md' (Markdown) or 'adoc' (AsciiDoc). * @param {string} [opts.lastUpdated] - Optional ISO timestamp indicating when the data was last updated. + * @param {boolean} [opts.tabs=false] - Whether to use tabs format. + * @param {string} [opts.template] - Optional path to custom template file. * @returns {string} The rendered output string. - * @throws {Error} If the providers array is missing or empty. + * @throws {Error} If the providers/clusterTypes array is missing or empty, or if template compilation fails. */ -function renderCloudRegions({ providers, format, lastUpdated }) { - if (!Array.isArray(providers) || providers.length === 0) { - throw new Error('No providers/regions found in YAML.'); +function renderCloudRegions({ providers, clusterTypes, format, lastUpdated, tabs = false, template }) { + if (tabs) { + if (!Array.isArray(clusterTypes) || clusterTypes.length === 0) { + throw new Error('No cluster types/regions found in YAML.'); + } + // Only allow tabs mode for adoc format unless custom template is provided + if (format !== 'adoc' && !template) { + throw new Error(`Tabs mode is only supported for AsciiDoc format. Either use format='adoc' or provide a custom template.`); + } + } else { + if (!Array.isArray(providers) || providers.length === 0) { + throw new Error('No providers/regions found in YAML.'); + } } + if (!['md', 'adoc'].includes(format)) { throw new Error(`Unsupported format: ${format}. Use 'md' or 'adoc'.`); } - // Sort regions alphabetically within each provider - const sortedProviders = providers.map(provider => ({ - ...provider, - regions: [...provider.regions].sort((a, b) => a.name.localeCompare(b.name)) - })); - const templateFile = path.join(__dirname, `cloud-regions-table-${format}.hbs`); + + let templateFile; + if (template) { + templateFile = template; + } else if (tabs && format === 'adoc') { + templateFile = path.join(__dirname, 'cloud-regions-table-adoc-tabs.hbs'); + } else { + templateFile = path.join(__dirname, `cloud-regions-table-${format}.hbs`); + } + if (!fs.existsSync(templateFile)) { throw new Error(`Template file not found: ${templateFile}`); } - let templateSrc, template; + + let templateSrc, compiledTemplate; try { templateSrc = fs.readFileSync(templateFile, 'utf8'); - template = handlebars.compile(templateSrc); + compiledTemplate = handlebars.compile(templateSrc); } catch (err) { throw new Error(`Failed to compile Handlebars template at ${templateFile}: ${err.message}`); } + try { - return template({ providers: sortedProviders, lastUpdated }); + if (tabs && (format === 'adoc' || template)) { + // Use clusterTypes for tabs format (adoc-tabs template or custom template in tabs mode) + return compiledTemplate({ clusterTypes, lastUpdated }); + } else { + // Use providers for regular md/html templates + // Sort regions alphabetically within each provider for non-tabs format + const sortedProviders = providers.map(provider => ({ + ...provider, + regions: [...provider.regions].sort((a, b) => a.name.localeCompare(b.name)) + })); + return compiledTemplate({ providers: sortedProviders, lastUpdated }); + } } catch (err) { throw new Error(`Failed to render Handlebars template at ${templateFile}: ${err.message}`); } diff --git a/tools/cloud-tier-table/cloud-tier-table-html.hbs b/tools/cloud-tier-table/cloud-tier-table-html.hbs new file mode 100644 index 0000000..6539135 --- /dev/null +++ b/tools/cloud-tier-table/cloud-tier-table-html.hbs @@ -0,0 +1,686 @@ + + + +
+ + + + + + + + +
+ + +
+
+ + + + + {{#each headers}} + + {{/each}} + + + + {{#each rows}} + + + {{#each ../limitKeys}} + + {{/each}} + + {{/each}} + +
Tier{{name}}
{{tier}}{{lookup .. this}}
+
+
+ + +
+ {{#each rows}} +
+
+

{{tier}}

+ {{#if cloud_provider}}{{cloud_provider}}{{else}}Unknown{{/if}} +
+
    + {{#each ../limitKeys}} +
  • + {{this}} + {{lookup .. this}} +
  • + {{/each}} +
+
+ {{/each}} +
+ + diff --git a/tools/cloud-tier-table/generate-cloud-tier-table.js b/tools/cloud-tier-table/generate-cloud-tier-table.js new file mode 100644 index 0000000..ac31506 --- /dev/null +++ b/tools/cloud-tier-table/generate-cloud-tier-table.js @@ -0,0 +1,734 @@ +const fs = require('fs'); +const path = require('path'); +const yaml = require('js-yaml'); +const fetch = globalThis.fetch; + +// Hardcoded list of keys to extract (top-level and cluster_config) +const LIMIT_KEYS = [ + // Top-level + 'cloud_provider', + 'machine_type', + 'nodes_count', + // Master data advertised limits + 'advertisedMaxIngress', + 'advertisedMaxEgress', + 'advertisedMaxPartitionCount', + 'advertisedMaxClientCount', + // cluster_config + 'kafka_topics_max', +]; + +/** + * Map a header key to a human-readable label. + * @param {string} key - The header key to convert. + * @returns {string} The human-readable label for `key`, or `key` unchanged if no mapping exists. + */ +function humanLabel(key) { + if (key === 'cloud_provider') return 'Cloud Provider'; + if (key === 'machine_type') return 'Machine Type'; + if (key === 'nodes_count') return 'Number of Nodes'; + if (key === 'advertisedMaxIngress') return 'Max Ingress (bps)'; + if (key === 'advertisedMaxEgress') return 'Max Egress (bps)'; + if (key === 'advertisedMaxPartitionCount') return 'Max Partitions'; + if (key === 'advertisedMaxClientCount') return 'Max Client Connections'; + if (key === 'kafka_topics_max') return 'Max Kafka Topics'; + return key; +} + +/** + * Convert a provider identifier into a human-friendly provider name. + * @param {*} val - Provider identifier (e.g., 'aws', 'gcp', 'azure'); comparison is case-insensitive. Falsy values produce an empty string. + * @returns {string} `'AWS'`, `'GCP'`, or `'Azure'` for known providers; empty string for falsy input; otherwise returns the original value. + */ +function humanProvider(val) { + if (!val) return ''; + const map = { aws: 'AWS', gcp: 'GCP', azure: 'Azure' }; + return map[String(val).toLowerCase()] || val; +} + +/** + * Loads master-data.yaml (from an HTTP URL or local path), validates its products list, and returns a normalized list of public tiers. + * + * @param {string} masterDataUrl - HTTP URL (GitHub API file response expected) or local filesystem path to the master-data.yaml file. + * @returns {Array} An array of public tier objects with the following fields: `name`, `configProfileName`, `cloudProvider`, `advertisedMaxIngress`, `advertisedMaxEgress`, `advertisedMaxPartitionCount`, and `advertisedMaxClientCount`. + * @throws {Error} If masterDataUrl is missing or not a string, fetching or file reading fails, YAML parsing fails, the products array is missing/invalid, or no valid public tiers are found. + */ +async function fetchPublicTiers(masterDataUrl) { + try { + if (!masterDataUrl || typeof masterDataUrl !== 'string') { + throw new Error('masterDataUrl must be a valid string'); + } + + const headers = {}; + if (process.env.GITHUB_TOKEN) { + headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`; + headers['User-Agent'] = 'cloud-tier-table-tool'; + } + + let masterDataYaml; + if (masterDataUrl.startsWith('http')) { + // Fetch from GitHub API + const response = await fetch(masterDataUrl, { headers }); + if (!response.ok) { + throw new Error(`Failed to fetch master data: ${response.status} ${response.statusText}`); + } + const data = await response.json(); + if (!data.content) { + throw new Error('GitHub API response missing content field'); + } + masterDataYaml = Buffer.from(data.content, 'base64').toString('utf8'); + } else { + if (!fs.existsSync(masterDataUrl)) { + throw new Error(`Local master data file not found: ${masterDataUrl}`); + } + masterDataYaml = fs.readFileSync(masterDataUrl, 'utf8'); + } + + let masterData; + try { + masterData = yaml.load(masterDataYaml); + } catch (error) { + throw new Error(`Failed to parse master data YAML: ${error.message}`); + } + + if (!masterData || typeof masterData !== 'object') { + throw new Error('Master data is not a valid object'); + } + if (!masterData.products || !Array.isArray(masterData.products)) { + throw new Error('Master data missing or invalid products array'); + } + + let processedCount = 0; + let filteredCount = 0; + const errors = []; + + const publicTiers = masterData.products + .filter((product, index) => { + try { + if (!product || typeof product !== 'object') { + errors.push(`Product at index ${index} is not a valid object`); + return false; + } + if (!product.name) { + errors.push(`Product at index ${index} missing name`); + return false; + } + if (!product.redpandaConfigProfileName) { + errors.push(`Product "${product.name}" missing redpandaConfigProfileName`); + return false; + } + if (product.isPublic !== true) { + filteredCount++; + return false; + } + processedCount++; + return true; + } catch (error) { + errors.push(`Error processing product at index ${index}: ${error.message}`); + return false; + } + }) + .map(product => ({ + name: product.name, + configProfileName: product.redpandaConfigProfileName, + cloudProvider: product.cloudProvider, + advertisedMaxIngress: product.advertisedMaxIngress, + advertisedMaxEgress: product.advertisedMaxEgress, + advertisedMaxPartitionCount: product.advertisedMaxPartitionCount, + advertisedMaxClientCount: product.advertisedMaxClientCount + })); + + // Log processing summary + if (process.env.DEBUG_TIER_PROCESSING || errors.length > 0) { + console.log(`Master data processing summary: ${processedCount} public tiers, ${filteredCount} filtered out, ${errors.length} errors`); + if (errors.length > 0) { + console.warn('Master data processing errors:'); + errors.forEach(error => console.warn(` - ${error}`)); + } + } + + if (publicTiers.length === 0) { + throw new Error('No valid public tiers found in master data'); + } + + return publicTiers; + } catch (error) { + console.error(`Error fetching public tiers: ${error.message}`); + throw new Error(`Failed to fetch public tiers: ${error.message}`); + } +} + +/** + * Load and parse YAML from a local file path, HTTP(S) URL, or the special GitHub install-pack API directory. + * + * When given the GitHub install-pack API directory URL, selects the latest versioned YAML file (names like `1.2.yml` or `1.2.3.yaml`) and parses it. For HTTP(S) inputs, fetches the URL and parses the response body. For local file paths, reads and parses the file contents. If GITHUB_TOKEN is set, requests include Authorization and User-Agent headers. + * + * @param {string} input - Local filesystem path, HTTP(S) URL, or the GitHub API directory URL 'https://api.github.com/repos/redpanda-data/cloudv2/contents/install-pack'. + * @returns {Object} The parsed YAML content as a JavaScript object. + * @throws {Error} If `input` is not a valid string, the network or filesystem access fails, no suitable versioned YAML is found in the install-pack directory, or the YAML content cannot be parsed into an object. + */ +async function parseYaml(input) { + try { + if (!input || typeof input !== 'string') { + throw new Error('input parameter must be a valid string'); + } + + // If input is the special API directory, fetch the latest version YAML + const apiDir = 'https://api.github.com/repos/redpanda-data/cloudv2/contents/install-pack'; + if (input === apiDir) { + const headers = {}; + if (process.env.GITHUB_TOKEN) { + headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`; + headers['User-Agent'] = 'cloud-tier-table-tool'; + } + + let res; + try { + res = await fetch(apiDir, { headers }); + } catch (error) { + throw new Error(`Network error fetching install-pack directory: ${error.message}`); + } + + if (!res.ok) { + throw new Error(`Failed to fetch install-pack directory: ${res.status} ${res.statusText}`); + } + + let files; + try { + files = await res.json(); + } catch (error) { + throw new Error(`Failed to parse install-pack directory response: ${error.message}`); + } + + if (!Array.isArray(files)) { + throw new Error('Install-pack directory response is not an array'); + } + + // Find YAML files with version numbers + const versionFiles = files.filter(f => { + if (!f.name || !f.download_url) return false; + if (!(f.name.endsWith('.yml') || f.name.endsWith('.yaml'))) return false; + const versionPart = f.name.replace(/\.(yml|yaml)$/i, ''); + // Require at least major.minor, and all segments must be numeric (no trailing dot, no empty segments) + // E.g. 1.2, 1.2.3, 10.0.1 are valid; 1, 1., 1..2, 1.2a, 1.2. are not + if (!/^\d+\.\d+(?:\.\d+)*$/.test(versionPart)) return false; + const segments = versionPart.split('.'); + return segments.every(seg => /^\d+$/.test(seg)); + }); + + if (!versionFiles.length) { + throw new Error('No version YAML files found in cloudv2/install-pack directory.'); + } + + // Parse and sort by version + const sortedFiles = versionFiles + .map(f => { + const versionPart = f.name.replace(/\.(yml|yaml)$/i, ''); + const segments = versionPart.split('.').map(s => parseInt(s, 10)); + return { ...f, version: segments }; + }) + .sort((a, b) => { + // Compare version arrays lexicographically + for (let i = 0; i < Math.max(a.version.length, b.version.length); i++) { + const aVal = a.version[i] || 0; + const bVal = b.version[i] || 0; + if (aVal !== bVal) return bVal - aVal; // Descending order + } + return 0; + }); + + const latestFile = sortedFiles[0]; + console.log(`Using latest version file: ${latestFile.name}`); + + let yamlResponse; + try { + yamlResponse = await fetch(latestFile.download_url, { headers }); + } catch (error) { + throw new Error(`Network error fetching ${latestFile.name}: ${error.message}`); + } + + if (!yamlResponse.ok) { + throw new Error(`Failed to fetch ${latestFile.name}: ${yamlResponse.status} ${yamlResponse.statusText}`); + } + + let yamlText; + try { + yamlText = await yamlResponse.text(); + } catch (error) { + throw new Error(`Failed to read YAML content from ${latestFile.name}: ${error.message}`); + } + + try { + return yaml.load(yamlText); + } catch (error) { + throw new Error(`Failed to parse YAML from ${latestFile.name}: ${error.message}`); + } + } + + // Handle URL or local file + let yamlText; + if (input.startsWith('http')) { + const headers = {}; + if (process.env.GITHUB_TOKEN) { + headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`; + headers['User-Agent'] = 'cloud-tier-table-tool'; + } + + let response; + try { + response = await fetch(input, { headers }); + } catch (error) { + throw new Error(`Network error fetching ${input}: ${error.message}`); + } + + if (!response.ok) { + throw new Error(`Failed to fetch ${input}: ${response.status} ${response.statusText}`); + } + + try { + yamlText = await response.text(); + } catch (error) { + throw new Error(`Failed to read content from ${input}: ${error.message}`); + } + } else { + // Local file + const fs = require('fs'); + if (!fs.existsSync(input)) { + throw new Error(`Local file not found: ${input}`); + } + + try { + yamlText = fs.readFileSync(input, 'utf8'); + } catch (error) { + throw new Error(`Failed to read local file ${input}: ${error.message}`); + } + } + + try { + const parsed = yaml.load(yamlText); + if (!parsed || typeof parsed !== 'object') { + throw new Error('YAML content is not a valid object'); + } + return parsed; + } catch (error) { + throw new Error(`Failed to parse YAML content: ${error.message}`); + } + } catch (error) { + console.error(`Error parsing YAML from "${input}": ${error.message}`); + throw error; + } +} + + +/** + * Extract version number from config profile name + * @param {string} profileName - Config profile name like "tier-2-aws-v3-arm" + * @returns {number} Version number or 0 if no version found + */ +function extractVersion(profileName) { + try { + if (!profileName || typeof profileName !== 'string') { + return 0; + } + const versionMatch = profileName.match(/-v(\d+)(?:-|$)/); + return versionMatch ? parseInt(versionMatch[1], 10) : 0; + } catch (error) { + console.warn(`Warning: Failed to extract version from profile name "${profileName}": ${error.message}`); + return 0; + } +} + +/** + * Selects the highest-versioned config profile name matching the given target profile. + * + * @param {Object} configProfiles - Map of profile names to profile definitions. + * @param {string} targetProfile - The target profile name to match (from master data). + * @returns {string} The matching profile name with the largest numeric `-vN` suffix, or `targetProfile` if no versioned variants are found or on failure. + */ +function findHighestVersionProfile(configProfiles, targetProfile) { + try { + if (!configProfiles || typeof configProfiles !== 'object') { + throw new Error('configProfiles must be a valid object'); + } + if (!targetProfile || typeof targetProfile !== 'string') { + throw new Error('targetProfile must be a valid string'); + } + + // If exact match exists, check for versioned variants + if (configProfiles[targetProfile]) { + // Look for versioned variants of this profile + const baseName = targetProfile.replace(/-v\d+/, ''); // Remove existing version if any + const versionedProfiles = Object.keys(configProfiles) + .filter(name => { + try { + // Match profiles that start with the base name and have version suffix + const withoutVersion = name.replace(/-v\d+/, ''); + return withoutVersion === baseName; + } catch (error) { + console.warn(`Warning: Failed to process profile name "${name}": ${error.message}`); + return false; + } + }) + .map(name => ({ + name, + version: extractVersion(name) + })) + .sort((a, b) => b.version - a.version); // Sort by version descending + + if (versionedProfiles.length > 0) { + const selectedProfile = versionedProfiles[0].name; + if (process.env.DEBUG_TIER_SELECTION) { + console.log(`Debug: Selected "${selectedProfile}" (v${versionedProfiles[0].version}) from ${versionedProfiles.length} variants of "${targetProfile}"`); + } + return selectedProfile; + } + } + + return targetProfile; + } catch (error) { + console.error(`Error finding highest version profile for "${targetProfile}": ${error.message}`); + return targetProfile; // Fallback to original profile name + } +} + +/** + * Builds table row objects by merging public tier metadata with matching config profiles. + * + * Each returned row maps the tier display name to the requested limit keys (either `customLimits` or the module's default keys). + * Missing values are represented as the string "N/A". Duplicate rows with the same tier name and resolved config profile are removed. + * + * @param {Object} tiers - Parsed tiers YAML object; must contain a `config_profiles` object mapping profile names to definitions. + * @param {Array} publicTiers - Array of public tier descriptors; each entry must include `name` and `configProfileName`. + * @param {Array} [customLimits] - Optional list of limit keys to extract; when omitted the module's default LIMIT_KEYS are used. + * @returns {Array} An array of row objects. Each row has a `tier` property (display name) and entries for each requested limit key. + * @throws {Error} If inputs are invalid or row construction fails (e.g., missing `config_profiles`, non-array `publicTiers`, or other fatal processing errors). + */ +function buildTableRows(tiers, publicTiers, customLimits) { + try { + // Use custom limits if provided, otherwise use default LIMIT_KEYS + const limitKeys = customLimits && Array.isArray(customLimits) && customLimits.length > 0 + ? customLimits + : LIMIT_KEYS; + + // Validate inputs + if (!tiers || typeof tiers !== 'object') { + throw new Error('tiers parameter must be a valid object'); + } + if (!tiers.config_profiles || typeof tiers.config_profiles !== 'object') { + throw new Error('tiers.config_profiles must be a valid object'); + } + if (!publicTiers || !Array.isArray(publicTiers)) { + throw new Error('publicTiers parameter must be a valid array'); + } + + let processedCount = 0; + let errorCount = 0; + const errors = []; + + const rows = publicTiers + .map((publicTier, index) => { + try { + if (!publicTier || typeof publicTier !== 'object') { + throw new Error(`Public tier at index ${index} is not a valid object`); + } + if (!publicTier.configProfileName) { + throw new Error(`Public tier at index ${index} missing configProfileName`); + } + if (!publicTier.name) { + throw new Error(`Public tier at index ${index} missing name`); + } + + // Find the highest version profile for this tier + const actualProfileName = findHighestVersionProfile(tiers.config_profiles, publicTier.configProfileName); + return { ...publicTier, actualProfileName }; + } catch (error) { + errorCount++; + errors.push(`Error processing public tier ${index}: ${error.message}`); + return null; + } + }) + .filter(publicTier => { + if (!publicTier) return false; + const exists = tiers.config_profiles[publicTier.actualProfileName]; + if (!exists) { + errorCount++; + errors.push(`Config profile "${publicTier.actualProfileName}" not found for tier "${publicTier.name}"`); + } + return exists; + }) + .map(publicTier => { + try { + const configProfile = tiers.config_profiles[publicTier.actualProfileName]; + const row = { tier: publicTier.name }; + + for (const key of limitKeys) { + try { + let value; + + // Check if this is a master data field first + if (['advertisedMaxIngress', 'advertisedMaxEgress', 'advertisedMaxPartitionCount', 'advertisedMaxClientCount'].includes(key)) { + value = publicTier[key] || 'N/A'; + } else if (configProfile[key] !== undefined) { + value = configProfile[key]; + } else if (configProfile.cluster_config && configProfile.cluster_config[key] !== undefined) { + value = configProfile.cluster_config[key]; + } else { + value = 'N/A'; + } + + // Humanize provider value + if (key === 'cloud_provider') { + value = humanProvider(value); + } + row[key] = value; + } catch (error) { + console.warn(`Warning: Failed to process key "${key}" for tier "${publicTier.name}": ${error.message}`); + row[key] = 'N/A'; + } + } + + // Add the actual profile name for deduplication + row._actualProfileName = publicTier.actualProfileName; + processedCount++; + return row; + } catch (error) { + errorCount++; + errors.push(`Error building row for tier "${publicTier.name}": ${error.message}`); + return null; + } + }) + .filter(row => row !== null) + // Deduplicate rows that have the same tier name and actual config profile + .filter((row, index, array) => { + try { + const duplicateIndex = array.findIndex(other => + other.tier === row.tier && + other._actualProfileName === row._actualProfileName + ); + return duplicateIndex === index; // Keep only the first occurrence + } catch (error) { + console.warn(`Warning: Failed to deduplicate row for tier "${row.tier}": ${error.message}`); + return true; // Keep the row if deduplication fails + } + }) + // Remove the internal _actualProfileName field + .map(row => { + try { + const { _actualProfileName, ...cleanRow } = row; + return cleanRow; + } catch (error) { + console.warn(`Warning: Failed to clean row for tier "${row.tier}": ${error.message}`); + return row; // Return original row if cleaning fails + } + }); + + // Group rows with identical data values + const groupedRows = []; + const processedRows = new Set(); + + for (let i = 0; i < rows.length; i++) { + if (processedRows.has(i)) continue; + + const currentRow = rows[i]; + const duplicateIndices = [i]; + + // Find all rows with identical data (excluding tier name) + for (let j = i + 1; j < rows.length; j++) { + if (processedRows.has(j)) continue; + + const otherRow = rows[j]; + let isIdentical = true; + + // Compare all limit values (excluding the tier name) + for (const key of limitKeys) { + if (currentRow[key] !== otherRow[key]) { + isIdentical = false; + break; + } + } + + if (isIdentical) { + duplicateIndices.push(j); + } + } + + // Mark all duplicate indices as processed + duplicateIndices.forEach(idx => processedRows.add(idx)); + + // Create a new row with combined tier names + const combinedRow = { ...currentRow }; + if (duplicateIndices.length > 1) { + // Sort tier names for consistent ordering + const tierNames = duplicateIndices.map(idx => rows[idx].tier).sort(); + combinedRow.tier = tierNames.join(', '); + } + + groupedRows.push(combinedRow); + } + + // Log processing summary + if (process.env.DEBUG_TIER_PROCESSING || errors.length > 0) { + console.log(`Tier processing summary: ${processedCount} processed, ${errorCount} errors, ${rows.length} individual rows, ${groupedRows.length} final grouped rows`); + if (errors.length > 0) { + console.warn('Processing errors:'); + errors.forEach(error => console.warn(` - ${error}`)); + } + } + + return groupedRows; + } catch (error) { + console.error(`Fatal error in buildTableRows: ${error.message}`); + throw new Error(`Failed to build table rows: ${error.message}`); + } +} + +/** + * Render an array of tier rows as a Markdown table. + * + * @param {Array} rows - Array of row objects where each row has a `tier` property and keys matching entries in `limitKeys`. + * @param {Array} [limitKeys=LIMIT_KEYS] - Ordered list of keys to include as columns after the "Tier" column; each key's value is taken from the corresponding property on a row. + * @returns {string} A Markdown-formatted table with a header row ("Tier" followed by the provided keys' labels) and one row per entry in `rows`. + */ +function toMarkdown(rows, limitKeys = LIMIT_KEYS) { + const headers = ['Tier', ...limitKeys.map(humanLabel)]; + const lines = []; + lines.push('| ' + headers.join(' | ') + ' |'); + lines.push('|' + headers.map(() => '---').join('|') + '|'); + for (const row of rows) { + lines.push('| ' + [row.tier, ...limitKeys.map(k => row[k])].join(' | ') + ' |'); + } + return lines.join('\n'); +} + +/** + * Render table rows as an AsciiDoc table. + * + * @param {Array} rows - Array of row objects; each object must include a `tier` property and values for the keys listed in `limitKeys`. + * @param {Array} [limitKeys=LIMIT_KEYS] - Ordered list of keys to include as columns (excluding the leading "Tier" column). + * @returns {string} An AsciiDoc-formatted table containing a header row ("Tier" plus humanized `limitKeys`) and one data row per entry in `rows`. + */ +function toAsciiDoc(rows, limitKeys = LIMIT_KEYS) { + const headers = ['Tier', ...limitKeys.map(humanLabel)]; + let out = '[options="header"]\n|===\n'; + out += '| ' + headers.join(' | ') + '\n'; + for (const row of rows) { + out += '| ' + [row.tier, ...limitKeys.map(k => row[k])].join(' | ') + '\n'; + } + out += '|===\n'; + return out; +} + +/** + * Render rows as CSV with a "Tier" column followed by the provided limit keys. + * + * @param {Array} rows - Array of row objects where each object contains a `tier` property and keys matching `limitKeys`. + * @param {Array} [limitKeys=LIMIT_KEYS] - Ordered list of keys to include as CSV columns after the "Tier" column. + * @returns {string} CSV-formatted text with a header row and one line per input row; values are quoted and internal quotes doubled. + */ +function toCSV(rows, limitKeys = LIMIT_KEYS) { + const headers = ['Tier', ...limitKeys]; + const esc = v => { + const s = String(v).replace(/"/g, '""'); + return `"${s}"`; + }; + const lines = []; + lines.push(headers.join(',')); + for (const row of rows) { + lines.push([row.tier, ...limitKeys.map(k => row[k])].map(esc).join(',')); + } + return lines.join('\n'); +} + +/** + * Render the provided rows into HTML using a Handlebars template. + * + * The template is compiled from the file at `templatePath` and is invoked with a context + * containing: `rows`, `headers` (array of {name, index_plus_one}), `limitKeys`, `cloudProviders`, + * and `uniqueTiers`. + * + * @param {Array} rows - Table row objects to render; each row is passed through to the template. + * @param {string} templatePath - Filesystem path to a Handlebars template. + * @param {Array} [limitKeys=LIMIT_KEYS] - Ordered list of limit keys used to build headers and passed to the template. + * @returns {string} The rendered HTML string. + */ +function toHTML(rows, templatePath, limitKeys = LIMIT_KEYS) { + const fs = require('fs'); + const handlebars = require('handlebars'); + const templateSource = fs.readFileSync(templatePath, 'utf8'); + const compiled = handlebars.compile(templateSource); + // Pass headers and limitKeys for template rendering + const headers = limitKeys.map(humanLabel); + // Precompute index_plus_one for each header for template + const headersWithIndex = headers.map((h, i) => ({ name: h, index_plus_one: i + 1 })); + // Extract unique cloud providers and tiers for dropdowns + // Exclude empty/blank providers and trim + const cloudProviders = Array.from(new Set( + rows.map(r => humanProvider(r.cloud_provider && String(r.cloud_provider).trim())).filter(Boolean) + )); + const uniqueTiers = Array.from(new Set(rows.map(r => r.tier).filter(Boolean))); + return compiled({ + rows, + headers: headersWithIndex, + limitKeys: limitKeys, + cloudProviders, + uniqueTiers + }); +} + +/** + * Generate a cloud tier table from local or remote YAML input and master-data, rendered in the requested format. + * + * @param {Object} options - Function options. + * @param {string} options.input - Path or URL to the input YAML (or the special GitHub install-pack API directory URL) containing tiers/config profiles. + * @param {string} [options.output] - Output destination (not used by this function; included for CLI compatibility). + * @param {string} [options.format='html'] - Output format: 'html', 'md' (Markdown), 'adoc' (AsciiDoc), or 'csv'. + * @param {string} [options.template] - Path to a Handlebars template to use for rendering; for 'html' format a default template is used when this is not provided. + * @param {string} [options.masterData] - URL or filesystem path to master-data.yaml used to fetch public tier definitions. + * @param {string[]} [options.limits] - Custom ordered list of limit keys to include; when omitted the default LIMIT_KEYS set is used. + * @returns {string} Generated table content in the requested format (HTML, Markdown, AsciiDoc, or CSV). + */ +async function generateCloudTierTable({ + input, + output, + format = 'html', + template, + masterData, + limits +}) { + const [tiers, publicTiers] = await Promise.all([ + parseYaml(input), + fetchPublicTiers(masterData) + ]); + const limitKeys = limits && Array.isArray(limits) && limits.length > 0 ? limits : LIMIT_KEYS; + const rows = buildTableRows(tiers, publicTiers, limitKeys); + const fmt = (format || 'md').toLowerCase(); + if (fmt === 'html') { + // Use provided template, or default to cloud-tier-table-html.hbs + const templatePath = template || require('path').resolve(__dirname, 'cloud-tier-table-html.hbs'); + return toHTML(rows, templatePath, limitKeys); + } + if (template) { + const templateSource = fs.readFileSync(template, 'utf8'); + const handlebars = require('handlebars'); + const compiled = handlebars.compile(templateSource); + return compiled({ rows, limitKeys: limitKeys }); + } + switch (fmt) { + case 'md': + return toMarkdown(rows, limitKeys); + case 'adoc': + return toAsciiDoc(rows, limitKeys); + case 'csv': + return toCSV(rows, limitKeys); + default: + throw new Error(`Unknown format: ${format}`); + } +} + +module.exports = { + generateCloudTierTable, + extractVersion, + findHighestVersionProfile, + parseYaml, + fetchPublicTiers +}; \ No newline at end of file diff --git a/tools/cloud-tier-table/generate-discrepancy-report.js b/tools/cloud-tier-table/generate-discrepancy-report.js new file mode 100644 index 0000000..8a4868f --- /dev/null +++ b/tools/cloud-tier-table/generate-discrepancy-report.js @@ -0,0 +1,375 @@ +#!/usr/bin/env node + +const { generateCloudTierTable } = require('./generate-cloud-tier-table.js'); +const Papa = require('papaparse'); + +/** + * Compute the percentage difference from an advertised value to an actual value. + * @param {number} advertised - The reference (advertised) value. + * @param {number} actual - The observed or configured value to compare. + * @returns {number|null} The percentage difference ((actual - advertised) / advertised * 100); `null` if `advertised` is falsy or zero. + */ +function calculatePercentageDiff(advertised, actual) { + if (!advertised || advertised === 0) return null; + return ((actual - advertised) / advertised) * 100; +} + +/** + * Convert a bytes-per-second value into a human-readable Mbps or Kbps string. + * @param {number} bps - Bytes per second; falsy values (e.g., 0, null, undefined) produce `'N/A'`. + * @returns {string} A formatted throughput string like `" Mbps"` or `" Kbps"`, or `'N/A'` when input is falsy. + */ +function formatThroughput(bps) { + if (!bps) return 'N/A'; + const mbps = bps / (1024 * 1024); + if (mbps < 1) { + const kbps = bps / 1024; + return `${kbps.toFixed(1)} Kbps`; + } + return `${mbps.toFixed(1)} Mbps`; +} + +/** + * Format a number with locale-specific thousands separators. + * + * If `num` is null or undefined the function returns `"N/A"`; a numeric `0` is formatted normally. + * @param {number} num - The number to format. + * @returns {string} The number formatted with locale-specific separators, or `"N/A"` if input is null or undefined. + */ +function formatNumber(num) { + if (!num && num !== 0) return 'N/A'; + return num.toLocaleString(); +} + +/** + * Classifies a percentage difference into a severity level. + * @param {number} percentDiff - Percentage difference (positive if actual > advertised, negative if actual < advertised). + * @returns {string} `unknown` if `percentDiff` is null or undefined; otherwise `minor` for absolute percentage <= 5, `moderate` for <= 25, `major` for <= 50, and `critical` for > 50. + */ +function getSeverity(percentDiff) { + if (percentDiff === null || percentDiff === undefined) return 'unknown'; + const abs = Math.abs(percentDiff); + if (abs <= 5) return 'minor'; + if (abs <= 25) return 'moderate'; + if (abs <= 50) return 'major'; + return 'critical'; +} + +/** + * Get severity emoji + * @param {string} severity - Severity level + * @returns {string} Emoji + */ +function getSeverityEmoji(severity) { + switch (severity) { + case 'minor': return '🟢'; + case 'moderate': return '🟡'; + case 'major': return '🟠'; + case 'critical': return '🔴'; + default: return '⚪'; + } +} + +/** + * Safely parse an integer from tier data with error handling + * @param {Object} tier - Tier data object + * @param {string} key - Key to parse from tier data + * @param {string} tierName - Tier name for logging + * @returns {number} Parsed integer or 0 if invalid + */ +function safeParseInt(tier, key, tierName) { + const value = tier[key]; + if (value === undefined || value === null || value === '') { + console.warn(`Warning: Missing value for key "${key}" in tier "${tierName}"`); + return 0; + } + + const parsed = parseInt(value, 10); + if (isNaN(parsed)) { + console.warn(`Warning: Invalid numeric value "${value}" for key "${key}" in tier "${tierName}"`); + return 0; + } + + return parsed; +} + +/** + * Create a discrepancy analysis entry for a single metric. + * + * @param {string} metricName - Human-readable metric name. + * @param {number} advertised - Advertised/configured value to compare against. + * @param {number} actual - Actual observed or configured value. + * @param {(value: number) => string} formatter - Formatter that converts numeric values to display strings. + * @returns {{metric: string, advertised: number, advertisedFormatted: string, actual: number, actualFormatted: string, percentageDiff: number|null, severity: string, emoji: string}} An object containing the metric name, raw and formatted advertised/actual values, the percentage difference (or `null` if unavailable), severity label, and a severity emoji. + */ +function analyzeMetric(metricName, advertised, actual, formatter) { + const percentageDiff = calculatePercentageDiff(advertised, actual); + const severity = getSeverity(percentageDiff); + + return { + metric: metricName, + advertised: advertised, + advertisedFormatted: formatter(advertised), + actual: actual, + actualFormatted: formatter(actual), + percentageDiff: percentageDiff, + severity: severity, + emoji: getSeverityEmoji(severity) + }; +} + +/** + * Builds a discrepancy analysis object for a cloud tier. + * + * @param {Object} tier - Tier data object containing configuration and advertised values. Expected keys include: + * `Tier` or `tier_name`, `cloud_provider`, `machine_type`, `nodes_count`, + * `advertisedMaxIngress`, `advertisedMaxEgress`, `advertisedMaxPartitionCount`, `advertisedMaxClientCount`, + * `kafka_throughput_limit_node_in_bps`, `kafka_throughput_limit_node_out_bps`, + * `topic_partitions_per_shard`, `kafka_connections_max`. + * @returns {Object} An analysis object with: + * - `tierName` (string): tier identifier, + * - `cloudProvider` (string), + * - `machineType` (string), + * - `nodeCount` (number), + * - `discrepancies` (Array): list of per-metric analysis objects (metric name, advertised value, actual/config value, formatted values, percentageDiff, severity, emoji). + */ +function analyzeTierDiscrepancies(tier) { + const tierName = tier.Tier || tier.tier_name; + const analysis = { + tierName: tierName, + cloudProvider: tier.cloud_provider, + machineType: tier.machine_type, + nodeCount: tier.nodes_count, + discrepancies: [] + }; + + // Ingress throughput analysis + const advertisedIngress = safeParseInt(tier, 'advertisedMaxIngress', tierName); + const configIngress = safeParseInt(tier, 'kafka_throughput_limit_node_in_bps', tierName); + analysis.discrepancies.push(analyzeMetric( + 'Ingress Throughput', + advertisedIngress, + configIngress, + formatThroughput + )); + + // Egress throughput analysis + const advertisedEgress = safeParseInt(tier, 'advertisedMaxEgress', tierName); + const configEgress = safeParseInt(tier, 'kafka_throughput_limit_node_out_bps', tierName); + analysis.discrepancies.push(analyzeMetric( + 'Egress Throughput', + advertisedEgress, + configEgress, + formatThroughput + )); + + // Partition count analysis + const advertisedPartitions = safeParseInt(tier, 'advertisedMaxPartitionCount', tierName); + const partitionsPerShard = safeParseInt(tier, 'topic_partitions_per_shard', tierName); + const nodeCount = safeParseInt(tier, 'nodes_count', tierName); + const configPartitions = partitionsPerShard * nodeCount; + analysis.discrepancies.push(analyzeMetric( + 'Max Partitions', + advertisedPartitions, + configPartitions, + formatNumber + )); + + // Client connections analysis + const advertisedClients = safeParseInt(tier, 'advertisedMaxClientCount', tierName); + const configClients = safeParseInt(tier, 'kafka_connections_max', tierName); + analysis.discrepancies.push(analyzeMetric( + 'Max Client Connections', + advertisedClients, + configClients, + formatNumber + )); + + return analysis; +} + +/** + * Generate a comprehensive discrepancy report for Redpanda Cloud Tier configurations. + * + * Produces a report that compares advertised tier values against actual configuration values + * and classifies discrepancies by severity. The report can be returned as Markdown (default) + * or as a JSON string containing the generated date, a summary, and per-tier analyses. + * + * @param {Object} [options] - Options to control input sources and output format. + * @param {string} [options.input] - URL or path to the install-pack source used to derive tier data. + * @param {string} [options.masterData] - URL or path to master-data.yaml used to resolve advertised values. + * @param {string} [options.format] - Output format: 'markdown' or 'json' (case-insensitive). Defaults to 'markdown'. + * @returns {string} The generated report as a formatted string (Markdown or JSON). + * @throws {Error} If CSV parsing fails, an unsupported format is requested, or report generation encounters an error. + */ +async function generateDiscrepancyReport(options = {}) { + const { + input = 'https://api.github.com/repos/redpanda-data/cloudv2/contents/install-pack', + masterData = 'https://api.github.com/repos/redpanda-data/cloudv2-infra/contents/master-data.yaml', + format = 'markdown' + } = options; + + try { + // Get the raw data by generating table with CSV format + const tableData = await generateCloudTierTable({ + input, + masterData, + format: 'csv', + output: null + }); + + // Parse CSV data using papaparse for robust handling of quotes, commas, and newlines + const parseResult = Papa.parse(tableData.trim(), { + header: true, + skipEmptyLines: true, + transformHeader: (header) => header.trim().replace(/^"|"$/g, ''), // Remove surrounding quotes + transform: (value) => value.trim().replace(/^"|"$/g, '') // Remove surrounding quotes from values + }); + + if (parseResult.errors.length > 0) { + throw new Error(`CSV parsing failed: ${parseResult.errors.map(e => e.message).join(', ')}`); + } + + const rows = parseResult.data; + + // Analyze each tier + const analyses = rows.map(analyzeTierDiscrepancies); + + // Normalize format input and validate + const mode = (format || 'markdown').toLowerCase(); + if (!['markdown', 'json'].includes(mode)) { + throw new Error(`Unsupported format: ${format}. Supported formats are 'markdown' and 'json'.`); + } + + // Generate report + let report = ''; + + if (mode === 'markdown') { + report += '# Redpanda Cloud Tier Discrepancy Report\n\n'; + report += `Generated on: ${new Date().toISOString().split('T')[0]}\n\n`; + report += '## Executive Summary\n\n'; + + // Summary statistics + let totalIssues = 0; + let criticalIssues = 0; + let majorIssues = 0; + let moderateIssues = 0; + let minorIssues = 0; + + analyses.forEach(analysis => { + analysis.discrepancies.forEach(disc => { + if (disc.severity !== 'minor') totalIssues++; + switch (disc.severity) { + case 'critical': criticalIssues++; break; + case 'major': majorIssues++; break; + case 'moderate': moderateIssues++; break; + case 'minor': minorIssues++; break; + } + }); + }); + + report += `- **Total Tiers Analyzed**: ${analyses.length}\n`; + report += `- **Total Issues Found**: ${totalIssues}\n`; + report += `- **🔴 Critical Issues**: ${criticalIssues}\n`; + report += `- **🟠 Major Issues**: ${majorIssues}\n`; + report += `- **🟡 Moderate Issues**: ${moderateIssues}\n`; + report += `- **🟢 Minor Issues**: ${minorIssues}\n\n`; + + report += '## Detailed Analysis\n\n'; + + // Group by cloud provider + const groupedByProvider = {}; + analyses.forEach(analysis => { + if (!groupedByProvider[analysis.cloudProvider]) { + groupedByProvider[analysis.cloudProvider] = []; + } + groupedByProvider[analysis.cloudProvider].push(analysis); + }); + + Object.keys(groupedByProvider).sort().forEach(provider => { + report += `### ${provider}\n\n`; + + groupedByProvider[provider].forEach(analysis => { + report += `#### ${analysis.tierName} (${analysis.machineType})\n\n`; + report += `**Configuration**: ${analysis.nodeCount} nodes\n\n`; + + // Create table for this tier + report += '| Metric | Advertised | Actual | Difference | Status |\n'; + report += '|--------|------------|--------|------------|--------|\n'; + + analysis.discrepancies.forEach(disc => { + const diffText = disc.percentageDiff !== null + ? `${disc.percentageDiff > 0 ? '+' : ''}${disc.percentageDiff.toFixed(1)}%` + : 'N/A'; + + report += `| ${disc.metric} | ${disc.advertisedFormatted} | ${disc.actualFormatted} | ${diffText} | ${disc.emoji} ${disc.severity} |\n`; + }); + + report += '\n'; + + // Highlight major issues + const majorIssues = analysis.discrepancies.filter(d => ['critical', 'major'].includes(d.severity)); + if (majorIssues.length > 0) { + report += '**⚠️ Major Issues:**\n'; + majorIssues.forEach(issue => { + const direction = issue.percentageDiff > 0 ? 'higher' : 'lower'; + report += `- ${issue.metric}: Config is ${Math.abs(issue.percentageDiff).toFixed(1)}% ${direction} than advertised\n`; + }); + report += '\n'; + } + }); + }); + + report += '## Recommendations\n\n'; + report += '1. **🔴 Critical/Major Issues**: Immediate review required for tiers with >25% discrepancies\n'; + report += '2. **📊 Throughput Alignment**: Standardize ingress/egress limits across machine types within tiers\n'; + report += '3. **👥 Client Connection Review**: Many config limits are significantly lower than advertised\n'; + report += '4. **📈 Partition Capacity**: Some tiers exceed advertised partition limits in config\n'; + report += '5. **🔄 Regular Audits**: Implement automated checks to prevent future discrepancies\n\n'; + + } else if (mode === 'json') { + report = JSON.stringify({ + generatedDate: new Date().toISOString(), + summary: { + totalTiers: analyses.length, + totalIssues: analyses.reduce((sum, a) => sum + a.discrepancies.filter(d => d.severity !== 'minor').length, 0) + }, + analyses + }, null, 2); + } + + return report; + + } catch (error) { + throw new Error(`Failed to generate discrepancy report: ${error.message}`); + } +} + +module.exports = { + generateDiscrepancyReport, + analyzeTierDiscrepancies, + calculatePercentageDiff, + formatThroughput, + formatNumber +}; + +// CLI usage +if (require.main === module) { + const args = process.argv.slice(2); + const options = {}; + + for (let i = 0; i < args.length; i += 2) { + const key = args[i].replace(/^--/, ''); + const value = args[i + 1]; + options[key] = value; + } + + generateDiscrepancyReport(options) + .then(report => { + console.log(report); + }) + .catch(error => { + console.error('Error:', error.message); + process.exit(1); + }); +} \ No newline at end of file