Skip to content

Commit

Permalink
feat: Add grace period to uptime check
Browse files Browse the repository at this point in the history
  • Loading branch information
Redm4x committed Apr 25, 2024
1 parent 9ac6a43 commit eafb3ec
Show file tree
Hide file tree
Showing 19 changed files with 275 additions and 132 deletions.
10 changes: 7 additions & 3 deletions api/src/routes/internal/gpu.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import { chainDb } from "@src/db/dbConnection";
import { toUTC } from "@src/utils";
import { isValidBech32Address } from "@src/utils/addresses";
import { env } from "@src/utils/env";
import { sub } from "date-fns";
import { QueryTypes } from "sequelize";

const route = createRoute({
Expand Down Expand Up @@ -86,8 +89,8 @@ export default new OpenAPIHono().openapi(route, async (c) => {
"hostUri",
p."owner"
FROM provider p
INNER JOIN "providerSnapshot" ps ON ps.id=p."lastSnapshotId"
WHERE p."isOnline" IS TRUE
INNER JOIN "providerSnapshot" ps ON ps.id=p."lastSuccessfulSnapshotId"
WHERE p."isOnline" IS TRUE OR ps."checkDate" >= :grace_date
)
SELECT s."hostUri", n."name", n."gpuAllocatable" AS allocatable, n."gpuAllocated" AS allocated, gpu."modelId", gpu.vendor, gpu.name AS "modelName", gpu.interface, gpu."memorySize"
FROM snapshots s
Expand All @@ -110,7 +113,8 @@ export default new OpenAPIHono().openapi(route, async (c) => {
model: model ?? null,
memory_size: memory_size ?? null,
provider_address: provider_address ?? null,
provider_hosturi: provider_hosturi ?? null
provider_hosturi: provider_hosturi ?? null,
grace_date: toUTC(sub(new Date(), { minutes: env.ProviderUptimeGracePeriodMinutes }))
}
}
);
Expand Down
13 changes: 9 additions & 4 deletions api/src/routes/internal/gpuPrices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { Day, Transaction } from "@shared/dbSchemas/base";
import { cacheResponse } from "@src/caching/helpers";
import { chainDb } from "@src/db/dbConnection";
import { MsgCreateBid } from "@src/proto/akash/v1beta4";
import { toUTC } from "@src/utils";
import { averageBlockCountInAMonth, averageBlockCountInAnHour } from "@src/utils/constants";
import { env } from "@src/utils/env";
import { average, median, round, weightedAverage } from "@src/utils/math";
import { decodeMsg, uint8arrayToString } from "@src/utils/protobuf";
import { addDays } from "date-fns";
import { addDays, sub } from "date-fns";
import { Op, QueryTypes } from "sequelize";

const route = createRoute({
Expand Down Expand Up @@ -345,8 +347,8 @@ async function getGpus() {
"hostUri",
p."owner"
FROM provider p
INNER JOIN "providerSnapshot" ps ON ps.id=p."lastSnapshotId"
WHERE p."isOnline" IS TRUE
INNER JOIN "providerSnapshot" ps ON ps.id=p."lastSuccessfulSnapshotId"
WHERE p."isOnline" IS TRUE OR ps."checkDate" >= :grace_date
ORDER BY p."hostUri", p."createdHeight" DESC
)
SELECT s."hostUri", s."owner", n."name", n."gpuAllocatable" AS allocatable, LEAST(n."gpuAllocated", n."gpuAllocatable") AS allocated, gpu."modelId", gpu.vendor, gpu.name AS "modelName", gpu.interface, gpu."memorySize"
Expand All @@ -360,7 +362,10 @@ async function getGpus() {
gpu.vendor IS NOT NULL
`,
{
type: QueryTypes.SELECT
type: QueryTypes.SELECT,
replacements: {
grace_date: toUTC(sub(new Date(), { minutes: env.ProviderUptimeGracePeriodMinutes }))
}
}
);

Expand Down
27 changes: 19 additions & 8 deletions api/src/routes/internal/providerVersions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import { Provider } from "@shared/dbSchemas/akash";
import { chainDb } from "@src/db/dbConnection";
import { toUTC } from "@src/utils";
import { env } from "@src/utils/env";
import { round } from "@src/utils/math";
import { sub } from "date-fns";
import * as semver from "semver";
import { QueryTypes } from "sequelize";

const route = createRoute({
method: "get",
Expand All @@ -28,13 +32,20 @@ const route = createRoute({
});

export default new OpenAPIHono().openapi(route, async (c) => {
const providers = await Provider.findAll({
attributes: ["hostUri", "akashVersion"],
where: {
isOnline: true
},
group: ["hostUri", "akashVersion"]
});
const providers = await chainDb.query<{ hostUri: string; akashVersion: string }>(
`
SELECT DISTINCT ON ("hostUri") "hostUri","akashVersion"
FROM provider p
INNER JOIN "providerSnapshot" ps ON ps.id=p."lastSuccessfulSnapshotId"
WHERE p."isOnline" IS TRUE OR ps."checkDate" >= :grace_date
`,
{
type: QueryTypes.SELECT,
replacements: {
grace_date: toUTC(sub(new Date(), { minutes: env.ProviderUptimeGracePeriodMinutes }))
}
}
);

const grouped: { version: string; providers: string[] }[] = [];

Expand Down
1 change: 1 addition & 0 deletions api/src/routes/v1/providers/byAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const route = createRoute({
uptime30d: z.number(),
isValidVersion: z.boolean(),
isOnline: z.boolean(),
lastOnlineDate: z.string().nullable(),
isAudited: z.boolean(),
activeStats: z.object({
cpu: z.number(),
Expand Down
1 change: 1 addition & 0 deletions api/src/routes/v1/providers/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const route = createRoute({
uptime30d: z.number(),
isValidVersion: z.boolean(),
isOnline: z.boolean(),
lastOnlineDate: z.string().nullable(),
isAudited: z.boolean(),
activeStats: z.object({
cpu: z.number(),
Expand Down
53 changes: 33 additions & 20 deletions api/src/services/db/providerStatusService.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import { Provider, ProviderAttribute, ProviderAttributeSignature, ProviderSnapshotNode, ProviderSnapshotNodeGPU } from "@shared/dbSchemas/akash";
import { ProviderSnapshot } from "@shared/dbSchemas/akash/providerSnapshot";
import { toUTC } from "@src/utils";
import { add } from "date-fns";
import { add, sub } from "date-fns";
import { Op } from "sequelize";
import { mapProviderToList } from "@src/utils/map/provider";
import { getAuditors, getProviderAttributesSchema } from "../external/githubService";
import { ProviderDetail } from "@src/types/provider";
import { env } from "@src/utils/env";

export async function getNetworkCapacity() {
const providers = await Provider.findAll({
where: {
isOnline: true,
deletedHeight: null
}
},
include: [
{
required: false,
model: ProviderSnapshot,
as: "lastSuccessfulSnapshot",
where: { checkDate: { [Op.gte]: toUTC(sub(new Date(), { minutes: env.ProviderUptimeGracePeriodMinutes })) } }
}
]
});
const filteredProviders = providers.filter((value, index, self) => self.map((x) => x.hostUri).indexOf(value.hostUri) === index);

const filteredProviders = providers
.filter((x) => x.isOnline || x.lastSuccessfulSnapshot)
.filter((value, index, self) => self.map((x) => x.hostUri).indexOf(value.hostUri) === index);

const stats = {
activeProviderCount: filteredProviders.length,
Expand Down Expand Up @@ -67,7 +78,7 @@ export const getProviderList = async () => {
model: ProviderSnapshot,
attributes: ["id"],
required: true,
as: "lastSnapshot",
as: "lastSuccessfulSnapshot",
include: [
{
model: ProviderSnapshotNode,
Expand All @@ -87,8 +98,8 @@ export const getProviderList = async () => {
const [auditors, providerAttributeSchema] = await Promise.all([auditorsQuery, providerAttributeSchemaQuery]);

return distinctProviders.map((x) => {
const nodes = providerWithNodes.find((p) => p.owner === x.owner)?.lastSnapshot?.nodes;
return mapProviderToList(x, providerAttributeSchema, auditors, nodes);
const lastSuccessfulSnapshot = providerWithNodes.find((p) => p.owner === x.owner)?.lastSnapshot;
return mapProviderToList(x, providerAttributeSchema, auditors, lastSuccessfulSnapshot);
});
};

Expand Down Expand Up @@ -121,26 +132,28 @@ export const getProviderDetail = async (address: string): Promise<ProviderDetail
}
});

const lastSnapshot = await ProviderSnapshot.findOne({
where: {
id: provider.lastSnapshotId
},
order: [["checkDate", "DESC"]],
include: [
{
model: ProviderSnapshotNode,
include: [{ model: ProviderSnapshotNodeGPU }]
}
]
});
const lastSuccessfulSnapshot = provider.lastSuccessfulSnapshotId
? await ProviderSnapshot.findOne({
where: {
id: provider.lastSuccessfulSnapshotId
},
order: [["checkDate", "DESC"]],
include: [
{
model: ProviderSnapshotNode,
include: [{ model: ProviderSnapshotNodeGPU }]
}
]
})
: null;

const providerAttributeSchemaQuery = getProviderAttributesSchema();
const auditorsQuery = getAuditors();

const [auditors, providerAttributeSchema] = await Promise.all([auditorsQuery, providerAttributeSchemaQuery]);

return {
...mapProviderToList(provider, providerAttributeSchema, auditors, lastSnapshot?.nodes),
...mapProviderToList(provider, providerAttributeSchema, auditors, lastSuccessfulSnapshot),
uptime: uptimeSnapshots.map((ps) => ({
id: ps.id,
isOnline: ps.isOnline,
Expand Down
8 changes: 6 additions & 2 deletions api/src/services/db/statsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Op, QueryTypes } from "sequelize";
import { chainDb } from "@src/db/dbConnection";
import { ProviderActiveLeasesStats, ProviderStats, ProviderStatsKey } from "@src/types/graph";
import { cacheKeys, cacheResponse } from "@src/caching/helpers";
import { env } from "@src/utils/env";

type GraphData = {
currentValue: number;
Expand Down Expand Up @@ -203,14 +204,17 @@ export const getProviderGraphData = async (dataName: ProviderStatsKey) => {
SELECT DISTINCT ON("hostUri",DATE("checkDate")) DATE("checkDate") AS date, ps."activeCPU", ps."pendingCPU", ps."availableCPU", ps."activeGPU", ps."pendingGPU", ps."availableGPU", ps."activeMemory", ps."pendingMemory", ps."availableMemory", ps."activeStorage", ps."pendingStorage", ps."availableStorage", ps."isOnline"
FROM "providerSnapshot" ps
INNER JOIN "provider" ON "provider"."owner"=ps."owner"
WHERE ps."isLastOfDay" = TRUE AND ps."isOnline" = TRUE
WHERE ps."isLastSuccessOfDay" = TRUE AND ps."checkDate" >= DATE(ps."checkDate")::timestamp + INTERVAL '1 day' - INTERVAL :grace_duration || ' minutes'
ORDER BY "hostUri",DATE("checkDate"),"checkDate" DESC
) "dailyProviderStats"
ON DATE(d."date")="dailyProviderStats"."date"
GROUP BY d."date"
ORDER BY d."date" ASC`,
{
type: QueryTypes.SELECT
type: QueryTypes.SELECT,
replacements: {
grace_duration: env.ProviderUptimeGracePeriodMinutes
}
}
);
},
Expand Down
4 changes: 2 additions & 2 deletions api/src/types/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ export interface ProviderList {
}

export interface ProviderDetail extends ProviderList {
uptime: Array<{
uptime: {
id: string;
isOnline: boolean;
checkDate: Date;
}>;
}[];
}

export type ProviderAttributesSchema = {
Expand Down
4 changes: 4 additions & 0 deletions api/src/utils/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export const env = z
Auth0Issuer: z.string().optional(),
WebsiteUrl: z.string().optional(),
SecretToken: z.string().optional(),
ProviderUptimeGracePeriodMinutes: z
.number()
.optional()
.default(3 * 60),
NODE_API_BASE_PATH: z.string().optional().default("https://raw.githubusercontent.com/akash-network")
})
.parse(process.env);
7 changes: 4 additions & 3 deletions api/src/utils/map/provider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Provider, ProviderSnapshotNode } from "@shared/dbSchemas/akash";
import { Provider, ProviderSnapshot, ProviderSnapshotNode } from "@shared/dbSchemas/akash";
import { Auditor, ProviderAttributesSchema, ProviderList } from "@src/types/provider";
import { createFilterUnique } from "../array/array";
import semver from "semver";
Expand All @@ -7,11 +7,11 @@ export const mapProviderToList = (
provider: Provider,
providerAttributeSchema: ProviderAttributesSchema,
auditors: Array<Auditor>,
nodes?: ProviderSnapshotNode[]
lastSuccessfulSnapshot?: ProviderSnapshot
): ProviderList => {
const isValidVersion = provider.cosmosSdkVersion ? semver.gte(provider.cosmosSdkVersion, "v0.45.9") : false;
const name = provider.isOnline ? new URL(provider.hostUri).hostname : null;
const gpuModels = getDistinctGpuModelsFromNodes(nodes || []);
const gpuModels = getDistinctGpuModelsFromNodes(lastSuccessfulSnapshot?.nodes || []);

return {
owner: provider.owner,
Expand Down Expand Up @@ -55,6 +55,7 @@ export const mapProviderToList = (
uptime30d: provider.uptime30d,
isValidVersion,
isOnline: provider.isOnline,
lastOnlineDate: lastSuccessfulSnapshot?.checkDate,
isAudited: provider.providerAttributeSignatures.some((a) => auditors.some((y) => y.address === a.auditor)),
attributes: provider.providerAttributes.map((attr) => ({
key: attr.key,
Expand Down
2 changes: 1 addition & 1 deletion deploy-web/src/components/providers/ProviderMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const ProviderMap: React.FunctionComponent<Props> = ({ providers, initial
const { classes } = useStyles();
const [dotSize, setDotSize] = useState({ r: 5, w: 1 });
const theme = useTheme();
const activeProviders = providers.filter(x => x.isOnline || x.isOnline);
const activeProviders = providers.filter(x => x.isOnline);
const bgColor = theme.palette.mode === "dark" ? theme.palette.grey[800] : theme.palette.grey[400];
const [position, setPosition] = useState({ coordinates: initialCoordinates, zoom: initialZoom });
const isInitialPosition =
Expand Down
Loading

0 comments on commit eafb3ec

Please sign in to comment.