Skip to content

Commit

Permalink
feat: add provider stats endpoint
Browse files Browse the repository at this point in the history
feat: add provider stats endpoint
  • Loading branch information
Redm4x committed Oct 15, 2024
1 parent ed22ad7 commit 66e11a6
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 3 deletions.
3 changes: 2 additions & 1 deletion apps/api/src/routes/internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import gpu from "./gpu";
import gpuModels from "./gpuModels";
import gpuPrices from "./gpuPrices";
import leasesDuration from "./leasesDuration";
import providerDashboard from "./providerDashboard";
import providerVersions from "./providerVersions";

export default [providerVersions, gpu, leasesDuration, gpuModels, gpuPrices];
export default [providerVersions, gpu, leasesDuration, gpuModels, gpuPrices, providerDashboard];
167 changes: 167 additions & 0 deletions apps/api/src/routes/internal/providerDashboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { Block } from "@akashnetwork/database/dbSchemas";
import { Provider } from "@akashnetwork/database/dbSchemas/akash";
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import subHours from "date-fns/subHours";
import { Op } from "sequelize";

import { getProviderActiveResourcesAtHeight, getProviderEarningsAtHeight, getProviderTotalLeaseCountAtHeight } from "@src/services/db/statsService";
import { openApiExampleProviderAddress } from "@src/utils/constants";

const route = createRoute({
method: "get",
path: "/provider-dashboard/{owner}",
summary: "Get dashboard data for provider console.",
request: {
params: z.object({
owner: z.string().openapi({ example: openApiExampleProviderAddress })
})
},
responses: {
200: {
description: "Dashboard data",
content: {
"application/json": {
schema: z.object({
current: z.object({
date: z.string(),
height: z.number(),
activeLeaseCount: z.number(),
totalLeaseCount: z.number(),
dailyLeaseCount: z.number(),
totalUAktEarned: z.number(),
dailyUAktEarned: z.number(),
totalUUsdcEarned: z.number(),
dailyUUsdcEarned: z.number(),
totalUUsdEarned: z.number(),
dailyUUsdEarned: z.number(),
activeCPU: z.number(),
activeGPU: z.number(),
activeMemory: z.number(),
activeEphemeralStorage: z.number(),
activePersistentStorage: z.number(),
activeStorage: z.number()
}),
previous: z.object({
date: z.string(),
height: z.number(),
activeLeaseCount: z.number(),
totalLeaseCount: z.number(),
dailyLeaseCount: z.number(),
totalUAktEarned: z.number(),
dailyUAktEarned: z.number(),
totalUUsdcEarned: z.number(),
dailyUUsdcEarned: z.number(),
totalUUsdEarned: z.number(),
dailyUUsdEarned: z.number(),
activeCPU: z.number(),
activeGPU: z.number(),
activeMemory: z.number(),
activeEphemeralStorage: z.number(),
activePersistentStorage: z.number(),
activeStorage: z.number()
})
})
}
}
},
404: {
description: "Provider not found"
}
}
});

export default new OpenAPIHono().openapi(route, async c => {
const owner = c.req.param("owner");

const provider = await Provider.findOne({
where: { owner: owner }
});

if (!provider) {
return c.json({ error: "Provider not found" }, 404);
}

const latestBlock = await Block.findOne({
where: {
isProcessed: true,
totalUUsdSpent: { [Op.not]: null }
},
order: [["height", "DESC"]]
});

const [earlierBlock24h, earlierBlock48h] = await Promise.all([
Block.findOne({
order: [["datetime", "ASC"]],
where: {
datetime: { [Op.gte]: subHours(latestBlock.datetime, 24) }
}
}),
Block.findOne({
order: [["datetime", "ASC"]],
where: {
datetime: { [Op.gte]: subHours(latestBlock.datetime, 48) }
}
})
]);

const [
activeStats,
previousActiveStats,
currentTotalLeaseCount,
previousTotalLeaseCount,
secondPreviousTotalLeaseCount,
currentTotalEarnings,
previousTotalEarnings,
secondPreviousTotalEarnings
] = await Promise.all([
getProviderActiveResourcesAtHeight(owner, latestBlock.height),
getProviderActiveResourcesAtHeight(owner, earlierBlock24h.height),
getProviderTotalLeaseCountAtHeight(owner, latestBlock.height),
getProviderTotalLeaseCountAtHeight(owner, earlierBlock24h.height),
getProviderTotalLeaseCountAtHeight(owner, earlierBlock48h.height),
getProviderEarningsAtHeight(owner, provider.createdHeight, latestBlock.height),
getProviderEarningsAtHeight(owner, provider.createdHeight, earlierBlock24h.height),
getProviderEarningsAtHeight(owner, provider.createdHeight, earlierBlock48h.height)
]);

return c.json({
current: {
date: latestBlock.datetime,
height: latestBlock.height,
activeLeaseCount: activeStats.count,
totalLeaseCount: currentTotalLeaseCount,
dailyLeaseCount: currentTotalLeaseCount - previousTotalLeaseCount,
totalUAktEarned: currentTotalEarnings.uakt,
dailyUAktEarned: currentTotalEarnings.uakt - previousTotalEarnings.uakt,
totalUUsdcEarned: currentTotalEarnings.uusdc,
dailyUUsdcEarned: currentTotalEarnings.uusdc - previousTotalEarnings.uusdc,
totalUUsdEarned: currentTotalEarnings.uusd,
dailyUUsdEarned: currentTotalEarnings.uusd - previousTotalEarnings.uusd,
activeCPU: activeStats.cpu,
activeGPU: activeStats.gpu,
activeMemory: activeStats.memory,
activeEphemeralStorage: activeStats.ephemeralStorage,
activePersistentStorage: activeStats.persistentStorage,
activeStorage: activeStats.ephemeralStorage + activeStats.persistentStorage
},
previous: {
date: earlierBlock24h.datetime,
height: earlierBlock24h.height,
activeLeaseCount: previousActiveStats.count,
totalLeaseCount: previousTotalLeaseCount,
dailyLeaseCount: previousTotalLeaseCount - secondPreviousTotalLeaseCount,
totalUAktEarned: previousTotalEarnings.uakt,
dailyUAktEarned: previousTotalEarnings.uakt - secondPreviousTotalEarnings.uakt,
totalUUsdcEarned: previousTotalEarnings.uusdc,
dailyUUsdcEarned: previousTotalEarnings.uusdc - secondPreviousTotalEarnings.uusdc,
totalUUsdEarned: previousTotalEarnings.uusd,
dailyUUsdEarned: previousTotalEarnings.uusd - secondPreviousTotalEarnings.uusd,
activeCPU: previousActiveStats.cpu,
activeGPU: previousActiveStats.gpu,
activeMemory: previousActiveStats.memory,
activeEphemeralStorage: previousActiveStats.ephemeralStorage,
activePersistentStorage: previousActiveStats.persistentStorage,
activeStorage: previousActiveStats.ephemeralStorage + previousActiveStats.persistentStorage
}
});
});
102 changes: 101 additions & 1 deletion apps/api/src/services/db/statsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ export const getProviderActiveLeasesGraphData = async (providerAddress: string)
LEFT JOIN "lease" l
ON l."providerAddress" = :providerAddress
AND l."createdHeight" <= d."lastBlockHeightYet"
AND (l."closedHeight" IS NULL OR l."closedHeight" > d."lastBlockHeightYet")
AND COALESCE(l."closedHeight", l."predictedClosedHeight") > d."lastBlockHeightYet"
AND (l."predictedClosedHeight" IS NULL OR l."predictedClosedHeight" > d."lastBlockHeightYet")
INNER JOIN "provider" p
ON p."owner" = :providerAddress
Expand Down Expand Up @@ -334,3 +334,103 @@ export const getProviderActiveLeasesGraphData = async (providerAddress: string)
}
};
};

export async function getProviderTotalLeaseCountAtHeight(provider: string, height: number) {
const [{ count: totalLeaseCount }] = await chainDb.query<{ count: number }>(
`SELECT COUNT(*) FROM lease l WHERE "providerAddress"=:provider AND l."createdHeight" <= :height`,
{
type: QueryTypes.SELECT,
replacements: { provider: provider, height: height }
}
);

return totalLeaseCount;
}

export async function getProviderActiveResourcesAtHeight(provider: string, height: number) {
const [activeStats] = await chainDb.query<{
count: number;
cpu: number;
memory: number;
ephemeralStorage: number;
persistentStorage: number;
gpu: number;
}>(
`
SELECT
COUNT(*) AS "count",
SUM("cpuUnits") AS "cpu",
SUM("memoryQuantity") AS "memory",
SUM("ephemeralStorageQuantity") AS "ephemeralStorage",
SUM("persistentStorageQuantity") AS "persistentStorage",
SUM("gpuUnits") AS "gpu"
FROM lease
WHERE
"providerAddress"=:provider
AND "createdHeight" <= :height
AND COALESCE("closedHeight", "predictedClosedHeight") > :height`,
{
type: QueryTypes.SELECT,
replacements: {
provider: provider,
height: height
}
}
);

return activeStats;
}

export async function getProviderEarningsAtHeight(provider: string, providerCreatedHeight: number, height: number) {
const days = await chainDb.query<{ date: string; aktPrice: number; totalUAkt: number; totalUUsdc: number }>(
`
WITH provider_leases AS (
SELECT dseq, price, "createdHeight", "closedHeight","predictedClosedHeight", "denom"
FROM lease
WHERE "providerAddress"=:provider
)
SELECT
d.date, d."aktPrice",s."totalUAkt",s."totalUUsdc"
FROM day d
LEFT JOIN LATERAL (
WITH active_leases AS (
SELECT
l.dseq,
l.price,
l.denom,
(LEAST(d."lastBlockHeightYet", COALESCE(l."closedHeight", l."predictedClosedHeight"), :height) - GREATEST(d."firstBlockHeight", l."createdHeight", :providerCreatedHeight)) AS duration
FROM provider_leases l
WHERE
l."createdHeight" <= LEAST(d."lastBlockHeightYet", :height)
AND COALESCE(l."closedHeight", l."predictedClosedHeight") >= GREATEST(d."firstBlockHeight", :providerCreatedHeight)
),
billed_leases AS (
SELECT
(CASE WHEN l.denom='uakt' THEN l.price*l.duration ELSE 0 END) AS "uakt_earned",
(CASE WHEN l.denom='uusdc' THEN l.price*l.duration ELSE 0 END) AS "uusdc_earned"
FROM active_leases l
)
SELECT
SUM(l.uakt_earned) AS "totalUAkt",
SUM(l.uusdc_earned) AS "totalUUsdc"
FROM billed_leases l
) AS s ON 1=1
WHERE d."lastBlockHeightYet" >= :providerCreatedHeight AND d."firstBlockHeight" <= :height
ORDER BY d.date DESC
`,
{
type: QueryTypes.SELECT,
replacements: {
provider: provider,
height: height,
providerCreatedHeight: providerCreatedHeight
}
}
);

return {
uakt: days.reduce((acc, d) => acc + d.totalUAkt, 0),
uusdc: days.reduce((acc, d) => acc + d.totalUUsdc, 0),
uusd: days.reduce((acc, d) => acc + (d.totalUAkt * d.aktPrice + d.totalUUsdc), 0)
};
}
3 changes: 2 additions & 1 deletion packages/database/dbSchemas/akash/lease.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { Provider } from "./provider";
{ unique: false, fields: ["closedHeight"] },
{ unique: false, fields: ["predictedClosedHeight"] },
{ unique: false, fields: ["deploymentId"] },
{ unique: false, fields: ["owner", "dseq", "gseq", "oseq"] }
{ unique: false, fields: ["owner", "dseq", "gseq", "oseq"] },
{ unique: false, fields: ["providerAddress", "closedHeight", "createdHeight"] }
]
})
export class Lease extends Model {
Expand Down

0 comments on commit 66e11a6

Please sign in to comment.