@@ -153,6 +153,7 @@ class StringDictionaryBuffer {
153153 SBTable* _table;
154154 std::atomic<int > _size{0 };
155155 StringArena _arena;
156+ int _counter_offset{0 }; // 0 = no page/byte tracking
156157
157158 static unsigned int hash (const char * key, size_t length) {
158159 unsigned int h = 2166136261U ;
@@ -165,22 +166,24 @@ class StringDictionaryBuffer {
165166 }
166167
167168 // Free only overflow SBTable chain nodes (not key strings — arena-owned).
168- // Same DFS traversal as the old freeTable but without the per-key free .
169- static void freeOverflowNodes (SBTable* table) {
169+ // Returns the number of overflow nodes freed (excludes the root table) .
170+ static int freeOverflowNodes (SBTable* table) {
170171 struct Frame { SBTable* t; int row; };
171172 Frame stk[34 ];
172173 int top = 0 ;
174+ int freed = 0 ;
173175 stk[top++] = {table, 0 };
174176 while (top > 0 ) {
175177 Frame& f = stk[top - 1 ];
176178 if (f.row >= ROWS ) {
177- if (f.t != table) free (f.t );
179+ if (f.t != table) { free (f.t ); freed++; }
178180 --top;
179181 continue ;
180182 }
181183 SBRow* row = &f.t ->rows [f.row ++];
182184 if (row->next && top < 34 ) stk[top++] = {row->next , 0 };
183185 }
186+ return freed;
184187 }
185188
186189 static void collectTable (const SBTable* table,
@@ -223,6 +226,16 @@ class StringDictionaryBuffer {
223226 StringDictionaryBuffer (StringDictionaryBuffer&&) = delete ;
224227 StringDictionaryBuffer& operator =(StringDictionaryBuffer&&) = delete ;
225228
229+ // Enable DICTIONARY_PAGES / DICTIONARY_BYTES tracking for this buffer.
230+ // Called by StringDictionary after construction; counts the root SBTable.
231+ void initCounters (int offset) {
232+ _counter_offset = offset;
233+ if (_table != nullptr ) {
234+ Counters::increment (DICTIONARY_PAGES , 1 , offset);
235+ Counters::increment (DICTIONARY_BYTES , (long long )sizeof (SBTable), offset);
236+ }
237+ }
238+
226239 // Signal-safe read-only probe. Returns 0 on miss.
227240 u32 lookup (const char * key, size_t len) const {
228241 const SBTable* table = _table;
@@ -274,7 +287,12 @@ class StringDictionaryBuffer {
274287 if (!row->next ) {
275288 SBTable* nt = static_cast <SBTable*>(calloc (1 , sizeof (SBTable)));
276289 if (nt == nullptr ) return 0 ;
277- if (!__sync_bool_compare_and_swap (&row->next , nullptr , nt)) free (nt);
290+ if (!__sync_bool_compare_and_swap (&row->next , nullptr , nt)) {
291+ free (nt);
292+ } else if (_counter_offset != 0 ) {
293+ Counters::increment (DICTIONARY_PAGES , 1 , _counter_offset);
294+ Counters::increment (DICTIONARY_BYTES , (long long )sizeof (SBTable), _counter_offset);
295+ }
278296 }
279297 table = __atomic_load_n (&row->next , __ATOMIC_ACQUIRE);
280298 h = (h >> ROW_BITS ) | (h << (32 - ROW_BITS ));
@@ -300,10 +318,14 @@ class StringDictionaryBuffer {
300318 // Call only with no concurrent accessors.
301319 void clear () {
302320 if (_table == nullptr ) { _size.store (0 , std::memory_order_relaxed); return ; }
303- freeOverflowNodes (_table);
321+ int freed = freeOverflowNodes (_table);
304322 memset (_table, 0 , sizeof (SBTable));
305323 _arena.reset ();
306324 _size.store (0 , std::memory_order_relaxed);
325+ if (_counter_offset != 0 && freed > 0 ) {
326+ Counters::decrement (DICTIONARY_PAGES , freed, _counter_offset);
327+ Counters::decrement (DICTIONARY_BYTES , (long long )(freed * sizeof (SBTable)), _counter_offset);
328+ }
307329 }
308330
309331 int size () const { return _size.load (std::memory_order_relaxed); }
@@ -355,7 +377,13 @@ class StringDictionary {
355377
356378public:
357379 explicit StringDictionary (int counter_offset = 0 )
358- : _rot(&_a, &_b, &_c), _counter_offset(counter_offset) {}
380+ : _rot(&_a, &_b, &_c), _counter_offset(counter_offset) {
381+ if (counter_offset != 0 ) {
382+ _a.initCounters (counter_offset);
383+ _b.initCounters (counter_offset);
384+ _c.initCounters (counter_offset);
385+ }
386+ }
359387
360388 // Insert into active buffer; returns globally stable id. NOT signal-safe.
361389 u32 lookup (const char * key, size_t len) {
0 commit comments