cmcfarlen opened a new pull request, #13616:
URL: https://github.com/apache/trafficserver/pull/13616

   ## Problem
   
   `ts::Metrics::Storage` has no removal path. `create()` allocates a slot and 
a name and nothing ever undoes either, so a metric name lives for the life of 
the process.
   
   Any code that decides *whether* to publish a name based on a runtime 
changeable input therefore makes a permanent commitment the first time it 
publishes. The decision is latched at first creation and can never be revisited.
   
   The case that surfaced this is the per upstream server connection metrics 
from #13506. `proxy.config.http.per_server.connection.metric_aggregate` is 
`RECU_DYNAMIC` and overridable, and at value `2` the per group 
`<fqdn>.<ip>:<port>` metrics are supposed to stay hidden while only the per 
hostname aggregates are published. On a box that ran for a while at `0` before 
being switched to `2`, both shapes are present in `traffic_ctl metric match 
per_server`, and no reload can remove the first set. The config change took 
effect correctly for everything created after it; the names created before it 
simply cannot be withdrawn.
   
   ## What this does
   
   Adds a *tombstone*: a per slot mark that makes a metric invisible to 
iteration.
   
   ```cpp
   auto &m = ts::Metrics::instance();
   
   m.tombstone(id);                       // by id
   m.tombstone("proxy.process.example");  // or by name
   m.tombstone(id, false);                // clear the mark
   ```
   
   A tombstoned metric:
   
   - is skipped by iteration, so it disappears from `traffic_ctl metric match`, 
the JSONRPC record lookup and `stats_over_http` with no change in any of those 
consumers;
   - still resolves by exact name through `lookup()`, so `RecLookupRecord`, 
`LogAccess` field resolution and `TSStatFindName` keep working;
   - keeps its atomic, which may still be read and written, so a `Derived` 
aggregate sourcing from it is unaffected;
   - is resurrected by `create()` on the same name, returning the same id with 
its accumulated value intact.
   
   A tombstone expresses a publication policy, not a lifetime. Any `IdType` or 
`AtomicType *` a caller already holds stays valid across a tombstone and 
resurrect.
   
   This PR adds the mechanism only. Nothing in the tree calls it, so every 
existing metric enumerates exactly as before. The `ConnectionTracker` fix is a 
follow up.
   
   ## Implementation notes
   
   **Storage.** A parallel `FlagStorage` array in the blob, rather than a 
member of `NameAndId`: an `std::atomic` member would make that tuple neither 
copyable nor movable, and the slot is written with a tuple assignment. Blobs 
are already built with `make_unique`, which value initializes, so flags start 
zero with no change to `addBlob()`. Reads are lock free at relaxed ordering, 
matching the rest of the class. Cost is 1 KiB per 1024 slots against a blob 
that is already about 48 KiB.
   
   Set and clear use `fetch_or` / `fetch_and(~TOMBSTONE)` rather than a whole 
word store, so a flag added later is not clobbered.
   
   **Iterator.** The skip loop needs an end bound, and deriving it from `end()` 
per element would take the storage mutex per element. Instead the iterator 
captures the bound once at construction and `end()` becomes a pure sentinel 
that reads no storage. That is strictly less locking than before, where `end()` 
locked on every construction. It relies on iteration ids being monotonic in 
(blob, offset), which holds because both ends use `MetricType::COUNTER` so the 
type bits are zero.
   
   Iteration is now explicitly a snapshot taken at `begin()`: a metric created 
mid iteration is never seen rather than sometimes seen. This matches the 
reasoning already recorded in `RecLookupRecord` about `find()`/`end()` racing 
with concurrent registration.
   
   `operator==` is three way. Any exhausted iterator equals the end sentinel 
and equals any other exhausted iterator, since two of them may have skipped a 
different number of marked slots; two live iterators still compare by position, 
unchanged. The hand written `operator!=` is removed in favor of the C++20 
synthesized one.
   
   **`find()` returns `end()` for a tombstoned metric.** Iteration never visits 
a marked slot, so an iterator pointing at one is a range bound that a skipping 
walk steps straight over and never reaches. Use `lookup()` to read a tombstoned 
metric.
   
   ## Tests
   
   `test_Metrics.cc`: skipped by iteration, still resolvable by name and id, 
resurrect via `create()`, tombstone by name, clearing the mark, `begin()` 
skipping a marked first slot, a marked run at the end of the store, iterating 
to a bound that is not `end()` with marked slots inside the range, `find()` 
yielding `end()`, iterator comparison, an unallocated id, and independence 
between the published and hidden stores.
   
   `test_RecHiddenMetricLookup.cc`: a tombstoned metric is not enumerated by 
`RecLookupMatchingRecords`, is still found by `RecLookupRecord`, and returns to 
enumeration when the mark is cleared.
   
   Documented in `doc/developer-guide/internal-libraries/Metrics.en.rst`.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to