diff --git a/app/ProcessCpuProfiler.ts b/app/ProcessCpuProfiler.ts deleted file mode 100644 index 31bbf45117b8a38b3b5a6e447f1896025738a2a2..0000000000000000000000000000000000000000 --- a/app/ProcessCpuProfiler.ts +++ /dev/null @@ -1,72 +0,0 @@ -const SAMPLING_PERIOD = 150 // milliseconds -const MAX_SAMPLES_DISTANCE = 20 * 1000000 // seconds - -function getMicrosecondsTime() { - const [ seconds, nanoseconds ] = process.hrtime() - return seconds * 1000000 + nanoseconds / 1000 -} - -interface CpuUsage { - user: number - system:number -} - -interface CpuUsageAt { - usage:number - at:number // microseconds timestamp - elapsed:number // microseconds elapsed for this result -} - -export class ProcessCpuProfiler { - - private cumulatedUsage: CpuUsage - private startedAt:number // microseconds timestamp - private samples:CpuUsageAt[] = [] - - constructor(samplingPeriod = SAMPLING_PERIOD) { - // Initial state - const start = getMicrosecondsTime() - this.startedAt = start - this.cumulatedUsage = process.cpuUsage() - this.samples.push({ usage: 0, at: start, elapsed: 1 }) - // Periodic sample - setInterval(() => { - const newSampleAt = getMicrosecondsTime() - const newUsage:CpuUsage = process.cpuUsage() - const elapsed = newSampleAt - this.lastSampleAt - const userDiff = newUsage.user - this.cumulatedUsage.user - const usagePercent = userDiff / elapsed // The percent of time consumed by the process since last sample - this.samples.push({ usage: usagePercent, at: newSampleAt, elapsed }) - while(this.samplesDistance > MAX_SAMPLES_DISTANCE) { - this.samples.shift() - } - this.cumulatedUsage = newUsage - // console.log('Time elapsed: %s microseconds, = %s %CPU', elapsed, (usagePercent*100).toFixed(2)) - }, samplingPeriod) - } - - private get lastSampleAt() { - return this.samples[this.samples.length - 1].at - } - - private get samplesDistance() { - return this.samples[this.samples.length - 1].at - this.samples[0].at - } - - cpuUsageOverLastMilliseconds(elapsedMilliseconds:number) { - return this.cpuUsageOverLastX(elapsedMilliseconds * 1000) - } - - private cpuUsageOverLastX(nbMicrosecondsElapsed:number) { - return this.getSamplesResult(getMicrosecondsTime() - nbMicrosecondsElapsed) - } - - private getSamplesResult(minTimestamp:number) { - const matchingSamples = this.samples.filter(s => s.at >= minTimestamp - SAMPLING_PERIOD * 1000) - const cumulativeElapsed = matchingSamples.reduce((sum, s) => sum + s.elapsed, 0) - return matchingSamples.reduce((cumulated, percent) => { - const weight = percent.elapsed / cumulativeElapsed - return cumulated + percent.usage * weight - }, 0) - } -} \ No newline at end of file diff --git a/app/modules/prover/lib/blockProver.ts b/app/modules/prover/lib/blockProver.ts index ccafd0717a18bf15ef74be23f33abae1c1a40f06..9cd215d98d478a456857b52f9f5c5388e1afa153 100644 --- a/app/modules/prover/lib/blockProver.ts +++ b/app/modules/prover/lib/blockProver.ts @@ -44,9 +44,6 @@ export class WorkerFarm { }) } - get nbWorkers() { - return this.theEngine.getNbWorkers() - } changeCPU(cpu:any) { return this.theEngine.setConf({ cpu }) @@ -178,6 +175,7 @@ export class BlockProver { const start = Date.now(); let result = await powFarm.askNewProof({ newPoW: { + turnDuration: os.arch().match(/arm/) ? CommonConstants.POW_TURN_DURATION_ARM : CommonConstants.POW_TURN_DURATION_PC, conf: { cpu: this.conf.cpu, prefix: this.conf.prefix, @@ -196,10 +194,10 @@ export class BlockProver { throw 'Proof-of-work computation canceled because block received'; } else { const proof = result.block; - const testsCount = result.testsCount * powFarm.nbWorkers + const testsCount = result.testsCount; const duration = (Date.now() - start); - const testsPerSecond = testsCount / (duration / 1000) - this.logger.info('Done: #%s, %s in %ss (~%s tests, ~%s tests/s, using %s cores, CPU %s%)', block.number, proof.hash, (duration / 1000).toFixed(2), testsCount, testsPerSecond.toFixed(2), powFarm.nbWorkers, Math.floor(100*this.conf.cpu)) + const testsPerSecond = (testsCount / (duration / 1000)).toFixed(2); + this.logger.info('Done: #%s, %s in %ss (%s tests, ~%s tests/s)', block.number, proof.hash, (duration / 1000).toFixed(2), testsCount, testsPerSecond); this.logger.info('FOUND proof-of-work with %s leading zeros followed by [0-' + highMark + ']!', nbZeros); return BlockDTO.fromJSONObject(proof) } diff --git a/app/modules/prover/lib/engine.ts b/app/modules/prover/lib/engine.ts index d06a8b234c95c62f4581ab572935a25bba8744f9..b4e6ad14ffd7eff58b0a7ea16c88afecf6d60c48 100644 --- a/app/modules/prover/lib/engine.ts +++ b/app/modules/prover/lib/engine.ts @@ -25,27 +25,32 @@ export class PowEngine { this.id = this.cluster.clusterId } - getNbWorkers() { - return this.cluster.nbWorkers - } - forceInit() { return this.cluster.initCluster() } async prove(stuff:any) { - - if (this.cluster.hasProofPending) { - await this.cluster.cancelWork() - } - return await this.cluster.proveByWorkers(stuff) - } + + if (this.cluster.hasProofPending) { + await this.cluster.cancelWork() + } + + const cpus = os.cpus() + + if (os.arch().match(/arm/) || cpus[0].model.match(/Atom/)) { + stuff.newPoW.conf.nbCores /= 2; // Make sure that only once each physical core is used (for Hyperthreading). + } + return await this.cluster.proveByWorkers(stuff) + } cancel() { return this.cluster.cancelWork() } setConf(value:any) { + if (os.arch().match(/arm/) && value.cpu !== undefined) { + value.cpu /= 2; // Don't know exactly why is ARM so much saturated by PoW, so let's divide by 2 + } return this.cluster.changeConf(value) } diff --git a/app/modules/prover/lib/powCluster.ts b/app/modules/prover/lib/powCluster.ts index 140c887a5fa311e5d6ce1aec8f77d4fcb66c5052..4d4820777cb9b279aebea2d09680d1be0b3aab1f 100644 --- a/app/modules/prover/lib/powCluster.ts +++ b/app/modules/prover/lib/powCluster.ts @@ -195,6 +195,7 @@ export class Master { highMark: stuff.newPoW.highMark, pair: _.clone(stuff.newPoW.pair), forcedTime: stuff.newPoW.forcedTime, + turnDuration: stuff.newPoW.turnDuration, conf: { medianTimeBlocks: stuff.newPoW.conf.medianTimeBlocks, avgGenTime: stuff.newPoW.conf.avgGenTime, diff --git a/app/modules/prover/lib/proof.ts b/app/modules/prover/lib/proof.ts index 02927aac80c5750b1a23c391480d98ae257358c4..9b15c0be5aaff08b078c5dd495dbb0432be983ea 100644 --- a/app/modules/prover/lib/proof.ts +++ b/app/modules/prover/lib/proof.ts @@ -6,11 +6,15 @@ import {ProverConstants} from "./constants" import {KeyGen} from "../../../lib/common-libs/crypto/keyring" import {dos2unix} from "../../../lib/common-libs/dos2unix" import {rawer} from "../../../lib/common-libs/index" -import {ProcessCpuProfiler} from "../../../ProcessCpuProfiler" const moment = require('moment'); const querablep = require('querablep'); +const PAUSES_PER_TURN = 5; + +// This value can be changed +let TURN_DURATION_IN_MILLISEC = 100; + let computing = querablep(Promise.resolve(null)); let askedStop = false; @@ -86,6 +90,7 @@ function beginNewProofOfWork(stuff:any) { prefix *= 100 * ProverConstants.NONCE_RANGE } const highMark = stuff.highMark; + const turnDuration = stuff.turnDuration || TURN_DURATION_IN_MILLISEC let sigFunc = null; if (signatureFunc && lastSecret === pair.sec) { sigFunc = signatureFunc; @@ -103,17 +108,13 @@ function beginNewProofOfWork(stuff:any) { let testsCount = 0; let found = false; + let score = 0; let turn = 0; - const profiler = new ProcessCpuProfiler(100) - let cpuUsage = profiler.cpuUsageOverLastMilliseconds(1) - // We limit the number of tests according to CPU usage - let testsPerRound = 1 - let turnDuration = 20 // We initially goes quickly to the max speed = 50 reevaluations per second (1000 / 20) while (!found && !askedStop) { /***************** - * A TURN ~ 100ms + * A TURN ****************/ await Promise.race([ @@ -124,9 +125,26 @@ function beginNewProofOfWork(stuff:any) { // II. Process the turn's PoW (async () => { + /***************** + * A TURN OF POW ~= 100ms by default + * -------------------- + * + * The concept of "turn" is required to limit the CPU usage. + * We need a time reference to have the speed = nb tests / period of time. + * Here we have: + * + * - speed = testsCount / turn + * + * We have taken 1 turn = 100ms to control the CPU usage after 100ms of PoW. This means that during the + * very first 100ms of the PoW, CPU usage = 100%. Then it becomes controlled to the %CPU set. + ****************/ + // Prove let i = 0; const thisTurn = turn; + const pausePeriod = score ? score / PAUSES_PER_TURN : 10; // number of pauses per turn + // We limit the number of tests according to CPU usage + const testsPerRound = score ? Math.floor(score * currentCPU) : 1000 * 1000 * 1000 // Time is updated regularly during the proof block.time = getBlockTime(block, conf, forcedTime) @@ -178,7 +196,7 @@ function beginNewProofOfWork(stuff:any) { if (!found && !askedStop) { i++; testsCount++; - if (i % testsPerRound === 0) { + if (i % pausePeriod === 0) { await countDown(0); // Very low pause, just the time to process eventual end of the turn } } @@ -190,24 +208,12 @@ function beginNewProofOfWork(stuff:any) { if (!found) { // CPU speed recording - if (turn > 0) { - const oldTestsPerRound = testsPerRound - cpuUsage = profiler.cpuUsageOverLastMilliseconds(turnDuration) - if (cpuUsage > currentCPU + 0.005 || cpuUsage < currentCPU - 0.005) { - let powVariationFactor - // powVariationFactor = currentCPU / (cpuUsage || 0.01) / 5 // divide by 2 to avoid extreme responses - if (currentCPU > cpuUsage) { - powVariationFactor = 1.01 - testsPerRound = Math.max(1, Math.ceil(testsPerRound * powVariationFactor)) - } else { - powVariationFactor = 0.99 - testsPerRound = Math.max(1, Math.floor(testsPerRound * powVariationFactor)) - } - } + if (turn > 0 && !score) { + score = testsCount; } /***************** - * UNLOAD CPU CHARGE FOR THIS TURN + * UNLOAD CPU CHARGE ****************/ // We wait for a maximum time of `turnDuration`. // This will trigger the end of the turn by the concurrent race I. During that time, the proof.js script @@ -220,9 +226,6 @@ function beginNewProofOfWork(stuff:any) { // Next turn turn++ - - turnDuration += 1 - turnDuration = Math.min(turnDuration, 1000) // Max 1 second per turn } /*****************