| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 7ab13b7 commit 4a907bd
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1680,6 +1680,22 @@ changes: | |||
| 1680 | 1680 | ||
| 1681 | 1681 | Specify the maximum size, in bytes, of HTTP headers. Defaults to 16 KiB. | |
| 1682 | 1682 | ||
| 1683 | + ### `--max-old-space-size-percentage=PERCENTAGE` | ||
| 1684 | + | ||
| 1685 | + Sets the max memory size of V8's old memory section as a percentage of available system memory. | ||
| 1686 | + This flag takes precedence over `--max-old-space-size` when both are specified. | ||
| 1687 | + | ||
| 1688 | + The `PERCENTAGE` parameter must be a number greater than 0 and up to 100. representing the percentage | ||
| 1689 | + of available system memory to allocate to the V8 heap. | ||
| 1690 | + | ||
| 1691 | + ```bash | ||
| 1692 | + # Using 50% of available system memory | ||
| 1693 | + node --max-old-space-size-percentage=50 index.js | ||
| 1694 | + | ||
| 1695 | + # Using 75% of available system memory | ||
| 1696 | + node --max-old-space-size-percentage=75 index.js | ||
| 1697 | + ``` | ||
| 1698 | + | ||
| 1683 | 1699 | ### `--napi-modules` | |
| 1684 | 1700 | ||
| 1685 | 1701 | <!-- YAML | |
@@ -3395,6 +3411,7 @@ one is included in the list below. | |||
| 3395 | 3411 | * `--inspect` | |
| 3396 | 3412 | * `--localstorage-file` | |
| 3397 | 3413 | * `--max-http-header-size` | |
| 3414 | + * `--max-old-space-size-percentage` | ||
| 3398 | 3415 | * `--napi-modules` | |
| 3399 | 3416 | * `--network-family-autoselection-attempt-timeout` | |
| 3400 | 3417 | * `--no-addons` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -266,6 +266,9 @@ | |||
| 266 | 266 | "max-http-header-size": { | |
| 267 | 267 | "type": "number" | |
| 268 | 268 | }, | |
| 269 | + "max-old-space-size-percentage": { | ||
| 270 | + "type": "string" | ||
| 271 | + }, | ||
| 269 | 272 | "network-family-autoselection": { | |
| 270 | 273 | "type": "boolean" | |
| 271 | 274 | }, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -332,6 +332,16 @@ The file used to store localStorage data. | |||
| 332 | 332 | .It Fl -max-http-header-size Ns = Ns Ar size | |
| 333 | 333 | Specify the maximum size of HTTP headers in bytes. Defaults to 16 KiB. | |
| 334 | 334 | . | |
| 335 | + .It Fl -max-old-space-size-percentage Ns = Ns Ar percentage | ||
| 336 | + Sets the max memory size of V8's old memory section as a percentage of available system memory. | ||
| 337 | + This flag takes precedence over | ||
| 338 | + .Fl -max-old-space-size | ||
| 339 | + when both are specified. | ||
| 340 | + The | ||
| 341 | + .Ar percentage | ||
| 342 | + parameter must be a number greater than 0 and up to 100, representing the percentage | ||
| 343 | + of available system memory to allocate to the V8 heap. | ||
| 344 | + . | ||
| 335 | 345 | .It Fl -napi-modules | |
| 336 | 346 | This option is a no-op. | |
| 337 | 347 | It is kept for compatibility. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -765,6 +765,13 @@ static ExitCode ProcessGlobalArgsInternal(std::vector<std::string>* args, | |||
| 765 | 765 | v8_args.emplace_back("--harmony-import-attributes"); | |
| 766 | 766 | } | |
| 767 | 767 | ||
| 768 | + if (!per_process::cli_options->per_isolate->max_old_space_size_percentage | ||
| 769 | + .empty()) { | ||
| 770 | + v8_args.emplace_back( | ||
| 771 | + "--max_old_space_size=" + | ||
| 772 | + per_process::cli_options->per_isolate->max_old_space_size); | ||
| 773 | + } | ||
| 774 | + | ||
| 768 | 775 | auto env_opts = per_process::cli_options->per_isolate->per_env; | |
| 769 | 776 | if (std::ranges::find(v8_args, "--abort-on-uncaught-exception") != | |
| 770 | 777 | 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> | |
@@ -107,8 +109,49 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors, | |||
| 107 | 109 | per_isolate->CheckOptions(errors, argv); | |
| 108 | 110 | } | |
| 109 | 111 | ||
| 112 | + void PerIsolateOptions::HandleMaxOldSpaceSizePercentage( | ||
| 113 | + std::vector<std::string>* errors, | ||
| 114 | + std::string* max_old_space_size_percentage) { | ||
| 115 | + std::string original_input_for_error = *max_old_space_size_percentage; | ||
| 116 | + // Parse the percentage value | ||
| 117 | + char* end_ptr; | ||
| 118 | + double percentage = | ||
| 119 | + std::strtod(max_old_space_size_percentage->c_str(), &end_ptr); | ||
| 120 | + | ||
| 121 | + // Validate the percentage value | ||
| 122 | + if (*end_ptr != '\0' || percentage <= 0.0 || percentage > 100.0) { | ||
| 123 | + errors->push_back("--max-old-space-size-percentage must be greater " | ||
| 124 | + "than 0 and up to 100. Got: " + | ||
| 125 | + original_input_for_error); | ||
| 126 | + return; | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + // Get available memory in bytes | ||
| 130 | + uint64_t total_memory = uv_get_total_memory(); | ||
| 131 | + uint64_t constrained_memory = uv_get_constrained_memory(); | ||
| 132 | + | ||
| 133 | + // Use constrained memory if available, otherwise use total memory | ||
| 134 | + // This logic correctly handles the documented guarantees. | ||
| 135 | + // Use uint64_t for the result to prevent data loss on 32-bit systems. | ||
| 136 | + uint64_t available_memory = | ||
| 137 | + (constrained_memory > 0 && constrained_memory != UINT64_MAX) | ||
| 138 | + ? constrained_memory | ||
| 139 | + : total_memory; | ||
| 140 | + | ||
| 141 | + // Convert to MB and calculate the percentage | ||
| 142 | + uint64_t memory_mb = available_memory / (1024 * 1024); | ||
| 143 | + uint64_t calculated_mb = static_cast<size_t>(memory_mb * percentage / 100.0); | ||
| 144 | + | ||
| 145 | + // Convert back to string | ||
| 146 | + max_old_space_size = std::to_string(calculated_mb); | ||
| 147 | + } | ||
| 148 | + | ||
| 110 | 149 | void PerIsolateOptions::CheckOptions(std::vector<std::string>* errors, | |
| 111 | 150 | std::vector<std::string>* argv) { | |
| 151 | + if (!max_old_space_size_percentage.empty()) { | ||
| 152 | + HandleMaxOldSpaceSizePercentage(errors, &max_old_space_size_percentage); | ||
| 153 | + } | ||
| 154 | + | ||
| 112 | 155 | per_env->CheckOptions(errors, argv); | |
| 113 | 156 | } | |
| 114 | 157 | ||
@@ -1082,6 +1125,11 @@ PerIsolateOptionsParser::PerIsolateOptionsParser( | |||
| 1082 | 1125 | V8Option{}, | |
| 1083 | 1126 | kAllowedInEnvvar); | |
| 1084 | 1127 | AddOption("--max-old-space-size", "", V8Option{}, kAllowedInEnvvar); | |
| 1128 | + AddOption("--max-old-space-size-percentage", | ||
| 1129 | + "set V8's max old space size as a percentage of available memory " | ||
| 1130 | + "(e.g., '50%'). Takes precedence over --max-old-space-size.", | ||
| 1131 | + &PerIsolateOptions::max_old_space_size_percentage, | ||
| 1132 | + kAllowedInEnvvar); | ||
| 1085 | 1133 | AddOption("--max-semi-space-size", "", V8Option{}, kAllowedInEnvvar); | |
| 1086 | 1134 | AddOption("--perf-basic-prof", "", V8Option{}, kAllowedInEnvvar); | |
| 1087 | 1135 | AddOption( | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -285,13 +285,17 @@ class PerIsolateOptions : public Options { | |||
| 285 | 285 | bool report_uncaught_exception = false; | |
| 286 | 286 | bool report_on_signal = false; | |
| 287 | 287 | bool experimental_shadow_realm = false; | |
| 288 | + std::string max_old_space_size_percentage; | ||
| 289 | + std::string max_old_space_size; | ||
| 288 | 290 | int64_t stack_trace_limit = 10; | |
| 289 | 291 | std::string report_signal = "SIGUSR2"; | |
| 290 | 292 | bool build_snapshot = false; | |
| 291 | 293 | std::string build_snapshot_config; | |
| 292 | 294 | inline EnvironmentOptions* get_per_env_options(); | |
| 293 | 295 | void CheckOptions(std::vector<std::string>* errors, | |
| 294 | 296 | std::vector<std::string>* argv) override; | |
| 297 | + void HandleMaxOldSpaceSizePercentage(std::vector<std::string>* errors, | ||
| 298 | + std::string* max_old_space_size); | ||
| 295 | 299 | ||
| 296 | 300 | inline std::shared_ptr<PerIsolateOptions> Clone() const; | |
| 297 | 301 | ||
| 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