| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 7067d79 commit 686ac49
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1702,6 +1702,22 @@ changes: | |||
| 1702 | 1702 | ||
| 1703 | 1703 | Specify the maximum size, in bytes, of HTTP headers. Defaults to 16 KiB. | |
| 1704 | 1704 | ||
| 1705 | + ### `--max-old-space-size-percentage=PERCENTAGE` | ||
| 1706 | + | ||
| 1707 | + Sets the max memory size of V8's old memory section as a percentage of available system memory. | ||
| 1708 | + This flag takes precedence over `--max-old-space-size` when both are specified. | ||
| 1709 | + | ||
| 1710 | + The `PERCENTAGE` parameter must be a number greater than 0 and up to 100. representing the percentage | ||
| 1711 | + of available system memory to allocate to the V8 heap. | ||
| 1712 | + | ||
| 1713 | + ```bash | ||
| 1714 | + # Using 50% of available system memory | ||
| 1715 | + node --max-old-space-size-percentage=50 index.js | ||
| 1716 | + | ||
| 1717 | + # Using 75% of available system memory | ||
| 1718 | + node --max-old-space-size-percentage=75 index.js | ||
| 1719 | + ``` | ||
| 1720 | + | ||
| 1705 | 1721 | ### `--napi-modules` | |
| 1706 | 1722 | ||
| 1707 | 1723 | <!-- YAML | |
@@ -3387,6 +3403,7 @@ one is included in the list below. | |||
| 3387 | 3403 | * `--inspect` | |
| 3388 | 3404 | * `--localstorage-file` | |
| 3389 | 3405 | * `--max-http-header-size` | |
| 3406 | + * `--max-old-space-size-percentage` | ||
| 3390 | 3407 | * `--napi-modules` | |
| 3391 | 3408 | * `--network-family-autoselection-attempt-timeout` | |
| 3392 | 3409 | * `--no-addons` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -278,6 +278,9 @@ | |||
| 278 | 278 | "max-http-header-size": { | |
| 279 | 279 | "type": "number" | |
| 280 | 280 | }, | |
| 281 | + "max-old-space-size-percentage": { | ||
| 282 | + "type": "string" | ||
| 283 | + }, | ||
| 281 | 284 | "network-family-autoselection": { | |
| 282 | 285 | "type": "boolean" | |
| 283 | 286 | }, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -346,6 +346,16 @@ The file used to store localStorage data. | |||
| 346 | 346 | .It Fl -max-http-header-size Ns = Ns Ar size | |
| 347 | 347 | Specify the maximum size of HTTP headers in bytes. Defaults to 16 KiB. | |
| 348 | 348 | . | |
| 349 | + .It Fl -max-old-space-size-percentage Ns = Ns Ar percentage | ||
| 350 | + Sets the max memory size of V8's old memory section as a percentage of available system memory. | ||
| 351 | + This flag takes precedence over | ||
| 352 | + .Fl -max-old-space-size | ||
| 353 | + when both are specified. | ||
| 354 | + The | ||
| 355 | + .Ar percentage | ||
| 356 | + parameter must be a number greater than 0 and up to 100, representing the percentage | ||
| 357 | + of available system memory to allocate to the V8 heap. | ||
| 358 | + . | ||
| 349 | 359 | .It Fl -napi-modules | |
| 350 | 360 | This option is a no-op. | |
| 351 | 361 | It is kept for compatibility. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -818,6 +818,13 @@ static ExitCode ProcessGlobalArgsInternal(std::vector<std::string>* args, | |||
| 818 | 818 | // anymore. | |
| 819 | 819 | v8_args.emplace_back("--no-harmony-import-assertions"); | |
| 820 | 820 | ||
| 821 | + if (!per_process::cli_options->per_isolate->max_old_space_size_percentage | ||
| 822 | + .empty()) { | ||
| 823 | + v8_args.emplace_back( | ||
| 824 | + "--max_old_space_size=" + | ||
| 825 | + per_process::cli_options->per_isolate->max_old_space_size); | ||
| 826 | + } | ||
| 827 | + | ||
| 821 | 828 | auto env_opts = per_process::cli_options->per_isolate->per_env; | |
| 822 | 829 | if (std::find(v8_args.begin(), v8_args.end(), | |
| 823 | 830 | "--abort-on-uncaught-exception") != v8_args.end() || | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,13 +7,15 @@ | |||
| 7 | 7 | #include "node_external_reference.h" | |
| 8 | 8 | #include "node_internals.h" | |
| 9 | 9 | #include "node_sea.h" | |
| 10 | + #include "uv.h" | ||
| 10 | 11 | #if HAVE_OPENSSL | |
| 11 | 12 | #include "openssl/opensslv.h" | |
| 12 | 13 | #endif | |
| 13 | 14 | ||
| 14 | 15 | #include <algorithm> | |
| 15 | 16 | #include <array> | |
| 16 | 17 | #include <charconv> | |
| 18 | + #include <cstdint> | ||
| 17 | 19 | #include <limits> | |
| 18 | 20 | #include <sstream> | |
| 19 | 21 | #include <string_view> | |
@@ -103,8 +105,49 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors, | |||
| 103 | 105 | per_isolate->CheckOptions(errors, argv); | |
| 104 | 106 | } | |
| 105 | 107 | ||
| 108 | + void PerIsolateOptions::HandleMaxOldSpaceSizePercentage( | ||
| 109 | + std::vector<std::string>* errors, | ||
| 110 | + std::string* max_old_space_size_percentage) { | ||
| 111 | + std::string original_input_for_error = *max_old_space_size_percentage; | ||
| 112 | + // Parse the percentage value | ||
| 113 | + char* end_ptr; | ||
| 114 | + double percentage = | ||
| 115 | + std::strtod(max_old_space_size_percentage->c_str(), &end_ptr); | ||
| 116 | + | ||
| 117 | + // Validate the percentage value | ||
| 118 | + if (*end_ptr != '\0' || percentage <= 0.0 || percentage > 100.0) { | ||
| 119 | + errors->push_back("--max-old-space-size-percentage must be greater " | ||
| 120 | + "than 0 and up to 100. Got: " + | ||
| 121 | + original_input_for_error); | ||
| 122 | + return; | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + // Get available memory in bytes | ||
| 126 | + uint64_t total_memory = uv_get_total_memory(); | ||
| 127 | + uint64_t constrained_memory = uv_get_constrained_memory(); | ||
| 128 | + | ||
| 129 | + // Use constrained memory if available, otherwise use total memory | ||
| 130 | + // This logic correctly handles the documented guarantees. | ||
| 131 | + // Use uint64_t for the result to prevent data loss on 32-bit systems. | ||
| 132 | + uint64_t available_memory = | ||
| 133 | + (constrained_memory > 0 && constrained_memory != UINT64_MAX) | ||
| 134 | + ? constrained_memory | ||
| 135 | + : total_memory; | ||
| 136 | + | ||
| 137 | + // Convert to MB and calculate the percentage | ||
| 138 | + uint64_t memory_mb = available_memory / (1024 * 1024); | ||
| 139 | + uint64_t calculated_mb = static_cast<size_t>(memory_mb * percentage / 100.0); | ||
| 140 | + | ||
| 141 | + // Convert back to string | ||
| 142 | + max_old_space_size = std::to_string(calculated_mb); | ||
| 143 | + } | ||
| 144 | + | ||
| 106 | 145 | void PerIsolateOptions::CheckOptions(std::vector<std::string>* errors, | |
| 107 | 146 | std::vector<std::string>* argv) { | |
| 147 | + if (!max_old_space_size_percentage.empty()) { | ||
| 148 | + HandleMaxOldSpaceSizePercentage(errors, &max_old_space_size_percentage); | ||
| 149 | + } | ||
| 150 | + | ||
| 108 | 151 | per_env->CheckOptions(errors, argv); | |
| 109 | 152 | } | |
| 110 | 153 | ||
@@ -987,6 +1030,11 @@ PerIsolateOptionsParser::PerIsolateOptionsParser( | |||
| 987 | 1030 | V8Option{}, | |
| 988 | 1031 | kAllowedInEnvvar); | |
| 989 | 1032 | AddOption("--max-old-space-size", "", V8Option{}, kAllowedInEnvvar); | |
| 1033 | + AddOption("--max-old-space-size-percentage", | ||
| 1034 | + "set V8's max old space size as a percentage of available memory " | ||
| 1035 | + "(e.g., '50%'). Takes precedence over --max-old-space-size.", | ||
| 1036 | + &PerIsolateOptions::max_old_space_size_percentage, | ||
| 1037 | + kAllowedInEnvvar); | ||
| 990 | 1038 | AddOption("--max-semi-space-size", "", V8Option{}, kAllowedInEnvvar); | |
| 991 | 1039 | AddOption("--perf-basic-prof", "", V8Option{}, kAllowedInEnvvar); | |
| 992 | 1040 | AddOption( | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -283,13 +283,17 @@ class PerIsolateOptions : public Options { | |||
| 283 | 283 | bool report_uncaught_exception = false; | |
| 284 | 284 | bool report_on_signal = false; | |
| 285 | 285 | bool experimental_shadow_realm = false; | |
| 286 | + std::string max_old_space_size_percentage; | ||
| 287 | + std::string max_old_space_size; | ||
| 286 | 288 | int64_t stack_trace_limit = 10; | |
| 287 | 289 | std::string report_signal = "SIGUSR2"; | |
| 288 | 290 | bool build_snapshot = false; | |
| 289 | 291 | std::string build_snapshot_config; | |
| 290 | 292 | inline EnvironmentOptions* get_per_env_options(); | |
| 291 | 293 | void CheckOptions(std::vector<std::string>* errors, | |
| 292 | 294 | std::vector<std::string>* argv) override; | |
| 295 | + void HandleMaxOldSpaceSizePercentage(std::vector<std::string>* errors, | ||
| 296 | + std::string* max_old_space_size); | ||
| 293 | 297 | ||
| 294 | 298 | inline std::shared_ptr<PerIsolateOptions> Clone() const; | |
| 295 | 299 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,134 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This test validates the --max-old-space-size-percentage flag functionality | ||
| 4 | + | ||
| 5 | + require('../common'); | ||
| 6 | + const assert = require('node:assert'); | ||
| 7 | + const { spawnSync } = require('child_process'); | ||
| 8 | + const os = require('os'); | ||
| 9 | + | ||
| 10 | + // Valid cases | ||
| 11 | + const validPercentages = [ | ||
| 12 | + '1', '10', '25', '50', '75', '99', '100', '25.5', | ||
| 13 | + ]; | ||
| 14 | + | ||
| 15 | + // Invalid cases | ||
| 16 | + const invalidPercentages = [ | ||
| 17 | + ['', /--max-old-space-size-percentage= requires an argument/], | ||
| 18 | + ['0', /--max-old-space-size-percentage must be greater than 0 and up to 100\. Got: 0/], | ||
| 19 | + ['101', /--max-old-space-size-percentage must be greater than 0 and up to 100\. Got: 101/], | ||
| 20 | + ['-1', /--max-old-space-size-percentage must be greater than 0 and up to 100\. Got: -1/], | ||
| 21 | + ['abc', /--max-old-space-size-percentage must be greater than 0 and up to 100\. Got: abc/], | ||
| 22 | + ['1%', /--max-old-space-size-percentage must be greater than 0 and up to 100\. Got: 1%/], | ||
| 23 | + ]; | ||
| 24 | + | ||
| 25 | + // Test valid cases | ||
| 26 | + validPercentages.forEach((input) => { | ||
| 27 | + const result = spawnSync(process.execPath, [ | ||
| 28 | + `--max-old-space-size-percentage=${input}`, | ||
| 29 | + ], { stdio: ['pipe', 'pipe', 'pipe'] }); | ||
| 30 | + assert.strictEqual(result.status, 0, `Expected exit code 0 for valid input ${input}`); | ||
| 31 | + assert.strictEqual(result.stderr.toString(), '', `Expected empty stderr for valid input ${input}`); | ||
| 32 | + }); | ||
| 33 | + | ||
| 34 | + // Test invalid cases | ||
| 35 | + invalidPercentages.forEach((input) => { | ||
| 36 | + const result = spawnSync(process.execPath, [ | ||
| 37 | + `--max-old-space-size-percentage=${input[0]}`, | ||
| 38 | + ], { stdio: ['pipe', 'pipe', 'pipe'] }); | ||
| 39 | + assert.notStrictEqual(result.status, 0, `Expected non-zero exit for invalid input ${input[0]}`); | ||
| 40 | + assert(input[1].test(result.stderr.toString()), `Unexpected error message for invalid input ${input[0]}`); | ||
| 41 | + }); | ||
| 42 | + | ||
| 43 | + // Test NODE_OPTIONS with valid percentages | ||
| 44 | + validPercentages.forEach((input) => { | ||
| 45 | + const result = spawnSync(process.execPath, [], { | ||
| 46 | + stdio: ['pipe', 'pipe', 'pipe'], | ||
| 47 | + env: { ...process.env, NODE_OPTIONS: `--max-old-space-size-percentage=${input}` } | ||
| 48 | + }); | ||
| 49 | + assert.strictEqual(result.status, 0, `NODE_OPTIONS: Expected exit code 0 for valid input ${input}`); | ||
| 50 | + assert.strictEqual(result.stderr.toString(), '', `NODE_OPTIONS: Expected empty stderr for valid input ${input}`); | ||
| 51 | + }); | ||
| 52 | + | ||
| 53 | + // Test NODE_OPTIONS with invalid percentages | ||
| 54 | + invalidPercentages.forEach((input) => { | ||
| 55 | + const result = spawnSync(process.execPath, [], { | ||
| 56 | + stdio: ['pipe', 'pipe', 'pipe'], | ||
| 57 | + env: { ...process.env, NODE_OPTIONS: `--max-old-space-size-percentage=${input[0]}` } | ||
| 58 | + }); | ||
| 59 | + assert.notStrictEqual(result.status, 0, `NODE_OPTIONS: Expected non-zero exit for invalid input ${input[0]}`); | ||
| 60 | + assert(input[1].test(result.stderr.toString()), `NODE_OPTIONS: Unexpected error message for invalid input ${input[0]}`); | ||
| 61 | + }); | ||
| 62 | + | ||
| 63 | + // Test percentage calculation validation | ||
| 64 | + function getHeapSizeForPercentage(percentage) { | ||
| 65 | + const result = spawnSync(process.execPath, [ | ||
| 66 | + '--max-old-space-size=3000', // This value should be ignored, since percentage takes precedence | ||
| 67 | + `--max-old-space-size-percentage=${percentage}`, | ||
| 68 | + '--max-old-space-size=1000', // This value should be ignored, since percentage take precedence | ||
| 69 | + '-e', ` | ||
| 70 | + const v8 = require('v8'); | ||
| 71 | + const stats = v8.getHeapStatistics(); | ||
| 72 | + const heapSizeLimitMB = Math.floor(stats.heap_size_limit / 1024 / 1024); | ||
| 73 | + console.log(heapSizeLimitMB); | ||
| 74 | + `, | ||
| 75 | + ], { | ||
| 76 | + stdio: ['pipe', 'pipe', 'pipe'], | ||
| 77 | + env: { | ||
| 78 | + ...process.env, | ||
| 79 | + NODE_OPTIONS: `--max-old-space-size=2000` // This value should be ignored, since percentage takes precedence | ||
| 80 | + } | ||
| 81 | + }); | ||
| 82 | + | ||
| 83 | + if (result.status !== 0) { | ||
| 84 | + throw new Error(`Failed to get heap size for ${percentage}: ${result.stderr.toString()}`); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + return parseInt(result.stdout.toString(), 10); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + const testPercentages = [25, 50, 75, 100]; | ||
| 91 | + const heapSizes = {}; | ||
| 92 | + | ||
| 93 | + // Get heap sizes for all test percentages | ||
| 94 | + testPercentages.forEach((percentage) => { | ||
| 95 | + heapSizes[percentage] = getHeapSizeForPercentage(percentage); | ||
| 96 | + }); | ||
| 97 | + | ||
| 98 | + // Test relative relationships between percentages | ||
| 99 | + // 50% should be roughly half of 100% | ||
| 100 | + const ratio50to100 = heapSizes[50] / heapSizes[100]; | ||
| 101 | + assert( | ||
| 102 | + ratio50to100 >= 0.4 && ratio50to100 <= 0.6, | ||
| 103 | + `50% heap size should be roughly half of 100% (got ${ratio50to100.toFixed(2)}, expected ~0.5)` | ||
| 104 | + ); | ||
| 105 | + | ||
| 106 | + // 25% should be roughly quarter of 100% | ||
| 107 | + const ratio25to100 = heapSizes[25] / heapSizes[100]; | ||
| 108 | + assert( | ||
| 109 | + ratio25to100 >= 0.15 && ratio25to100 <= 0.35, | ||
| 110 | + `25% heap size should be roughly quarter of 100% (got ${ratio25to100.toFixed(2)}, expected ~0.25)` | ||
| 111 | + ); | ||
| 112 | + | ||
| 113 | + // 75% should be roughly three-quarters of 100% | ||
| 114 | + const ratio75to100 = heapSizes[75] / heapSizes[100]; | ||
| 115 | + assert( | ||
| 116 | + ratio75to100 >= 0.65 && ratio75to100 <= 0.85, | ||
| 117 | + `75% heap size should be roughly three-quarters of 100% (got ${ratio75to100.toFixed(2)}, expected ~0.75)` | ||
| 118 | + ); | ||
| 119 | + | ||
| 120 | + // Validate heap sizes against system memory | ||
| 121 | + const totalMemoryMB = Math.floor(os.totalmem() / 1024 / 1024); | ||
| 122 | + const margin = 10; // 5% margin | ||
| 123 | + testPercentages.forEach((percentage) => { | ||
| 124 | + const upperLimit = totalMemoryMB * ((percentage + margin) / 100); | ||
| 125 | + assert( | ||
| 126 | + heapSizes[percentage] <= upperLimit, | ||
| 127 | + `Heap size for ${percentage}% (${heapSizes[percentage]} MB) should not exceed upper limit (${upperLimit} MB)` | ||
| 128 | + ); | ||
| 129 | + const lowerLimit = totalMemoryMB * ((percentage - margin) / 100); | ||
| 130 | + assert( | ||
| 131 | + heapSizes[percentage] >= lowerLimit, | ||
| 132 | + `Heap size for ${percentage}% (${heapSizes[percentage]} MB) should not be less than lower limit (${lowerLimit} MB)` | ||
| 133 | + ); | ||
| 134 | + }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments