Skip to content

Evaluate alternative provider measurement #518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions lib/preprocess.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class Measurement {
constructor (m, pointerize = (v) => v) {
this.participantAddress = pointerize(parseParticipantAddress(m.participant_address))
this.retrievalResult = pointerize(getRetrievalResult(m))
this.alternativeProviderRetrievalResult = pointerize(getAlternativeProviderRetrievalResult(m))
this.cid = pointerize(m.cid)
this.minerId = pointerize(m.miner_id)
// Note: providerId is recorded by spark-publish but we don't use it for evaluations yet
Expand All @@ -41,6 +42,14 @@ export class Measurement {
this.stationId = pointerize(m.station_id)
this.carChecksum = pointerize(m.car_checksum)
this.carTooLarge = m.car_too_large
this.alternativeProviderCheck = {
statusCode: m.alternative_provider_check_status_code,
timeout: m.alternative_provider_check_timeout,
carTooLarge: m.alternative_provider_check_car_too_large,
endAt: parseDateTime(m.alternative_provider_check_end_at),
protocol: m.alternative_provider_check_protocol,
providerId: pointerize(m.alternative_provider_check_provider_id)
}
}
}

Expand Down Expand Up @@ -319,3 +328,31 @@ export const getRetrievalResult = (measurement) => {

return ok ? 'OK' : 'UNKNOWN_ERROR'
}

/**
* Evaluates the alternative provider retrieval result.
*
* Alternative provider retrieval results are evaluated only if the indexer result is `NO_VALID_ADVERTISEMENT`
* and the network retrieval status code is set.
*
* @param {Partial<import('./typings.js').RawMeasurement>} measurement
* @return {import('./typings.js').RetrievalResult}
*/
export const getAlternativeProviderRetrievalResult = (measurement) => {
if (measurement.indexer_result !== 'NO_VALID_ADVERTISEMENT' || !measurement.alternative_provider_check_status_code) {
return getRetrievalResult(measurement)
}

/** @type {Partial<import('./typings.js').RawMeasurement>} */
const alternativeProviderMeasurement = {
indexer_result: 'OK',
status_code: measurement.alternative_provider_check_status_code,
timeout: measurement.alternative_provider_check_timeout,
alternative_provider_check_status_code: measurement.alternative_provider_check_status_code,
car_too_large: measurement.alternative_provider_check_car_too_large,
end_at: measurement.alternative_provider_check_end_at,
protocol: measurement.alternative_provider_check_protocol
}

return getRetrievalResult(alternativeProviderMeasurement)
}
7 changes: 5 additions & 2 deletions lib/provider-retrieval-result-stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const withPgClient = fn => async ({ createPgClient, ...args }) => {
}

export const build = committees => {
/** @type {Map<string, {total: number, successful: number, successfulHttp:number, successfulHttpHead: number}>} */
/** @type {Map<string, {total: number, successful: number, successfulHttp:number, successfulHttpHead: number, successfulAltProvider: number }>} */
const providerRetrievalResultStats = new Map()
for (const c of committees) {
// IMPORTANT: include minority results in the calculation
Expand All @@ -25,7 +25,7 @@ export const build = committees => {
if (m.retrievalResult.match(/^IPNI_ERROR_5\d\d$/)) continue

const minerId = m.minerId
const retrievalStats = providerRetrievalResultStats.get(minerId) ?? { total: 0, successful: 0, successfulHttp: 0, successfulHttpHead: 0 }
const retrievalStats = providerRetrievalResultStats.get(minerId) ?? { total: 0, successful: 0, successfulHttp: 0, successfulHttpHead: 0, successfulAltProvider: 0 }
retrievalStats.total++
if (m.retrievalResult === 'OK') {
retrievalStats.successful++
Expand All @@ -36,6 +36,9 @@ export const build = committees => {
}
}
}

if (m.alternativeProviderRetrievalResult === 'OK') retrievalStats.successfulAltProvider++

providerRetrievalResultStats.set(minerId, retrievalStats)
}
}
Expand Down
23 changes: 23 additions & 0 deletions lib/public-stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const updatePublicStats = async ({ createPgClient, committees, allMeasure
try {
for (const [minerId, retrievalResultStats] of stats.entries()) {
await updateRetrievalStats(pgClient, minerId, retrievalResultStats)
await updateDailyAlternativeProviderRetrievalStats(pgClient, retrievalResultStats)
}
await updateIndexerQueryStats(pgClient, committees)
await updateDailyDealsStats(pgClient, committees, findDealClients)
Expand Down Expand Up @@ -407,3 +408,25 @@ function buildPerPartyStats (committees, perDealParty, partyName) {
)
return flatStats
}

/**
* @param {pg.Client} pgClient
* @param {object} stats
* @param {number} stats.total
* @param {number} stats.successfulAltProvider
*/
export const updateDailyAlternativeProviderRetrievalStats = async (pgClient, { total, successfulAltProvider }) => {
debug('Updating public daily alternative provider retrieval stats: total += %s successful += %s', total, successfulAltProvider)
await pgClient.query(`
INSERT INTO daily_alternative_provider_retrieval_stats
(day, total, successful)
VALUES
(now(), $1, $2)
ON CONFLICT(day) DO UPDATE SET
total = daily_alternative_provider_retrieval_stats.total + $1,
successful = daily_alternative_provider_retrieval_stats.successful + $2
`, [
total,
successfulAltProvider
])
}
5 changes: 5 additions & 0 deletions lib/retrieval-stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const buildRetrievalStats = (measurements, telemetryPoint) => {
const sizeValues = []
let httpSuccesses = 0
let indexerServerErrorCount = 0
let alternativeProviderRetrievalSuccess = 0

for (const m of measurements) {
// `retrievalResult` should be always set by lib/preprocess.js, so we should never encounter
Expand Down Expand Up @@ -106,6 +107,8 @@ export const buildRetrievalStats = (measurements, telemetryPoint) => {
// A successful HTTP response is a response with result breakdown set to OK and the protocol being used is set to HTTP.
if (m.retrievalResult === 'OK' && m.protocol === 'http') { httpSuccesses++ }

if (m.alternativeProviderRetrievalResult === 'OK') { alternativeProviderRetrievalSuccess++ }

if (m.retrievalResult.match(/^IPNI_ERROR_5\d\d$/)) {
indexerServerErrorCount++
}
Expand All @@ -114,6 +117,7 @@ export const buildRetrievalStats = (measurements, telemetryPoint) => {
const totalForRSR = totalCount - indexerServerErrorCount
const successRate = totalForRSR ? resultBreakdown.OK / totalForRSR : 0
const successRateHttp = totalForRSR ? httpSuccesses / totalForRSR : 0
const altProviderRSR = totalForRSR ? alternativeProviderRetrievalSuccess / totalForRSR : 0
telemetryPoint.intField('total_for_success_rates', totalForRSR)
telemetryPoint.intField('unique_tasks', uniqueTasksCount)
telemetryPoint.floatField('success_rate', successRate)
Expand All @@ -122,6 +126,7 @@ export const buildRetrievalStats = (measurements, telemetryPoint) => {
telemetryPoint.intField('inet_groups', inetGroups.size)
telemetryPoint.intField('measurements', totalCount)
telemetryPoint.intField('download_bandwidth', downloadBandwidth)
telemetryPoint.floatField('alternative_provider_success_rate', altProviderRSR)

addHistogramToPoint(telemetryPoint, ttfbValues, 'ttfb_')
addHistogramToPoint(telemetryPoint, durationValues, 'duration_')
Expand Down
6 changes: 6 additions & 0 deletions lib/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ export interface RawMeasurement {
| 'NO_VALID_ADVERTISEMENT'
| 'ERROR_FETCH'
| `ERROR_${number}`;
alternative_provider_check_status_code: number | undefined | null;
alternative_provider_check_timeout: boolean;
alternative_provider_check_car_too_large: boolean;
alternative_provider_check_end_at: string;
alternative_provider_check_protocol: string;
alternative_provider_check_provider_id: string;
}

export type CreatePgClient = () => Promise<import('pg').Client>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE daily_alternative_provider_retrieval_stats (
day DATE NOT NULL PRIMARY KEY,
total INT NOT NULL,
successful INT NOT NULL
);
11 changes: 10 additions & 1 deletion test/helpers/test-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,16 @@ export const VALID_MEASUREMENT = {
retrievalResult: 'OK',
indexerResult: 'OK',
taskingEvaluation: null,
consensusEvaluation: null
consensusEvaluation: null,
alternativeProviderRetrievalResult: 'OK',
alternativeProviderCheck: {
statusCode: null,
timeout: false,
carTooLarge: false,
endAt: null,
protocol: 'http',
providerId: 'ALTPROVIDERID'
}
}

// Fraud detection is mutating the measurements parsed from JSON
Expand Down
91 changes: 90 additions & 1 deletion test/preprocess.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
preprocess,
Measurement,
parseMeasurements,
assertValidMeasurement
assertValidMeasurement,
getAlternativeProviderRetrievalResult
} from '../lib/preprocess.js'
import { Point } from '../lib/telemetry.js'
import assert from 'node:assert'
Expand Down Expand Up @@ -416,3 +417,91 @@ describe('assertValidMeasurement', () => {
assert.throws(() => assertValidMeasurement(measurement), /first_byte_at must be greater than or equal to start_at/)
})
})

describe('getAlternativeProviderRetrievalResult', () => {
/** @type {Partial<import('../lib/typings.js').RawMeasurement>} */
const SUCCESSFUL_RETRIEVAL = {
spark_version: '1.5.2',
participant_address: 'f410fgkhpcrbmdvic52o3nivftrjxr7nzw47updmuzra',
station_id: VALID_STATION_ID,
finished_at: '2023-11-01T09:42:03.246Z',
timeout: false,
start_at: '2023-11-01T09:40:03.393Z',
status_code: 200,
first_byte_at: '1970-01-01T00:00:00.000Z',
end_at: '1970-01-01T00:00:00.000Z',
byte_length: 1234,
inet_group: 'ue49TX_JdYjI',
cid: 'bafkreihstuf2qcu3hs64ersidh46cjtilxcoipmzgu3pifwzmkqdjpraqq',
miner_id: 'f1abc',
provider_address: '/ip4/108.89.91.150/tcp/46717/p2p/12D3KooWSsaFCtzDJUEhLQYDdwoFtdCMqqfk562UMvccFz12kYxU',
provider_id: 'PROVIDERID',
protocol: 'http',
indexer_result: 'OK'
}

it('successful retrieval', () => {
const result = getAlternativeProviderRetrievalResult({
...SUCCESSFUL_RETRIEVAL
})
assert.strictEqual(result, 'OK')
})

it('TIMEOUT - no alternative provider retrieval measurements', () => {
const result = getAlternativeProviderRetrievalResult({
...SUCCESSFUL_RETRIEVAL,
timeout: true
})
assert.strictEqual(result, 'TIMEOUT')
})

it('TIMEOUT - successful alternative provider retrieval measurements', () => {
const result = getAlternativeProviderRetrievalResult({
...SUCCESSFUL_RETRIEVAL,
timeout: true,
alternative_provider_check_car_too_large: false,
alternative_provider_check_timeout: false,
alternative_provider_check_status_code: 200,
alternative_provider_check_end_at: '2023-11-01T09:42:03.246Z',
alternative_provider_check_protocol: 'http',
alternative_provider_check_provider_id: 'ALTPROVIDERID'
})
assert.strictEqual(result, 'TIMEOUT')
})

it('NO_VALID_ADVERTISEMENT - no alternative provider retrieval measurements', () => {
const result = getAlternativeProviderRetrievalResult({
...SUCCESSFUL_RETRIEVAL,
indexer_result: 'NO_VALID_ADVERTISEMENT'
})
assert.strictEqual(result, 'IPNI_NO_VALID_ADVERTISEMENT')
})

it('NO_VALID_ADVERTISEMENT - successful alternative provider retrieval measurements', () => {
const result = getAlternativeProviderRetrievalResult({
...SUCCESSFUL_RETRIEVAL,
indexer_result: 'NO_VALID_ADVERTISEMENT',
alternative_provider_check_car_too_large: false,
alternative_provider_check_timeout: false,
alternative_provider_check_status_code: 200,
alternative_provider_check_end_at: '2023-11-01T09:42:03.246Z',
alternative_provider_check_protocol: 'http',
alternative_provider_check_provider_id: 'ALTPROVIDERID'
})
assert.strictEqual(result, 'OK')
})

it('NO_VALID_ADVERTISEMENT - TIMEOUT alternative provider retrieval measurements', () => {
const result = getAlternativeProviderRetrievalResult({
...SUCCESSFUL_RETRIEVAL,
indexer_result: 'NO_VALID_ADVERTISEMENT',
alternative_provider_check_car_too_large: false,
alternative_provider_check_timeout: true,
alternative_provider_check_status_code: 500,
alternative_provider_check_end_at: '2023-11-01T09:42:03.246Z',
alternative_provider_check_protocol: 'http',
alternative_provider_check_provider_id: 'ALTPROVIDERID'
})
assert.strictEqual(result, 'TIMEOUT')
})
})
8 changes: 4 additions & 4 deletions test/provider-retrieval-result-stats.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ describe('Provider Retrieval Result Stats', () => {
}
])
assert.deepStrictEqual(stats, new Map([
['0', { total: 2, successful: 2, successfulHttp: 1, successfulHttpHead: 1 }],
['1', { total: 2, successful: 0, successfulHttp: 0, successfulHttpHead: 0 }]
['0', { total: 2, successful: 2, successfulHttp: 1, successfulHttpHead: 1, successfulAltProvider: 0 }],
['1', { total: 2, successful: 0, successfulHttp: 0, successfulHttpHead: 0, successfulAltProvider: 0 }]
]))
})
})
Expand Down Expand Up @@ -178,8 +178,8 @@ describe('Provider Retrieval Result Stats', () => {
contract_address: ieContractAddress,
measurement_batches: round.measurementBatches,
provider_retrieval_result_stats: {
0: { successful: 2, total: 2, successfulHttp: 1, successfulHttpHead: 1 },
1: { successful: 0, total: 2, successfulHttp: 0, successfulHttpHead: 0 }
0: { successful: 2, total: 2, successfulHttp: 1, successfulHttpHead: 1, successfulAltProvider: 0 },
1: { successful: 0, total: 2, successfulHttp: 0, successfulHttpHead: 0, successfulAltProvider: 0 }
},
round_details: 'baguqeerawg5jfpiy2g5xp5d422uwa3mpyzkmiguoeecesds7q65mn2hdoa4q',
round_index: String(round.index),
Expand Down
46 changes: 42 additions & 4 deletions test/public-stats.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('public-stats', () => {
await pgClient.query('DELETE FROM retrieval_timings')
await pgClient.query('DELETE FROM daily_client_retrieval_stats')
await pgClient.query('DELETE FROM daily_allocator_retrieval_stats')
await pgClient.query('DELETE FROM daily_alternative_provider_retrieval_stats')

// Run all tests inside a transaction to ensure `now()` always returns the same value
// See https://dba.stackexchange.com/a/63549/125312
Expand Down Expand Up @@ -724,7 +725,7 @@ describe('public-stats', () => {
findDealClients
)
const { rows } = await pgClient.query(
`SELECT
`SELECT
day::TEXT,
client_id,
total,
Expand Down Expand Up @@ -770,7 +771,7 @@ describe('public-stats', () => {
findDealClients
)
const { rows } = await pgClient.query(
`SELECT
`SELECT
day::TEXT,
client_id,
total,
Expand Down Expand Up @@ -965,7 +966,7 @@ describe('public-stats', () => {
findDealAllocators
)
const { rows } = await pgClient.query(
`SELECT
`SELECT
day::TEXT,
allocator_id,
total,
Expand Down Expand Up @@ -1011,7 +1012,7 @@ describe('public-stats', () => {
findDealAllocators
)
const { rows } = await pgClient.query(
`SELECT
`SELECT
day::TEXT,
allocator_id,
total,
Expand Down Expand Up @@ -1168,6 +1169,43 @@ describe('public-stats', () => {
{ day: today, allocator_id: 'f0allocator', total: 4, successful: 3 }
])
})

describe('daily_alternative_provider_retrieval_stats ', () => {
it('creates or updates the row for today', async () => {
/** @type {Measurement[]} */
const allMeasurements = [
{ ...VALID_MEASUREMENT, alternativeProviderRetrievalResult: 'OK' },
{ ...VALID_MEASUREMENT, alternativeProviderRetrievalResult: 'OK' },

{ ...VALID_MEASUREMENT, alternativeProviderRetrievalResult: 'TIMEOUT' },
{ ...VALID_MEASUREMENT, alternativeProviderRetrievalResult: 'CAR_TOO_LARGE' },
{ ...VALID_MEASUREMENT, alternativeProviderRetrievalResult: 'IPNI_ERROR_FETCH' }
]

const committees = buildEvaluatedCommitteesFromMeasurements(allMeasurements)
const { rows: created } = await pgClient.query(
'SELECT * FROM daily_alternative_provider_retrieval_stats'
)
assert.deepStrictEqual(created, [])
await updatePublicStats({
createPgClient,
committees,
allMeasurements,
findDealClients: (_minerId, _cid) => ['f0client'],
findDealAllocators: (_minerId, _cid) => ['f0allocator']
})
const { rows } = await pgClient.query(
`SELECT
day::TEXT,
total,
successful
FROM daily_alternative_provider_retrieval_stats`)

assert.deepStrictEqual(rows, [
{ day: today, total: 5, successful: 2 }
])
})
})
})

const getCurrentDate = async () => {
Expand Down
Loading