Skip to content
Merged
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
46 changes: 45 additions & 1 deletion tests/unit/handlers/metrics.handlers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const {
calculateGroupedEfficiencySummary,
getMinerStatus,
getMinersByContainer,
getMinerCountsByContainer,
getInventorySummary,
processMinerStatusData,
processGroupedMinerStatusData,
Expand Down Expand Up @@ -2133,6 +2134,30 @@ test('getMinersByContainer - no data returns empty container map', async (t) =>
t.pass()
})

test('getMinerCountsByContainer - fetches only count fields and sums them per container', async (t) => {
let payload = null
const mockCtx = withDataProxy({
conf: { orks: [{ rpcPublicKey: 'a' }, { rpcPublicKey: 'b' }] },
net_r0: {
jRequest: async (key, method, p) => {
payload = p
return key === 'a'
? [[{ ts: 1, offline_cnt: { c1: 3 }, power_mode_normal_cnt: { c1: 10, c2: 4 } }]]
: [[{ ts: 1, error_cnt: { c1: 1 } }]]
}
}
})

const result = await getMinerCountsByContainer(mockCtx)

t.is(payload.keys[0].key, 'stat-rtd', 'should read the realtime snapshot')
t.absent(payload.aggrFields.hashrate_mhs_5m_container_group_sum_aggr, 'should not request hashrate')
t.absent(payload.aggrFields.temperature_c_group_max_aggr, 'should not request temperature')
t.absent(payload.aggrFields.hashrate_mhs_5m_active_container_group_cnt, 'should not request active count')
t.alike(result.containers, { c1: { minerCount: 14 }, c2: { minerCount: 4 } }, 'should sum status counts across orks per container')
t.pass()
})

// ==================== Inventory Summary Tests ====================

test('getInventorySummary - rolls up miner and spare-part counts by status/location', async (t) => {
Expand Down Expand Up @@ -3831,7 +3856,9 @@ test('getInventoryMinerDistribution - builds per-type rows with locations and ca
[{ miner_inventory_location_group_cnt_aggr: { 'site.warehouse': 1 } }]
]
}
if (method === 'tailLogMulti') return [[{}]]
if (method === 'tailLogMulti') {
return [[{ offline_cnt: { c1: 1 }, power_mode_normal_cnt: { c1: 1 } }]]
}
return []
}
}
Expand All @@ -3846,6 +3873,22 @@ test('getInventoryMinerDistribution - builds per-type rows with locations and ca
{ 'info.site': { $eq: 'site-a' } },
'should scope miners to the configured site'
)
t.absent(minerListCall.payload.status, 'should not request snap data for miners')

const containerCountCall = calls.find(c => c.method === 'tailLogMulti' && !c.payload.aggrFields.miner_inventory_location_group_cnt_aggr)
t.alike(
Object.keys(containerCountCall.payload.aggrFields).sort(),
[
'error_cnt',
'not_mining_cnt',
'offline_cnt',
'power_mode_high_cnt',
'power_mode_low_cnt',
'power_mode_normal_cnt',
'power_mode_sleep_cnt'
],
'should only request per-container count fields'
)

const locationCall = calls.find(c => c.method === 'tailLogMulti' && c.payload.aggrFields.miner_inventory_location_group_cnt_aggr)
t.alike(
Expand All @@ -3859,6 +3902,7 @@ test('getInventoryMinerDistribution - builds per-type rows with locations and ca
t.is(s19Row.locations['miner.room'], 1, 'should report aggregated locations')
t.is(s19Row.locations.unknown, 1, 'unknown = count minus located miners')
t.is(s19Row.totalPositions, 3, 'capacity from container miner tag')
t.is(s19Row.freePositions, 1, 'free = capacity minus connected miner counts')

const m53Row = result.rows.find(r => r.type === 'miner-wm-m53s')
t.is(m53Row.locations.unknown, 0, 'located miners leave no unknowns')
Expand Down
42 changes: 37 additions & 5 deletions workers/lib/server/handlers/metrics.handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,39 @@ function processMinersByType (results) {
return { types }
}

const CONTAINER_MINER_COUNT_AGGR_FIELDS = {
[AGGR_FIELDS.OFFLINE_CNT]: 1,
[AGGR_FIELDS.ERROR_CNT]: 1,
[AGGR_FIELDS.NOT_MINING_CNT]: 1,
[AGGR_FIELDS.SLEEP_CNT]: 1,
[AGGR_FIELDS.POWER_MODE_LOW_CNT]: 1,
[AGGR_FIELDS.POWER_MODE_NORMAL_CNT]: 1,
[AGGR_FIELDS.POWER_MODE_HIGH_CNT]: 1
}

async function getMinerCountsByContainer (ctx) {
const results = await ctx.dataProxy.requestDataMap(RPC_METHODS.TAIL_LOG_MULTI, {
keys: [{ key: LOG_KEYS.STAT_RTD, type: WORKER_TYPES.MINER, tag: WORKER_TAGS.MINER }],
limit: 1,
aggrFields: CONTAINER_MINER_COUNT_AGGR_FIELDS
})

const counts = {}
for (const orkResult of results) {
const entry = extractKeyEntry(orkResult, 0)
if (!entry) continue
for (const field of Object.keys(CONTAINER_MINER_COUNT_AGGR_FIELDS)) {
mergeGroupedField(counts, entry[field])
}
}

const containers = {}
for (const [id, minerCount] of Object.entries(counts)) {
containers[id] = { minerCount }
}
return { containers }
}

const CONTAINER_MINER_TAG_REGEX = /container_miner-[^_]+_(.+)/

function computeInstalledCapacity (containers, byContainer) {
Expand Down Expand Up @@ -1092,15 +1125,13 @@ async function getInventoryMinerDistribution (ctx, req) {
const [minerResults, containerResults, byContainer] = await Promise.all([
ctx.dataProxy.requestDataAllPages(RPC_METHODS.LIST_THINGS, {
query: minersQuery,
fields: { id: 1, type: 1 },
status: 1
fields: { id: 1, type: 1 }
}),
ctx.dataProxy.requestDataAllPages(RPC_METHODS.LIST_THINGS, {
query: { tags: { $in: [WORKER_TAGS.CONTAINER] } },
fields: { id: 1, tags: 1, 'info.container': 1, 'info.nominalMinerCapacity': 1 },
status: 1
fields: { id: 1, tags: 1, 'info.container': 1, 'info.nominalMinerCapacity': 1 }
}),
getMinersByContainer(ctx, req)
getMinerCountsByContainer(ctx)
])

const miners = flattenRpcResults(minerResults)
Expand Down Expand Up @@ -1754,6 +1785,7 @@ module.exports = {
processGroupedMinerStatusData,
getMinersByContainer,
processMinersByContainer,
getMinerCountsByContainer,
getInventorySummary,
processInventorySummary,
getMinersByType,
Expand Down
Loading