diff --git a/lib/evaluate.js b/lib/evaluate.js index cfcda85..03eb6cd 100644 --- a/lib/evaluate.js +++ b/lib/evaluate.js @@ -55,7 +55,7 @@ export const evaluate = async ({ const started = Date.now() - const { committees } = await runFraudDetection({ + const { committees, timings: fraudDetectionTimings } = await runFraudDetection({ roundIndex, measurements, sparkRoundDetails, @@ -143,6 +143,9 @@ export const evaluate = async ({ point.intField('honest_measurements', measurementsToReward.length) point.intField('set_scores_duration_ms', setScoresDuration) point.intField('fraud_detection_duration_ms', fraudDetectionDuration) + for (const { influxField, duration } of fraudDetectionTimings) { + point.intField(`fraud_detection_timings_${influxField}_ms`, duration) + } for (const [type, count] of Object.entries(evaluationOutcomes)) { point.intField(`measurements_${type}`, count) @@ -236,6 +239,7 @@ export const evaluate = async ({ * @param {import('./typings.js').RoundDetails} args.sparkRoundDetails * @param {number} args.requiredCommitteeSize * @param {Pick} args.logger + * @returns {Promise<{committees: import('./committee.js').Committee[], timings: {influxField: string, duration: number}[]}>} */ export const runFraudDetection = async ({ roundIndex, @@ -244,6 +248,29 @@ export const runFraudDetection = async ({ requiredCommitteeSize, logger }) => { + const timings = { + taskBuilding: { + influxField: 'task_building', + start: null, + end: null + }, + taskingEvaluation: { + influxField: 'tasking_evaluation', + start: null, + end: null + }, + inetGroupsEvaluation: { + influxField: 'inet_groups_evaluation', + start: null, + end: null + }, + majorityEvaluation: { + influxField: 'majority_evaluation', + start: null, + end: null + } + } + const randomness = await pRetry( () => getRandomnessForSparkRound(Number(sparkRoundDetails.startEpoch)), { @@ -254,17 +281,18 @@ export const runFraudDetection = async ({ } ) - const taskBuildingStarted = Date.now() + timings.taskBuilding.start = new Date() const tasksAllowedForStations = await getTasksAllowedForStations( sparkRoundDetails, randomness, measurements ) + timings.taskBuilding.end = new Date() logger.log( 'EVALUATE ROUND %s: built per-node task lists in %sms [Tasks=%s;TN=%s;Nodes=%s]', roundIndex, - Date.now() - taskBuildingStarted, + timings.taskBuilding.end - timings.taskBuilding.start, sparkRoundDetails.retrievalTasks.length, sparkRoundDetails.maxTasksPerNode, tasksAllowedForStations.size @@ -274,6 +302,7 @@ export const runFraudDetection = async ({ // 1. Filter out measurements not belonging to any valid task in this round // or missing some of the required fields like `inet_group` // + timings.taskingEvaluation.start = new Date() for (const m of measurements) { // sanity checks to get nicer errors if we forget to set required fields in unit tests assert(typeof m.inet_group === 'string', 'missing inet_group') @@ -294,10 +323,12 @@ export const runFraudDetection = async ({ m.taskingEvaluation = 'TASK_WRONG_NODE' } } + timings.taskingEvaluation.end = new Date() // // 2. Accept only maxTasksPerNode measurements from each inet group // + timings.inetGroupsEvaluation.start = new Date() /** @type {Map} */ const inetGroups = new Map() for (const m of measurements) { @@ -364,10 +395,12 @@ export const runFraudDetection = async ({ debug(' pa: %s h: %s task: %s - REWARD', m.participantAddress, h, taskId) } } + timings.inetGroupsEvaluation.end = new Date() // // 3. Group measurements to per-task committees and find majority result // + timings.majorityEvaluation.start = new Date() // PERFORMANCE: Avoid duplicating the array of measurements because there are // hundreds of thousands of them. All the function groupMeasurementsToCommittees @@ -384,6 +417,7 @@ export const runFraudDetection = async ({ for (const c of committees.values()) { c.evaluate({ requiredCommitteeSize }) } + timings.majorityEvaluation.end = new Date() if (debug.enabled) { for (const m of measurements) { @@ -399,7 +433,12 @@ export const runFraudDetection = async ({ } } - return { committees: Array.from(committees.values()) } + return { + committees: Array.from(committees.values()), + timings: Object + .values(timings) + .map(({ influxField, start, end }) => ({ influxField, duration: end - start })) + } } /**