@@ -2,12 +2,136 @@ import type { Pointer } from './libxml2raw';
22import './disposeShim.mjs' ;
33import './metadataShim.mjs' ;
44
5+ interface MemTracker {
6+ trackAllocate ( obj : XmlDisposable ) : void ;
7+ trackDeallocate ( obj : XmlDisposable ) : void ;
8+ report ( ) : any ;
9+ }
10+
11+ const noopTracker : MemTracker = {
12+ trackAllocate ( ) : void {
13+ } ,
14+
15+ trackDeallocate ( ) : void {
16+ } ,
17+
18+ report ( ) : any {
19+ } ,
20+ } ;
21+
22+ interface MemTrackingInfo {
23+ object : WeakRef < XmlDisposable > ;
24+ classname : string ;
25+ }
26+
27+ class MemTrackerImpl implements MemTracker {
28+ // js/ts doesn't have a builtin universal id for objects as in python,
29+ // we build a similar thing
30+ idCounter : number ;
31+
32+ // from object to id
33+ disposableId : WeakMap < XmlDisposable , number > ;
34+
35+ // from id to tracking info
36+ disposableInfo : Map < number , MemTrackingInfo > ;
37+
38+ constructor ( ) {
39+ this . idCounter = 0 ;
40+ this . disposableId = new WeakMap < XmlDisposable , number > ( ) ;
41+ this . disposableInfo = new Map < number , MemTrackingInfo > ( ) ;
42+ }
43+
44+ trackAllocate ( obj : XmlDisposable ) : void {
45+ this . idCounter += 1 ;
46+ this . disposableId . set ( obj , this . idCounter ) ;
47+ this . disposableInfo . set ( this . idCounter , {
48+ object : new WeakRef ( obj ) ,
49+ classname : obj . constructor . name ,
50+ } ) ;
51+ }
52+
53+ trackDeallocate ( obj : XmlDisposable ) : void {
54+ const id = this . disposableId . get ( obj ) ;
55+ if ( id ) { // the object may be created before the diagnosis enabled
56+ this . disposableInfo . delete ( id ) ;
57+ this . disposableId . delete ( obj ) ;
58+ }
59+ }
60+
61+ report ( ) : any {
62+ const report : any = { } ;
63+ this . disposableInfo . forEach ( ( info ) => {
64+ const classReport = report [ info . classname ] ||= { // eslint-disable-line no-multi-assign
65+ garbageCollected : 0 ,
66+ totalInstances : 0 ,
67+ instances : [ ] ,
68+ } ;
69+ classReport . totalInstances += 1 ;
70+ const obj = info . object . deref ( ) ;
71+ if ( obj != null ) {
72+ classReport . instances . push ( { instance : obj } ) ;
73+ } else {
74+ classReport . garbageCollected += 1 ;
75+ }
76+ } ) ;
77+ return report ;
78+ }
79+ }
80+
81+ /**
82+ * Memory Diagnostic options.
83+ */
84+ export interface MemDiagOptions {
85+ /**
86+ * Enabling the memory diagnostics.
87+ * Note the tracking information will be lost when it is disabled.
88+ */
89+ enabled : boolean ;
90+ }
91+
92+ /**
93+ * Set up memory diagnostic helpers.
94+ *
95+ * When enabled, it will record allocated {@link XmlDisposable} objects
96+ * (and its subclass objects) and track if
97+ * {@link XmlDisposable#dispose} is called.
98+ *
99+ * Note that the allocation will not be monitored before memory diagnostics is enabled.
100+ *
101+ * @param options
102+ * @see {@link memReport }
103+ */
104+ export function memDiag ( options : MemDiagOptions ) {
105+ if ( options . enabled ) {
106+ tracker = new MemTrackerImpl ( ) ;
107+ } else {
108+ tracker = noopTracker ;
109+ }
110+ }
111+
112+ /**
113+ * Get the report of un-disposed objects.
114+ * @returns The report (JSON) object, whose format may vary according to the settings,
115+ * and is subject to change.
116+ * Returns undefined if memory diagnostic is not enabled.
117+ * @see {@link memDiag }
118+ */
119+ export function memReport ( ) : any {
120+ return tracker . report ( ) ;
121+ }
122+
123+ let tracker : MemTracker = noopTracker ;
124+
5125/**
6126 * Base implementation of interface Disposable to handle wasm memory.
7127 *
8128 * Remember to call `dispose()` for any subclass object.
9129 */
10130export abstract class XmlDisposable implements Disposable {
131+ protected constructor ( ) {
132+ tracker . trackAllocate ( this ) ;
133+ }
134+
11135 /**
12136 * Alias of {@link "[dispose]"}.
13137 *
@@ -18,6 +142,7 @@ export abstract class XmlDisposable implements Disposable {
18142 const metadata = ( this . constructor as any ) [ Symbol . metadata ] ;
19143 const propsToRelease = metadata [ Symbol . dispose ] as Array < string | symbol > ;
20144 propsToRelease . forEach ( ( prop ) => { ( this as any ) [ prop ] = 0 ; } ) ;
145+ tracker . trackDeallocate ( this ) ;
21146 }
22147
23148 /**
@@ -44,7 +169,7 @@ export abstract class XmlDisposable implements Disposable {
44169 * @param free function to release the managed wasm resource
45170 * @returns the decorator
46171 */
47- export function disposeBy < This extends object > ( free : ( value : Pointer ) => void ) {
172+ export function disposeBy < This extends XmlDisposable > ( free : ( value : Pointer ) => void ) {
48173 return function decorator (
49174 target : ClassAccessorDecoratorTarget < This , Pointer > ,
50175 ctx : ClassAccessorDecoratorContext < This , Pointer > ,
0 commit comments