@@ -27,10 +27,11 @@ import {
2727 Counter ,
2828 ObservableUpDownCounter ,
2929 Gauge ,
30+ Observable ,
3031} from '../../../src' ;
3132import { NoopMeterProvider } from '../../../src/metrics/NoopMeterProvider' ;
3233import { ProxyMeter } from '../../../src/metrics/ProxyMeter' ;
33- import { NoopMeter } from '../../../src/metrics/NoopMeter' ;
34+ import { NoopMeter , NOOP_METER } from '../../../src/metrics/NoopMeter' ;
3435
3536describe ( 'ProxyMeter' , ( ) => {
3637 let provider : ProxyMeterProvider ;
@@ -77,6 +78,36 @@ describe('ProxyMeter', () => {
7778 assert . doesNotThrow ( ( ) => histogram . record ( 1 ) ) ;
7879 assert . doesNotThrow ( ( ) => observable . addCallback ( ( ) => { } ) ) ;
7980 } ) ;
81+
82+ it ( 'creates additional synchronous instruments that remain no-ops before delegation' , ( ) => {
83+ const meter = provider . getMeter ( 'test' ) ;
84+
85+ const gauge = meter . createGauge ( 'gauge' ) ;
86+ const upDownCounter = meter . createUpDownCounter ( 'upDown' ) ;
87+
88+ assert . doesNotThrow ( ( ) => gauge . record ( 5 ) ) ;
89+ assert . doesNotThrow ( ( ) => upDownCounter . add ( - 1 ) ) ;
90+ } ) ;
91+
92+ it ( 'creates observable counters that buffer callbacks before delegation' , ( ) => {
93+ const meter = provider . getMeter ( 'test' ) ;
94+
95+ const observableCounter = meter . createObservableCounter ( 'observable-counter' ) ;
96+ const observableUpDownCounter = meter . createObservableUpDownCounter (
97+ 'observable-up-down-counter'
98+ ) ;
99+ const counterCallback = sandbox . stub ( ) ;
100+ const upDownCallback = sandbox . stub ( ) ;
101+
102+ assert . doesNotThrow ( ( ) => observableCounter . addCallback ( counterCallback ) ) ;
103+ assert . doesNotThrow ( ( ) => observableCounter . removeCallback ( counterCallback ) ) ;
104+ assert . doesNotThrow ( ( ) =>
105+ observableUpDownCounter . addCallback ( upDownCallback )
106+ ) ;
107+ assert . doesNotThrow ( ( ) =>
108+ observableUpDownCounter . removeCallback ( upDownCallback )
109+ ) ;
110+ } ) ;
80111 } ) ;
81112
82113 describe ( 'when delegate is set before getMeter' , ( ) => {
@@ -212,6 +243,25 @@ describe('ProxyMeter', () => {
212243 const instrument = meter . createUpDownCounter ( 'test' ) ;
213244 assert . strictEqual ( instrument , delegateUpDownCounter ) ;
214245 } ) ;
246+
247+ it ( 'registers batch callbacks through the delegate once bound' , ( ) => {
248+ const proxyObservable = meter . createObservableGauge ( 'proxy' ) ;
249+ const foreignObservable : ObservableGauge = {
250+ addCallback : sandbox . stub ( ) ,
251+ removeCallback : sandbox . stub ( ) ,
252+ } ;
253+ const callback = sandbox . stub ( ) ;
254+
255+ meter . addBatchObservableCallback ( callback , [
256+ proxyObservable ,
257+ foreignObservable ,
258+ ] ) ;
259+
260+ sandbox . assert . calledOnce ( addBatchStub ) ;
261+ const [ , registeredObservables ] = addBatchStub . firstCall . args ;
262+ assert . strictEqual ( registeredObservables [ 0 ] , delegateObservableGauge ) ;
263+ assert . strictEqual ( registeredObservables [ 1 ] , foreignObservable ) ;
264+ } ) ;
215265 } ) ;
216266
217267 describe ( 'when instruments are created before delegate is set' , ( ) => {
@@ -235,6 +285,122 @@ describe('ProxyMeter', () => {
235285 sandbox . assert . calledOnceWithExactly ( addStub , 7 ) ;
236286 } ) ;
237287
288+ it ( 'hydrates gauges that were created before delegation' , ( ) => {
289+ const meter = provider . getMeter ( 'test' ) ;
290+ const gauge = meter . createGauge ( 'pre-gauge' ) ;
291+ const recordStub = sandbox . stub ( ) ;
292+ const delegateGauge : Gauge = {
293+ record : recordStub ,
294+ } as Gauge ;
295+ const delegateMeter = new NoopMeter ( ) ;
296+ sandbox . stub ( delegateMeter , 'createGauge' ) . returns ( delegateGauge ) ;
297+
298+ provider . setDelegate ( {
299+ getMeter ( ) {
300+ return delegateMeter ;
301+ } ,
302+ } ) ;
303+
304+ gauge . record ( 9 ) ;
305+ sandbox . assert . calledOnceWithExactly ( recordStub , 9 ) ;
306+ } ) ;
307+
308+ it ( 'hydrates histograms that were created before delegation' , ( ) => {
309+ const meter = provider . getMeter ( 'test' ) ;
310+ const histogram = meter . createHistogram ( 'pre-histogram' ) ;
311+ const recordStub = sandbox . stub ( ) ;
312+ const delegateHistogram : Histogram = {
313+ record : recordStub ,
314+ } as Histogram ;
315+ const delegateMeter = new NoopMeter ( ) ;
316+ sandbox . stub ( delegateMeter , 'createHistogram' ) . returns ( delegateHistogram ) ;
317+
318+ provider . setDelegate ( {
319+ getMeter ( ) {
320+ return delegateMeter ;
321+ } ,
322+ } ) ;
323+
324+ histogram . record ( 33 ) ;
325+ sandbox . assert . calledOnceWithExactly ( recordStub , 33 ) ;
326+ } ) ;
327+
328+ it ( 'hydrates up down counters that were created before delegation' , ( ) => {
329+ const meter = provider . getMeter ( 'test' ) ;
330+ const upDownCounter = meter . createUpDownCounter ( 'pre-updown' ) ;
331+ const addStub = sandbox . stub ( ) ;
332+ const delegateUpDownCounter : UpDownCounter = {
333+ add : addStub ,
334+ } as UpDownCounter ;
335+ const delegateMeter = new NoopMeter ( ) ;
336+ sandbox . stub ( delegateMeter , 'createUpDownCounter' ) . returns ( delegateUpDownCounter ) ;
337+
338+ provider . setDelegate ( {
339+ getMeter ( ) {
340+ return delegateMeter ;
341+ } ,
342+ } ) ;
343+
344+ upDownCounter . add ( - 11 ) ;
345+ sandbox . assert . calledOnceWithExactly ( addStub , - 11 ) ;
346+ } ) ;
347+
348+ it ( 'hydrates observable counters that were created before delegation' , ( ) => {
349+ const meter = provider . getMeter ( 'test' ) ;
350+ const observableCounter = meter . createObservableCounter ( 'pre-observable-counter' ) ;
351+ const callback = sandbox . stub ( ) ;
352+ observableCounter . addCallback ( callback ) ;
353+
354+ const delegateObservableCounter : ObservableCounter = {
355+ addCallback : sandbox . stub ( ) ,
356+ removeCallback : sandbox . stub ( ) ,
357+ } ;
358+ const delegateMeter = new NoopMeter ( ) ;
359+ sandbox
360+ . stub ( delegateMeter , 'createObservableCounter' )
361+ . returns ( delegateObservableCounter ) ;
362+
363+ provider . setDelegate ( {
364+ getMeter ( ) {
365+ return delegateMeter ;
366+ } ,
367+ } ) ;
368+
369+ sandbox . assert . calledOnceWithExactly (
370+ delegateObservableCounter . addCallback as sinon . SinonStub ,
371+ callback
372+ ) ;
373+ } ) ;
374+
375+ it ( 'hydrates observable up down counters that were created before delegation' , ( ) => {
376+ const meter = provider . getMeter ( 'test' ) ;
377+ const observableUpDownCounter = meter . createObservableUpDownCounter (
378+ 'pre-observable-updown'
379+ ) ;
380+ const callback = sandbox . stub ( ) ;
381+ observableUpDownCounter . addCallback ( callback ) ;
382+
383+ const delegateObservableUpDownCounter : ObservableUpDownCounter = {
384+ addCallback : sandbox . stub ( ) ,
385+ removeCallback : sandbox . stub ( ) ,
386+ } ;
387+ const delegateMeter = new NoopMeter ( ) ;
388+ sandbox
389+ . stub ( delegateMeter , 'createObservableUpDownCounter' )
390+ . returns ( delegateObservableUpDownCounter ) ;
391+
392+ provider . setDelegate ( {
393+ getMeter ( ) {
394+ return delegateMeter ;
395+ } ,
396+ } ) ;
397+
398+ sandbox . assert . calledOnceWithExactly (
399+ delegateObservableUpDownCounter . addCallback as sinon . SinonStub ,
400+ callback
401+ ) ;
402+ } ) ;
403+
238404 it ( 'hydrates observable callbacks that were added before delegation' , ( ) => {
239405 const meter = provider . getMeter ( 'test' ) ;
240406 const observable = meter . createObservableGauge ( 'observable' ) ;
@@ -291,5 +457,194 @@ describe('ProxyMeter', () => {
291457 const [ , registeredObservables ] = addBatchStub . firstCall . args ;
292458 assert . strictEqual ( registeredObservables [ 0 ] , delegateObservable ) ;
293459 } ) ;
460+
461+ it ( 'remaps proxy observables when registering batch callbacks after delegation' , ( ) => {
462+ const meter = provider . getMeter ( 'test' ) ;
463+ const proxyObservable = meter . createObservableGauge ( 'proxy-batch' ) ;
464+ const callback = sandbox . stub ( ) ;
465+ const delegateObservable : ObservableGauge = {
466+ addCallback : sandbox . stub ( ) ,
467+ removeCallback : sandbox . stub ( ) ,
468+ } ;
469+ const delegateMeter = new NoopMeter ( ) ;
470+ sandbox
471+ . stub ( delegateMeter , 'createObservableGauge' )
472+ . returns ( delegateObservable ) ;
473+ const addBatchStub = sandbox . stub (
474+ delegateMeter ,
475+ 'addBatchObservableCallback'
476+ ) ;
477+
478+ provider . setDelegate ( {
479+ getMeter ( ) {
480+ return delegateMeter ;
481+ } ,
482+ } ) ;
483+
484+ meter . addBatchObservableCallback ( callback , [ proxyObservable ] ) ;
485+
486+ sandbox . assert . calledOnce ( addBatchStub ) ;
487+ const [ , registeredObservables ] = addBatchStub . firstCall . args ;
488+ assert . strictEqual ( registeredObservables [ 0 ] , delegateObservable ) ;
489+ } ) ;
490+
491+ it ( 'removes batch callbacks via the current delegate before delegation' , ( ) => {
492+ const meter = provider . getMeter ( 'test' ) ;
493+ const observable = meter . createObservableGauge ( 'batch' ) ;
494+ const callback = sandbox . stub ( ) ;
495+
496+ meter . addBatchObservableCallback ( callback , [ observable ] ) ;
497+
498+ const noopMeter = new NoopMeter ( ) ;
499+ const getMeterStub = sandbox
500+ . stub ( meter as unknown as { _getMeter : ( ) => Meter } , '_getMeter' )
501+ . returns ( noopMeter ) ;
502+ sandbox . stub ( noopMeter , 'removeBatchObservableCallback' ) ;
503+
504+ assert . doesNotThrow ( ( ) =>
505+ meter . removeBatchObservableCallback ( callback , [ observable ] )
506+ ) ;
507+ sandbox . assert . calledOnce ( getMeterStub ) ;
508+ sandbox . assert . calledOnceWithExactly (
509+ noopMeter . removeBatchObservableCallback as sinon . SinonStub ,
510+ callback ,
511+ [ observable ]
512+ ) ;
513+ } ) ;
514+
515+ it ( 'removes batch callbacks by delegating to the noop meter when unset' , ( ) => {
516+ const meter = provider . getMeter ( 'test' ) ;
517+ const observable = meter . createObservableGauge ( 'noop-batch' ) ;
518+ const callback = sandbox . stub ( ) ;
519+ meter . addBatchObservableCallback ( callback , [ observable ] ) ;
520+
521+ const noopSpy = sandbox . spy ( NOOP_METER , 'removeBatchObservableCallback' ) ;
522+
523+ meter . removeBatchObservableCallback ( callback , [ observable ] ) ;
524+
525+ sandbox . assert . calledOnce ( noopSpy ) ;
526+ } ) ;
527+ } ) ;
528+
529+ describe ( 'proxy instrument internals' , ( ) => {
530+ it ( 'does not track instruments that already have delegates' , ( ) => {
531+ const meter = provider . getMeter ( 'test' ) as ProxyMeter ;
532+ const privateMeter = meter as unknown as {
533+ _trackInstrument : ( instrument : unknown ) => void ;
534+ _instruments : Set < unknown > ;
535+ } ;
536+ privateMeter . _instruments . clear ( ) ;
537+ const instrument = {
538+ hasDelegate : sandbox . stub ( ) . returns ( true ) ,
539+ bindDelegate : sandbox . stub ( ) ,
540+ } ;
541+
542+ privateMeter . _trackInstrument ( instrument ) ;
543+
544+ assert . strictEqual ( privateMeter . _instruments . size , 0 ) ;
545+ } ) ;
546+
547+ it ( 'leaves non-proxy observables untouched when mapping delegates' , ( ) => {
548+ const meter = provider . getMeter ( 'test' ) as ProxyMeter ;
549+ const privateMeter = meter as unknown as {
550+ _mapObservablesToDelegates : ( observables : Observable [ ] ) => Observable [ ] ;
551+ } ;
552+ const observable : Observable = {
553+ addCallback : sandbox . stub ( ) ,
554+ removeCallback : sandbox . stub ( ) ,
555+ } ;
556+
557+ const [ result ] = privateMeter . _mapObservablesToDelegates ( [ observable ] ) ;
558+
559+ assert . strictEqual ( result , observable ) ;
560+ } ) ;
561+
562+ it ( 'maps proxy observables to their delegates after binding' , ( ) => {
563+ const meter = provider . getMeter ( 'test' ) as ProxyMeter ;
564+ const privateMeter = meter as unknown as {
565+ _mapObservablesToDelegates : ( observables : Observable [ ] ) => Observable [ ] ;
566+ } ;
567+ const proxyObservable = meter . createObservableGauge ( 'proxy' ) ;
568+ const delegateObservable : ObservableGauge = {
569+ addCallback : sandbox . stub ( ) ,
570+ removeCallback : sandbox . stub ( ) ,
571+ } ;
572+ const delegateMeter = new NoopMeter ( ) ;
573+ sandbox
574+ . stub ( delegateMeter , 'createObservableGauge' )
575+ . returns ( delegateObservable ) ;
576+
577+ provider . setDelegate ( {
578+ getMeter ( ) {
579+ return delegateMeter ;
580+ } ,
581+ } ) ;
582+
583+ const [ result ] = privateMeter . _mapObservablesToDelegates ( [
584+ proxyObservable as unknown as Observable ,
585+ ] ) ;
586+
587+ assert . strictEqual ( result , delegateObservable ) ;
588+ } ) ;
589+
590+ it ( 'does not bind pending instruments when no delegate is present' , ( ) => {
591+ const meter = provider . getMeter ( 'test' ) as ProxyMeter ;
592+ const privateMeter = meter as unknown as {
593+ _bindPendingInstruments : ( ) => void ;
594+ _instruments : Set < any > ;
595+ } ;
596+ const instrument = {
597+ hasDelegate : sandbox . stub ( ) . returns ( false ) ,
598+ bindDelegate : sandbox . stub ( ) ,
599+ } ;
600+ privateMeter . _instruments . add ( instrument ) ;
601+
602+ privateMeter . _bindPendingInstruments ( ) ;
603+
604+ sandbox . assert . notCalled ( instrument . bindDelegate ) ;
605+ assert . strictEqual ( privateMeter . _instruments . size , 1 ) ;
606+ } ) ;
607+
608+ it ( 'falls back to NOOP meter when no delegate is available' , ( ) => {
609+ const meter = provider . getMeter ( 'test' ) as ProxyMeter ;
610+ const privateMeter = meter as unknown as { _getMeter : ( ) => Meter } ;
611+
612+ const result = privateMeter . _getMeter ( ) ;
613+
614+ assert . strictEqual ( result , NOOP_METER ) ;
615+ } ) ;
616+
617+ it ( 'allows proxy instruments to attempt delegate binding before delegation' , ( ) => {
618+ const meter = provider . getMeter ( 'test' ) ;
619+ const counter = meter . createCounter ( 'lazy' ) ;
620+
621+ assert . doesNotThrow ( ( ) =>
622+ ( counter as unknown as { bindDelegate : ( ) => void } ) . bindDelegate ( )
623+ ) ;
624+ } ) ;
625+
626+ it ( 'hydrates instruments lazily when pending state flush is prevented' , ( ) => {
627+ const meter = provider . getMeter ( 'test' ) as ProxyMeter ;
628+ const counter = meter . createCounter ( 'lazy' ) ;
629+ const delegateCounter : Counter = {
630+ add : sandbox . stub ( ) ,
631+ } as Counter ;
632+ const delegateMeter = new NoopMeter ( ) ;
633+ sandbox . stub ( delegateMeter , 'createCounter' ) . returns ( delegateCounter ) ;
634+ sandbox . stub ( meter as unknown as { _flushPendingState : ( ) => void } , '_flushPendingState' ) ;
635+
636+ provider . setDelegate ( {
637+ getMeter ( ) {
638+ return delegateMeter ;
639+ } ,
640+ } ) ;
641+
642+ counter . add ( 3 ) ;
643+
644+ sandbox . assert . calledOnce (
645+ delegateMeter . createCounter as sinon . SinonStub
646+ ) ;
647+ sandbox . assert . calledOnce ( delegateCounter . add as sinon . SinonStub ) ;
648+ } ) ;
294649 } ) ;
295650} ) ;
0 commit comments