1
0
Fork 0

increase upload concurrency based on cpus, adjust highWaterMark, specify compression level

pull/1584/head
Rob Herley 2023-11-20 15:03:58 +00:00 committed by GitHub
parent 20f826bfe7
commit 7b01731091
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 38 additions and 11 deletions

View File

@ -1,3 +1,5 @@
import os from 'os'
// Used for controlling the highWaterMark value of the zip that is being streamed // Used for controlling the highWaterMark value of the zip that is being streamed
// The same value is used as the chunk size that is use during upload to blob storage // The same value is used as the chunk size that is use during upload to blob storage
export function getUploadChunkSize(): number { export function getUploadChunkSize(): number {
@ -34,3 +36,17 @@ export function getGitHubWorkspaceDir(): string {
} }
return ghWorkspaceDir return ghWorkspaceDir
} }
// Mimics behavior of azcopy: https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azcopy-optimize
// If your machine has fewer than 5 CPUs, then the value of this variable is set to 32.
// Otherwise, the default value is equal to 16 multiplied by the number of CPUs. The maximum value of this variable is 300.
export function getConcurrency() {
const numCPUs = os.cpus().length
if (numCPUs <= 4) {
return 32
}
const concurrency = 16 * numCPUs
return concurrency > 300 ? 300 : concurrency
}

View File

@ -38,6 +38,17 @@ export interface UploadOptions {
* input of 0 assumes default retention setting. * input of 0 assumes default retention setting.
*/ */
retentionDays?: number retentionDays?: number
/**
* The level of compression for Zlib to be applied to the artifact archive.
* The value can range from 0 to 9:
* - 0: No compression
* - 1: Best speed
* - 6: Default compression (same as GNU Gzip)
* - 9: Best compression
* Higher levels will result in better compression, but will take longer to complete.
* For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads.
*/
compressionLevel?: number
} }
/***************************************************************************** /*****************************************************************************

View File

@ -1,7 +1,7 @@
import {BlobClient, BlockBlobUploadStreamOptions} from '@azure/storage-blob' import {BlobClient, BlockBlobUploadStreamOptions} from '@azure/storage-blob'
import {TransferProgressEvent} from '@azure/core-http' import {TransferProgressEvent} from '@azure/core-http'
import {ZipUploadStream} from './zip' import {ZipUploadStream} from './zip'
import {getUploadChunkSize} from '../shared/config' import {getUploadChunkSize, getConcurrency} from '../shared/config'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as stream from 'stream' import * as stream from 'stream'
@ -29,13 +29,13 @@ export async function uploadZipToBlobStorage(
): Promise<BlobUploadResponse> { ): Promise<BlobUploadResponse> {
let uploadByteCount = 0 let uploadByteCount = 0
const maxBuffers = 5 const maxConcurrency = getConcurrency()
const bufferSize = getUploadChunkSize() const bufferSize = getUploadChunkSize()
const blobClient = new BlobClient(authenticatedUploadURL) const blobClient = new BlobClient(authenticatedUploadURL)
const blockBlobClient = blobClient.getBlockBlobClient() const blockBlobClient = blobClient.getBlockBlobClient()
core.debug( core.debug(
`Uploading artifact zip to blob storage with maxBuffers: ${maxBuffers}, bufferSize: ${bufferSize}` `Uploading artifact zip to blob storage with maxConcurrency: ${maxConcurrency}, bufferSize: ${bufferSize}`
) )
const uploadCallback = (progress: TransferProgressEvent): void => { const uploadCallback = (progress: TransferProgressEvent): void => {
@ -61,7 +61,7 @@ export async function uploadZipToBlobStorage(
await blockBlobClient.uploadStream( await blockBlobClient.uploadStream(
uploadStream, uploadStream,
bufferSize, bufferSize,
maxBuffers, maxConcurrency,
options options
) )

View File

@ -37,7 +37,7 @@ export async function uploadArtifact(
} }
} }
const zipUploadStream = await createZipUploadStream(zipSpecification) const zipUploadStream = await createZipUploadStream(zipSpecification, options?.compressionLevel)
// get the IDs needed for the artifact creation // get the IDs needed for the artifact creation
const backendIds = getBackendIdsFromToken() const backendIds = getBackendIdsFromToken()

View File

@ -5,6 +5,8 @@ import {createReadStream} from 'fs'
import {UploadZipSpecification} from './upload-zip-specification' import {UploadZipSpecification} from './upload-zip-specification'
import {getUploadChunkSize} from '../shared/config' import {getUploadChunkSize} from '../shared/config'
export const DEFAULT_COMPRESSION_LEVEL = 6
// Custom stream transformer so we can set the highWaterMark property // Custom stream transformer so we can set the highWaterMark property
// See https://github.com/nodejs/node/issues/8855 // See https://github.com/nodejs/node/issues/8855
export class ZipUploadStream extends stream.Transform { export class ZipUploadStream extends stream.Transform {
@ -21,14 +23,12 @@ export class ZipUploadStream extends stream.Transform {
} }
export async function createZipUploadStream( export async function createZipUploadStream(
uploadSpecification: UploadZipSpecification[] uploadSpecification: UploadZipSpecification[],
compressionLevel: number = DEFAULT_COMPRESSION_LEVEL
): Promise<ZipUploadStream> { ): Promise<ZipUploadStream> {
const zip = archiver.create('zip', { const zip = archiver.create('zip', {
zlib: {level: 9} // Sets the compression level. highWaterMark: getUploadChunkSize(),
// Available options are 0-9 zlib: {level: compressionLevel}
// 0 => no compression
// 1 => fastest with low compression
// 9 => highest compression ratio but the slowest
}) })
// register callbacks for various events during the zip lifecycle // register callbacks for various events during the zip lifecycle