1use std::{fmt, hash, sync::Arc};
2
3use metrics::Gauge;
4use stringtheory::MetaString;
5
6use crate::{
7 hash::{hash_context, ContextKey},
8 tags::{SharedTagSet, TagSet},
9};
10
11const BASE_CONTEXT_SIZE: usize = std::mem::size_of::<Context>() + std::mem::size_of::<ContextInner>();
12
13#[derive(Clone, Debug, Eq, Hash, PartialEq)]
15pub struct Context {
16 inner: Arc<ContextInner>,
17}
18
19impl Context {
20 pub fn from_static_name(name: &'static str) -> Self {
22 let tags = SharedTagSet::default();
23 let origin_tags = SharedTagSet::default();
24
25 let (key, _) = hash_context(name, &tags, &origin_tags);
26 Self {
27 inner: Arc::new(ContextInner {
28 name: MetaString::from_static(name),
29 tags,
30 origin_tags,
31 key,
32 active_count: Gauge::noop(),
33 }),
34 }
35 }
36
37 pub fn from_static_parts(name: &'static str, tags: &[&'static str]) -> Self {
39 let mut tag_set = TagSet::with_capacity(tags.len());
40 for tag in tags {
41 tag_set.insert_tag(MetaString::from_static(tag));
42 }
43
44 let origin_tags = SharedTagSet::default();
45
46 let (key, _) = hash_context(name, tags, &origin_tags);
47 Self {
48 inner: Arc::new(ContextInner {
49 name: MetaString::from_static(name),
50 tags: tag_set.into_shared(),
51 origin_tags,
52 key,
53 active_count: Gauge::noop(),
54 }),
55 }
56 }
57
58 pub fn from_parts<S: Into<MetaString>>(name: S, tags: SharedTagSet) -> Self {
60 let name = name.into();
61 let origin_tags = SharedTagSet::default();
62 let (key, _) = hash_context(&name, &tags, &origin_tags);
63 Self {
64 inner: Arc::new(ContextInner {
65 name,
66 tags,
67 origin_tags,
68 key,
69 active_count: Gauge::noop(),
70 }),
71 }
72 }
73
74 pub fn with_name<S: Into<MetaString>>(&self, name: S) -> Self {
76 let name = name.into();
78 let tags = self.inner.tags.clone();
79 let origin_tags = self.inner.origin_tags.clone();
80 let (key, _) = hash_context(&name, &tags, &origin_tags);
81
82 Self {
83 inner: Arc::new(ContextInner {
84 name,
85 tags,
86 origin_tags,
87 key,
88 active_count: Gauge::noop(),
89 }),
90 }
91 }
92
93 pub(crate) fn from_inner(inner: ContextInner) -> Self {
94 Self { inner: Arc::new(inner) }
95 }
96
97 #[cfg(test)]
98 pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
99 Arc::ptr_eq(&self.inner, &other.inner)
100 }
101
102 pub fn name(&self) -> &MetaString {
104 &self.inner.name
105 }
106
107 pub fn tags(&self) -> &SharedTagSet {
109 &self.inner.tags
110 }
111
112 pub fn origin_tags(&self) -> &SharedTagSet {
114 &self.inner.origin_tags
115 }
116
117 pub fn size_of(&self) -> usize {
132 let name_size = self.inner.name.len();
133 let tags_size = self.inner.tags.size_of();
134 let origin_tags_size = self.inner.origin_tags.size_of();
135
136 BASE_CONTEXT_SIZE + name_size + tags_size + origin_tags_size
137 }
138}
139
140impl From<&'static str> for Context {
141 fn from(name: &'static str) -> Self {
142 Self::from_static_name(name)
143 }
144}
145
146impl<'a> From<(&'static str, &'a [&'static str])> for Context {
147 fn from((name, tags): (&'static str, &'a [&'static str])) -> Self {
148 Self::from_static_parts(name, tags)
149 }
150}
151
152impl fmt::Display for Context {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 write!(f, "{}", self.inner.name)?;
155 if !self.inner.tags.is_empty() {
156 write!(f, "{{")?;
157
158 let mut needs_separator = false;
159 for tag in &self.inner.tags {
160 if needs_separator {
161 write!(f, ", ")?;
162 } else {
163 needs_separator = true;
164 }
165
166 write!(f, "{}", tag)?;
167 }
168
169 write!(f, "}}")?;
170 }
171
172 Ok(())
173 }
174}
175
176pub(super) struct ContextInner {
177 key: ContextKey,
178 name: MetaString,
179 tags: SharedTagSet,
180 origin_tags: SharedTagSet,
181 active_count: Gauge,
182}
183
184impl ContextInner {
185 pub fn from_parts(
186 key: ContextKey, name: MetaString, tags: SharedTagSet, origin_tags: SharedTagSet, active_count: Gauge,
187 ) -> Self {
188 Self {
189 key,
190 name,
191 tags,
192 origin_tags,
193 active_count,
194 }
195 }
196}
197
198impl Clone for ContextInner {
199 fn clone(&self) -> Self {
200 Self {
201 key: self.key,
202 name: self.name.clone(),
203 tags: self.tags.clone(),
204 origin_tags: self.origin_tags.clone(),
205
206 active_count: Gauge::noop(),
210 }
211 }
212}
213
214impl Drop for ContextInner {
215 fn drop(&mut self) {
216 self.active_count.decrement(1);
217 }
218}
219
220impl PartialEq<ContextInner> for ContextInner {
221 fn eq(&self, other: &ContextInner) -> bool {
222 self.key == other.key
224 }
225}
226
227impl Eq for ContextInner {}
228
229impl hash::Hash for ContextInner {
230 fn hash<H: hash::Hasher>(&self, state: &mut H) {
231 self.key.hash(state);
232 }
233}
234
235impl fmt::Debug for ContextInner {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 f.debug_struct("ContextInner")
238 .field("name", &self.name)
239 .field("tags", &self.tags)
240 .field("key", &self.key)
241 .finish()
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use crate::tags::Tag;
249
250 const SIZE_OF_CONTEXT_NAME: &str = "size_of_test_metric";
251 const SIZE_OF_CONTEXT_CHANGED_NAME: &str = "size_of_test_metric_changed";
252 const SIZE_OF_CONTEXT_TAGS: &[&str] = &["size_of_test_tag1", "size_of_test_tag2"];
253 const SIZE_OF_CONTEXT_ORIGIN_TAGS: &[&str] = &["size_of_test_origin_tag1", "size_of_test_origin_tag2"];
254
255 fn tag_set(tags: &[&str]) -> SharedTagSet {
256 tags.iter().map(|s| Tag::from(*s)).collect::<TagSet>().into_shared()
257 }
258
259 #[test]
260 fn size_of_context_from_static_name() {
261 let context = Context::from_static_name(SIZE_OF_CONTEXT_NAME);
262 assert_eq!(context.size_of(), BASE_CONTEXT_SIZE + SIZE_OF_CONTEXT_NAME.len());
263 }
264
265 #[test]
266 fn size_of_context_from_static_parts() {
267 let tags = tag_set(SIZE_OF_CONTEXT_TAGS);
268
269 let context = Context::from_static_parts(SIZE_OF_CONTEXT_NAME, SIZE_OF_CONTEXT_TAGS);
270 assert_eq!(
271 context.size_of(),
272 BASE_CONTEXT_SIZE + SIZE_OF_CONTEXT_NAME.len() + tags.size_of()
273 );
274 }
275
276 #[test]
277 fn size_of_context_from_parts() {
278 let tags = tag_set(SIZE_OF_CONTEXT_TAGS);
279
280 let context = Context::from_parts(SIZE_OF_CONTEXT_NAME, tags.clone());
281 assert_eq!(
282 context.size_of(),
283 BASE_CONTEXT_SIZE + SIZE_OF_CONTEXT_NAME.len() + tags.size_of()
284 );
285 }
286
287 #[test]
288 fn size_of_context_with_name() {
289 let context = Context::from_static_name(SIZE_OF_CONTEXT_NAME).with_name(SIZE_OF_CONTEXT_CHANGED_NAME);
291 assert_eq!(
292 context.size_of(),
293 BASE_CONTEXT_SIZE + SIZE_OF_CONTEXT_CHANGED_NAME.len()
294 );
295
296 let tags = tag_set(SIZE_OF_CONTEXT_TAGS);
297
298 let context = Context::from_static_parts(SIZE_OF_CONTEXT_NAME, SIZE_OF_CONTEXT_TAGS)
299 .with_name(SIZE_OF_CONTEXT_CHANGED_NAME);
300 assert_eq!(
301 context.size_of(),
302 BASE_CONTEXT_SIZE + SIZE_OF_CONTEXT_CHANGED_NAME.len() + tags.size_of()
303 );
304 }
305
306 #[test]
307 fn size_of_context_origin_tags() {
308 let tags = tag_set(SIZE_OF_CONTEXT_TAGS);
309 let origin_tags = tag_set(SIZE_OF_CONTEXT_ORIGIN_TAGS);
310
311 let (key, _) = hash_context(SIZE_OF_CONTEXT_NAME, SIZE_OF_CONTEXT_TAGS, SIZE_OF_CONTEXT_ORIGIN_TAGS);
312
313 let context = Context::from_inner(ContextInner {
314 key,
315 name: MetaString::from_static(SIZE_OF_CONTEXT_NAME),
316 tags: tags.clone(),
317 origin_tags: origin_tags.clone(),
318 active_count: Gauge::noop(),
319 });
320
321 assert_eq!(
323 context.size_of(),
324 BASE_CONTEXT_SIZE + SIZE_OF_CONTEXT_NAME.len() + tags.size_of() + origin_tags.size_of()
325 );
326 }
327}