feat: revamp cold start time counter
This commit is contained in:
parent
72325a4217
commit
c7727fc1be
3
web/drizzle/0045_careful_cerise.sql
Normal file
3
web/drizzle/0045_careful_cerise.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TYPE "workflow_run_status" ADD VALUE 'started';--> statement-breakpoint
|
||||||
|
ALTER TYPE "workflow_run_status" ADD VALUE 'queued';--> statement-breakpoint
|
||||||
|
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "queued_at" timestamp;
|
1297
web/drizzle/meta/0045_snapshot.json
Normal file
1297
web/drizzle/meta/0045_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -316,6 +316,13 @@
|
|||||||
"when": 1706317908300,
|
"when": 1706317908300,
|
||||||
"tag": "0044_panoramic_mister_fear",
|
"tag": "0044_panoramic_mister_fear",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 45,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706336448134,
|
||||||
|
"tag": "0045_careful_cerise",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { parseDataSafe } from "../../../../lib/parseDataSafe";
|
import { parseDataSafe } from "../../../../lib/parseDataSafe";
|
||||||
import { db } from "@/db/db";
|
import { db } from "@/db/db";
|
||||||
import {
|
import {
|
||||||
|
WorkflowRunStatusSchema,
|
||||||
userUsageTable,
|
userUsageTable,
|
||||||
workflowRunOutputs,
|
workflowRunOutputs,
|
||||||
workflowRunsTable,
|
workflowRunsTable,
|
||||||
@ -14,9 +15,8 @@ import { z } from "zod";
|
|||||||
|
|
||||||
const Request = z.object({
|
const Request = z.object({
|
||||||
run_id: z.string(),
|
run_id: z.string(),
|
||||||
status: z
|
status: WorkflowRunStatusSchema.optional(),
|
||||||
.enum(["not-started", "running", "uploading", "success", "failed"])
|
time: z.date().optional(),
|
||||||
.optional(),
|
|
||||||
output_data: z.any().optional(),
|
output_data: z.any().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -24,9 +24,27 @@ export async function POST(request: Request) {
|
|||||||
const [data, error] = await parseDataSafe(Request, request);
|
const [data, error] = await parseDataSafe(Request, request);
|
||||||
if (!data || error) return error;
|
if (!data || error) return error;
|
||||||
|
|
||||||
const { run_id, status, output_data } = data;
|
const { run_id, status, time, output_data } = data;
|
||||||
|
|
||||||
// console.log(run_id, status, output_data);
|
if (status == "started" && time != undefined) {
|
||||||
|
// It successfully started, update the started_at time
|
||||||
|
await db
|
||||||
|
.update(workflowRunsTable)
|
||||||
|
.set({
|
||||||
|
started_at: time,
|
||||||
|
})
|
||||||
|
.where(eq(workflowRunsTable.id, run_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == "queued" && time != undefined) {
|
||||||
|
// It successfully started, update the started_at time
|
||||||
|
await db
|
||||||
|
.update(workflowRunsTable)
|
||||||
|
.set({
|
||||||
|
queued_at: time,
|
||||||
|
})
|
||||||
|
.where(eq(workflowRunsTable.id, run_id));
|
||||||
|
}
|
||||||
|
|
||||||
if (output_data) {
|
if (output_data) {
|
||||||
const workflow_run_output = await db.insert(workflowRunOutputs).values({
|
const workflow_run_output = await db.insert(workflowRunOutputs).values({
|
||||||
@ -82,12 +100,6 @@ export async function POST(request: Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// const workflow_version = await db.query.workflowVersionTable.findFirst({
|
|
||||||
// where: eq(workflowRunsTable.id, workflow_run[0].workflow_version_id),
|
|
||||||
// });
|
|
||||||
|
|
||||||
// revalidatePath(`./${workflow_version?.workflow_id}`);
|
|
||||||
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
message: "success",
|
message: "success",
|
||||||
|
@ -2,18 +2,18 @@ import { RunInputs } from "@/components/RunInputs";
|
|||||||
import { RunOutputs } from "@/components/RunOutputs";
|
import { RunOutputs } from "@/components/RunOutputs";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { TableCell, TableRow } from "@/components/ui/table";
|
import { TableCell, TableRow } from "@/components/ui/table";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { getDuration, getRelativeTime } from "@/lib/getRelativeTime";
|
import { getDuration, getRelativeTime } from "@/lib/getRelativeTime";
|
||||||
import { type findAllRuns } from "@/server/findAllRuns";
|
import { type findAllRuns } from "@/server/findAllRuns";
|
||||||
@ -21,54 +21,54 @@ import { Suspense } from "react";
|
|||||||
import { LiveStatus } from "./LiveStatus";
|
import { LiveStatus } from "./LiveStatus";
|
||||||
|
|
||||||
export async function RunDisplay({
|
export async function RunDisplay({
|
||||||
run,
|
run,
|
||||||
}: {
|
}: {
|
||||||
run: Awaited<ReturnType<typeof findAllRuns>>[0];
|
run: Awaited<ReturnType<typeof findAllRuns>>[0];
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
|
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>{run.number}</TableCell>
|
<TableCell>{run.number}</TableCell>
|
||||||
<TableCell className="font-medium truncate">
|
<TableCell className="font-medium truncate">
|
||||||
{run.machine?.name}
|
{run.machine?.name}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="truncate">
|
<TableCell className="truncate">
|
||||||
{getRelativeTime(run.created_at)}
|
{getRelativeTime(run.created_at)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{run.version?.version}</TableCell>
|
<TableCell>{run.version?.version}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge variant="outline" className="truncate">
|
<Badge variant="outline" className="truncate">
|
||||||
{run.origin}
|
{run.origin}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="truncate">
|
<TableCell className="truncate">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>{getDuration(run.duration)}</TooltipTrigger>
|
<TooltipTrigger>{getDuration(run.duration)}</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<div>Cold start: {getDuration(run.cold_start_duration)}</div>
|
<div>Cold start: {getDuration(run.cold_start_duration)}</div>
|
||||||
<div>Run duration: {getDuration(run.run_duration)}</div>
|
<div>Run duration: {getDuration(run.run_duration)}</div>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<LiveStatus run={run} />
|
<LiveStatus run={run} />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-w-3xl">
|
<DialogContent className="max-w-3xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Run outputs</DialogTitle>
|
<DialogTitle>Run outputs</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
You can view your run's outputs here
|
You can view your run's outputs here
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="max-h-96 overflow-y-scroll">
|
<div className="max-h-96 overflow-y-scroll">
|
||||||
<RunInputs run={run} />
|
<RunInputs run={run} />
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<RunOutputs run_id={run.id} />
|
<RunOutputs run_id={run.id} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="max-h-96 overflow-y-scroll">{view}</div> */}
|
{/* <div className="max-h-96 overflow-y-scroll">{view}</div> */}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ export const workflowVersionRelations = relations(
|
|||||||
fields: [workflowVersionTable.workflow_id],
|
fields: [workflowVersionTable.workflow_id],
|
||||||
references: [workflowTable.id],
|
references: [workflowTable.id],
|
||||||
}),
|
}),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const workflowRunStatus = pgEnum("workflow_run_status", [
|
export const workflowRunStatus = pgEnum("workflow_run_status", [
|
||||||
@ -102,6 +102,8 @@ export const workflowRunStatus = pgEnum("workflow_run_status", [
|
|||||||
"uploading",
|
"uploading",
|
||||||
"success",
|
"success",
|
||||||
"failed",
|
"failed",
|
||||||
|
"started",
|
||||||
|
"queued",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const deploymentEnvironment = pgEnum("deployment_environment", [
|
export const deploymentEnvironment = pgEnum("deployment_environment", [
|
||||||
@ -116,6 +118,8 @@ export const workflowRunOrigin = pgEnum("workflow_run_origin", [
|
|||||||
"public-share",
|
"public-share",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const WorkflowRunStatusSchema = z.enum(workflowRunStatus.enumValues);
|
||||||
|
|
||||||
export const WorkflowRunOriginSchema = z.enum(workflowRunOrigin.enumValues);
|
export const WorkflowRunOriginSchema = z.enum(workflowRunOrigin.enumValues);
|
||||||
export type WorkflowRunOriginType = z.infer<typeof WorkflowRunOriginSchema>;
|
export type WorkflowRunOriginType = z.infer<typeof WorkflowRunOriginSchema>;
|
||||||
|
|
||||||
@ -142,7 +146,7 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", {
|
|||||||
() => workflowVersionTable.id,
|
() => workflowVersionTable.id,
|
||||||
{
|
{
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
workflow_inputs:
|
workflow_inputs:
|
||||||
jsonb("workflow_inputs").$type<Record<string, string | number>>(),
|
jsonb("workflow_inputs").$type<Record<string, string | number>>(),
|
||||||
@ -158,7 +162,11 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", {
|
|||||||
origin: workflowRunOrigin("origin").notNull().default("api"),
|
origin: workflowRunOrigin("origin").notNull().default("api"),
|
||||||
status: workflowRunStatus("status").notNull().default("not-started"),
|
status: workflowRunStatus("status").notNull().default("not-started"),
|
||||||
ended_at: timestamp("ended_at"),
|
ended_at: timestamp("ended_at"),
|
||||||
|
// comfy deploy run created time
|
||||||
created_at: timestamp("created_at").defaultNow().notNull(),
|
created_at: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
// modal gpu cold start begin
|
||||||
|
queued_at: timestamp("queued_at"),
|
||||||
|
// modal gpu function actual start time
|
||||||
started_at: timestamp("started_at"),
|
started_at: timestamp("started_at"),
|
||||||
gpu: machineGPUOptions("gpu"),
|
gpu: machineGPUOptions("gpu"),
|
||||||
machine_type: machinesType("machine_type"),
|
machine_type: machinesType("machine_type"),
|
||||||
@ -182,7 +190,7 @@ export const workflowRunRelations = relations(
|
|||||||
fields: [workflowRunsTable.workflow_id],
|
fields: [workflowRunsTable.workflow_id],
|
||||||
references: [workflowTable.id],
|
references: [workflowTable.id],
|
||||||
}),
|
}),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// We still want to keep the workflow run record.
|
// We still want to keep the workflow run record.
|
||||||
@ -206,7 +214,7 @@ export const workflowOutputRelations = relations(
|
|||||||
fields: [workflowRunOutputs.run_id],
|
fields: [workflowRunOutputs.run_id],
|
||||||
references: [workflowRunsTable.id],
|
references: [workflowRunsTable.id],
|
||||||
}),
|
}),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// when user delete, also delete all the workflow versions
|
// when user delete, also delete all the workflow versions
|
||||||
@ -239,7 +247,7 @@ export const snapshotType = z.object({
|
|||||||
z.object({
|
z.object({
|
||||||
hash: z.string(),
|
hash: z.string(),
|
||||||
disabled: z.boolean(),
|
disabled: z.boolean(),
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
file_custom_nodes: z.array(z.any()),
|
file_custom_nodes: z.array(z.any()),
|
||||||
});
|
});
|
||||||
@ -254,7 +262,7 @@ export const showcaseMedia = z.array(
|
|||||||
z.object({
|
z.object({
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
isCover: z.boolean().default(false),
|
isCover: z.boolean().default(false),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const showcaseMediaNullable = z
|
export const showcaseMediaNullable = z
|
||||||
@ -262,7 +270,7 @@ export const showcaseMediaNullable = z
|
|||||||
z.object({
|
z.object({
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
isCover: z.boolean().default(false),
|
isCover: z.boolean().default(false),
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
.nullable();
|
.nullable();
|
||||||
|
|
||||||
@ -376,15 +384,10 @@ export const modelUploadType = pgEnum("model_upload_type", [
|
|||||||
"other",
|
"other",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// https://www.answeroverflow.com/m/1125106227387584552
|
// https://www.answeroverflow.com/m/1125106227387584552
|
||||||
const modelTypes = [
|
const modelTypes = ["checkpoint", "lora", "embedding", "vae"] as const;
|
||||||
"checkpoint",
|
|
||||||
"lora",
|
|
||||||
"embedding",
|
|
||||||
"vae",
|
|
||||||
] as const
|
|
||||||
export const modelType = pgEnum("model_type", modelTypes);
|
export const modelType = pgEnum("model_type", modelTypes);
|
||||||
export type modelEnumType = typeof modelTypes[number]
|
export type modelEnumType = (typeof modelTypes)[number];
|
||||||
|
|
||||||
export const modelTable = dbSchema.table("models", {
|
export const modelTable = dbSchema.table("models", {
|
||||||
id: uuid("id").primaryKey().defaultRandom().notNull(),
|
id: uuid("id").primaryKey().defaultRandom().notNull(),
|
||||||
@ -447,16 +450,13 @@ export const modelRelations = relations(modelTable, ({ one }) => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const modalVolumeRelations = relations(
|
export const modalVolumeRelations = relations(userVolume, ({ many, one }) => ({
|
||||||
userVolume,
|
model: many(modelTable),
|
||||||
({ many, one }) => ({
|
user: one(usersTable, {
|
||||||
model: many(modelTable),
|
fields: [userVolume.user_id],
|
||||||
user: one(usersTable, {
|
references: [usersTable.id],
|
||||||
fields: [userVolume.user_id],
|
}),
|
||||||
references: [usersTable.id],
|
}));
|
||||||
}),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export const subscriptionPlan = pgEnum("subscription_plan", [
|
export const subscriptionPlan = pgEnum("subscription_plan", [
|
||||||
"basic",
|
"basic",
|
||||||
@ -484,18 +484,15 @@ export const subscriptionStatusTable = dbSchema.table("subscription_status", {
|
|||||||
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const insertCivitaiModelSchema = createInsertSchema(
|
export const insertCivitaiModelSchema = createInsertSchema(modelTable, {
|
||||||
modelTable,
|
civitai_url: (schema) =>
|
||||||
{
|
schema.civitai_url
|
||||||
civitai_url: (schema) =>
|
.trim()
|
||||||
schema.civitai_url
|
.url({ message: "URL required" })
|
||||||
.trim()
|
.includes("civitai.com/models", {
|
||||||
.url({ message: "URL required" })
|
message: "civitai.com/models link required",
|
||||||
.includes("civitai.com/models", {
|
}),
|
||||||
message: "civitai.com/models link required",
|
});
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export type UserType = InferSelectModel<typeof usersTable>;
|
export type UserType = InferSelectModel<typeof usersTable>;
|
||||||
export type WorkflowType = InferSelectModel<typeof workflowTable>;
|
export type WorkflowType = InferSelectModel<typeof workflowTable>;
|
||||||
@ -503,7 +500,5 @@ export type MachineType = InferSelectModel<typeof machinesTable>;
|
|||||||
export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>;
|
export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>;
|
||||||
export type DeploymentType = InferSelectModel<typeof deploymentsTable>;
|
export type DeploymentType = InferSelectModel<typeof deploymentsTable>;
|
||||||
export type ModelType = InferSelectModel<typeof modelTable>;
|
export type ModelType = InferSelectModel<typeof modelTable>;
|
||||||
export type UserVolumeType = InferSelectModel<
|
export type UserVolumeType = InferSelectModel<typeof userVolume>;
|
||||||
typeof userVolume
|
|
||||||
>;
|
|
||||||
export type UserUsageType = InferSelectModel<typeof userUsageTable>;
|
export type UserUsageType = InferSelectModel<typeof userUsageTable>;
|
||||||
|
@ -229,15 +229,6 @@ export const createRun = withServerPromise(
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It successfully started, update the started_at time
|
|
||||||
|
|
||||||
await db
|
|
||||||
.update(workflowRunsTable)
|
|
||||||
.set({
|
|
||||||
started_at: new Date(),
|
|
||||||
})
|
|
||||||
.where(eq(workflowRunsTable.id, workflow_run[0].id));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
workflow_run_id: workflow_run[0].id,
|
workflow_run_id: workflow_run[0].id,
|
||||||
message: "Successful workflow run",
|
message: "Successful workflow run",
|
||||||
|
@ -16,42 +16,40 @@ export async function findAllRuns({
|
|||||||
offset = 0,
|
offset = 0,
|
||||||
}: RunsSearchTypes) {
|
}: RunsSearchTypes) {
|
||||||
return await db.query.workflowRunsTable.findMany({
|
return await db.query.workflowRunsTable.findMany({
|
||||||
where: eq(workflowRunsTable.workflow_id, workflow_id),
|
where: eq(workflowRunsTable.workflow_id, workflow_id),
|
||||||
orderBy: desc(workflowRunsTable.created_at),
|
orderBy: desc(workflowRunsTable.created_at),
|
||||||
offset: offset,
|
offset: offset,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
extras: {
|
extras: {
|
||||||
number: sql<number>`row_number() over (order by created_at)`.as(
|
number: sql<number>`row_number() over (order by created_at)`.as("number"),
|
||||||
"number",
|
total: sql<number>`count(*) over ()`.as("total"),
|
||||||
),
|
duration:
|
||||||
total: sql<number>`count(*) over ()`.as("total"),
|
sql<number>`(extract(epoch from ended_at) - extract(epoch from created_at))`.as(
|
||||||
duration:
|
"duration",
|
||||||
sql<number>`(extract(epoch from ended_at) - extract(epoch from created_at))`.as(
|
),
|
||||||
"duration",
|
cold_start_duration:
|
||||||
),
|
sql<number>`(extract(epoch from started_at) - extract(epoch from queued_at))`.as(
|
||||||
cold_start_duration:
|
"cold_start_duration",
|
||||||
sql<number>`(extract(epoch from started_at) - extract(epoch from created_at))`.as(
|
),
|
||||||
"cold_start_duration",
|
run_duration:
|
||||||
),
|
sql<number>`(extract(epoch from ended_at) - extract(epoch from started_at))`.as(
|
||||||
run_duration:
|
"run_duration",
|
||||||
sql<number>`(extract(epoch from ended_at) - extract(epoch from started_at))`.as(
|
),
|
||||||
"run_duration",
|
},
|
||||||
),
|
with: {
|
||||||
},
|
machine: {
|
||||||
with: {
|
columns: {
|
||||||
machine: {
|
name: true,
|
||||||
columns: {
|
endpoint: true,
|
||||||
name: true,
|
},
|
||||||
endpoint: true,
|
},
|
||||||
},
|
version: {
|
||||||
},
|
columns: {
|
||||||
version: {
|
version: true,
|
||||||
columns: {
|
},
|
||||||
version: true,
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function findAllRunsWithCounts(props: RunsSearchTypes) {
|
export async function findAllRunsWithCounts(props: RunsSearchTypes) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user