Skip to content

Commit

Permalink
Render service definition in front end
Browse files Browse the repository at this point in the history
  • Loading branch information
John Smith committed Jan 10, 2024
1 parent 3a17ee2 commit 5d5184e
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}>;
}
}>({
Expand Down Expand Up @@ -86,23 +85,9 @@ export function ServiceLiveTables({
<DataTable
data={data.service.functions.map((s) => ({
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."
/>
Expand Down
24 changes: 14 additions & 10 deletions admin/client/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
24 changes: 14 additions & 10 deletions control-plane/src/modules/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
82 changes: 28 additions & 54 deletions control-plane/src/modules/management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<string, Map<string, Omit<FunctionDetails, "name">>>
);

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],
Expand Down

0 comments on commit 5d5184e

Please sign in to comment.