mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 11:59:00 +00:00
massive progress
This commit is contained in:
@@ -73,4 +73,5 @@ pub const Classes = struct {
|
||||
pub const BytesInternalReadableStreamSource = JSC.WebCore.ByteStream.Source;
|
||||
pub const BrotliEncoder = JSC.API.BrotliEncoder;
|
||||
pub const BrotliDecoder = JSC.API.BrotliDecoder;
|
||||
pub const RecordableHistogram = JSC.Node.RecordableHistogram; // for new class, be sure to add here
|
||||
};
|
||||
|
||||
@@ -209,7 +209,10 @@ static const HashTableValue JSPerformancePrototypeTableValues[] = {
|
||||
// { "clearResourceTimings"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPerformancePrototypeFunction_clearResourceTimings, 0 } },
|
||||
// { "setResourceTimingBufferSize"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPerformancePrototypeFunction_setResourceTimingBufferSize, 1 } },
|
||||
{ "mark"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPerformancePrototypeFunction_mark, 1 } },
|
||||
{ "timerify"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, performanceTimerifyCodeGenerator, 1 } },
|
||||
|
||||
{ "timerify"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, performanceTimerifyCodeGenerator, 2 } }, // this routes through codegen'ed CPP to timerify in Performance.ts
|
||||
// ERIK note because performanceTimerifyCodeGenerator has "CodeGenerator" at the end, we know the C++ bindings are generated for it based on the existance of "Performance.ts" in src/js/builtins "export timerify"
|
||||
|
||||
{ "clearMarks"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPerformancePrototypeFunction_clearMarks, 0 } },
|
||||
{ "measure"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPerformancePrototypeFunction_measure, 1 } },
|
||||
{ "clearMeasures"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPerformancePrototypeFunction_clearMeasures, 0 } },
|
||||
|
||||
@@ -549,4 +549,36 @@ export default [
|
||||
// createWriteStream: { fn: "createWriteStream", length: 2 },
|
||||
},
|
||||
}),
|
||||
define({
|
||||
name: "RecordableHistogram",
|
||||
construct: false,
|
||||
noConstructor: true,
|
||||
finalize: true, // this triggers the deallocation for bun.destroy
|
||||
configurable: false,
|
||||
hasPendingActivity: false,
|
||||
klass: {},
|
||||
JSType: "0b11101110",
|
||||
proto: {
|
||||
min: { getter: "min" },
|
||||
max: { getter: "max" },
|
||||
mean: { getter: "mean" },
|
||||
exceeds: { getter: "exceeds" },
|
||||
stddev: { getter: "stddev" },
|
||||
count: { getter: "count" },
|
||||
percentiles: { getter: "percentiles" },
|
||||
reset: { fn: "reset", length: 0 },
|
||||
record: { fn: "record", length: 1 },
|
||||
recordDelta: { fn: "recordDelta", length: 0 },
|
||||
add: { fn: "add", length: 1 },
|
||||
percentile: { fn: "percentile", length: 1 },
|
||||
minBigInt: { fn: "minBigInt", length: 0 },
|
||||
maxBigInt: { fn: "maxBigInt", length: 0 },
|
||||
exceedsBigInt: { fn: "exceedsBigInt", length: 0 },
|
||||
countBigInt: { fn: "countBigInt", length: 0 },
|
||||
percentilesBigInt: { fn: "percentilesBigInt", length: 0 },
|
||||
percentileBigInt: { fn: "percentileBigInt", length: 1 },
|
||||
toJSON: { fn: "toJSON", length: 0 },
|
||||
},
|
||||
values: [],
|
||||
}),
|
||||
];
|
||||
|
||||
181
src/bun.js/node/node_perf_hooks_histogram_binding.zig
Normal file
181
src/bun.js/node/node_perf_hooks_histogram_binding.zig
Normal file
@@ -0,0 +1,181 @@
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const meta = bun.meta;
|
||||
const JSC = bun.JSC;
|
||||
const JSValue = JSC.JSValue;
|
||||
|
||||
pub const RecordableHistogram = struct {
|
||||
pub usingnamespace JSC.Codegen.JSRecordableHistogram;
|
||||
|
||||
min: u64 = 1,
|
||||
max: u64 = 2,
|
||||
mean: f64 = 3, // todo: make this optional
|
||||
exceeds: u64 = 4,
|
||||
stddev: f64 = 5, // todo: make this optional
|
||||
count: u64 = 6,
|
||||
percentilesInternal: std.AutoHashMap(f32, f32) = std.AutoHashMap(f32, f32).init(bun.default_allocator),
|
||||
|
||||
const This = @This();
|
||||
|
||||
//todo: these should also be explicit functions, IE both .max and .max() work
|
||||
pub const min = getter(.min);
|
||||
pub const max = getter(.max);
|
||||
pub const mean = getter(.mean);
|
||||
pub const exceeds = getter(.exceeds);
|
||||
pub const stddev = getter(.stddev);
|
||||
pub const count = getter(.count);
|
||||
|
||||
// we need a special getter for percentiles because it's a hashmap
|
||||
pub const percentiles = @as(
|
||||
PropertyGetter,
|
||||
struct {
|
||||
pub fn callback(this: *This, globalThis: *JSC.JSGlobalObject) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
_ = globalThis;
|
||||
return .undefined;
|
||||
}
|
||||
}.callback,
|
||||
);
|
||||
|
||||
pub fn record(this: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
_ = globalThis;
|
||||
_ = callframe;
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
pub fn recordDelta(this: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
_ = globalThis;
|
||||
_ = callframe;
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
pub fn add(this: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
const args = callframe.arguments(1).slice();
|
||||
if (args.len != 1) {
|
||||
globalThis.throwInvalidArguments("Expected 1 argument", .{});
|
||||
return .zero;
|
||||
}
|
||||
// todo: make below work
|
||||
// const other = args[0].to(RecordableHistogram);
|
||||
// _ = other;
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
pub fn reset(this: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
_ = globalThis;
|
||||
_ = callframe;
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
pub fn countBigInt(this: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
_ = globalThis;
|
||||
_ = callframe;
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
pub fn minBigInt(this: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
_ = globalThis;
|
||||
_ = callframe;
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
pub fn maxBigInt(this: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
_ = globalThis;
|
||||
_ = callframe;
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
pub fn exceedsBigInt(this: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
_ = globalThis;
|
||||
_ = callframe;
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
pub fn percentile(this: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
const args = callframe.arguments(1).slice();
|
||||
if (args.len != 1) {
|
||||
globalThis.throwInvalidArguments("Expected 1 argument", .{});
|
||||
return .zero;
|
||||
}
|
||||
// todo: make below work
|
||||
// const percent = args[0].to(f64);
|
||||
// _ = percent;
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
pub fn percentileBigInt(this: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
const args = callframe.arguments(1).slice();
|
||||
if (args.len != 1) {
|
||||
globalThis.throwInvalidArguments("Expected 1 argument", .{});
|
||||
return .zero;
|
||||
}
|
||||
// todo: make below work
|
||||
// const percent = args[0].to(f64);
|
||||
// _ = percent;
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
pub fn percentilesBigInt(this: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
_ = globalThis;
|
||||
_ = callframe;
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
pub fn toJSON(this: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
_ = this;
|
||||
_ = globalThis;
|
||||
_ = callframe;
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
const PropertyGetter = fn (this: *This, globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
|
||||
fn getter(comptime field: meta.FieldEnum(This)) PropertyGetter {
|
||||
return struct {
|
||||
pub fn callback(this: *This, globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
|
||||
const v = @field(this, @tagName(field));
|
||||
return globalObject.toJS(v, .temporary);
|
||||
}
|
||||
}.callback;
|
||||
}
|
||||
|
||||
pub const value = getter(.value);
|
||||
|
||||
// since we create this with bun.new, we need to have it be destroyable
|
||||
// our node.classes.ts has finalize=true to generate the call to finalize
|
||||
pub fn finalize(this: *This) callconv(.C) void {
|
||||
this.percentilesInternal.deinit();
|
||||
bun.destroy(this);
|
||||
}
|
||||
};
|
||||
|
||||
fn createHistogram(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
|
||||
return bun.new(RecordableHistogram, .{}).toJS(globalThis);
|
||||
}
|
||||
|
||||
pub fn createPerfHooksHistogramBinding(global: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
|
||||
const histogram = JSC.JSValue.createEmptyObject(global, 1);
|
||||
histogram.put(
|
||||
global,
|
||||
bun.String.init("createHistogram"),
|
||||
JSC.JSFunction.create(
|
||||
global,
|
||||
"createHistogram",
|
||||
&createHistogram,
|
||||
3, // function length
|
||||
.{},
|
||||
),
|
||||
);
|
||||
|
||||
return histogram;
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { performance } from "node:perf_hooks";
|
||||
// import { performance, createHistogram } from "node:perf_hooks";
|
||||
// cannot use import statement outside a module, so we do:
|
||||
const { performance, createHistogram } = require("perf_hooks");
|
||||
|
||||
const fn = () => {
|
||||
console.log("this is the function that will be timed");
|
||||
};
|
||||
|
||||
let wrapped = performance.timerify(fn);
|
||||
wrapped();
|
||||
const histogram = createHistogram();
|
||||
histogram.update(2);
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
export function timerify(fn: Function) {
|
||||
export function timerify(fn: Function, options) {
|
||||
const { histogram } = options;
|
||||
|
||||
// create histogram class
|
||||
class Histogram {
|
||||
record(duration: number) {
|
||||
console.log(`Recording duration: ${duration}`);
|
||||
}
|
||||
}
|
||||
|
||||
// wrap fn in a timer and return the wrapped function
|
||||
return function (...args: any[]) {
|
||||
const start = performance.now();
|
||||
const result = fn(...args);
|
||||
const end = performance.now();
|
||||
console.log(`Function took ${end - start}ms`);
|
||||
|
||||
if (histogram) {
|
||||
console.log("recorded");
|
||||
histogram.record(Math.ceil((end - start) * 1e6));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -98,6 +98,10 @@ Object.setPrototypeOf(PerformanceResourceTiming, PerformanceEntry);
|
||||
|
||||
export default {
|
||||
performance: {
|
||||
// perf_hooks is a builtin global, so JSC is aware of it through C++ bindings
|
||||
// see JSPerformance.cpp HashTableValue for more details
|
||||
// perf_hooks has a performance module with these exported functions,
|
||||
// most implemented in C++ but I decide to do timerify in JS
|
||||
mark(f) {
|
||||
return performance.mark(...arguments);
|
||||
},
|
||||
@@ -105,7 +109,9 @@ export default {
|
||||
return performance.measure(...arguments);
|
||||
},
|
||||
timerify(f) {
|
||||
return performance.timerify(...arguments);
|
||||
// in this case, we want it to go back to JS function since we're passing a JS function
|
||||
// go to JSPerformance.cpp HashTableValue to see how this is handled differently from the rest
|
||||
return performance.timerify(...arguments); // this routes to JSPerformance.cpp
|
||||
},
|
||||
clearMarks(f) {
|
||||
return performance.clearMarks(...arguments);
|
||||
@@ -162,7 +168,8 @@ export default {
|
||||
throwNotImplemented("perf_hooks.monitorEventLoopDelay");
|
||||
},
|
||||
createHistogram() {
|
||||
throwNotImplemented("perf_hooks.createHistogram");
|
||||
const { createHistogram } = $zig("node_perf_hooks_histogram_binding.zig", "createPerfHooksHistogramBinding");
|
||||
return createHistogram();
|
||||
},
|
||||
PerformanceResourceTiming,
|
||||
};
|
||||
|
||||
@@ -61,6 +61,7 @@ pub const Node = struct {
|
||||
pub usingnamespace @import("./bun.js/node/node_fs_stat_watcher.zig");
|
||||
pub usingnamespace @import("./bun.js/node/node_fs_binding.zig");
|
||||
pub usingnamespace @import("./bun.js/node/node_os.zig");
|
||||
pub usingnamespace @import("./bun.js/node/node_perf_hooks_histogram_binding.zig");
|
||||
pub const fs = @import("./bun.js/node/node_fs_constant.zig");
|
||||
pub const Util = struct {
|
||||
pub const parseArgs = @import("./bun.js/node/util/parse_args.zig").parseArgs;
|
||||
|
||||
176
src/notes.md
Normal file
176
src/notes.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# performance.timerify project
|
||||
|
||||
goal: implement timerify:
|
||||
timerify(fn, {histogram: Histogram})
|
||||
|
||||
timerify modifies the passed-in histogram by registering events into it. The histogram can later print performance distribution after many runs.
|
||||
|
||||
Two main parts of this so far:
|
||||
|
||||
- implement timerify function in JS (because it wraps a user's function and that's annoying to pass across language boundaries)
|
||||
- implement Histogram class in Zig (used by timerify but nice to have performance and it's in CPP in node)
|
||||
|
||||
## How I got timerify to work
|
||||
|
||||
Thanks Jarred for the help on this one.
|
||||
|
||||
performance.timerify is part of the `node:perf_hooks` global, so importing it takes this code path:
|
||||
|
||||
- `perf_hooks.ts`
|
||||
- this file was already here
|
||||
- note we get "Performance" from globalThis, so that's proof it's a global
|
||||
- `performance.timerify` calls via `JSPerformance.cpp`
|
||||
- `JSPerformance.cpp`
|
||||
- this file was already here
|
||||
- name prefix "JS" implies codegen but this case is exception since it's part of webcore which we'd copied in and now manually edit
|
||||
- note the `JSPerformancePrototypeTableValues`:
|
||||
- for the other performance functions, they're implemented in C++ so we use `JSC::PropertyAttribute::Function` and `HashTableValue::NativeFunctionType` which call their respective C++ implementations
|
||||
- for timerify, we use `PropertyAttribute::Builtin` and `HashTableValue::BuiltinGeneratorType` which will call through an _autogenerated_ C++ file to arrive back at a JS/TS file. It uses the `performanceTimerifyCodeGenerator` identifier, which I'll explain below.
|
||||
- `Performance.ts`
|
||||
- I created this file
|
||||
- the existance of this TS file in `src/js/builtins` means it'll receive codegen for any exported functions
|
||||
- we `export timerify`
|
||||
- codegen will generate this glue function: (camelCased) {filename}{functionName}CodeGenerator, which is why we used `performanceTimerifyCodeGenerator` in `JSPerformance.cpp`
|
||||
|
||||
Calling `timerify` from JS will just feel like you're calling `Performance.ts``timerify` directly. It "just works" as if the glue code weren't there.
|
||||
|
||||
## how I got Histogram to work
|
||||
|
||||
This one was a bit harder, because I implement the class in Zig.
|
||||
|
||||
I followed `node_fs.zig` for inspiration. Thanks Dave as well for walking me through initial setup.
|
||||
|
||||
importing Histogram from node:perf_hooks follows this code path:
|
||||
|
||||
- `perf_hooks.ts`
|
||||
- note the key bit here: `$zig("node_perf_hooks_histogram_binding.zig", "createPerfHooksHistogramBinding");`
|
||||
- `$zig()` calls into a zig binding
|
||||
- `node_perf_hooks_histogram_binding.zig` is in `src/bun.js/node`
|
||||
- `createPerfHooksHistogramBinding` is the function name
|
||||
- `node_perf_hooks_histogram_binding.zig`
|
||||
- I made this file
|
||||
- let's look at `createPerfHooksHistogramBinding`:
|
||||
|
||||
```javascript
|
||||
pub fn createPerfHooksHistogramBinding(global: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
|
||||
const histogram = JSC.JSValue.createEmptyObject(global, 1);
|
||||
histogram.put(
|
||||
global,
|
||||
bun.String.init("createHistogram"),
|
||||
JSC.JSFunction.create(
|
||||
global,
|
||||
"createHistogram",
|
||||
&createHistogram,
|
||||
3, // function length
|
||||
.{},
|
||||
),
|
||||
);
|
||||
|
||||
return histogram;
|
||||
}
|
||||
```
|
||||
|
||||
- it must be pub fn
|
||||
- it must receive `(global: *JSC.JSGlobalObject)`
|
||||
- it must use `callconv(.C)` since it needs to speak C ABI for JS interop
|
||||
- it must return a `JSC.JSValue`
|
||||
- we want to create an object in JSC, which is the `JSC.JSValue` that's passed back to `perf_hooks.ts`
|
||||
- we add the `createHistogram` as a function onto it. This is the name JS sees. The `3` is the max quantity of args.
|
||||
|
||||
- let's look at `createHistogram` itself, which is the function actually called by JS.
|
||||
|
||||
```javascript
|
||||
fn createHistogram(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
|
||||
return bun.new(Histogram, .{}).toJS(globalThis);
|
||||
}
|
||||
```
|
||||
|
||||
- `bun.new(T, init_value)` creates the object on heap, and `.toJS(globalThis)` makes it a `JSC.JSValue` for return
|
||||
- you'll see later where the destroy happens.
|
||||
- following this pattern, we can have an arbitrary Histogram struct, fully in Zig.
|
||||
|
||||
But there's more to do. We have the createHistogram function but we need to codegen a way to represent the Histogram class itself and pass it across Zig/JS boundaries.
|
||||
|
||||
- `src/bun.js/node/node.classes.ts`
|
||||
- this file defines classes that need to be codegen'ed
|
||||
- I add the histogram class, important fields have comments:
|
||||
|
||||
```javascript
|
||||
define({
|
||||
name: "Histogram", // the name in JS
|
||||
construct: false,
|
||||
noConstructor: true,
|
||||
finalize: true, // this triggers the deallocation for bun.destroy
|
||||
configurable: false,
|
||||
hasPendingActivity: false,
|
||||
klass: {},
|
||||
JSType: "0b11101110",
|
||||
proto: {
|
||||
// we put methods here
|
||||
update: { // name in JS
|
||||
fn: "update", // name in Zig
|
||||
length: 1, // quantity of args
|
||||
},
|
||||
|
||||
foo: { getter: "foo" }, // public getters, so that histogram.foo is gettable from JS
|
||||
},
|
||||
|
||||
values: [],
|
||||
}),
|
||||
```
|
||||
|
||||
- lastly, add the class to `generated_class_list.zig` as `JSC.Node.Histogram`
|
||||
|
||||
Ok, we're almost there. Now we need to look at the Histogram struct for a few subtle things we need to do.
|
||||
|
||||
```javascript
|
||||
pub const Histogram = struct {
|
||||
pub usingnamespace JSC.Codegen.JSHistogram; // Important, makes the JS interop work
|
||||
|
||||
// Zig native stuff
|
||||
foo: u64 = 0,
|
||||
const This = @This();
|
||||
|
||||
// We can a custom, arbitrary method, make sure to add it to the class in node.classes.ts
|
||||
// It must have this function signature: (*Self, globalThis, callframe) callconv(.C) JSValue
|
||||
pub fn update(self: *This, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
|
||||
// args come from the callframe
|
||||
const args = callframe.arguments(1).slice();
|
||||
if (args.len != 1) {
|
||||
globalThis.throwInvalidArguments("Expected 1 argument", .{});
|
||||
return .zero;
|
||||
}
|
||||
self.foo = args[0].to(u64);
|
||||
return .undefined; // JSValue equivalent of a void return
|
||||
}
|
||||
|
||||
// Below is a bit of boilerplate we need
|
||||
|
||||
// This boilerplate is for writting getters, at least for trivial types.
|
||||
const PropertyGetter = fn (this: *This, globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
|
||||
fn getter(comptime field: meta.FieldEnum(This)) PropertyGetter {
|
||||
return struct {
|
||||
pub fn callback(this: *This, globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
|
||||
const v = @field(this, @tagName(field));
|
||||
return globalObject.toJS(v, .temporary);
|
||||
}
|
||||
}.callback;
|
||||
}
|
||||
|
||||
// We can add a getter to make self.foo public.
|
||||
// Make sure this is also added to node.classes.ts
|
||||
pub const foo = getter(.foo);
|
||||
|
||||
// since we create Histogram objects with bun.new, we need to have it be destroyable
|
||||
// our node.classes.ts has finalize=true to generate the call to finalize
|
||||
// it expects this specific function name to destroy itself.
|
||||
pub fn finalize(self: *Histogram) callconv(.C) void {
|
||||
bun.destroy(self);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Upcoming:
|
||||
|
||||
- actually adding Histogram as an argument into timerify and making timerify call Histogram methods
|
||||
- implementing all of Histogram
|
||||
Reference in New Issue
Block a user