diff --git a/internal-tooling/performApplicationPerformanceTest.ts b/internal-tooling/performApplicationPerformanceTest.ts index 5212a2144..b1784c6a2 100644 --- a/internal-tooling/performApplicationPerformanceTest.ts +++ b/internal-tooling/performApplicationPerformanceTest.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import yargs from 'yargs'; +const yargs = require('yargs'); import {promises as fsp, rmSync} from 'fs'; import { Bucket, @@ -28,33 +28,24 @@ import {performance} from 'perf_hooks'; import {parentPort} from 'worker_threads'; import { BLOCK_SIZE_IN_BYTES, - DEFAULT_PROJECT_ID, - DEFAULT_NUMBER_OF_OBJECTS, - DEFAULT_SMALL_FILE_SIZE_BYTES, - DEFAULT_LARGE_FILE_SIZE_BYTES, NODE_DEFAULT_HIGHWATER_MARK_BYTES, generateRandomDirectoryStructure, getValidationType, performanceTestSetup, TestResult, + performanceTestCommand, + getLowHighFileSize, + PERFORMANCE_TEST_TYPES, } from './performanceUtils'; -import {TRANSFER_MANAGER_TEST_TYPES} from './performanceTest'; const TEST_NAME_STRING = 'nodejs-perf-metrics-application'; -const DEFAULT_BUCKET_NAME = 'nodejs-perf-metrics-shaffeeullah'; let bucket: Bucket; const checkType = getValidationType(); const argv = yargs(process.argv.slice(2)) - .options({ - bucket: {type: 'string', default: DEFAULT_BUCKET_NAME}, - small: {type: 'number', default: DEFAULT_SMALL_FILE_SIZE_BYTES}, - large: {type: 'number', default: DEFAULT_LARGE_FILE_SIZE_BYTES}, - projectid: {type: 'string', default: DEFAULT_PROJECT_ID}, - numobjects: {type: 'number', default: DEFAULT_NUMBER_OF_OBJECTS}, - }) + .command(performanceTestCommand) .parseSync(); /** @@ -74,15 +65,16 @@ async function main() { cpuTimeUs: 0, status: '[OK]', chunkSize: 0, + workers: argv.workers, }; - ({bucket} = await performanceTestSetup(argv.projectid, argv.bucket)); + ({bucket} = await performanceTestSetup(argv.project!, argv.bucket!)); - switch (argv.testtype) { - case TRANSFER_MANAGER_TEST_TYPES.APPLICATION_UPLOAD_MULTIPLE_OBJECTS: + switch (argv.test_type) { + case PERFORMANCE_TEST_TYPES.APPLICATION_UPLOAD_MULTIPLE_OBJECTS: result = await performWriteTest(); break; - case TRANSFER_MANAGER_TEST_TYPES.APPLICATION_DOWNLOAD_MULTIPLE_OBJECTS: + case PERFORMANCE_TEST_TYPES.APPLICATION_DOWNLOAD_MULTIPLE_OBJECTS: result = await performReadTest(); break; // case TRANSFER_MANAGER_TEST_TYPES.APPLICATION_LARGE_FILE_DOWNLOAD: @@ -129,11 +121,12 @@ async function downloadInParallel(bucket: Bucket, options: DownloadOptions) { async function performWriteTest(): Promise { await bucket.deleteFiles(); //start clean + const fileSizeRange = getLowHighFileSize(argv.object_size); const creationInfo = generateRandomDirectoryStructure( - argv.numobjects, + argv.num_objects, TEST_NAME_STRING, - argv.small, - argv.large + fileSizeRange.low, + fileSizeRange.high ); const start = performance.now(); @@ -155,6 +148,7 @@ async function performWriteTest(): Promise { cpuTimeUs: -1, status: '[OK]', chunkSize: creationInfo.totalSizeInBytes, + workers: argv.workers, }; return result; } @@ -165,12 +159,13 @@ async function performWriteTest(): Promise { * @returns {Promise} Promise that resolves to an array of test results for the iteration. */ async function performReadTest(): Promise { + const fileSizeRange = getLowHighFileSize(argv.object_size); await bucket.deleteFiles(); // start clean const creationInfo = generateRandomDirectoryStructure( - argv.numobjects, + argv.num_objects, TEST_NAME_STRING, - argv.small, - argv.large + fileSizeRange.low, + fileSizeRange.high ); await uploadInParallel(bucket, creationInfo.paths, {validation: checkType}); @@ -190,6 +185,7 @@ async function performReadTest(): Promise { cpuTimeUs: -1, status: '[OK]', chunkSize: creationInfo.totalSizeInBytes, + workers: argv.workers, }; rmSync(TEST_NAME_STRING, {recursive: true, force: true}); diff --git a/internal-tooling/performPerformanceTest.ts b/internal-tooling/performPerformanceTest.ts index d3b434def..a8f40bccb 100644 --- a/internal-tooling/performPerformanceTest.ts +++ b/internal-tooling/performPerformanceTest.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import yargs from 'yargs'; +const yargs = require('yargs'); import {performance} from 'perf_hooks'; // eslint-disable-next-line node/no-unsupported-features/node-builtins import {parentPort} from 'worker_threads'; @@ -22,33 +22,29 @@ import * as path from 'path'; import { BLOCK_SIZE_IN_BYTES, cleanupFile, - DEFAULT_LARGE_FILE_SIZE_BYTES, - DEFAULT_PROJECT_ID, - DEFAULT_SMALL_FILE_SIZE_BYTES, generateRandomFile, generateRandomFileName, + getLowHighFileSize, getValidationType, NODE_DEFAULT_HIGHWATER_MARK_BYTES, + performanceTestCommand, performanceTestSetup, + PERFORMANCE_TEST_TYPES, TestResult, } from './performanceUtils'; import {Bucket} from '../src'; +import {rmSync} from 'fs'; const TEST_NAME_STRING = 'nodejs-perf-metrics'; const DEFAULT_NUMBER_OF_WRITES = 1; const DEFAULT_NUMBER_OF_READS = 3; -const DEFAULT_BUCKET_NAME = 'nodejs-perf-metrics'; +const DEFAULT_RANGE_READS = 3; let bucket: Bucket; const checkType = getValidationType(); const argv = yargs(process.argv.slice(2)) - .options({ - bucket: {type: 'string', default: DEFAULT_BUCKET_NAME}, - small: {type: 'number', default: DEFAULT_SMALL_FILE_SIZE_BYTES}, - large: {type: 'number', default: DEFAULT_LARGE_FILE_SIZE_BYTES}, - projectid: {type: 'string', default: DEFAULT_PROJECT_ID}, - }) + .command(performanceTestCommand) .parseSync(); /** @@ -56,10 +52,79 @@ const argv = yargs(process.argv.slice(2)) * to the parent thread. */ async function main() { - const results = await performWriteReadTest(); + let results: TestResult[] = []; + + ({bucket} = await performanceTestSetup(argv.project!, argv.bucket!)); + + switch (argv.test_type) { + case PERFORMANCE_TEST_TYPES.WRITE_ONE_READ_THREE: + results = await performWriteReadTest(); + break; + case PERFORMANCE_TEST_TYPES.RANGE_READ: + results = await performRangedReadTest(); + break; + default: + break; + } + parentPort?.postMessage(results); } +/** + * Performs an iteration of a ranged read test. Only the last result will be reported. + * + * @returns {Promise} Promise that resolves to an array of test results for the iteration. + */ +async function performRangedReadTest(): Promise { + const results: TestResult[] = []; + const fileSizeRange = getLowHighFileSize(argv.object_size); + const fileName = generateRandomFileName(TEST_NAME_STRING); + const sizeInBytes = generateRandomFile( + fileName, + fileSizeRange.low, + fileSizeRange.high, + __dirname + ); + const file = bucket.file(`${fileName}`); + const destinationFileName = generateRandomFileName(TEST_NAME_STRING); + const destination = path.join(__dirname, destinationFileName); + + const iterationResult: TestResult = { + op: 'READ', + objectSize: sizeInBytes, + appBufferSize: BLOCK_SIZE_IN_BYTES, + libBufferSize: NODE_DEFAULT_HIGHWATER_MARK_BYTES, + crc32Enabled: false, + md5Enabled: false, + apiName: 'JSON', + elapsedTimeUs: 0, + cpuTimeUs: -1, + status: '[OK]', + chunkSize: argv.range_read_size, + workers: argv.workers, + }; + + await bucket.upload(`${__dirname}/${fileName}`); + cleanupFile(fileName); + + for (let i = 0; i < DEFAULT_RANGE_READS; i++) { + const start = performance.now(); + await file.download({ + start: 0, + end: argv.range_read_size, + destination, + }); + const end = performance.now(); + cleanupFile(destinationFileName); + iterationResult.elapsedTimeUs = Math.round((end - start) * 1000); + } + + rmSync(TEST_NAME_STRING, {recursive: true, force: true}); + await file.delete(); + results.push(iterationResult); + return results; +} + /** * Performs an iteration of the Write 1 / Read 3 performance measuring test. * @@ -67,10 +132,15 @@ async function main() { */ async function performWriteReadTest(): Promise { const results: TestResult[] = []; + const fileSizeRange = getLowHighFileSize(argv.object_size); const fileName = generateRandomFileName(TEST_NAME_STRING); - const sizeInBytes = generateRandomFile(fileName, argv.small, argv.large); - - ({bucket} = await performanceTestSetup(argv.projectid, argv.bucket)); + const file = bucket.file(`${fileName}`); + const sizeInBytes = generateRandomFile( + fileName, + fileSizeRange.low, + fileSizeRange.high, + __dirname + ); for (let j = 0; j < DEFAULT_NUMBER_OF_WRITES; j++) { let start = 0; @@ -88,6 +158,7 @@ async function performWriteReadTest(): Promise { cpuTimeUs: -1, status: '[OK]', chunkSize: sizeInBytes, + workers: argv.workers, }; start = performance.now(); @@ -98,23 +169,24 @@ async function performWriteReadTest(): Promise { results.push(iterationResult); } + const iterationResult: TestResult = { + op: 'READ', + objectSize: sizeInBytes, + appBufferSize: BLOCK_SIZE_IN_BYTES, + libBufferSize: NODE_DEFAULT_HIGHWATER_MARK_BYTES, + crc32Enabled: checkType === 'crc32c', + md5Enabled: checkType === 'md5', + apiName: 'JSON', + elapsedTimeUs: 0, + cpuTimeUs: -1, + status: '[OK]', + chunkSize: sizeInBytes, + workers: argv.workers, + }; + for (let j = 0; j < DEFAULT_NUMBER_OF_READS; j++) { let start = 0; let end = 0; - const file = bucket.file(`${fileName}`); - const iterationResult: TestResult = { - op: `READ[${j}]`, - objectSize: sizeInBytes, - appBufferSize: BLOCK_SIZE_IN_BYTES, - libBufferSize: NODE_DEFAULT_HIGHWATER_MARK_BYTES, - crc32Enabled: checkType === 'crc32c', - md5Enabled: checkType === 'md5', - apiName: 'JSON', - elapsedTimeUs: 0, - cpuTimeUs: -1, - status: '[OK]', - chunkSize: sizeInBytes, - }; const destinationFileName = generateRandomFileName(TEST_NAME_STRING); const destination = path.join(__dirname, destinationFileName); @@ -125,11 +197,11 @@ async function performWriteReadTest(): Promise { cleanupFile(destinationFileName); iterationResult.elapsedTimeUs = Math.round((end - start) * 1000); - results.push(iterationResult); } - cleanupFile(fileName); - + rmSync(TEST_NAME_STRING, {recursive: true, force: true}); + await file.delete(); + results.push(iterationResult); return results; } diff --git a/internal-tooling/performTransferManagerTest.ts b/internal-tooling/performTransferManagerTest.ts index 711dbd135..fca6f7c14 100644 --- a/internal-tooling/performTransferManagerTest.ts +++ b/internal-tooling/performTransferManagerTest.ts @@ -14,34 +14,29 @@ * limitations under the License. */ +const yargs = require('yargs'); // eslint-disable-next-line node/no-unsupported-features/node-builtins import {parentPort} from 'worker_threads'; -import yargs from 'yargs'; import {Bucket, TransferManager} from '../src'; -import {TRANSFER_MANAGER_TEST_TYPES} from './performanceTest'; import { BLOCK_SIZE_IN_BYTES, cleanupFile, - DEFAULT_LARGE_FILE_SIZE_BYTES, - DEFAULT_PROJECT_ID, - DEFAULT_SMALL_FILE_SIZE_BYTES, generateRandomDirectoryStructure, generateRandomFile, generateRandomFileName, getValidationType, NODE_DEFAULT_HIGHWATER_MARK_BYTES, - DEFAULT_NUMBER_OF_OBJECTS, performanceTestSetup, TestResult, + performanceTestCommand, + getLowHighFileSize, + PERFORMANCE_TEST_TYPES, } from './performanceUtils'; import {performance} from 'perf_hooks'; import {rmSync} from 'fs'; import * as path from 'path'; const TEST_NAME_STRING = 'tm-perf-metrics'; -const DEFAULT_BUCKET_NAME = 'nodejs-transfer-manager-perf-metrics'; -const DEFAULT_NUMBER_OF_PROMISES = 2; -const DEFAULT_CHUNK_SIZE_BYTES = 16 * 1024 * 1024; const DIRECTORY_PROBABILITY = 0.1; let bucket: Bucket; @@ -49,23 +44,7 @@ let transferManager: TransferManager; const checkType = getValidationType(); const argv = yargs(process.argv.slice(2)) - .options({ - bucket: {type: 'string', default: DEFAULT_BUCKET_NAME}, - small: {type: 'number', default: DEFAULT_SMALL_FILE_SIZE_BYTES}, - large: {type: 'number', default: DEFAULT_LARGE_FILE_SIZE_BYTES}, - numpromises: {type: 'number', default: DEFAULT_NUMBER_OF_PROMISES}, - numobjects: {type: 'number', default: DEFAULT_NUMBER_OF_OBJECTS}, - chunksize: {type: 'number', default: DEFAULT_CHUNK_SIZE_BYTES}, - projectid: {type: 'string', default: DEFAULT_PROJECT_ID}, - testtype: { - type: 'string', - choices: [ - TRANSFER_MANAGER_TEST_TYPES.TRANSFER_MANAGER_UPLOAD_MANY_FILES, - TRANSFER_MANAGER_TEST_TYPES.TRANSFER_MANAGER_DOWNLOAD_MANY_FILES, - TRANSFER_MANAGER_TEST_TYPES.TRANSFER_MANAGER_CHUNKED_FILE_DOWNLOAD, - ], - }, - }) + .command(performanceTestCommand) .parseSync(); /** @@ -85,21 +64,22 @@ async function main() { cpuTimeUs: 0, status: '[OK]', chunkSize: 0, + workers: argv.workers, }; ({bucket, transferManager} = await performanceTestSetup( - argv.projectid, - argv.bucket + argv.project!, + argv.bucket! )); - switch (argv.testtype) { - case TRANSFER_MANAGER_TEST_TYPES.TRANSFER_MANAGER_UPLOAD_MANY_FILES: + switch (argv.test_type) { + case PERFORMANCE_TEST_TYPES.TRANSFER_MANAGER_UPLOAD_MANY_FILES: result = await performUploadManyFilesTest(); break; - case TRANSFER_MANAGER_TEST_TYPES.TRANSFER_MANAGER_DOWNLOAD_MANY_FILES: + case PERFORMANCE_TEST_TYPES.TRANSFER_MANAGER_DOWNLOAD_MANY_FILES: result = await performDownloadManyFilesTest(); break; - case TRANSFER_MANAGER_TEST_TYPES.TRANSFER_MANAGER_CHUNKED_FILE_DOWNLOAD: + case PERFORMANCE_TEST_TYPES.TRANSFER_MANAGER_CHUNKED_FILE_DOWNLOAD: result = await performDownloadFileInChunksTest(); break; default: @@ -122,17 +102,18 @@ async function performTestCleanup() { * @returns {Promise} A promise that resolves containing information about the test results. */ async function performUploadManyFilesTest(): Promise { + const fileSizeRange = getLowHighFileSize(argv.object_size); const creationInfo = generateRandomDirectoryStructure( - argv.numobjects, + argv.num_objects, TEST_NAME_STRING, - argv.small, - argv.large, + fileSizeRange.low, + fileSizeRange.high, DIRECTORY_PROBABILITY ); const start = performance.now(); await transferManager.uploadManyFiles(creationInfo.paths, { - concurrencyLimit: argv.numpromises, + concurrencyLimit: argv.workers, passthroughOptions: { validation: checkType, }, @@ -153,6 +134,7 @@ async function performUploadManyFilesTest(): Promise { cpuTimeUs: -1, status: '[OK]', chunkSize: creationInfo.totalSizeInBytes, + workers: argv.workers, }; return result; @@ -164,16 +146,17 @@ async function performUploadManyFilesTest(): Promise { * @returns {Promise} A promise that resolves containing information about the test results. */ async function performDownloadManyFilesTest(): Promise { + const fileSizeRange = getLowHighFileSize(argv.object_size); const creationInfo = generateRandomDirectoryStructure( - argv.numobjects, + argv.num_objects, TEST_NAME_STRING, - argv.small, - argv.large, + fileSizeRange.low, + fileSizeRange.high, DIRECTORY_PROBABILITY ); await transferManager.uploadManyFiles(creationInfo.paths, { - concurrencyLimit: argv.numpromises, + concurrencyLimit: argv.workers, passthroughOptions: { validation: checkType, }, @@ -181,7 +164,7 @@ async function performDownloadManyFilesTest(): Promise { const start = performance.now(); await transferManager.downloadManyFiles(TEST_NAME_STRING, { prefix: path.join(__dirname, '..', '..'), - concurrencyLimit: argv.numpromises, + concurrencyLimit: argv.workers, passthroughOptions: { validation: checkType, }, @@ -202,6 +185,7 @@ async function performDownloadManyFilesTest(): Promise { cpuTimeUs: -1, status: '[OK]', chunkSize: creationInfo.totalSizeInBytes, + workers: argv.workers, }; return result; } @@ -212,11 +196,12 @@ async function performDownloadManyFilesTest(): Promise { * @returns {Promise} A promise that resolves containing information about the test results. */ async function performDownloadFileInChunksTest(): Promise { + const fileSizeRange = getLowHighFileSize(argv.object_size); const fileName = generateRandomFileName(TEST_NAME_STRING); const sizeInBytes = generateRandomFile( fileName, - argv.small, - argv.large, + fileSizeRange.low, + fileSizeRange.high, __dirname ); const file = bucket.file(`${fileName}`); @@ -225,8 +210,8 @@ async function performDownloadFileInChunksTest(): Promise { cleanupFile(fileName); const start = performance.now(); await transferManager.downloadFileInChunks(file, { - concurrencyLimit: argv.numpromises, - chunkSizeBytes: argv.chunksize, + concurrencyLimit: argv.workers, + chunkSizeBytes: argv.range_read_size, destination: path.join(__dirname, fileName), validation: checkType === 'crc32c' ? checkType : false, }); @@ -245,9 +230,11 @@ async function performDownloadFileInChunksTest(): Promise { elapsedTimeUs: Math.round((end - start) * 1000), cpuTimeUs: -1, status: '[OK]', - chunkSize: argv.chunksize, + chunkSize: argv.range_read_size, + workers: argv.workers, }; + rmSync(TEST_NAME_STRING, {recursive: true, force: true}); return result; } diff --git a/internal-tooling/performanceTest.ts b/internal-tooling/performanceTest.ts index ec97fb6c9..357713426 100644 --- a/internal-tooling/performanceTest.ts +++ b/internal-tooling/performanceTest.ts @@ -14,73 +14,30 @@ * limitations under the License. */ +const yargs = require('yargs'); import {appendFile} from 'fs/promises'; // eslint-disable-next-line node/no-unsupported-features/node-builtins import {Worker} from 'worker_threads'; -import yargs = require('yargs'); import { convertToCSVFormat, convertToCloudMonitoringFormat, TestResult, log, + performanceTestCommand, + OUTPUT_FORMATS, + PERFORMANCE_TEST_TYPES, } from './performanceUtils'; import {existsSync} from 'fs'; import {writeFile} from 'fs/promises'; -const DEFAULT_BUCKET_NAME = 'nodejs-performance-test'; -const DEFAULT_ITERATIONS = 100; -const DEFAULT_THREADS = 1; const CSV_HEADERS = 'Op,ObjectSize,AppBufferSize,LibBufferSize,Crc32cEnabled,MD5Enabled,ApiName,ElapsedTimeUs,CpuTimeUs,Status\n'; -export const enum TRANSFER_MANAGER_TEST_TYPES { - WRITE_ONE_READ_THREE = 'w1r3', - TRANSFER_MANAGER_UPLOAD_MANY_FILES = 'tm-upload', - TRANSFER_MANAGER_DOWNLOAD_MANY_FILES = 'tm-download', - TRANSFER_MANAGER_CHUNKED_FILE_DOWNLOAD = 'tm-chunked', - APPLICATION_LARGE_FILE_DOWNLOAD = 'application-large', - APPLICATION_UPLOAD_MULTIPLE_OBJECTS = 'application-upload', - APPLICATION_DOWNLOAD_MULTIPLE_OBJECTS = 'application-download', -} - -const enum OUTPUT_FORMATS { - CSV = 'csv', - CLOUD_MONITORING = 'cloudmon', -} const argv = yargs(process.argv.slice(2)) - .options({ - bucket: {type: 'string', default: DEFAULT_BUCKET_NAME}, - iterations: {type: 'number', default: DEFAULT_ITERATIONS}, - numthreads: {type: 'number', default: DEFAULT_THREADS}, - testtype: { - type: 'string', - choices: [ - TRANSFER_MANAGER_TEST_TYPES.WRITE_ONE_READ_THREE, - TRANSFER_MANAGER_TEST_TYPES.TRANSFER_MANAGER_UPLOAD_MANY_FILES, - TRANSFER_MANAGER_TEST_TYPES.TRANSFER_MANAGER_DOWNLOAD_MANY_FILES, - TRANSFER_MANAGER_TEST_TYPES.TRANSFER_MANAGER_CHUNKED_FILE_DOWNLOAD, - TRANSFER_MANAGER_TEST_TYPES.APPLICATION_DOWNLOAD_MULTIPLE_OBJECTS, - TRANSFER_MANAGER_TEST_TYPES.APPLICATION_LARGE_FILE_DOWNLOAD, - TRANSFER_MANAGER_TEST_TYPES.APPLICATION_UPLOAD_MULTIPLE_OBJECTS, - ], - default: TRANSFER_MANAGER_TEST_TYPES.WRITE_ONE_READ_THREE, - }, - format: { - type: 'string', - choices: [OUTPUT_FORMATS.CSV, OUTPUT_FORMATS.CLOUD_MONITORING], - default: OUTPUT_FORMATS.CSV, - }, - filename: { - type: 'string', - }, - debug: { - type: 'boolean', - default: false, - }, - }) + .command(performanceTestCommand) .parseSync(); -let iterationsRemaining = argv.iterations; +let iterationsRemaining = argv.samples; /** * Main entry point for performing a Write 1 Read 3 performance measurement test. @@ -89,7 +46,7 @@ let iterationsRemaining = argv.iterations; * specified by the iterations parameter or 100 if not specified. */ function main() { - let numThreads = argv.numthreads; + let numThreads = argv.workers; if (numThreads > iterationsRemaining) { log( `${numThreads} is greater than number of iterations (${iterationsRemaining}). Using ${iterationsRemaining} threads instead.`, @@ -97,7 +54,7 @@ function main() { ); numThreads = iterationsRemaining; } - if (argv.testtype !== TRANSFER_MANAGER_TEST_TYPES.WRITE_ONE_READ_THREE) { + if (argv.test_type !== PERFORMANCE_TEST_TYPES.WRITE_ONE_READ_THREE) { numThreads = 1; } for (let i = 0; i < numThreads; i++) { @@ -116,24 +73,26 @@ function createWorker() { argv.debug ); let testPath = ''; - if (argv.testtype === TRANSFER_MANAGER_TEST_TYPES.WRITE_ONE_READ_THREE) { + if ( + argv.test_type === PERFORMANCE_TEST_TYPES.WRITE_ONE_READ_THREE || + argv.test_type === PERFORMANCE_TEST_TYPES.RANGE_READ + ) { testPath = `${__dirname}/performPerformanceTest.js`; } else if ( - argv.testtype === - TRANSFER_MANAGER_TEST_TYPES.TRANSFER_MANAGER_UPLOAD_MANY_FILES || - argv.testtype === - TRANSFER_MANAGER_TEST_TYPES.TRANSFER_MANAGER_CHUNKED_FILE_DOWNLOAD || - argv.testtype === - TRANSFER_MANAGER_TEST_TYPES.TRANSFER_MANAGER_DOWNLOAD_MANY_FILES + argv.test_type === + PERFORMANCE_TEST_TYPES.TRANSFER_MANAGER_UPLOAD_MANY_FILES || + argv.test_type === + PERFORMANCE_TEST_TYPES.TRANSFER_MANAGER_CHUNKED_FILE_DOWNLOAD || + argv.test_type === + PERFORMANCE_TEST_TYPES.TRANSFER_MANAGER_DOWNLOAD_MANY_FILES ) { testPath = `${__dirname}/performTransferManagerTest.js`; } else if ( - argv.testtype === - TRANSFER_MANAGER_TEST_TYPES.APPLICATION_UPLOAD_MULTIPLE_OBJECTS || - argv.testtype === - TRANSFER_MANAGER_TEST_TYPES.APPLICATION_LARGE_FILE_DOWNLOAD || - argv.testtype === - TRANSFER_MANAGER_TEST_TYPES.APPLICATION_DOWNLOAD_MULTIPLE_OBJECTS + argv.test_type === + PERFORMANCE_TEST_TYPES.APPLICATION_UPLOAD_MULTIPLE_OBJECTS || + argv.test_type === PERFORMANCE_TEST_TYPES.APPLICATION_LARGE_FILE_DOWNLOAD || + argv.test_type === + PERFORMANCE_TEST_TYPES.APPLICATION_DOWNLOAD_MULTIPLE_OBJECTS ) { testPath = `${__dirname}/performApplicationPerformanceTest.js`; } @@ -163,29 +122,28 @@ async function recordResult(results: TestResult[] | TestResult) { const resultsToAppend: TestResult[] = Array.isArray(results) ? results : [results]; - if ( - argv.filename && - argv.format === OUTPUT_FORMATS.CSV && - !existsSync(argv.filename) + argv.file_name && + argv.output_type === OUTPUT_FORMATS.CSV && + !existsSync(argv.file_name) ) { - await writeFile(argv.filename, CSV_HEADERS); + await writeFile(argv.file_name, CSV_HEADERS); } - if (argv.format === OUTPUT_FORMATS.CSV) { - argv.filename + if (argv.output_type === OUTPUT_FORMATS.CSV) { + argv.file_name ? await appendFile( - argv.filename, + argv.file_name, `${convertToCSVFormat(resultsToAppend)}\n` ) : log(convertToCSVFormat(resultsToAppend), true); - } else if (argv.format === OUTPUT_FORMATS.CLOUD_MONITORING) { + } else if (argv.output_type === OUTPUT_FORMATS.CLOUD_MONITORING) { for await (const outputString of convertToCloudMonitoringFormat( resultsToAppend, - argv.bucket + argv.bucket! )) { - argv.filename - ? await appendFile(argv.filename, `${outputString}\n`) + argv.file_name + ? await appendFile(argv.file_name, `${outputString}\n`) : log(outputString, true); } } diff --git a/internal-tooling/performanceUtils.ts b/internal-tooling/performanceUtils.ts index 8e1c38a9a..3c2996f9b 100644 --- a/internal-tooling/performanceUtils.ts +++ b/internal-tooling/performanceUtils.ts @@ -17,16 +17,41 @@ import {execSync} from 'child_process'; import {mkdirSync, mkdtempSync, unlinkSync} from 'fs'; import * as path from 'path'; +import yargs = require('yargs'); import {Bucket, Storage, TransferManager} from '../src'; export const BLOCK_SIZE_IN_BYTES = 1024; -export const DEFAULT_SMALL_FILE_SIZE_BYTES = 5120; -export const DEFAULT_LARGE_FILE_SIZE_BYTES = 2.147e9; export const NODE_DEFAULT_HIGHWATER_MARK_BYTES = 16384; export const DEFAULT_DIRECTORY_PROBABILITY = 0.1; -export const DEFAULT_PROJECT_ID = 'GCS_NODE_PERFORMANCE_METRICS'; export const DEFAULT_NUMBER_OF_OBJECTS = 1000; + +export const OUTPUT_FORMATS = { + CSV: 'cloud-monitoring-csv', + CLOUD_MONITORING: 'cloud-monitoring', +} as const; + +export const PERFORMANCE_TEST_TYPES = { + WRITE_ONE_READ_THREE: 'w1r3', + RANGE_READ: 'range-read', + TRANSFER_MANAGER_UPLOAD_MANY_FILES: 'tm-upload', + TRANSFER_MANAGER_DOWNLOAD_MANY_FILES: 'tm-download', + TRANSFER_MANAGER_CHUNKED_FILE_DOWNLOAD: 'tm-chunked', + APPLICATION_LARGE_FILE_DOWNLOAD: 'application-large', + APPLICATION_UPLOAD_MULTIPLE_OBJECTS: 'application-upload', + APPLICATION_DOWNLOAD_MULTIPLE_OBJECTS: 'application-download', +} as const; + +const APIS = { + JSON: 'json', +} as const; + +const DEFAULT_SAMPLES = 8000; +const DEFAULT_WORKERS = 16; const SSB_SIZE_THRESHOLD_BYTES = 1048576; +const DEFAULT_OBJECT_RANGE_SIZE_BYTES = '1048576..1048576'; +const DEFAULT_RANGE_READ_SIZE_BYTES = 0; //0 means read the full object +const DEFAULT_MINIMUM_READ_OFFSET_BYTES = 0; +const DEFAULT_MAXIMUM_READ_OFFSET_BYTES = 0; export interface TestResult { op: string; @@ -40,8 +65,83 @@ export interface TestResult { cpuTimeUs: number; status: '[OK]'; chunkSize: number; + workers: number; } +export interface Arguments { + project?: string; + bucket?: string; + output_type: string; + samples: number; + workers: number; + api: string; + object_size: string; + range_read_size: number; + minimum_read_offset: number; + maximum_read_offset: number; + debug: boolean; + file_name: string | undefined; + num_objects: number; + test_type: string; +} + +export const performanceTestCommand: yargs.CommandModule<{}, Arguments> = { + command: 'performance-test', + builder(yargs) { + return yargs + .option('project', {type: 'string', demand: true}) + .option('bucket', {type: 'string', demand: true}) + .option('output_type', { + type: 'string', + choices: [OUTPUT_FORMATS.CSV, OUTPUT_FORMATS.CLOUD_MONITORING], + default: OUTPUT_FORMATS.CLOUD_MONITORING, + }) + .option('samples', {type: 'number', default: DEFAULT_SAMPLES}) + .option('workers', {type: 'number', default: DEFAULT_WORKERS}) + .option('api', { + type: 'string', + choices: [APIS.JSON], + default: APIS.JSON, + }) + .option('object_size', { + type: 'string', + default: DEFAULT_OBJECT_RANGE_SIZE_BYTES, + }) + .option('range_read_size', { + type: 'number', + default: DEFAULT_RANGE_READ_SIZE_BYTES, + }) + .option('minimum_read_offset', { + type: 'number', + default: DEFAULT_MINIMUM_READ_OFFSET_BYTES, + }) + .option('maximum_read_offset', { + type: 'number', + default: DEFAULT_MAXIMUM_READ_OFFSET_BYTES, + }) + .option('debug', {type: 'boolean', default: false}) + .option('file_name', {type: 'string'}) + .option('num_objects', { + type: 'number', + default: DEFAULT_NUMBER_OF_OBJECTS, + }) + .option('test_type', { + type: 'string', + choices: [ + PERFORMANCE_TEST_TYPES.WRITE_ONE_READ_THREE, + PERFORMANCE_TEST_TYPES.TRANSFER_MANAGER_UPLOAD_MANY_FILES, + PERFORMANCE_TEST_TYPES.TRANSFER_MANAGER_DOWNLOAD_MANY_FILES, + PERFORMANCE_TEST_TYPES.TRANSFER_MANAGER_CHUNKED_FILE_DOWNLOAD, + PERFORMANCE_TEST_TYPES.APPLICATION_DOWNLOAD_MULTIPLE_OBJECTS, + PERFORMANCE_TEST_TYPES.APPLICATION_LARGE_FILE_DOWNLOAD, + PERFORMANCE_TEST_TYPES.APPLICATION_UPLOAD_MULTIPLE_OBJECTS, + ], + default: PERFORMANCE_TEST_TYPES.WRITE_ONE_READ_THREE, + }); + }, + async handler() {}, +}; + export interface RandomDirectoryCreationInformation { paths: string[]; totalSizeInBytes: number; @@ -111,8 +211,12 @@ export function generateRandomFileName(baseName: string): string { */ export function generateRandomFile( fileName: string, - fileSizeLowerBoundBytes: number = DEFAULT_SMALL_FILE_SIZE_BYTES, - fileSizeUpperBoundBytes: number = DEFAULT_LARGE_FILE_SIZE_BYTES, + fileSizeLowerBoundBytes: number = getLowHighFileSize( + DEFAULT_OBJECT_RANGE_SIZE_BYTES + ).low, + fileSizeUpperBoundBytes: number = getLowHighFileSize( + DEFAULT_OBJECT_RANGE_SIZE_BYTES + ).high, currentDirectory: string = mkdtempSync(randomString()) ): number { const fileSizeBytes = randomInteger( @@ -140,8 +244,12 @@ export function generateRandomFile( export function generateRandomDirectoryStructure( maxObjects: number, baseName: string, - fileSizeLowerBoundBytes: number = DEFAULT_SMALL_FILE_SIZE_BYTES, - fileSizeUpperBoundBytes: number = DEFAULT_LARGE_FILE_SIZE_BYTES, + fileSizeLowerBoundBytes: number = getLowHighFileSize( + DEFAULT_OBJECT_RANGE_SIZE_BYTES + ).low, + fileSizeUpperBoundBytes: number = getLowHighFileSize( + DEFAULT_OBJECT_RANGE_SIZE_BYTES + ).high, directoryProbability: number = DEFAULT_DIRECTORY_PROBABILITY ): RandomDirectoryCreationInformation { let curPath = baseName; @@ -267,7 +375,8 @@ export async function* convertToCloudMonitoringFormat( generation="",\ upload_id="",\ retry_count="",\ - status_code=""} ${throughput}`; + workers="${curResult.workers}",\ + status_code="${curResult.status}"} ${throughput}`; } } @@ -299,3 +408,21 @@ export function log( isError ? console.error(messageOrError) : console.log(messageOrError); } } + +/** + * Converts the provided rangeSize from string format to an object containing the low and high size values. + * + * @param {string} rangeSize a string in the format low..high. + * + * @returns {object} An object containing integers low and high. + */ +export function getLowHighFileSize(rangeSize: string): { + low: number; + high: number; +} { + const split = rangeSize.split('..'); + return { + low: parseInt(split[0]), + high: parseInt(split[1]), + }; +}