Skip to content

Commit b2ac664

Browse files
committed
chore(middleware-http-debug-log): add middleware timer
1 parent d7e8fb4 commit b2ac664

File tree

3 files changed

+147
-0
lines changed

3 files changed

+147
-0
lines changed

packages/middleware-http-debug-log/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,37 @@ Output (data redacted):
9494
</ListAllMyBucketsResult>
9595
<<<=== Response Body End ======
9696
```
97+
98+
## Usage (middleware timing)
99+
100+
```ts
101+
import { DynamoDB } from "@aws-sdk/client-dynamodb";
102+
import { getMiddlewareTimerPlugin } from "@aws-sdk/middleware-http-debug-log";
103+
104+
const client = new DynamoDB();
105+
106+
const storage = {};
107+
108+
client.middlewareStack.use(getMiddlewareTimerPlugin({ storage }));
109+
110+
await client.listTables({});
111+
await client.listTables({});
112+
await client.listTables({});
113+
114+
console.log({
115+
storage,
116+
});
117+
```
118+
119+
Output:
120+
121+
```
122+
{
123+
storage: {
124+
serializerMiddleware: [ 4.506417000000056, 0.24324999999998909, 0.22945800000002237 ],
125+
retryMiddleware: [ 0.3532910000000129, 0.06491599999992559, 0.08433399999989888 ],
126+
httpSigningMiddleware: [ 2.129708000000022, 0.6848749999999768, 0.6588739999999689 ],
127+
deserializerMiddleware: [ 3.7174159999999574, 0.8131239999999593, 0.9889160000001311 ]
128+
}
129+
}
130+
```
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import {
2+
HandlerExecutionContext,
3+
InitializeHandler,
4+
InitializeHandlerArguments,
5+
MiddlewareStack,
6+
SerializeHandler,
7+
SerializeHandlerArguments,
8+
} from "@smithy/types";
9+
10+
/**
11+
* FOR DEBUG USE ONLY.
12+
* This plugin logs or records the timings of the middleware named in options.middlewareNames.
13+
*
14+
* @param options.middlewareNames - names of the middleware to measure.
15+
* @param options.storage - a container for the data. If non is provided, metrics will be logged to console.
16+
*
17+
* @beta
18+
*/
19+
export function getMiddlewareTimerPlugin(
20+
options: {
21+
middlewareNames?: string[];
22+
storage?: Record<string, number[]>;
23+
} = {}
24+
) {
25+
return {
26+
applyToStack(middlewareStack: MiddlewareStack<any, any>) {
27+
const middlewareNames =
28+
options.middlewareNames ??
29+
(["serializerMiddleware", "deserializerMiddleware", "httpSigningMiddleware", "retryMiddleware"] as string[]);
30+
for (const mw of middlewareNames) {
31+
middlewareStack.addRelativeTo(
32+
(next: SerializeHandler<any, any>, context: HandlerExecutionContext) =>
33+
async (args: SerializeHandlerArguments<any>) => {
34+
context.middlewareTimings[mw] = context.middlewareTimings[mw] ?? ({} as Record<string, number>);
35+
context.middlewareTimings[mw].leaderPreNext = performance.now();
36+
const handlerOutput = await next(args);
37+
context.middlewareTimings[mw].leaderPostNext = performance.now();
38+
return handlerOutput;
39+
},
40+
{
41+
name: mw + "-before",
42+
override: true,
43+
toMiddleware: mw,
44+
relation: "before",
45+
}
46+
);
47+
48+
middlewareStack.addRelativeTo(
49+
(next: SerializeHandler<any, any>, context: HandlerExecutionContext) =>
50+
async (args: SerializeHandlerArguments<any>) => {
51+
context.middlewareTimings[mw] = context.middlewareTimings[mw] ?? ({} as Record<string, number>);
52+
context.middlewareTimings[mw].trailerPreNext = performance.now();
53+
const r = await next(args);
54+
context.middlewareTimings[mw].trailerPostNext = performance.now();
55+
return r;
56+
},
57+
{
58+
name: mw + "-after",
59+
override: true,
60+
toMiddleware: mw,
61+
relation: "after",
62+
}
63+
);
64+
}
65+
66+
middlewareStack.add(
67+
(next: InitializeHandler<any, any>, context: HandlerExecutionContext) =>
68+
async (args: InitializeHandlerArguments<any>) => {
69+
context.middlewareTimings = context.middlewareTimings ?? ({} as Record<string, Record<string, number>>);
70+
const handlerOutput = await next(args);
71+
72+
for (const [mw, timings] of Object.entries(
73+
context.middlewareTimings as Record<string, Record<string, number>>
74+
)) {
75+
const { leaderPreNext, trailerPreNext, trailerPostNext, leaderPostNext } = timings;
76+
/**
77+
* Formula note:
78+
*
79+
* Let M be the middleware we are measuring.
80+
* Let L be the middleware prior to the measured middleware,
81+
* and T be the middleware after the measured middleware.
82+
*
83+
* We take timings before L calls next, after L calls next,
84+
* before T calls next, and after T calls next.
85+
*
86+
* The own-time of M is therefore the period from:
87+
* L-pre-next (before entering M from L) -> T-pre-next (after exiting M to T)
88+
* plus
89+
* T-post-next (return to M from T) -> L-post-next (exiting M to L)
90+
*/
91+
const middlewareOwnTime = trailerPreNext - leaderPreNext + leaderPostNext - trailerPostNext;
92+
93+
if (options.storage) {
94+
options.storage[mw] = options.storage[mw] ?? [];
95+
options.storage[mw].push(middlewareOwnTime);
96+
} else {
97+
console.log({
98+
[mw]: middlewareOwnTime,
99+
});
100+
}
101+
}
102+
return handlerOutput;
103+
},
104+
{
105+
name: "middleware-timing-logger-middleware",
106+
override: true,
107+
step: "initialize",
108+
}
109+
);
110+
},
111+
};
112+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { getHttpDebugLogPlugin, type HttpDebugLoggingOptions } from "./getHttpDebugLogPlugin";
2+
export { getMiddlewareTimerPlugin } from "./getMiddlewareTimerPlugin";

0 commit comments

Comments
 (0)