Skip to content

Report Response, Request, NodeHTTPResponse memory cost more accurately #21595

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/bun-uws/src/AsyncSocketData.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ struct BackPressure {
size_t totalLength() {
return buffer.length();
}

size_t memoryCost() const {
return buffer.capacity();
}
};

/* Depending on how we want AsyncSocket to function, this will need to change */
Expand All @@ -83,6 +87,10 @@ struct AsyncSocketData {

/* Or empty */
AsyncSocketData() = default;

size_t memoryCost() const {
return buffer.memoryCost();
}
};

}
Expand Down
3 changes: 3 additions & 0 deletions packages/bun-uws/src/HttpResponse.h
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,9 @@ struct HttpResponse : public AsyncSocket<SSL> {
httpResponseData->offset = offset;
}

size_t memoryCost() {
return sizeof(HttpResponse<SSL>) + this->getHttpResponseData()->memoryCost();
}
};

}
Expand Down
4 changes: 4 additions & 0 deletions packages/bun-uws/src/HttpResponseData.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
#ifdef UWS_WITH_PROXY
ProxyParser proxyParser;
#endif

size_t memoryCost() {
return sizeof(HttpResponseData<SSL>) + this->AsyncSocketData<SSL>::memoryCost();
}
};

}
2 changes: 1 addition & 1 deletion packages/bun-uws/src/WebSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ struct WebSocket : AsyncSocket<SSL> {
};

size_t memoryCost() {
return getBufferedAmount() + sizeof(WebSocket);
return sizeof(WebSocket<SSL, isServer, USERDATA>) + this->getAsyncSocketData()->memoryCost();
}

/* Sending fragmented messages puts a bit of effort on the user; you must not interleave regular sends
Expand Down
9 changes: 9 additions & 0 deletions src/bun.js/api/server/NodeHTTPResponse.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,15 @@ fn handleCorked(globalObject: *jsc.JSGlobalObject, function: jsc.JSValue, result
};
}

pub fn memoryCost(this: *const NodeHTTPResponse) usize {
var counter: usize = @sizeOf(NodeHTTPResponse);
counter += this.buffered_request_body_data_during_pause.memoryCost();
if (!this.flags.socket_closed) {
counter += this.raw_response.memoryCost();
}
return counter;
}

pub fn setTimeout(this: *NodeHTTPResponse, seconds: u8) void {
if (this.flags.request_has_completed or this.flags.socket_closed) {
return;
Expand Down
6 changes: 5 additions & 1 deletion src/bun.js/api/server/RequestContext.zig
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool,

pub fn memoryCost(this: *const RequestContext) usize {
// The Sink and ByteStream aren't owned by this.
return @sizeOf(RequestContext) + this.request_body_buf.capacity + this.response_buf_owned.capacity + this.blob.memoryCost();
return @sizeOf(RequestContext) +
this.request_body_buf.capacity +
this.response_buf_owned.capacity +
this.blob.memoryCost() +
(if (this.resp) |response| response.memoryCost() else 0);
}

pub inline fn isAsync(this: *const RequestContext) bool {
Expand Down
9 changes: 9 additions & 0 deletions src/bun.js/webcore/Body.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ pub fn clone(this: *Body, globalThis: *JSGlobalObject) bun.JSError!Body {
};
}

pub fn memoryCost(this: *const Body) usize {
return this.value.memoryCost();
}

pub fn estimatedSize(this: *const Body) usize {
return this.value.estimatedSize();
}

pub fn writeFormat(this: *Body, comptime Formatter: type, formatter: *Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
const Writer = @TypeOf(writer);

Expand Down Expand Up @@ -392,6 +400,7 @@ pub const Value = union(Tag) {
return switch (this.*) {
.InternalBlob => this.InternalBlob.bytes.items.len,
.WTFStringImpl => this.WTFStringImpl.memoryCost(),
// TODO: make this track if the readablestream has been set as the current JSValue
.Locked => this.Locked.sizeHint(),
// .InlineBlob => this.InlineBlob.sliceConst().len,
else => 0,
Expand Down
13 changes: 13 additions & 0 deletions src/bun.js/webcore/Response.zig
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ pub fn estimatedSize(this: *Response) callconv(.C) usize {
return this.reported_estimated_size;
}

pub fn memoryCost(this: *const Response) usize {
var counter: usize = @sizeOf(Response);
counter += this.body.memoryCost();
counter += this.url.estimatedSize();
counter += this.init.memoryCost();

return counter;
}

pub fn calculateEstimatedByteSize(this: *Response) void {
this.reported_estimated_size = this.body.value.estimatedSize() +
this.url.byteSlice().len +
Expand Down Expand Up @@ -614,6 +623,10 @@ pub const Init = struct {
status_text: bun.String = bun.String.empty,
method: Method = Method.GET,

pub fn memoryCost(this: *const Init) usize {
return this.status_text.estimatedSize();
}

pub fn clone(this: Init, ctx: *JSGlobalObject) bun.JSError!Init {
var that = this;
const headers = this.headers;
Expand Down
1 change: 1 addition & 0 deletions src/bun.js/webcore/response.classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export default [
JSType: "0b11101110",
configurable: false,
estimatedSize: true,
memoryCost: true,
overridesToJS: true,
klass: {
json: {
Expand Down
8 changes: 8 additions & 0 deletions src/deps/libuwsockets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1807,6 +1807,14 @@ __attribute__((callback (corker, ctx)))
}
}

size_t uws_res_memory_cost(int ssl, uws_res_r res) {
if (ssl) {
return ((uWS::HttpResponse<true>*)res)->memoryCost();
} else {
return ((uWS::HttpResponse<false>*)res)->memoryCost();
}
}

size_t uws_ws_memory_cost(int ssl, uws_websocket_t *ws) {
if (ssl) {
return ((TLSWebSocket*)ws)->memoryCost();
Expand Down
12 changes: 12 additions & 0 deletions src/deps/uws/Response.zig
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,10 @@ pub fn NewResponse(ssl_flag: i32) type {
ctx,
);
}

pub fn memoryCost(this: *Response) usize {
return c.uws_res_memory_cost(ssl_flag, this.downcast());
}
};
}

Expand All @@ -316,6 +320,13 @@ pub const AnyResponse = union(enum) {
SSL: *uws.NewApp(true).Response,
TCP: *uws.NewApp(false).Response,

pub fn memoryCost(this: AnyResponse) usize {
return switch (this) {
.SSL => |resp| resp.memoryCost(),
.TCP => |resp| resp.memoryCost(),
};
}

pub fn assertSSL(this: AnyResponse) *uws.NewApp(true).Response {
return switch (this) {
.SSL => |resp| resp,
Expand Down Expand Up @@ -670,6 +681,7 @@ const c = struct {
pub extern fn uws_res_prepare_for_sendfile(ssl: i32, res: *c.uws_res) void;
pub extern fn uws_res_get_native_handle(ssl: i32, res: *c.uws_res) *Socket;
pub extern fn uws_res_get_remote_address_as_text(ssl: i32, res: *c.uws_res, dest: *[*]const u8) usize;
pub extern fn uws_res_memory_cost(ssl: i32, res: *c.uws_res) usize;

pub extern fn uws_res_on_data(
ssl: i32,
Expand Down
19 changes: 19 additions & 0 deletions test/js/bun/util/heap-snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ describe("Native types report their size correctly", () => {
delete globalThis.request;
});

it("Response includes status_text", () => {
var withStatusText = new Response("hello", {
headers: {
"Content-Type": "text/plain",
},
statusText: "yo yo yo",
});

var withoutStatusText = new Response("hello", {
headers: {
"Content-Type": "text/plain",
},
});

expect(estimateShallowMemoryUsageOf(withStatusText)).toBeGreaterThan(
estimateShallowMemoryUsageOf(withoutStatusText),
);
});

it("Response", () => {
var response = new Response(Buffer.alloc(1024 * 1024 * 4, "yoo"), {
headers: {
Expand Down