From 1678a227ed811ce1a817873cf3fffec4d5c39f29 Mon Sep 17 00:00:00 2001 From: John Smith Date: Thu, 11 Jan 2024 07:55:16 +1030 Subject: [PATCH 1/4] Render service definition in front end --- .../[serviceName]/ServiceLiveTables.tsx | 27 ++---- admin/client/contract.ts | 24 +++--- control-plane/src/modules/contract.ts | 24 +++--- control-plane/src/modules/management.ts | 82 +++++++------------ 4 files changed, 62 insertions(+), 95 deletions(-) diff --git a/admin/app/clusters/[clusterId]/services/[serviceName]/ServiceLiveTables.tsx b/admin/app/clusters/[clusterId]/services/[serviceName]/ServiceLiveTables.tsx index 244acc48..b6d9fdb7 100644 --- a/admin/app/clusters/[clusterId]/services/[serviceName]/ServiceLiveTables.tsx +++ b/admin/app/clusters/[clusterId]/services/[serviceName]/ServiceLiveTables.tsx @@ -32,10 +32,9 @@ export function ServiceLiveTables({ name: string; functions: Array<{ name: string; - totalSuccess: number; - totalFailure: number; - avgExecutionTimeSuccess: number | null; - avgExecutionTimeFailure: number | null; + idempotent: boolean | null; + rate: {per: 'minute' | 'hour', limit: number} | null; + cacheTTL: number | null; }>; } }>({ @@ -86,23 +85,9 @@ export function ServiceLiveTables({ ({ Function: s.name, - "Total Requests": s.totalSuccess + s.totalFailure, - "Failure Rate": `${( - (s.totalFailure / (s.totalSuccess + s.totalFailure)) * - 99 - ).toFixed(2)}%`, - "Average Execution Time (Success)": `${ - s.avgExecutionTimeSuccess === undefined || - s.avgExecutionTimeSuccess === null - ? "N/A" - : `${s.avgExecutionTimeSuccess?.toFixed(2)}ms` - }`, - "Average Execution Time (Failure)": `${ - s.avgExecutionTimeFailure === undefined || - s.avgExecutionTimeFailure === null - ? "N/A" - : `${s.avgExecutionTimeFailure?.toFixed(2)}ms` - }`, + Idempotent: s.idempotent ? "Yes" : "No", + "Rate Limit": s.rate === null ? "N/A" : `${s.rate.limit}/${s.rate.per}`, + "Cache TTL": s.cacheTTL === null ? "N/A" : `${s.cacheTTL}s`, }))} noDataMessage="No functions have been detected recently." /> diff --git a/admin/client/contract.ts b/admin/client/contract.ts index ebfb4d61..746ddf8d 100644 --- a/admin/client/contract.ts +++ b/admin/client/contract.ts @@ -194,16 +194,20 @@ export const contract = c.router({ z.object({ name: z.string(), functions: z.array( - z.object({ - name: z.string(), - totalSuccess: z.number(), - totalFailure: z.number(), - avgExecutionTimeSuccess: z.number().nullable(), - avgExecutionTimeFailure: z.number().nullable(), - }) - ), - }) - ), + z.object({ + name: z.string(), + idempotent: z.boolean().nullable(), + rate: z + .object({ + per: z.enum(["minute", "hour"]), + limit: z.number(), + }) + .nullable(), + cacheTTL: z.number().nullable(), + }) + ) + }) + ) }), 401: z.undefined(), 404: z.undefined(), diff --git a/control-plane/src/modules/contract.ts b/control-plane/src/modules/contract.ts index bbec011a..24838bda 100644 --- a/control-plane/src/modules/contract.ts +++ b/control-plane/src/modules/contract.ts @@ -212,16 +212,20 @@ export const contract = c.router({ z.object({ name: z.string(), functions: z.array( - z.object({ - name: z.string(), - totalSuccess: z.number(), - totalFailure: z.number(), - avgExecutionTimeSuccess: z.number().nullable(), - avgExecutionTimeFailure: z.number().nullable(), - }) - ), - }) - ), + z.object({ + name: z.string(), + idempotent: z.boolean().nullable(), + rate: z + .object({ + per: z.enum(["minute", "hour"]), + limit: z.number(), + }) + .nullable(), + cacheTTL: z.number().nullable(), + }) + ) + }) + ) }), 401: z.undefined(), 404: z.undefined(), diff --git a/control-plane/src/modules/management.ts b/control-plane/src/modules/management.ts index 635b72f7..b2d9a7dc 100644 --- a/control-plane/src/modules/management.ts +++ b/control-plane/src/modules/management.ts @@ -57,10 +57,9 @@ export const createCluster = async ({ type FunctionDetails = { name: string; - avgExecutionTimeSuccess: number | null; - avgExecutionTimeFailure: number | null; - totalSuccess: number; - totalFailure: number; + idempotent: boolean | null; + rate: {per: 'minute' | 'hour', limit: number} | null; + cacheTTL: number | null; }; export const getClusterDetailsForUser = async ({ managementToken, @@ -158,65 +157,40 @@ export const getClusterDetailsForUser = async ({ ) ); - // Fetch all function / service combinations for the cluster within the last 12 hours - // This can be replaced with something more robust once we have a catalog of service / functions - let functions = await data.db + + const services = await data.db .select({ - service: data.jobs.service, - target_fn: data.jobs.target_fn, - avgExecutionTime: - sql`avg(${data.jobs.function_execution_time_ms})`.mapWith(Number), - total: sql`count(${data.jobs.id})`.mapWith(Number), - result_type: data.jobs.result_type, + service: data.services.service, + definition: data.services.definition, }) - .from(data.jobs) - .groupBy(data.jobs.service, data.jobs.target_fn, data.jobs.result_type) + .from(data.services) .where( and( - eq(data.jobs.owner_hash, clusterId), - // in the last 12 hours - gte(data.jobs.created_at, new Date(Date.now() - 1000 * 60 * 60 * 12)) + eq(data.services.cluster_id, clusterId), ) ); - // Build a map of service -> function -> details merging the error and success results - const serviceFnMap = functions.reduce( - (acc, current) => { - const serviceName = current.service; - if (!serviceName) { - return acc; + const serviceResult = services.map((service) => { + const definition = service.definition as any; + if (!definition || !Array.isArray(definition['functions'])) { + return { + name: service.service, + functions: [], } + } - const isSuccess = current.result_type === "resolution"; - - const service = acc.get(serviceName) ?? new Map(); - service.set(current.target_fn, { - ...(isSuccess - ? { avgExecutionTimeSuccess: current.avgExecutionTime } - : { avgExecutionTimeFailure: current.avgExecutionTime }), - ...(isSuccess - ? { totalSuccess: current.total } - : { totalFailure: current.total }), - ...service.get(current.target_fn), - }); - - acc.set(serviceName, service); - - return acc; - }, - new Map() as Map>> - ); - - const serviceResult = Array.from(serviceFnMap).map(([name, functionMap]) => ({ - name: name, - functions: Array.from(functionMap).map(([fnName, fnDetails]) => ({ - name: fnName, - ...fnDetails, - // If there is no success or failure, default to 0 - totalSuccess: fnDetails.totalSuccess ?? 0, - totalFailure: fnDetails.totalFailure ?? 0, - })), - })); + return { + name: service.service, + functions: definition.functions.map((fn: any) => { + return { + name: fn.name, + idempotent: fn.idempotent, + rate: fn.rate, + cacheTTL: fn.cacheTTL, + }; + }), + } + }) return { ...clusters[0], From b5178f7658dd37c36b78ecd01c5147ddddc70c8e Mon Sep 17 00:00:00 2001 From: John Smith Date: Thu, 11 Jan 2024 18:31:37 +1030 Subject: [PATCH 2/4] Correct ClusterLiveTable type information --- admin/app/clusters/[clusterId]/ClusterLiveTables.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/admin/app/clusters/[clusterId]/ClusterLiveTables.tsx b/admin/app/clusters/[clusterId]/ClusterLiveTables.tsx index 844c559d..8b82834f 100644 --- a/admin/app/clusters/[clusterId]/ClusterLiveTables.tsx +++ b/admin/app/clusters/[clusterId]/ClusterLiveTables.tsx @@ -48,10 +48,9 @@ export function ClusterLiveTables({ name: string; functions: Array<{ name: string; - totalSuccess: number; - totalFailure: number; - avgExecutionTimeSuccess: number | null; - avgExecutionTimeFailure: number | null; + idempotent: boolean | null; + rate: {per: 'minute' | 'hour', limit: number} | null; + cacheTTL: number | null; }>; }>; }>({ From 3084053c84245e78dd6946f60dd8591caf6760d2 Mon Sep 17 00:00:00 2001 From: Nadeesha Cabral Date: Fri, 12 Jan 2024 06:28:31 +1100 Subject: [PATCH 3/4] Fix the service tables --- .../[serviceName]/ServiceLiveTables.tsx | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/admin/app/clusters/[clusterId]/services/[serviceName]/ServiceLiveTables.tsx b/admin/app/clusters/[clusterId]/services/[serviceName]/ServiceLiveTables.tsx index 029e6784..d5b158fa 100644 --- a/admin/app/clusters/[clusterId]/services/[serviceName]/ServiceLiveTables.tsx +++ b/admin/app/clusters/[clusterId]/services/[serviceName]/ServiceLiveTables.tsx @@ -30,11 +30,11 @@ export function ServiceLiveTables({ }[]; service?: { name: string; - functions: Array<{ + functions?: Array<{ name: string; - idempotent: boolean | null; - rate: {per: 'minute' | 'hour', limit: number} | null; - cacheTTL: number | null; + idempotent?: boolean | null; + rate?: { per: "minute" | "hour"; limit: number } | null; + cacheTTL?: number | null; }>; }; }>({ @@ -62,7 +62,7 @@ export function ServiceLiveTables({ jobs: clusterResult.body.jobs .filter((f) => f.service == serviceName) .slice(-10), - service: clusterResult.body.services + service: clusterResult.body.definitions .filter((s) => s.name == serviceName) .pop(), }); @@ -86,13 +86,25 @@ export function ServiceLiveTables({ {(data.service !== undefined && (
+

Function Registry

+

+ These are the functions that are registered for this service. +

({ - Function: s.name, - Idempotent: s.idempotent ? "Yes" : "No", - "Rate Limit": s.rate === null ? "N/A" : `${s.rate.limit}/${s.rate.per}`, - "Cache TTL": s.cacheTTL === null ? "N/A" : `${s.cacheTTL}s`, - }))} + data={ + data.service.functions?.map((s) => ({ + Function: s.name, + Idempotent: s.idempotent ? "Yes" : "No", + "Rate Limit": + s.rate === null || s.rate === undefined + ? "N/A" + : `${s.rate?.limit}/${s.rate?.per}`, + "Cache TTL": + s.cacheTTL === null || s.cacheTTL === undefined + ? "N/A" + : `${s.cacheTTL}s`, + })) ?? [] + } noDataMessage="No functions have been detected recently." />
From 70199f90a3f094a0387cb89f411f5a31f95177e2 Mon Sep 17 00:00:00 2001 From: John Smith Date: Fri, 12 Jan 2024 07:59:15 +1030 Subject: [PATCH 4/4] Handle optional functions key --- .../clusters/[clusterId]/ClusterLiveTables.tsx | 15 ++------------- .../[clusterId]/services/ServiceSummary.tsx | 6 +++--- admin/client/contract.ts | 3 +-- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/admin/app/clusters/[clusterId]/ClusterLiveTables.tsx b/admin/app/clusters/[clusterId]/ClusterLiveTables.tsx index f3d33f25..31aaca64 100644 --- a/admin/app/clusters/[clusterId]/ClusterLiveTables.tsx +++ b/admin/app/clusters/[clusterId]/ClusterLiveTables.tsx @@ -43,25 +43,15 @@ export function ClusterLiveTables({ status: string; functionExecutionTime: number | null; }[]; - services: Array<{ - name: string; - functions: Array<{ - name: string; - idempotent: boolean | null; - rate: {per: 'minute' | 'hour', limit: number} | null; - cacheTTL: number | null; - }>; - }>; definitions: Array<{ name: string; - functions: Array<{ + functions?: Array<{ name: string; }>; }>; }>({ machines: [], jobs: [], - services: [], definitions: [], }); @@ -84,8 +74,7 @@ export function ClusterLiveTables({ setData({ machines: clusterResult.body.machines, jobs: clusterResult.body.jobs, - services: clusterResult.body.services, - definitions: clusterResult.body.definitions || [], + definitions: clusterResult.body.definitions, }); } else { toast.error("Failed to fetch cluster details."); diff --git a/admin/app/clusters/[clusterId]/services/ServiceSummary.tsx b/admin/app/clusters/[clusterId]/services/ServiceSummary.tsx index 085840bf..7ded48d9 100644 --- a/admin/app/clusters/[clusterId]/services/ServiceSummary.tsx +++ b/admin/app/clusters/[clusterId]/services/ServiceSummary.tsx @@ -11,7 +11,7 @@ import Link from "next/link"; const Service = (props: { clusterId: string; name: string; - functions: Array<{ name: string }>; + functions?: Array<{ name: string }>; }) => { return (

functions

    - {props.functions.map((fn) => ( + {props.functions?.map((fn) => (
  • {fn.name}()
  • @@ -40,7 +40,7 @@ const Service = (props: { export const ServiceSummary = (props: { clusterId: string; - services: Array<{ name: string; functions: Array<{ name: string }> }>; + services: Array<{ name: string; functions?: Array<{ name: string }> }>; }) => { return (
    diff --git a/admin/client/contract.ts b/admin/client/contract.ts index fc0c7744..cf7e2153 100644 --- a/admin/client/contract.ts +++ b/admin/client/contract.ts @@ -224,8 +224,7 @@ export const contract = c.router({ .optional(), cacheTTL: z.number().optional(), }) - ) - .optional(), + ).optional() }) ), }),