@@ -5,12 +5,18 @@ use std::cell::RefCell;
55
66use deno_core:: GarbageCollected ;
77use deno_core:: op2;
8+ use hdrhistogram:: Histogram ;
9+
10+ const EMPTY_HISTOGRAM_MIN : u64 = i64:: MAX as u64 ;
811
912#[ derive( Debug , thiserror:: Error , deno_error:: JsError ) ]
1013pub enum PerfHooksError {
1114 #[ class( generic) ]
1215 #[ error( transparent) ]
1316 TokioEld ( #[ from] tokio_eld:: Error ) ,
17+ #[ class( generic) ]
18+ #[ error( transparent) ]
19+ HistogramCreation ( #[ from] hdrhistogram:: errors:: CreationError ) ,
1420}
1521
1622pub struct EldHistogram {
@@ -147,3 +153,280 @@ impl EldHistogram {
147153 self . eld . borrow ( ) . stdev ( )
148154 }
149155}
156+
157+ // Backs the user-facing `RecordableHistogram` returned by
158+ // `perf_hooks.createHistogram()`. Wraps an `hdrhistogram::Histogram<u64>`
159+ // configured with caller-supplied bounds, plus the bookkeeping needed for
160+ // `recordDelta()` and the `exceeds` counter (incremented when a recorded
161+ // value overflows the histogram's `highest` bound).
162+ pub struct BaseHistogram {
163+ inner : RefCell < Histogram < u64 > > ,
164+ highest : u64 ,
165+ exceeds : Cell < u64 > ,
166+ added_out_of_range : Cell < u64 > ,
167+ prev_delta_ns : Cell < Option < u64 > > ,
168+ }
169+
170+ // SAFETY: we're sure this can be GCed
171+ unsafe impl GarbageCollected for BaseHistogram {
172+ fn trace ( & self , _visitor : & mut deno_core:: v8:: cppgc:: Visitor ) { }
173+
174+ fn get_name ( & self ) -> & ' static std:: ffi:: CStr {
175+ c"BaseHistogram"
176+ }
177+ }
178+
179+ fn now_ns ( ) -> u64 {
180+ // Match Node's `process.hrtime` clock domain — monotonic nanoseconds.
181+ use std:: time:: Instant ;
182+ thread_local ! {
183+ static ORIGIN : Instant = Instant :: now( ) ;
184+ }
185+ ORIGIN . with ( |origin| origin. elapsed ( ) . as_nanos ( ) as u64 )
186+ }
187+
188+ #[ op2]
189+ impl BaseHistogram {
190+ // Creates a `RecordableHistogram` with the given bounds and significant
191+ // figures. Mirrors the behavior of Node's `createHistogram(options)`.
192+ //
193+ // Caller is responsible for validating bounds; this just forwards them to
194+ // `hdrhistogram::Histogram::new_with_bounds`.
195+ #[ constructor]
196+ #[ cppgc]
197+ pub fn new (
198+ #[ bigint] lowest : u64 ,
199+ #[ bigint] highest : u64 ,
200+ #[ smi] figures : u32 ,
201+ ) -> Result < BaseHistogram , PerfHooksError > {
202+ let inner =
203+ Histogram :: < u64 > :: new_with_bounds ( lowest, highest, figures as u8 ) ?;
204+ Ok ( BaseHistogram {
205+ inner : RefCell :: new ( inner) ,
206+ highest,
207+ exceeds : Cell :: new ( 0 ) ,
208+ added_out_of_range : Cell :: new ( 0 ) ,
209+ prev_delta_ns : Cell :: new ( None ) ,
210+ } )
211+ }
212+
213+ // Records a value into the histogram. If the value exceeds the configured
214+ // `highest`, increments the `exceeds` counter instead of erroring.
215+ #[ fast]
216+ fn record ( & self , #[ bigint] value : u64 ) {
217+ if value > self . highest {
218+ self . exceeds . set ( self . exceeds . get ( ) . saturating_add ( 1 ) ) ;
219+ return ;
220+ }
221+ let mut h = self . inner . borrow_mut ( ) ;
222+ if h. record ( value) . is_err ( ) {
223+ self . exceeds . set ( self . exceeds . get ( ) . saturating_add ( 1 ) ) ;
224+ }
225+ }
226+
227+ // Records the nanoseconds elapsed since the previous call to recordDelta.
228+ // The first call seeds the timestamp without recording (matches Node).
229+ #[ fast]
230+ fn record_delta ( & self ) {
231+ let now = now_ns ( ) ;
232+ if let Some ( prev) = self . prev_delta_ns . get ( ) {
233+ let delta = now. saturating_sub ( prev) ;
234+ if delta > self . highest {
235+ self . exceeds . set ( self . exceeds . get ( ) . saturating_add ( 1 ) ) ;
236+ self . prev_delta_ns . set ( Some ( now) ) ;
237+ return ;
238+ }
239+ let mut h = self . inner . borrow_mut ( ) ;
240+ if h. record ( delta) . is_err ( ) {
241+ self . exceeds . set ( self . exceeds . get ( ) . saturating_add ( 1 ) ) ;
242+ }
243+ }
244+ self . prev_delta_ns . set ( Some ( now) ) ;
245+ }
246+
247+ // Adds counts from another histogram into this one.
248+ #[ fast]
249+ fn add ( & self , #[ cppgc] other : & BaseHistogram ) {
250+ let other_h = other. inner . borrow ( ) ;
251+ let mut h = self . inner . borrow_mut ( ) ;
252+ let mut added_out_of_range = self . added_out_of_range . get ( ) ;
253+ for v in other_h. iter_recorded ( ) {
254+ if v. value_iterated_to ( ) > self . highest
255+ || h
256+ . record_n ( v. value_iterated_to ( ) , v. count_at_value ( ) )
257+ . is_err ( )
258+ {
259+ added_out_of_range =
260+ added_out_of_range. saturating_add ( v. count_at_value ( ) ) ;
261+ }
262+ }
263+ self . added_out_of_range . set ( added_out_of_range) ;
264+ self
265+ . exceeds
266+ . set ( self . exceeds . get ( ) . saturating_add ( other. exceeds . get ( ) ) ) ;
267+ }
268+
269+ #[ fast]
270+ fn reset ( & self ) {
271+ self . inner . borrow_mut ( ) . reset ( ) ;
272+ self . exceeds . set ( 0 ) ;
273+ self . added_out_of_range . set ( 0 ) ;
274+ self . prev_delta_ns . set ( None ) ;
275+ }
276+
277+ #[ fast]
278+ #[ number]
279+ fn percentile ( & self , percentile : f64 ) -> u64 {
280+ self . inner . borrow ( ) . value_at_percentile ( percentile)
281+ }
282+
283+ #[ fast]
284+ #[ bigint]
285+ fn percentile_big_int ( & self , percentile : f64 ) -> u64 {
286+ self . inner . borrow ( ) . value_at_percentile ( percentile)
287+ }
288+
289+ // Returns the percentile distribution as a flat `[percentile, value, ...]`
290+ // array. The JS layer turns it into a `Map`. We iterate the recorded values
291+ // and emit one entry per distinct value.
292+ //
293+ // Values are bounded by `highest` (validated to fit in a JS safe integer by
294+ // the createHistogram caller), so emitting them as f64 is lossless.
295+ #[ serde]
296+ fn percentiles ( & self ) -> Vec < f64 > {
297+ let h = self . inner . borrow ( ) ;
298+ let mut out = Vec :: new ( ) ;
299+ if h. is_empty ( ) {
300+ out. push ( 100.0 ) ;
301+ out. push ( 0.0 ) ;
302+ return out;
303+ }
304+ out. push ( 0.0 ) ;
305+ out. push ( h. min ( ) as f64 ) ;
306+ if h. len ( ) > 1 {
307+ let max = h. max ( ) ;
308+ let mut percentile = 50.0 ;
309+ while percentile < 100.0 {
310+ let value = h. value_at_percentile ( percentile) ;
311+ out. push ( percentile) ;
312+ out. push ( value as f64 ) ;
313+ if value >= max {
314+ break ;
315+ }
316+ percentile += ( 100.0 - percentile) / 2.0 ;
317+ }
318+ }
319+ out. push ( 100.0 ) ;
320+ out. push ( h. max ( ) as f64 ) ;
321+ out
322+ }
323+
324+ // Same shape as `percentiles`; the JS layer re-wraps the value entries as
325+ // BigInt when exposing them through `percentilesBigInt`.
326+ #[ serde]
327+ fn percentiles_big_int ( & self ) -> Vec < f64 > {
328+ let h = self . inner . borrow ( ) ;
329+ let mut out = Vec :: new ( ) ;
330+ if h. is_empty ( ) {
331+ out. push ( 100.0 ) ;
332+ out. push ( 0.0 ) ;
333+ return out;
334+ }
335+ out. push ( 0.0 ) ;
336+ out. push ( h. min ( ) as f64 ) ;
337+ if h. len ( ) > 1 {
338+ let max = h. max ( ) ;
339+ let mut percentile = 50.0 ;
340+ while percentile < 100.0 {
341+ let value = h. value_at_percentile ( percentile) ;
342+ out. push ( percentile) ;
343+ out. push ( value as f64 ) ;
344+ if value >= max {
345+ break ;
346+ }
347+ percentile += ( 100.0 - percentile) / 2.0 ;
348+ }
349+ }
350+ out. push ( 100.0 ) ;
351+ out. push ( h. max ( ) as f64 ) ;
352+ out
353+ }
354+
355+ #[ getter]
356+ #[ number]
357+ fn count ( & self ) -> u64 {
358+ self
359+ . inner
360+ . borrow ( )
361+ . len ( )
362+ . saturating_add ( self . added_out_of_range . get ( ) )
363+ }
364+
365+ #[ getter]
366+ #[ bigint]
367+ fn count_big_int ( & self ) -> u64 {
368+ self
369+ . inner
370+ . borrow ( )
371+ . len ( )
372+ . saturating_add ( self . added_out_of_range . get ( ) )
373+ }
374+
375+ #[ getter]
376+ #[ number]
377+ fn min ( & self ) -> u64 {
378+ let h = self . inner . borrow ( ) ;
379+ if h. is_empty ( ) {
380+ EMPTY_HISTOGRAM_MIN
381+ } else {
382+ h. min ( )
383+ }
384+ }
385+
386+ #[ getter]
387+ #[ bigint]
388+ fn min_big_int ( & self ) -> u64 {
389+ let h = self . inner . borrow ( ) ;
390+ if h. is_empty ( ) {
391+ EMPTY_HISTOGRAM_MIN
392+ } else {
393+ h. min ( )
394+ }
395+ }
396+
397+ #[ getter]
398+ #[ number]
399+ fn max ( & self ) -> u64 {
400+ self . inner . borrow ( ) . max ( )
401+ }
402+
403+ #[ getter]
404+ #[ bigint]
405+ fn max_big_int ( & self ) -> u64 {
406+ self . inner . borrow ( ) . max ( )
407+ }
408+
409+ #[ getter]
410+ fn mean ( & self ) -> f64 {
411+ let h = self . inner . borrow ( ) ;
412+ if h. is_empty ( ) { f64:: NAN } else { h. mean ( ) }
413+ }
414+
415+ #[ getter]
416+ fn stddev ( & self ) -> f64 {
417+ let h = self . inner . borrow ( ) ;
418+ if h. is_empty ( ) { f64:: NAN } else { h. stdev ( ) }
419+ }
420+
421+ #[ getter]
422+ #[ number]
423+ fn exceeds ( & self ) -> u64 {
424+ self . exceeds . get ( )
425+ }
426+
427+ #[ getter]
428+ #[ bigint]
429+ fn exceeds_big_int ( & self ) -> u64 {
430+ self . exceeds . get ( )
431+ }
432+ }
0 commit comments