Skip to content

Commit

Permalink
fix(renterd): contract churn alert percentage
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfreska committed Jan 9, 2025
1 parent 2a9d193 commit 2eae113
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/strange-pigs-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'renterd': patch
---

Fixed an issue with the contract churn alert's overall churn percentage.
145 changes: 143 additions & 2 deletions apps/renterd-e2e/src/specs/alerts.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Page, test, expect } from '@playwright/test'
import { navigateToAlerts } from '../fixtures/navigate'
import { afterTest, beforeTest } from '../fixtures/beforeTest'
import { AlertsResponse, busAlertsRoute } from '@siafoundation/renterd-types'
import {
AlertsResponse,
busAlertsRoute,
busContractsRoute,
ContractsResponse,
} from '@siafoundation/renterd-types'

test.beforeEach(async ({ page }) => {
await mockApiBusContracts(page)
await mockApiBusAlerts(page)
await beforeTest(page)
})
Expand All @@ -17,7 +23,7 @@ test('alert data', async ({ page }) => {

// Churn alert
const churnData = page.getByTestId('churn')
await expect(churnData.getByText('churn: 99.90%')).toBeVisible()
await expect(churnData.getByText('churn: 37.54%')).toBeVisible()
const churnDataContractB6 = churnData.getByTestId(
'b6f32dc39998bd85d730d39666360225af12fbad3bc18de4df50ce09073c9393'
)
Expand All @@ -36,6 +42,141 @@ test('alert data', async ({ page }) => {
await expect(objectsData.getByText('bucket1/nest2/file3.png')).toBeVisible()
})

async function mockApiBusContracts(page: Page) {
const json: ContractsResponse = [
{
id: 'b6f32dc39998bd85d730d39666360225af12fbad3bc18de4df50ce09073c9393',
hostKey: 'hk',
usability: 'bad',
proofHeight: 100,
revisionHeight: 100,
revisionNumber: 1,
startHeight: 100,
windowStart: 200,
windowEnd: 300,
renewedFrom: '',
spending: {
deletions: '0',
fundAccount: '0',
sectorRoots: '0',
uploads: '0',
},
initialRenterFunds: '2000',
size: 30000000,
state: 'active',
},
{
id: '26cd68ac42d4056f1494aef012bf9da4f753ba15e2831722eebf30a78243d534',
hostKey: 'hk',
usability: 'good',
proofHeight: 100,
revisionHeight: 100,
revisionNumber: 1,
startHeight: 100,
windowStart: 200,
windowEnd: 300,
renewedFrom: '',
spending: {
deletions: '0',
fundAccount: '0',
sectorRoots: '0',
uploads: '0',
},
initialRenterFunds: '2000',
size: 30000,
state: 'active',
},
{
id: '437b0c09f6167790fefc21000c4a4a81de109729151414526562721ee7802ac6',
hostKey: 'hk',
usability: 'bad',
proofHeight: 100,
revisionHeight: 100,
revisionNumber: 1,
startHeight: 100,
windowStart: 200,
windowEnd: 300,
renewedFrom: '',
spending: {
deletions: '0',
fundAccount: '0',
sectorRoots: '0',
uploads: '0',
},
initialRenterFunds: '2000',
size: 4000,
state: 'active',
},
{
id: '89dfc5594909fd468729b59096b26c886b25106e5479ceb1a28276420cb32fd3',
hostKey: 'hk',
usability: 'bad',
proofHeight: 100,
revisionHeight: 100,
revisionNumber: 1,
startHeight: 100,
windowStart: 200,
windowEnd: 300,
renewedFrom: '',
spending: {
deletions: '0',
fundAccount: '0',
sectorRoots: '0',
uploads: '0',
},
initialRenterFunds: '2000',
size: 10000,
state: 'active',
},
{
id: 'f0bbb8b6a1a6219beb510f0c4008bba9ed5687b5e617d10efce206022248ed59',
hostKey: 'hk',
usability: 'bad',
proofHeight: 100,
revisionHeight: 100,
revisionNumber: 1,
startHeight: 100,
windowStart: 200,
windowEnd: 300,
renewedFrom: '',
spending: {
deletions: '0',
fundAccount: '0',
sectorRoots: '0',
uploads: '0',
},
initialRenterFunds: '2000',
size: 50000,
state: 'active',
},
{
id: 'c7f32dc39998bd85d730d39666360225af12fbad3bc18de4df50ce09073c9666',
hostKey: 'hk',
usability: 'good',
proofHeight: 100,
revisionHeight: 100,
revisionNumber: 1,
startHeight: 100,
windowStart: 200,
windowEnd: 300,
renewedFrom: '',
spending: {
deletions: '0',
fundAccount: '0',
sectorRoots: '0',
uploads: '0',
},
initialRenterFunds: '2000',
size: 50000000,
state: 'active',
},
]
await page.route(`**/api${busContractsRoute}*`, async (route) => {
await route.fulfill({ json })
})
return json
}

async function mockApiBusAlerts(page: Page) {
const json: AlertsResponse = {
hasMore: false,
Expand Down
24 changes: 14 additions & 10 deletions apps/renterd/contexts/alerts/ChurnEventsField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { useMemo } from 'react'
import { Add16, Subtract16 } from '@siafoundation/react-icons'
import { cx } from 'class-variance-authority'
import { AlertChurnEvent } from '@siafoundation/renterd-types'
import { useContracts } from '../contracts'
import BigNumber from 'bignumber.js'

type Change = {
contractId: string
Expand All @@ -26,6 +28,7 @@ export function ChurnEventsField({
}: {
data: Record<string, AlertChurnEvent[]>
}) {
const { contractSizeTotal } = useContracts()
const churnEvents = useMemo(() => {
return objectEntries(data)
.map(([contractId, events]) => {
Expand All @@ -45,11 +48,6 @@ export function ChurnEventsField({
})
}, [data])

// calculate churn %: contracts bad size / total size
const totalSize = useMemo(
() => churnEvents.reduce((acc, { events }) => acc + events[0].size, 0),
[churnEvents]
)
const bads = useMemo(
() => churnEvents.filter(({ events }) => events[0].to === 'bad'),
[churnEvents]
Expand All @@ -59,12 +57,18 @@ export function ChurnEventsField({
[churnEvents]
)
const badSize = useMemo(
() => bads.reduce((acc, { events }) => acc + events[0].size, 0),
() =>
bads.reduce(
(acc, { events }) => acc.plus(events[0].size),
new BigNumber(0)
),
[bads]
)
// Calculate churn %: contracts bad size / total size.
const churn = useMemo(
() => (totalSize > 0 ? (badSize / totalSize) * 100 : 0),
[badSize, totalSize]
() =>
contractSizeTotal?.gt(0) ? badSize.div(contractSizeTotal).times(100) : 0,
[badSize, contractSizeTotal]
)

return (
Expand All @@ -76,15 +80,15 @@ export function ChurnEventsField({
<div className="flex-1" />
<Tooltip
content={`${humanBytes(badSize)} of ${humanBytes(
totalSize
contractSizeTotal
)} contract size removed`}
>
<div className="flex gap-1 items-center">
<Text size="12" color="contrast" ellipsis>
churn: {churn.toFixed(2)}%
</Text>
<Text size="12" color="subtle" ellipsis>
({humanBytes(badSize)} / {humanBytes(totalSize)})
({humanBytes(badSize)} / {humanBytes(contractSizeTotal)})
</Text>
</div>
</Tooltip>
Expand Down
9 changes: 9 additions & 0 deletions apps/renterd/contexts/contracts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { defaultDatasetRefreshInterval } from '../../config/swr'
import { useDataset } from './dataset'
import { useFilteredStats } from './useFilteredStats'
import { daysInMilliseconds } from '@siafoundation/units'
import BigNumber from 'bignumber.js'

const defaultLimit = 50

Expand Down Expand Up @@ -164,6 +165,13 @@ function useContractsMain() {
disabled: !selectedContract,
})

const contractSizeTotal = useMemo(
() =>
dataset?.reduce((acc, { size }) => acc.plus(size), new BigNumber(0)) ??
new BigNumber(0),
[dataset]
)

return {
datasetState,
limit,
Expand Down Expand Up @@ -206,6 +214,7 @@ function useContractsMain() {
fetchPrunableSize,
fetchPrunableSizeAll,
multiSelect,
contractSizeTotal,
}
}

Expand Down

0 comments on commit 2eae113

Please sign in to comment.