-
Notifications
You must be signed in to change notification settings - Fork 875
Allow a metric to be unlisted #13616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
321a970
e776b65
1f0f4ca
03452ff
0d16232
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -94,13 +94,20 @@ class Metrics | |
| static constexpr int METRIC_TYPE_MASK = 0x1FFF; | ||
|
|
||
| private: | ||
| using NameAndId = std::tuple<std::string, IdType>; | ||
| using LookupTable = std::unordered_map<std::string_view, IdType>; | ||
| using NameStorage = std::array<NameAndId, MAX_SIZE>; | ||
| using AtomicStorage = std::array<AtomicType, MAX_SIZE>; | ||
| using NamesAndAtomics = std::tuple<NameStorage, AtomicStorage>; | ||
| using NameAndId = std::tuple<std::string, IdType>; | ||
| using LookupTable = std::unordered_map<std::string_view, IdType>; | ||
| using NameStorage = std::array<NameAndId, MAX_SIZE>; | ||
| using AtomicStorage = std::array<AtomicType, MAX_SIZE>; | ||
| /// Per slot flag bits, see @c UNLISTED. A parallel array rather than a member of @c NameAndId | ||
| /// because an atomic member would make that tuple neither copyable nor movable, and the slot is | ||
| /// written there with a tuple assignment. | ||
| using FlagStorage = std::array<std::atomic<uint8_t>, MAX_SIZE>; | ||
| using NamesAndAtomics = std::tuple<NameStorage, AtomicStorage, FlagStorage>; | ||
| using BlobStorage = std::array<std::unique_ptr<NamesAndAtomics>, MAX_BLOBS>; | ||
|
|
||
| /// The slot exists and is still resolvable by name or id, but is skipped by iteration. | ||
| static constexpr uint8_t UNLISTED = 0x01; | ||
|
|
||
| public: | ||
| Metrics(const self_type &) = delete; | ||
| self_type &operator=(const self_type &) = delete; | ||
|
|
@@ -145,6 +152,57 @@ class Metrics | |
| { | ||
| return _storage->lookup(id, out_name, type); | ||
| } | ||
|
|
||
| /** Take @a id out of the store's listing. | ||
| * | ||
| * An unlisted metric keeps its slot, its name and its atomic. It is skipped by iteration, so it | ||
| * vanishes from everything that enumerates the store, but it still resolves through @c lookup and | ||
| * its value may still be read and written -- an unlisted number that still rings. Creating the | ||
| * same name again relists it and returns the same id. | ||
| * | ||
| * @return @c false if @a id does not name an allocated slot. | ||
| */ | ||
| bool | ||
| unlist(IdType id) | ||
| { | ||
| return _storage->set_listed(id, false); | ||
| } | ||
|
|
||
| /// Put @a id back in the listing. @see unlist | ||
| bool | ||
| relist(IdType id) | ||
| { | ||
| return _storage->set_listed(id, true); | ||
| } | ||
|
|
||
| /** Whether @a id is enumerated. | ||
| * | ||
| * @return @c false for an unlisted metric, and also for an id that names no allocated slot -- | ||
| * neither appears in iteration. | ||
| */ | ||
| bool | ||
| listed(IdType id) const | ||
| { | ||
| return _storage->listed(id); | ||
| } | ||
|
|
||
| /// Convenience for callers that publish by name and do not retain the id. @see unlist | ||
| bool | ||
| unlist(std::string_view name) | ||
| { | ||
| auto id = lookup(name); | ||
|
|
||
| return id != NOT_FOUND && unlist(id); | ||
| } | ||
|
|
||
| /// Convenience for callers that publish by name and do not retain the id. @see relist | ||
| bool | ||
| relist(std::string_view name) | ||
| { | ||
| auto id = lookup(name); | ||
|
|
||
| return id != NOT_FOUND && relist(id); | ||
| } | ||
| AtomicType & | ||
| operator[](IdType id) | ||
| { | ||
|
|
@@ -194,15 +252,25 @@ class Metrics | |
| // Static methods to encapsulate access to the atomic's | ||
| class iterator | ||
| { | ||
| friend class Metrics; | ||
|
|
||
|
|
||
| /// Tag for the end sentinel, which has no position and reads no storage. | ||
| struct end_tag { | ||
| }; | ||
|
|
||
| // Only Metrics hands these out, through begin(), end() and find(). A caller that could name an | ||
| // arbitrary position could name an unlisted one, which iteration must never visit. | ||
| explicit iterator(const Metrics &m); | ||
| iterator(const Metrics &m, IdType pos); | ||
| iterator(const Metrics &m, end_tag); | ||
|
Comment on lines
+261
to
+265
|
||
|
|
||
| public: | ||
| using iterator_category = std::input_iterator_tag; | ||
|
|
||
| using value_type = std::tuple<std::string_view, MetricType, int64_t>; | ||
| using difference_type = ptrdiff_t; | ||
| using pointer = value_type *; | ||
| using reference = value_type &; | ||
|
|
||
| iterator(const Metrics &m, IdType pos) : _metrics(m), _it(pos) {} | ||
|
|
||
| iterator & | ||
| operator++() | ||
| { | ||
|
|
@@ -231,43 +299,80 @@ class Metrics | |
| return std::make_tuple(name, type, metric->_value.load()); | ||
| } | ||
|
|
||
| /** Equality. | ||
| * | ||
| * Three way rather than a plain position compare: any exhausted iterator equals the end | ||
| * sentinel, and equals any other exhausted iterator, since two of them may have skipped a | ||
| * different number of unlisted slots. Two live iterators still compare by position. | ||
| * | ||
| * Two positional iterators may hold different snapshots, so exhaustion between them is judged | ||
| * against the earlier bound. Otherwise a walk could pass its own bound while a stop iterator | ||
| * made later was still live: they would never compare equal and @c operator++ could not make | ||
| * progress. The sentinel keeps its own answer, since its bound is meaningless. | ||
| * | ||
| * @note A snapshot is the sequence: iterators from different ones are no more comparable than | ||
| * iterators into different containers, and mixing them is unspecified. Within one snapshot | ||
| * equality is the equivalence relation an input iterator requires. The rule above keeps the | ||
| * unspecified case terminating rather than hanging. | ||
| */ | ||
| bool | ||
| operator==(const iterator &o) const | ||
| { | ||
| return _it == o._it && std::addressof(_metrics) == std::addressof(o._metrics); | ||
| } | ||
| if (std::addressof(_metrics) != std::addressof(o._metrics)) { | ||
| return false; | ||
| } | ||
|
|
||
| bool | ||
| operator!=(const iterator &o) const | ||
| { | ||
| return _it != o._it || std::addressof(_metrics) != std::addressof(o._metrics); | ||
| if (_end || o._end) { | ||
| return at_end() == o.at_end(); | ||
| } | ||
|
|
||
| auto const bound = _bound < o._bound ? _bound : o._bound; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Account for a saved subrange endpoint becoming unlisted The shared-bound rule does not cover listing changes within the same allocation snapshot. Create |
||
| bool const a = _it >= bound, b = o._it >= bound; | ||
|
|
||
| if (a || b) { | ||
| return a && b; | ||
| } | ||
| return _it == o._it; | ||
|
cmcfarlen marked this conversation as resolved.
|
||
| } | ||
|
cmcfarlen marked this conversation as resolved.
Comment on lines
318
to
336
|
||
|
|
||
|
cmcfarlen marked this conversation as resolved.
|
||
| private: | ||
| void next(); | ||
| void advance(); | ||
| void skip_unlisted(); | ||
|
|
||
| bool | ||
| at_end() const | ||
| { | ||
| return _end || _it >= _bound; | ||
| } | ||
|
|
||
| const Metrics &_metrics; | ||
| Metrics::IdType _it; | ||
| Metrics::IdType _it{0}; | ||
| /// One past the last slot allocated when this iterator was made. Iteration is a snapshot. | ||
| Metrics::IdType _bound{0}; | ||
| bool _end{false}; | ||
| }; | ||
|
|
||
| iterator | ||
| begin() const | ||
| { | ||
| return iterator(*this, 0); | ||
| return iterator(*this); | ||
| } | ||
|
|
||
| iterator | ||
| end() const | ||
| { | ||
| return iterator(*this, _storage->next_free_id()); | ||
| return iterator(*this, iterator::end_tag{}); | ||
| } | ||
|
|
||
| iterator | ||
| find(const std::string_view name) const | ||
| { | ||
| auto id = lookup(name); | ||
|
|
||
| if (id == NOT_FOUND) { | ||
| // An unlisted slot is never visited by iteration, so handing out an iterator to one would | ||
| // produce a bound that a skipping walk steps straight over. Reach it with lookup() instead. | ||
| if (id == NOT_FOUND || !listed(id)) { | ||
| return end(); | ||
| } else { | ||
| return iterator(*this, id); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Keep find() from returning a different metric after concurrent unlisting There is a check/use gap between |
||
|
|
@@ -349,6 +454,8 @@ class Metrics | |
| AtomicType *lookup(Metrics::IdType id, std::string_view *out_name = nullptr, MetricType *out_type = nullptr) const; | ||
| std::string_view name(IdType id) const; | ||
| MetricType type(IdType id) const; | ||
| bool set_listed(IdType id, bool listed); | ||
| bool listed(IdType id) const; | ||
|
|
||
| /// The id the next slot will get, which is also iteration's exclusive bound. | ||
| IdType | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.