From cef82defef2b8179cfcff0db3a78c327dbe4c91a Mon Sep 17 00:00:00 2001 From: harrider <22509508+harrider@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:10:30 -0500 Subject: [PATCH 1/9] Add v8 package --- package-lock.json | 10 ++++++++++ package.json | 1 + 2 files changed, 11 insertions(+) diff --git a/package-lock.json b/package-lock.json index 0088d917..6fa17367 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "throat": "^4.1.0", "tiny-attribution-generator": "0.1.2", "tmp": "0.0.33", + "v8": "0.1.0", "vso-node-api": "^6.2.8-preview", "winston": "^2.3.0", "winston-azure-application-insights": "^1.5.0" @@ -10929,6 +10930,15 @@ "uuid": "bin/uuid" } }, + "node_modules/v8": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/v8/-/v8-0.1.0.tgz", + "integrity": "sha512-cSrJCQ7WRDkSP8zbIwOO38kLSp1mGmBbx/I0pHdzQROZIMlO+qkiC4deQxg1I7pKguYJNMhMD5g/Nc1muiVyYw==", + "license": "MIT", + "bin": { + "v8": "bin/v8" + } + }, "node_modules/validator": { "version": "9.4.1", "resolved": "http://registry.npmjs.org/validator/-/validator-9.4.1.tgz", diff --git a/package.json b/package.json index fc52031b..97f27960 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "throat": "^4.1.0", "tiny-attribution-generator": "0.1.2", "tmp": "0.0.33", + "v8": "0.1.0", "vso-node-api": "^6.2.8-preview", "winston": "^2.3.0", "winston-azure-application-insights": "^1.5.0" From 084eb50fbefb8e6137bb6cbcffa4033bd913c586 Mon Sep 17 00:00:00 2001 From: harrider <22509508+harrider@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:14:36 -0500 Subject: [PATCH 2/9] Add heapstats logging --- app.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/app.js b/app.js index ce566c9c..c6a61155 100644 --- a/app.js +++ b/app.js @@ -100,6 +100,57 @@ function createApp(config) { crawlerSecret ) + // =================================================== + // Log the heap space statistics at regular intervals + // =================================================== + const v8 = require('v8') + const heapStatsInverval = 10000; // 10 seconds + + // Function to add commas to a number + const addCommas = (num) => Number(num).toLocaleString(); + + // Function to log the heap space statistics + const logHeapSpaceStats = () => { + // Get the current timestamp + const currentTimestamp = new Date().toISOString(); + + // Get the heap space statistics + const heapSpaceStats = v8.getHeapSpaceStatistics(); + + heapSpaceStats.forEach((space) => { + const heapStatsMessage = `[${currentTimestamp}] Heap Space Statistics: ` + + `Space Name: '${space.space_name}', ` + + `Space Size: '${addCommas(space.space_size)}' bytes, ` + + `Space Used Size: '${addCommas(space.space_used_size)}' bytes, ` + + `Space Available Size: '${addCommas(space.space_available_size)}' bytes, ` + + `Physical Space Size: '${addCommas(space.physical_space_size)}' bytes` + + '\n--------------------------'; + + logger.info(heapStatsMessage); + }); + + // Get the heap statistics + const heapStats = v8.getHeapStatistics(); + + const heapStatsMessage = `[${currentTimestamp}] Heap Statistics: ` + + `Total Heap Size: '${addCommas(heapStats.total_heap_size)}' bytes, ` + + `Total Heap Used Size: '${addCommas(heapStats.total_heap_size)}' bytes, ` + + `Total Physical Size: '${addCommas(heapStats.total_physical_size)}' bytes, ` + + `Total Available Size: '${addCommas(heapStats.total_available_size)}' bytes, ` + + `Used Heap Size: '${addCommas(heapStats.used_heap_size)}' bytes, ` + + `Heap Size Limit: '${addCommas(heapStats.heap_size_limit)}' bytes` + + '\n--------------------------'; + + logger.info(heapStatsMessage); + }; + + // Only run if not in a test environment + if (process.argv.every(arg => !arg.includes('mocha'))) { + // Set the interval to log the heap space statistics + setInterval(logHeapSpaceStats, heapStatsInverval); + } + // =================================================== + const app = express() app.use(cors()) app.options('*', cors()) From 87b065c8168338fd67c71dafef80cb88775b0f48 Mon Sep 17 00:00:00 2001 From: harrider <22509508+harrider@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:24:36 -0500 Subject: [PATCH 3/9] Add max heap size argument --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97f27960..ec1f6787 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "prettier:check": "prettier . --check", "prettier:write": "prettier . --write", "dev": "nodemon ./bin/www", - "start": "node ./bin/www", + "start": "node --max-old-space-size=8192 ./bin/www", "postinstall": "patch-package" }, "license": "MIT", From baff202a9e3fbe1b91ce3eb1f6143ec239be01d8 Mon Sep 17 00:00:00 2001 From: harrider <22509508+harrider@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:24:47 -0500 Subject: [PATCH 4/9] Add env vars to control heap stats logging --- bin/config.js | 6 +++++- full.env.json | 6 +++++- minimal.env.json | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/bin/config.js b/bin/config.js index eea97035..02e6e64e 100644 --- a/bin/config.js +++ b/bin/config.js @@ -100,5 +100,9 @@ module.exports = { crawlerKey: config.get('APPINSIGHTS_CRAWLER_APIKEY') }, appVersion: config.get('APP_VERSION'), - buildsha: config.get('BUILD_SHA') + buildsha: config.get('BUILD_SHA'), + heapstats: { + logHeapstats: config.get('LOG_NODE_HEAPSTATS'), + logInverval: config.get('LOG_NODE_HEAPSTATS_INTERVAL_MS') + } } diff --git a/full.env.json b/full.env.json index 8deb15c9..3c668a21 100644 --- a/full.env.json +++ b/full.env.json @@ -47,5 +47,9 @@ "CRAWLER_QUEUE_PROVIDER": "memory", "CRAWLER_SERVICE_PORT": "5000", "CRAWLER_GITHUB_TOKEN": "< GitHub PAT here >", - "SCANCODE_HOME": "< ScanCode install location e.g., c:\\installs\\scancode-toolkit-2.2.1 >" + "SCANCODE_HOME": "< ScanCode install location e.g., c:\\installs\\scancode-toolkit-2.2.1 >", + + "========== Heapstats Logging settings (OPTIONAL) ==========": "", + "LOG_NODE_HEAPSTATS": "", + "LOG_NODE_HEAPSTATS_INTERVAL_MS": "" } diff --git a/minimal.env.json b/minimal.env.json index 9f6916c9..54e23abd 100644 --- a/minimal.env.json +++ b/minimal.env.json @@ -8,5 +8,9 @@ "========== Service Curation settings ==========": "", "CURATION_GITHUB_REPO": "sample-curated-data", - "CURATION_GITHUB_TOKEN": "" + "CURATION_GITHUB_TOKEN": "", + + "========== Heapstats Logging settings (OPTIONAL) ==========": "", + "LOG_NODE_HEAPSTATS": "", + "LOG_NODE_HEAPSTATS_INTERVAL_MS": "" } From 2c7f8242f8c7c613bbd8037e178cb5684ac52016 Mon Sep 17 00:00:00 2001 From: harrider <22509508+harrider@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:25:20 -0500 Subject: [PATCH 5/9] Control heap stats logging with env vars --- app.js | 105 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 46 deletions(-) diff --git a/app.js b/app.js index c6a61155..986819be 100644 --- a/app.js +++ b/app.js @@ -101,53 +101,66 @@ function createApp(config) { ) // =================================================== - // Log the heap space statistics at regular intervals + // Log the heap statistics at regular intervals // =================================================== - const v8 = require('v8') - const heapStatsInverval = 10000; // 10 seconds - - // Function to add commas to a number - const addCommas = (num) => Number(num).toLocaleString(); - - // Function to log the heap space statistics - const logHeapSpaceStats = () => { - // Get the current timestamp - const currentTimestamp = new Date().toISOString(); - - // Get the heap space statistics - const heapSpaceStats = v8.getHeapSpaceStatistics(); - - heapSpaceStats.forEach((space) => { - const heapStatsMessage = `[${currentTimestamp}] Heap Space Statistics: ` - + `Space Name: '${space.space_name}', ` - + `Space Size: '${addCommas(space.space_size)}' bytes, ` - + `Space Used Size: '${addCommas(space.space_used_size)}' bytes, ` - + `Space Available Size: '${addCommas(space.space_available_size)}' bytes, ` - + `Physical Space Size: '${addCommas(space.physical_space_size)}' bytes` - + '\n--------------------------'; - - logger.info(heapStatsMessage); - }); - - // Get the heap statistics - const heapStats = v8.getHeapStatistics(); - - const heapStatsMessage = `[${currentTimestamp}] Heap Statistics: ` - + `Total Heap Size: '${addCommas(heapStats.total_heap_size)}' bytes, ` - + `Total Heap Used Size: '${addCommas(heapStats.total_heap_size)}' bytes, ` - + `Total Physical Size: '${addCommas(heapStats.total_physical_size)}' bytes, ` - + `Total Available Size: '${addCommas(heapStats.total_available_size)}' bytes, ` - + `Used Heap Size: '${addCommas(heapStats.used_heap_size)}' bytes, ` - + `Heap Size Limit: '${addCommas(heapStats.heap_size_limit)}' bytes` - + '\n--------------------------'; - - logger.info(heapStatsMessage); - }; - - // Only run if not in a test environment - if (process.argv.every(arg => !arg.includes('mocha'))) { - // Set the interval to log the heap space statistics - setInterval(logHeapSpaceStats, heapStatsInverval); + // NOTE: set 'LOG_NODE_HEAPSTATS' env var to 'true' to log heap stats + // NOTE: set 'LOG_NODE_HEAPSTATS_INTERVAL_MS' env var to '' for logging interval + const shouldLogHeapstats = config.heapstats.logHeapstats + ? config.heapstats.logHeapstats.toLowerCase() === 'true' + : false + + if (shouldLogHeapstats) { + const v8 = require('v8') + + const addCommas = num => Number(num).toLocaleString() + const isNumeric = num => !isNaN(Number(num)) + + // Set the heapstats logging interval + const maybeInterval = config.heapstats.logInverval + const heapStatsInverval = maybeInterval && isNumeric(maybeInterval) ? maybeInterval : 30000 + + // Function to log the heap space statistics + const logHeapSpaceStats = () => { + // Get the current timestamp + const currentTimestamp = new Date().toISOString() + + // Get the heap space statistics + const heapSpaceStats = v8.getHeapSpaceStatistics() + + heapSpaceStats.forEach(space => { + const heapStatsMessage = + `[${currentTimestamp}] Heap Space Statistics: ` + + `Space Name: '${space.space_name}', ` + + `Space Size: '${addCommas(space.space_size)}' bytes, ` + + `Space Used Size: '${addCommas(space.space_used_size)}' bytes, ` + + `Space Available Size: '${addCommas(space.space_available_size)}' bytes, ` + + `Physical Space Size: '${addCommas(space.physical_space_size)}' bytes` + + '\n--------------------------' + + logger.info(heapStatsMessage) + }) + + // Get the heap statistics + const heapStats = v8.getHeapStatistics() + + const heapStatsMessage = + `[${currentTimestamp}] Heap Statistics: ` + + `Total Heap Size: '${addCommas(heapStats.total_heap_size)}' bytes, ` + + `Total Heap Used Size: '${addCommas(heapStats.total_heap_size)}' bytes, ` + + `Total Physical Size: '${addCommas(heapStats.total_physical_size)}' bytes, ` + + `Total Available Size: '${addCommas(heapStats.total_available_size)}' bytes, ` + + `Used Heap Size: '${addCommas(heapStats.used_heap_size)}' bytes, ` + + `Heap Size Limit: '${addCommas(heapStats.heap_size_limit)}' bytes` + + '\n--------------------------' + + logger.info(heapStatsMessage) + } + + // Only run if not in a test environment + if (process.argv.every(arg => !arg.includes('mocha'))) { + // Set the interval to log the heap space statistics + setInterval(logHeapSpaceStats, heapStatsInverval) + } } // =================================================== From f024bcef02a3d8cc627a47a0ba551e274a490999 Mon Sep 17 00:00:00 2001 From: harrider <22509508+harrider@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:44:38 -0500 Subject: [PATCH 6/9] Refactor heap logging into dedicated module --- app.js | 66 ++----------------------------------- lib/heapLogger.js | 83 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 63 deletions(-) create mode 100644 lib/heapLogger.js diff --git a/app.js b/app.js index 986819be..b2c2dda1 100644 --- a/app.js +++ b/app.js @@ -100,69 +100,9 @@ function createApp(config) { crawlerSecret ) - // =================================================== - // Log the heap statistics at regular intervals - // =================================================== - // NOTE: set 'LOG_NODE_HEAPSTATS' env var to 'true' to log heap stats - // NOTE: set 'LOG_NODE_HEAPSTATS_INTERVAL_MS' env var to '' for logging interval - const shouldLogHeapstats = config.heapstats.logHeapstats - ? config.heapstats.logHeapstats.toLowerCase() === 'true' - : false - - if (shouldLogHeapstats) { - const v8 = require('v8') - - const addCommas = num => Number(num).toLocaleString() - const isNumeric = num => !isNaN(Number(num)) - - // Set the heapstats logging interval - const maybeInterval = config.heapstats.logInverval - const heapStatsInverval = maybeInterval && isNumeric(maybeInterval) ? maybeInterval : 30000 - - // Function to log the heap space statistics - const logHeapSpaceStats = () => { - // Get the current timestamp - const currentTimestamp = new Date().toISOString() - - // Get the heap space statistics - const heapSpaceStats = v8.getHeapSpaceStatistics() - - heapSpaceStats.forEach(space => { - const heapStatsMessage = - `[${currentTimestamp}] Heap Space Statistics: ` + - `Space Name: '${space.space_name}', ` + - `Space Size: '${addCommas(space.space_size)}' bytes, ` + - `Space Used Size: '${addCommas(space.space_used_size)}' bytes, ` + - `Space Available Size: '${addCommas(space.space_available_size)}' bytes, ` + - `Physical Space Size: '${addCommas(space.physical_space_size)}' bytes` + - '\n--------------------------' - - logger.info(heapStatsMessage) - }) - - // Get the heap statistics - const heapStats = v8.getHeapStatistics() - - const heapStatsMessage = - `[${currentTimestamp}] Heap Statistics: ` + - `Total Heap Size: '${addCommas(heapStats.total_heap_size)}' bytes, ` + - `Total Heap Used Size: '${addCommas(heapStats.total_heap_size)}' bytes, ` + - `Total Physical Size: '${addCommas(heapStats.total_physical_size)}' bytes, ` + - `Total Available Size: '${addCommas(heapStats.total_available_size)}' bytes, ` + - `Used Heap Size: '${addCommas(heapStats.used_heap_size)}' bytes, ` + - `Heap Size Limit: '${addCommas(heapStats.heap_size_limit)}' bytes` + - '\n--------------------------' - - logger.info(heapStatsMessage) - } - - // Only run if not in a test environment - if (process.argv.every(arg => !arg.includes('mocha'))) { - // Set the interval to log the heap space statistics - setInterval(logHeapSpaceStats, heapStatsInverval) - } - } - // =================================================== + // enable heap stats logging at an interval if configured + const trySetHeapLoggingAtInterval = require('./lib/heapLogger') + trySetHeapLoggingAtInterval(config, logger) const app = express() app.use(cors()) diff --git a/lib/heapLogger.js b/lib/heapLogger.js new file mode 100644 index 00000000..eebe778a --- /dev/null +++ b/lib/heapLogger.js @@ -0,0 +1,83 @@ +// (c) Copyright 2021, SAP SE and ClearlyDefined contributors. Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +// =================================================== +// Log the heap statistics at regular intervals +// =================================================== +// NOTE: set 'LOG_NODE_HEAPSTATS' env var to 'true' to log heap stats +// NOTE: set 'LOG_NODE_HEAPSTATS_INTERVAL_MS' env var to '' for logging interval +function trySetHeapLoggingAtInterval(config, logger) { + logger.debug('heapLogger.js :: Entered "trySetHeapLoggingAtInterval"...') + + const shouldLogHeapstats = config.heapstats.logHeapstats + ? config.heapstats.logHeapstats.toLowerCase() === 'true' + : false + + logger.debug(`heapLogger.js :: "shouldLogHeapstats" set to "${shouldLogHeapstats}"`) + + if (shouldLogHeapstats) { + const v8 = require('v8') + + const addCommas = num => Number(num).toLocaleString() + const isNumeric = num => !isNaN(Number(num)) + + // Set the heapstats logging interval + const maybeInterval = config.heapstats.logInverval + const heapStatsInverval = maybeInterval && isNumeric(maybeInterval) ? maybeInterval : 30000 + + logger.debug(`heapLogger.js :: heap stats logging interval will be "${heapStatsInverval}" ms`) + + // Function to log the heap space statistics + const logHeapSpaceStats = () => { + // Get the current timestamp + const currentTimestamp = new Date().toISOString() + + // Get the heap space statistics + const heapSpaceStats = v8.getHeapSpaceStatistics() + + heapSpaceStats.forEach(space => { + const heapStatsMessage = + `[${currentTimestamp}] Heap Space Statistics: ` + + `Space Name: '${space.space_name}', ` + + `Space Size: '${addCommas(space.space_size)}' bytes, ` + + `Space Used Size: '${addCommas(space.space_used_size)}' bytes, ` + + `Space Available Size: '${addCommas(space.space_available_size)}' bytes, ` + + `Physical Space Size: '${addCommas(space.physical_space_size)}' bytes` + + '\n--------------------------' + + logger.info(heapStatsMessage) + }) + + // Get the heap statistics + const heapStats = v8.getHeapStatistics() + + const heapStatsMessage = + `[${currentTimestamp}] Heap Statistics: ` + + `Total Heap Size: '${addCommas(heapStats.total_heap_size)}' bytes, ` + + `Total Heap Used Size: '${addCommas(heapStats.total_heap_size)}' bytes, ` + + `Total Physical Size: '${addCommas(heapStats.total_physical_size)}' bytes, ` + + `Total Available Size: '${addCommas(heapStats.total_available_size)}' bytes, ` + + `Used Heap Size: '${addCommas(heapStats.used_heap_size)}' bytes, ` + + `Heap Size Limit: '${addCommas(heapStats.heap_size_limit)}' bytes` + + '\n--------------------------' + + logger.info(heapStatsMessage) + } + + // Only run if not in a test environment + if (process.argv.every(arg => !arg.includes('mocha'))) { + logger.debug(`heapLogger.js :: setting heap stats logging at "${heapStatsInverval}" ms interval...`) + + // Set the interval to log the heap space statistics + setInterval(logHeapSpaceStats, heapStatsInverval) + + logger.debug(`heapLogger.js :: set heap stats logging at "${heapStatsInverval}" ms interval.`) + } + } else { + logger.debug('heapLogger.js :: heap stats logging not enabled.') + } + + logger.debug('heapLogger.js :: Exiting "trySetHeapLoggingAtInterval".') +} + +module.exports = trySetHeapLoggingAtInterval From c100b62f79bfe227dcb0b8df7aaca40c857d1284 Mon Sep 17 00:00:00 2001 From: harrider <22509508+harrider@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:54:02 -0500 Subject: [PATCH 7/9] Remove duplicate heap stat value --- lib/heapLogger.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/heapLogger.js b/lib/heapLogger.js index eebe778a..733234c0 100644 --- a/lib/heapLogger.js +++ b/lib/heapLogger.js @@ -6,6 +6,9 @@ // =================================================== // NOTE: set 'LOG_NODE_HEAPSTATS' env var to 'true' to log heap stats // NOTE: set 'LOG_NODE_HEAPSTATS_INTERVAL_MS' env var to '' for logging interval +// NOTE: To better understand heap stats being logged, check: +// - https://nodejs.org/docs/v22.12.0/api/v8.html#v8getheapspacestatistics +// - https://nodejs.org/docs/v22.12.0/api/v8.html#v8getheapstatistics function trySetHeapLoggingAtInterval(config, logger) { logger.debug('heapLogger.js :: Entered "trySetHeapLoggingAtInterval"...') @@ -54,7 +57,7 @@ function trySetHeapLoggingAtInterval(config, logger) { const heapStatsMessage = `[${currentTimestamp}] Heap Statistics: ` + `Total Heap Size: '${addCommas(heapStats.total_heap_size)}' bytes, ` + - `Total Heap Used Size: '${addCommas(heapStats.total_heap_size)}' bytes, ` + + `Total Heap Size Executable: '${addCommas(heapStats.total_heap_size_executable)}' bytes, ` + `Total Physical Size: '${addCommas(heapStats.total_physical_size)}' bytes, ` + `Total Available Size: '${addCommas(heapStats.total_available_size)}' bytes, ` + `Used Heap Size: '${addCommas(heapStats.used_heap_size)}' bytes, ` + From d6dbbcbbddf060f5cc4f4cb790deb497ceabf6ae Mon Sep 17 00:00:00 2001 From: harrider <22509508+harrider@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:59:05 -0500 Subject: [PATCH 8/9] Remove v8 npm package from package.json --- package-lock.json | 10 ---------- package.json | 1 - 2 files changed, 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6fa17367..0088d917 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,7 +60,6 @@ "throat": "^4.1.0", "tiny-attribution-generator": "0.1.2", "tmp": "0.0.33", - "v8": "0.1.0", "vso-node-api": "^6.2.8-preview", "winston": "^2.3.0", "winston-azure-application-insights": "^1.5.0" @@ -10930,15 +10929,6 @@ "uuid": "bin/uuid" } }, - "node_modules/v8": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/v8/-/v8-0.1.0.tgz", - "integrity": "sha512-cSrJCQ7WRDkSP8zbIwOO38kLSp1mGmBbx/I0pHdzQROZIMlO+qkiC4deQxg1I7pKguYJNMhMD5g/Nc1muiVyYw==", - "license": "MIT", - "bin": { - "v8": "bin/v8" - } - }, "node_modules/validator": { "version": "9.4.1", "resolved": "http://registry.npmjs.org/validator/-/validator-9.4.1.tgz", diff --git a/package.json b/package.json index ec1f6787..ed0e0ae5 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,6 @@ "throat": "^4.1.0", "tiny-attribution-generator": "0.1.2", "tmp": "0.0.33", - "v8": "0.1.0", "vso-node-api": "^6.2.8-preview", "winston": "^2.3.0", "winston-azure-application-insights": "^1.5.0" From 574ed37a4784775253f4132d3078937a609db40e Mon Sep 17 00:00:00 2001 From: "E. Lynette Rayle" Date: Wed, 11 Dec 2024 16:55:38 -0500 Subject: [PATCH 9/9] Update lib/heapLogger.js --- lib/heapLogger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/heapLogger.js b/lib/heapLogger.js index 733234c0..34522110 100644 --- a/lib/heapLogger.js +++ b/lib/heapLogger.js @@ -1,4 +1,4 @@ -// (c) Copyright 2021, SAP SE and ClearlyDefined contributors. Licensed under the MIT license. +// (c) Copyright 2024, Microsoft and ClearlyDefined contributors. Licensed under the MIT license. // SPDX-License-Identifier: MIT // ===================================================