Skip to content

Commit 74ea219

Browse files
authored
chore(middleware-http-debug-log): add middleware timer (#7126)
* chore(middleware-http-debug-log): add middleware timer * chore: typo
1 parent 6048181 commit 74ea219

File tree

3 files changed

+150
-0
lines changed

3 files changed

+150
-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: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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 none 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+
const middlewareNames =
26+
options.middlewareNames ??
27+
(["serializerMiddleware", "deserializerMiddleware", "httpSigningMiddleware", "retryMiddleware"] as string[]);
28+
29+
return {
30+
applyToStack(middlewareStack: MiddlewareStack<any, any>) {
31+
for (const mw of middlewareNames) {
32+
middlewareStack.addRelativeTo(
33+
(next: SerializeHandler<any, any>, context: HandlerExecutionContext) =>
34+
async (args: SerializeHandlerArguments<any>) => {
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].trailerPreNext = performance.now();
52+
const handlerOutput = await next(args);
53+
context.middlewareTimings[mw].trailerPostNext = performance.now();
54+
return handlerOutput;
55+
},
56+
{
57+
name: mw + "-after",
58+
override: true,
59+
toMiddleware: mw,
60+
relation: "after",
61+
}
62+
);
63+
}
64+
65+
middlewareStack.add(
66+
(next: InitializeHandler<any, any>, context: HandlerExecutionContext) =>
67+
async (args: InitializeHandlerArguments<any>) => {
68+
context.middlewareTimings = context.middlewareTimings ?? ({} as Record<string, Record<string, number>>);
69+
for (const mw of middlewareNames) {
70+
context.middlewareTimings[mw] = {} as Record<string, number>;
71+
}
72+
73+
const handlerOutput = await next(args);
74+
75+
for (const [mw, timings] of Object.entries(
76+
context.middlewareTimings as Record<string, Record<string, number>>
77+
)) {
78+
const { leaderPreNext, trailerPreNext, trailerPostNext, leaderPostNext } = timings;
79+
/**
80+
* Formula note:
81+
*
82+
* Let M be the middleware we are measuring.
83+
* Let L be the middleware prior to the measured middleware,
84+
* and T be the middleware after the measured middleware.
85+
*
86+
* We take timings before L calls next, after L calls next,
87+
* before T calls next, and after T calls next.
88+
*
89+
* The own-time of M is therefore the period from:
90+
* L-pre-next (before entering M from L) -> T-pre-next (after exiting M to T)
91+
* plus
92+
* T-post-next (return to M from T) -> L-post-next (exiting M to L)
93+
*/
94+
const middlewareOwnTime = trailerPreNext - leaderPreNext + leaderPostNext - trailerPostNext;
95+
96+
if (options.storage) {
97+
options.storage[mw] = options.storage[mw] ?? [];
98+
options.storage[mw].push(middlewareOwnTime);
99+
} else {
100+
console.log({
101+
[mw]: middlewareOwnTime,
102+
});
103+
}
104+
}
105+
return handlerOutput;
106+
},
107+
{
108+
name: "middleware-timing-logger-middleware",
109+
override: true,
110+
step: "initialize",
111+
}
112+
);
113+
},
114+
};
115+
}
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)