metrics-rs / metrics

A metrics ecosystem for Rust.
MIT License
1.08k stars 148 forks source link

Add support for `Arc<T>` to `metrics::Cow<'a, T>`. #402

Closed tobz closed 9 months ago

tobz commented 10 months ago

This PR adds support for creating metrics::Cow<'a, T> from Arc<T>.

Context

Generally, users specify a metric name and optional labels for the metric, each of which has a key and value pair. All three of these -- metric name, label key, and label value -- are wrapped in metrics::Cow<'static, str> (which is exported from the crate as SharedString for simplicity). Additionally, in some cases, we construct a metrics::Cow<'static, [Label]> when labels are entirely static.

All of this works fine and well for both static strings (string literals) and owned strings, but did not allow users to use strings shared via a smart pointer such as Arc<T>. It is common practice to amortize allocation costs for long-lived strings by sharing them via something like Arc<str>. You still pay the cost of the atomic operations here and there, but it can end up much cheaper in the long-term than constantly allocating small strings all over the place, and that's to say nothing of the potential heap fragmentation caused by doing so.

This is important to users as if you're not able to use static strings for a metric key (name, labels) then you're stuck with owned strings, which can involve many of these small allocations every single time a metric is emitted.

Solution

We've added support for passing Arc<T> when constructing a metrics::Cow<'a, T>. This involved a little bit of work to properly track the variant of our pointer (borrowed vs owned vs shared) but ultimately functions a lot like if we were simply cloning Arc<T> and holding it internally. Of course, we only deal with pointers and pointer metadata (in the context of our code, not pointer metadata as the stdlib might talk about) and so we've had to resort to the manual functions for interacting with Arc<T>, such as incrementing and decrementing the strong count by hand, and so on.

We've additionally simplified a lot of the From<T> implementations to avoid having one for each pointer variant, as well at a higher-level in the types that are based on metrics::Cow<'a, T> itself.