Skip to content

Commit eba567a

Browse files
authored
Adds timings do actor lib (#39)
Signed-off-by: Marcos Candeia <marrcooos@gmail.com>
1 parent a4685c2 commit eba567a

File tree

4 files changed

+99
-8
lines changed

4 files changed

+99
-8
lines changed

src/actors/discover.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
type ActorRuntime,
77
StdActorRuntime,
88
} from "./runtime.ts";
9-
import { ActorCfRuntime } from "./runtimes/cf/fetcher.ts";
109

1110
const IS_DENO = getRuntimeKey() === "deno";
1211

@@ -20,7 +19,7 @@ export const RuntimeClass: {
2019
>(
2120
Actor: TConstructor,
2221
) => TConstructor;
23-
} = IS_DENO ? StdActorRuntime : ActorCfRuntime;
22+
} = IS_DENO ? StdActorRuntime : StdActorRuntime;
2423

2524
export const RUNTIME = new RuntimeClass();
2625
export const Actor = RuntimeClass.Actor;

src/actors/storage/denoKv.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function assertIsDefined<V>(
4040
): asserts v is NonNullable<V> {
4141
const isDefined = v !== null && typeof v !== "undefined";
4242
if (!isDefined) {
43-
throw new Error(`Expected 'v' to be defined, but received ${v}`);
43+
throw new Error(`Expected 'DenoKv' to be defined, but received ${v}`);
4444
}
4545
}
4646
interface AtomicOp {
@@ -58,7 +58,7 @@ export class DenoKvActorStorage implements ActorStorage {
5858
constructor(protected options: StorageOptions) {
5959
assertIsDefined(kv);
6060
this.kv = kv;
61-
this.kvOrTransaction = options.atomicOp?.kv ?? kv;
61+
this.kvOrTransaction = options.atomicOp?.kv ?? this.kv;
6262
this.atomicOp = options.atomicOp;
6363
}
6464
private warnAlams() {

src/actors/stub/stub.server.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
EVENT_STREAM_RESPONSE_HEADER,
66
isEventStreamResponse,
77
} from "../stream.ts";
8+
import { createServerTimings, type ServerTimingsBuilder } from "../timings.ts";
89
import { serializeUint8Array } from "../util/buffers.ts";
910
import { isUpgrade, makeWebSocket } from "../util/channels/channel.ts";
1011
import { invokeNameAndMethod } from "../util/locator.ts";
@@ -50,6 +51,7 @@ export interface InvokeMiddlewareOptions {
5051
args: unknown[];
5152
metadata: unknown;
5253
request: Request;
54+
timings: ServerTimingsBuilder;
5355
}
5456

5557
export type InvokeMiddleware = (
@@ -67,11 +69,16 @@ const hasInvokeMiddleware = (
6769

6870
const invokeResponse = async (
6971
url: URL,
70-
options: InvokeOptions<any> & { request: Request },
72+
options: InvokeOptions<any> & {
73+
request: Request;
74+
timings: ServerTimingsBuilder;
75+
},
7176
): Promise<Response> => {
7277
try {
7378
const { request: req } = options;
79+
const invokeTiming = options.timings.start("invoke");
7480
const res = await invoke(options);
81+
invokeTiming.end();
7582
if (isUpgrade(res) && req.headers.get("upgrade") === "websocket") {
7683
const { socket, response } = upgradeWebSocket(req);
7784
const chunkSize = url.searchParams.get(STUB_MAX_CHUNK_SIZE_QS_NAME);
@@ -179,18 +186,21 @@ export const server = <T extends object>(
179186
instanceCreator: (name: string, req: Request) => Promise<T>,
180187
): (req: Request) => Promise<Response> => {
181188
return async (req: Request) => {
189+
const timings = createServerTimings();
190+
using _ = timings.start("actor-entire-request");
182191
const url = new URL(req.url);
183192
const locator = invokeNameAndMethod(url.pathname);
184193

185194
if (!locator) {
186195
return new Response(null, { status: 404 });
187196
}
188197

198+
const instanceCreatorTiming = timings.start("actor-instance-creator");
189199
const objInstance = await instanceCreator(
190200
locator.name,
191201
req,
192202
);
193-
203+
instanceCreatorTiming.end();
194204
const { name: stubName, method: stubMethod } = locator;
195205
if (!stubMethod || !stubName) {
196206
return new Response(
@@ -227,18 +237,36 @@ export const server = <T extends object>(
227237
metadata = parsedArgs.metadata;
228238
}
229239
const next = (mid: InvokeMiddlewareOptions) => {
240+
const invokeResponseTiming = mid.timings.start("invoke-response");
230241
return invokeResponse(url, {
231242
instance: objInstance,
232243
stubName,
233244
methodName: mid.method,
234245
args: mid.args,
235246
metadata: mid.metadata,
236247
request: mid.request,
248+
timings: mid.timings,
249+
}).finally(() => invokeResponseTiming.end()).then((res) => {
250+
try {
251+
res.headers.set("server-timing", mid.timings.printTimings());
252+
} catch {
253+
// some headers are immutable
254+
}
255+
return res;
237256
});
238257
};
239-
const options = { args, metadata, request: req, method: stubMethod };
258+
const options = {
259+
args,
260+
metadata,
261+
request: req,
262+
method: stubMethod,
263+
timings,
264+
};
240265
if (hasInvokeMiddleware(objInstance)) {
241-
return objInstance.onBeforeInvoke(options, next);
266+
const onBeforeInvokeTiming = timings.start("on-before-invoke");
267+
return objInstance.onBeforeInvoke(options, next).finally(() =>
268+
onBeforeInvokeTiming.end()
269+
);
242270
}
243271
return next(options);
244272
};

src/actors/timings.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
export interface Timing {
2+
name: string;
3+
desc?: string;
4+
start: number;
5+
end?: number;
6+
}
7+
8+
export interface ServerTimingsBuilder {
9+
get: () => readonly Timing[];
10+
start: (
11+
name: string,
12+
desc?: string,
13+
start?: number,
14+
) => {
15+
[Symbol.dispose]: () => void;
16+
end: () => void;
17+
setDesc: (desc: string | undefined) => void;
18+
name: () => string;
19+
};
20+
printTimings: () => string;
21+
}
22+
23+
export function createServerTimings(): ServerTimingsBuilder {
24+
const timings: Timing[] = [];
25+
26+
const start = (name: string, desc?: string, start?: number) => {
27+
const t: Timing = { name, desc, start: start || performance.now() };
28+
29+
timings.push(t);
30+
31+
return {
32+
[Symbol.dispose]: () => {
33+
t.end = performance.now();
34+
},
35+
end: () => {
36+
t.end = performance.now();
37+
},
38+
setDesc: (desc: string | undefined) => {
39+
t.desc = desc;
40+
},
41+
name: () => t.name,
42+
};
43+
};
44+
45+
const printTimings = () =>
46+
timings
47+
.map(({ name, desc, start, end }) => {
48+
if (!end || !name) return;
49+
50+
return `${encodeURIComponent(name)}${desc ? `;desc=${desc}` : ""};dur=${
51+
(end - start).toFixed(0)
52+
}`;
53+
})
54+
.filter(Boolean)
55+
.join(", ");
56+
57+
const get = (): readonly Timing[] => timings;
58+
59+
return {
60+
get,
61+
start,
62+
printTimings,
63+
};
64+
}

0 commit comments

Comments
 (0)