X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Futils.ts;h=7d6309d539e22e65b08b64b55924b6df8a51de74;hb=67f3f2d6cb8f915ec71f81c4533ab80a6c6a6f0f;hp=86b6edadf5da05a139d872202675e1ebda47f3f1;hpb=57a29f754af52b95f13a585c77612e87552af7da;p=poolifier.git diff --git a/src/utils.ts b/src/utils.ts index 86b6edad..7d6309d5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,18 +1,14 @@ import * as os from 'node:os' -import { webcrypto } from 'node:crypto' +import { getRandomValues, randomInt } from 'node:crypto' import { Worker as ClusterWorker } from 'node:cluster' import { Worker as ThreadWorker } from 'node:worker_threads' +import { cpus } from 'node:os' import type { - MeasurementStatisticsRequirements, - WorkerChoiceStrategyOptions -} from './pools/selection-strategies/selection-strategies-types' -import type { KillBehavior } from './worker/worker-options' -import { - type IWorker, - type MeasurementStatistics, - type WorkerType, - WorkerTypes -} from './pools/worker' + InternalWorkerChoiceStrategyOptions, + MeasurementStatisticsRequirements +} from './pools/selection-strategies/selection-strategies-types.js' +import type { KillBehavior } from './worker/worker-options.js' +import { type IWorker, type WorkerType, WorkerTypes } from './pools/worker.js' /** * Default task name. @@ -27,15 +23,21 @@ export const EMPTY_FUNCTION: () => void = Object.freeze(() => { }) /** - * Default worker choice strategy options. + * Gets default worker choice strategy options. + * + * @param retries - The number of worker choice retries. + * @returns The default worker choice strategy options. */ -export const DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS: WorkerChoiceStrategyOptions = - { - retries: 6, +const getDefaultInternalWorkerChoiceStrategyOptions = ( + retries: number +): InternalWorkerChoiceStrategyOptions => { + return { + retries, runTime: { median: false }, waitTime: { median: false }, elu: { median: false } } +} /** * Default measurement statistics requirements. @@ -104,7 +106,7 @@ export const getWorkerId = (worker: IWorker): number | undefined => { * @internal */ export const sleep = async (ms: number): Promise => { - await new Promise((resolve) => { + await new Promise(resolve => { setTimeout(resolve, ms) }) } @@ -224,50 +226,6 @@ export const isAsyncFunction = ( return typeof fn === 'function' && fn.constructor.name === 'AsyncFunction' } -/** - * Updates the given measurement statistics. - * - * @param measurementStatistics - The measurement statistics to update. - * @param measurementRequirements - The measurement statistics requirements. - * @param measurementValue - The measurement value. - * @param numberOfMeasurements - The number of measurements. - * @internal - */ -export const updateMeasurementStatistics = ( - measurementStatistics: MeasurementStatistics, - measurementRequirements: MeasurementStatisticsRequirements, - measurementValue: number -): void => { - if (measurementRequirements.aggregate) { - measurementStatistics.aggregate = - (measurementStatistics.aggregate ?? 0) + measurementValue - measurementStatistics.minimum = min( - measurementValue, - measurementStatistics.minimum ?? Infinity - ) - measurementStatistics.maximum = max( - measurementValue, - measurementStatistics.maximum ?? -Infinity - ) - if ( - (measurementRequirements.average || measurementRequirements.median) && - measurementValue != null - ) { - measurementStatistics.history.push(measurementValue) - if (measurementRequirements.average) { - measurementStatistics.average = average(measurementStatistics.history) - } else if (measurementStatistics.average != null) { - delete measurementStatistics.average - } - if (measurementRequirements.median) { - measurementStatistics.median = median(measurementStatistics.history) - } else if (measurementStatistics.median != null) { - delete measurementStatistics.median - } - } - } -} - /** * Generates a cryptographically secure random number in the [0,1[ range * @@ -275,7 +233,7 @@ export const updateMeasurementStatistics = ( * @internal */ export const secureRandom = (): number => { - return webcrypto.getRandomValues(new Uint32Array(1))[0] / 0x100000000 + return getRandomValues(new Uint32Array(1))[0] / 0x100000000 } /** @@ -299,3 +257,76 @@ export const min = (...args: number[]): number => */ export const max = (...args: number[]): number => args.reduce((maximum, num) => (maximum > num ? maximum : num), -Infinity) + +/** + * Wraps a function so that it can only be called once. + * + * @param fn - The function to wrap. + * @param context - The context to bind the function to. + * @returns The wrapped function. + * @internal + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const once = ( + fn: (...args: A) => R, + context: T +): ((...args: A) => R) => { + let result: R + return (...args: A) => { + if (fn != null) { + result = fn.apply(context, args) + ;(fn as unknown as undefined) = (context as unknown as undefined) = + undefined + } + return result + } +} + +const clone = (object: T): T => { + return JSON.parse(JSON.stringify(object)) as T +} + +export const buildInternalWorkerChoiceStrategyOptions = ( + poolMaxSize: number, + opts?: InternalWorkerChoiceStrategyOptions +): InternalWorkerChoiceStrategyOptions => { + opts = clone(opts ?? {}) + if (opts?.weights == null) { + opts.weights = getDefaultWeights(poolMaxSize) + } + return { + ...getDefaultInternalWorkerChoiceStrategyOptions( + poolMaxSize + Object.keys(opts.weights).length + ), + ...opts + } +} + +const getDefaultWeights = ( + poolMaxSize: number, + defaultWorkerWeight?: number +): Record => { + defaultWorkerWeight = defaultWorkerWeight ?? getDefaultWorkerWeight() + const weights: Record = {} + for (let workerNodeKey = 0; workerNodeKey < poolMaxSize; workerNodeKey++) { + weights[workerNodeKey] = defaultWorkerWeight + } + return weights +} + +const getDefaultWorkerWeight = (): number => { + const cpuSpeed = randomInt(500, 2500) + let cpusCycleTimeWeight = 0 + for (const cpu of cpus()) { + if (cpu.speed == null || cpu.speed === 0) { + cpu.speed = + cpus().find(cpu => cpu.speed != null && cpu.speed !== 0)?.speed ?? + cpuSpeed + } + // CPU estimated cycle time + const numberOfDigits = cpu.speed.toString().length - 1 + const cpuCycleTime = 1 / (cpu.speed / Math.pow(10, numberOfDigits)) + cpusCycleTimeWeight += cpuCycleTime * Math.pow(10, numberOfDigits) + } + return Math.round(cpusCycleTimeWeight / cpus().length) +}