1use std::fmt;
7
8use protobuf::CodedOutputStream;
9
10use crate::{
11 constants::{
12 DICT_NAME_STR_FIELD_NUMBER, DICT_ORIGIN_INFO_FIELD_NUMBER, DICT_RESOURCE_LEN_FIELD_NUMBER,
13 DICT_RESOURCE_NAME_FIELD_NUMBER, DICT_RESOURCE_STR_FIELD_NUMBER, DICT_RESOURCE_TYPE_FIELD_NUMBER,
14 DICT_SOURCE_TYPE_NAME_FIELD_NUMBER, DICT_TAGSETS_FIELD_NUMBER, DICT_TAGS_STR_FIELD_NUMBER,
15 DICT_UNIT_STR_FIELD_NUMBER, INTERVALS_FIELD_NUMBER, NAMES_FIELD_NUMBER, NUM_POINTS_FIELD_NUMBER,
16 ORIGIN_INFO_FIELD_NUMBER, RESOURCES_FIELD_NUMBER, SKETCH_BIN_CNTS_FIELD_NUMBER, SKETCH_BIN_KEYS_FIELD_NUMBER,
17 SKETCH_NUM_BINS_FIELD_NUMBER, SOURCE_TYPE_NAME_FIELD_NUMBER, TAGS_FIELD_NUMBER, TIMESTAMPS_FIELD_NUMBER,
18 TYPES_FIELD_NUMBER, UNIT_REFS_FIELD_NUMBER, VALS_FLOAT32_FIELD_NUMBER, VALS_FLOAT64_FIELD_NUMBER,
19 VALS_SINT64_FIELD_NUMBER,
20 },
21 interner::Interner,
22 types::{value_type_for_values, V3MetricType, V3ValueType},
23};
24
25#[derive(Debug)]
27pub struct V3EncodeError(protobuf::Error);
28
29impl fmt::Display for V3EncodeError {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 write!(f, "failed to encode V3 payload: {}", self.0)
32 }
33}
34
35impl std::error::Error for V3EncodeError {
36 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
37 Some(&self.0)
38 }
39}
40
41impl From<protobuf::Error> for V3EncodeError {
42 fn from(e: protobuf::Error) -> Self {
43 Self(e)
44 }
45}
46
47impl V3EncodeError {
48 pub fn into_inner(self) -> protobuf::Error {
50 self.0
51 }
52}
53
54pub(crate) const FLAG_NO_INDEX: u64 = 0x100;
55pub(crate) const FLAG_HAS_UNIT: u64 = 0x200;
56
57#[derive(Debug, Default)]
61pub(crate) struct V3EncodedData {
62 dict_name_bytes: Vec<u8>,
64 dict_tags_bytes: Vec<u8>,
65 dict_tagsets: Vec<i64>,
66 dict_resource_str_bytes: Vec<u8>,
67 dict_resource_len: Vec<i64>,
68 dict_resource_type: Vec<i64>,
69 dict_resource_name: Vec<i64>,
70 dict_source_type_bytes: Vec<u8>,
71 dict_origin_info: Vec<i32>,
72 dict_unit_bytes: Vec<u8>,
73
74 types: Vec<u64>,
76 names: Vec<i64>,
77 tags: Vec<i64>,
78 resources: Vec<i64>,
79 intervals: Vec<u64>,
80 num_points: Vec<u64>,
81 source_type_names: Vec<i64>,
82 origin_infos: Vec<i64>,
83 unit_refs: Vec<i64>, timestamps: Vec<i64>,
87 vals_sint64: Vec<i64>,
88 vals_float32: Vec<f32>,
89 vals_float64: Vec<f64>,
90
91 sketch_num_bins: Vec<u64>,
93 sketch_bin_keys: Vec<i32>,
94 sketch_bin_cnts: Vec<u32>,
95
96 value_encoding_stats: V3ValueEncodingStats,
97}
98
99pub struct V3EncodedMetrics {
101 pub payload: Vec<u8>,
103 pub stats: V3EncoderStats,
105}
106
107pub struct V3EncoderStats {
109 pub value_encoding_stats: V3ValueEncodingStats,
111 pub columns: Vec<V3ColumnBytes>,
113}
114
115#[derive(Clone, Copy, Debug, Default)]
117pub struct V3ValueEncodingStats {
118 pub zero: u64,
120 pub sint64: u64,
122 pub float32: u64,
124 pub float64: u64,
126}
127
128pub struct V3ColumnBytes {
130 pub field_number: u32,
132 pub bytes: Vec<u8>,
134 pub compressed_len: usize,
136}
137
138#[derive(Debug, Default)]
144pub struct V3Writer {
145 name_interner: Interner<String>,
147 tag_interner: Interner<String>,
148 tagset_interner: Interner<Vec<i64>>,
149 resource_str_interner: Interner<String>,
150 resource_interner: Interner<Vec<(i64, i64)>>,
151 source_type_interner: Interner<String>,
152 origin_interner: Interner<(i32, i32, i32)>,
153 unit_interner: Interner<String>,
154
155 dict_name_bytes: Vec<u8>,
157 dict_tags_bytes: Vec<u8>,
158 dict_tagsets: Vec<i64>,
159 dict_resource_str_bytes: Vec<u8>,
160 dict_resource_len: Vec<i64>,
161 dict_resource_type: Vec<i64>,
162 dict_resource_name: Vec<i64>,
163 dict_source_type_bytes: Vec<u8>,
164 dict_origin_info: Vec<i32>,
165 dict_unit_bytes: Vec<u8>,
166
167 types: Vec<u64>,
169 names: Vec<i64>,
170 tags: Vec<i64>,
171 resources: Vec<i64>,
172 intervals: Vec<u64>,
173 num_points: Vec<u64>,
174 source_type_names: Vec<i64>,
175 origin_infos: Vec<i64>,
176 unit_refs: Vec<i64>, timestamps: Vec<i64>,
180 vals_sint64: Vec<i64>,
181 vals_float32: Vec<f32>,
182 vals_float64: Vec<f64>,
183
184 sketch_num_bins: Vec<u64>,
186 sketch_bin_keys: Vec<i32>,
187 sketch_bin_cnts: Vec<u32>,
188
189 tag_ids: Vec<i64>,
191 resource_ids: Vec<(i64, i64)>,
192 value_encoding_stats: V3ValueEncodingStats,
193}
194
195impl V3Writer {
196 pub fn new() -> Self {
198 Self::default()
199 }
200
201 pub fn metric_count(&self) -> usize {
203 self.types.len()
204 }
205
206 pub fn estimated_uncompressed_len(&self) -> usize {
218 const VARINT_LEN_ALLOWANCE: usize = 4;
221
222 let dict_bytes = self.dict_name_bytes.len()
224 + self.dict_tags_bytes.len()
225 + self.dict_resource_str_bytes.len()
226 + self.dict_source_type_bytes.len()
227 + self.dict_unit_bytes.len();
228
229 let varint_entries = self.dict_tagsets.len()
231 + self.dict_resource_len.len()
232 + self.dict_resource_type.len()
233 + self.dict_resource_name.len()
234 + self.dict_origin_info.len()
235 + self.types.len()
236 + self.names.len()
237 + self.tags.len()
238 + self.resources.len()
239 + self.intervals.len()
240 + self.num_points.len()
241 + self.source_type_names.len()
242 + self.origin_infos.len()
243 + self.unit_refs.len()
244 + self.timestamps.len()
245 + self.vals_sint64.len()
246 + self.sketch_num_bins.len()
247 + self.sketch_bin_keys.len()
248 + self.sketch_bin_cnts.len();
249
250 let float_bytes = (self.vals_float32.len() * size_of::<f32>()) + (self.vals_float64.len() * size_of::<f64>());
252
253 dict_bytes + (varint_entries * VARINT_LEN_ALLOWANCE) + float_bytes
254 }
255
256 pub fn write(&mut self, metric_type: V3MetricType, name: &str) -> V3MetricBuilder<'_> {
261 let name_id = self.intern_name(name);
262 let metric_idx = self.types.len();
263 let point_start_idx = self.vals_float64.len();
264 let sint64_start_idx = self.vals_sint64.len();
265
266 self.types.push(metric_type.as_u64());
268 self.names.push(name_id);
269 self.tags.push(0);
270 self.resources.push(0);
271 self.intervals.push(0);
272 self.num_points.push(0);
273 self.source_type_names.push(0);
274 self.origin_infos.push(0);
275
276 V3MetricBuilder {
277 writer: self,
278 point_start_idx,
279 sint64_start_idx,
280 metric_idx,
281 unit_ref_idx: None,
282 }
283 }
284
285 fn finalize_inner(mut self) -> V3EncodedData {
286 delta_encode(&mut self.names);
288 delta_encode(&mut self.tags);
289 delta_encode(&mut self.resources);
290 delta_encode(&mut self.source_type_names);
291 delta_encode(&mut self.origin_infos);
292 delta_encode(&mut self.unit_refs);
293 delta_encode(&mut self.timestamps);
294
295 V3EncodedData {
296 dict_name_bytes: self.dict_name_bytes,
297 dict_tags_bytes: self.dict_tags_bytes,
298 dict_tagsets: self.dict_tagsets,
299 dict_resource_str_bytes: self.dict_resource_str_bytes,
300 dict_resource_len: self.dict_resource_len,
301 dict_resource_type: self.dict_resource_type,
302 dict_resource_name: self.dict_resource_name,
303 dict_source_type_bytes: self.dict_source_type_bytes,
304 dict_origin_info: self.dict_origin_info,
305 dict_unit_bytes: self.dict_unit_bytes,
306 types: self.types,
307 names: self.names,
308 tags: self.tags,
309 resources: self.resources,
310 intervals: self.intervals,
311 num_points: self.num_points,
312 source_type_names: self.source_type_names,
313 origin_infos: self.origin_infos,
314 unit_refs: self.unit_refs,
315 timestamps: self.timestamps,
316 vals_sint64: self.vals_sint64,
317 vals_float32: self.vals_float32,
318 vals_float64: self.vals_float64,
319 sketch_num_bins: self.sketch_num_bins,
320 sketch_bin_keys: self.sketch_bin_keys,
321 sketch_bin_cnts: self.sketch_bin_cnts,
322 value_encoding_stats: self.value_encoding_stats,
323 }
324 }
325
326 pub fn finalize(self) -> Result<V3EncodedMetrics, V3EncodeError> {
330 let data = self.finalize_inner();
331 let mut output = Vec::new();
332 let mut columns = Vec::new();
333
334 if !data.dict_name_bytes.is_empty() {
336 write_bytes_column(
337 &mut output,
338 &mut columns,
339 DICT_NAME_STR_FIELD_NUMBER,
340 &data.dict_name_bytes,
341 )?;
342 }
343 if !data.dict_tags_bytes.is_empty() {
344 write_bytes_column(
345 &mut output,
346 &mut columns,
347 DICT_TAGS_STR_FIELD_NUMBER,
348 &data.dict_tags_bytes,
349 )?;
350 }
351
352 write_packed_column(
354 &mut output,
355 &mut columns,
356 DICT_TAGSETS_FIELD_NUMBER,
357 |os| os.write_repeated_packed_sint64(DICT_TAGSETS_FIELD_NUMBER, &data.dict_tagsets),
358 |os| os.write_repeated_packed_sint64_no_tag(&data.dict_tagsets),
359 )?;
360
361 if !data.dict_resource_str_bytes.is_empty() {
362 write_bytes_column(
363 &mut output,
364 &mut columns,
365 DICT_RESOURCE_STR_FIELD_NUMBER,
366 &data.dict_resource_str_bytes,
367 )?;
368 }
369
370 write_packed_column(
371 &mut output,
372 &mut columns,
373 DICT_RESOURCE_LEN_FIELD_NUMBER,
374 |os| os.write_repeated_packed_int64(DICT_RESOURCE_LEN_FIELD_NUMBER, &data.dict_resource_len),
375 |os| os.write_repeated_packed_int64_no_tag(&data.dict_resource_len),
376 )?;
377 write_packed_column(
378 &mut output,
379 &mut columns,
380 DICT_RESOURCE_TYPE_FIELD_NUMBER,
381 |os| os.write_repeated_packed_sint64(DICT_RESOURCE_TYPE_FIELD_NUMBER, &data.dict_resource_type),
382 |os| os.write_repeated_packed_sint64_no_tag(&data.dict_resource_type),
383 )?;
384 write_packed_column(
385 &mut output,
386 &mut columns,
387 DICT_RESOURCE_NAME_FIELD_NUMBER,
388 |os| os.write_repeated_packed_sint64(DICT_RESOURCE_NAME_FIELD_NUMBER, &data.dict_resource_name),
389 |os| os.write_repeated_packed_sint64_no_tag(&data.dict_resource_name),
390 )?;
391
392 if !data.dict_source_type_bytes.is_empty() {
393 write_bytes_column(
394 &mut output,
395 &mut columns,
396 DICT_SOURCE_TYPE_NAME_FIELD_NUMBER,
397 &data.dict_source_type_bytes,
398 )?;
399 }
400
401 write_packed_column(
402 &mut output,
403 &mut columns,
404 DICT_ORIGIN_INFO_FIELD_NUMBER,
405 |os| os.write_repeated_packed_int32(DICT_ORIGIN_INFO_FIELD_NUMBER, &data.dict_origin_info),
406 |os| os.write_repeated_packed_int32_no_tag(&data.dict_origin_info),
407 )?;
408 if !data.dict_unit_bytes.is_empty() {
409 write_bytes_column(
410 &mut output,
411 &mut columns,
412 DICT_UNIT_STR_FIELD_NUMBER,
413 &data.dict_unit_bytes,
414 )?;
415 }
416
417 write_packed_column(
419 &mut output,
420 &mut columns,
421 TYPES_FIELD_NUMBER,
422 |os| os.write_repeated_packed_uint64(TYPES_FIELD_NUMBER, &data.types),
423 |os| os.write_repeated_packed_uint64_no_tag(&data.types),
424 )?;
425 write_packed_column(
426 &mut output,
427 &mut columns,
428 NAMES_FIELD_NUMBER,
429 |os| os.write_repeated_packed_sint64(NAMES_FIELD_NUMBER, &data.names),
430 |os| os.write_repeated_packed_sint64_no_tag(&data.names),
431 )?;
432 write_packed_column(
433 &mut output,
434 &mut columns,
435 TAGS_FIELD_NUMBER,
436 |os| os.write_repeated_packed_sint64(TAGS_FIELD_NUMBER, &data.tags),
437 |os| os.write_repeated_packed_sint64_no_tag(&data.tags),
438 )?;
439 write_packed_column(
440 &mut output,
441 &mut columns,
442 RESOURCES_FIELD_NUMBER,
443 |os| os.write_repeated_packed_sint64(RESOURCES_FIELD_NUMBER, &data.resources),
444 |os| os.write_repeated_packed_sint64_no_tag(&data.resources),
445 )?;
446 write_packed_column(
447 &mut output,
448 &mut columns,
449 INTERVALS_FIELD_NUMBER,
450 |os| os.write_repeated_packed_uint64(INTERVALS_FIELD_NUMBER, &data.intervals),
451 |os| os.write_repeated_packed_uint64_no_tag(&data.intervals),
452 )?;
453 write_packed_column(
454 &mut output,
455 &mut columns,
456 NUM_POINTS_FIELD_NUMBER,
457 |os| os.write_repeated_packed_uint64(NUM_POINTS_FIELD_NUMBER, &data.num_points),
458 |os| os.write_repeated_packed_uint64_no_tag(&data.num_points),
459 )?;
460 write_packed_column(
461 &mut output,
462 &mut columns,
463 SOURCE_TYPE_NAME_FIELD_NUMBER,
464 |os| os.write_repeated_packed_sint64(SOURCE_TYPE_NAME_FIELD_NUMBER, &data.source_type_names),
465 |os| os.write_repeated_packed_sint64_no_tag(&data.source_type_names),
466 )?;
467 write_packed_column(
468 &mut output,
469 &mut columns,
470 ORIGIN_INFO_FIELD_NUMBER,
471 |os| os.write_repeated_packed_sint64(ORIGIN_INFO_FIELD_NUMBER, &data.origin_infos),
472 |os| os.write_repeated_packed_sint64_no_tag(&data.origin_infos),
473 )?;
474 write_packed_column(
475 &mut output,
476 &mut columns,
477 UNIT_REFS_FIELD_NUMBER,
478 |os| os.write_repeated_packed_sint64(UNIT_REFS_FIELD_NUMBER, &data.unit_refs),
479 |os| os.write_repeated_packed_sint64_no_tag(&data.unit_refs),
480 )?;
481
482 write_packed_column(
484 &mut output,
485 &mut columns,
486 TIMESTAMPS_FIELD_NUMBER,
487 |os| os.write_repeated_packed_sint64(TIMESTAMPS_FIELD_NUMBER, &data.timestamps),
488 |os| os.write_repeated_packed_sint64_no_tag(&data.timestamps),
489 )?;
490 write_packed_column(
491 &mut output,
492 &mut columns,
493 VALS_SINT64_FIELD_NUMBER,
494 |os| os.write_repeated_packed_sint64(VALS_SINT64_FIELD_NUMBER, &data.vals_sint64),
495 |os| os.write_repeated_packed_sint64_no_tag(&data.vals_sint64),
496 )?;
497 write_packed_column(
498 &mut output,
499 &mut columns,
500 VALS_FLOAT32_FIELD_NUMBER,
501 |os| os.write_repeated_packed_float(VALS_FLOAT32_FIELD_NUMBER, &data.vals_float32),
502 |os| os.write_repeated_packed_float_no_tag(&data.vals_float32),
503 )?;
504 write_packed_column(
505 &mut output,
506 &mut columns,
507 VALS_FLOAT64_FIELD_NUMBER,
508 |os| os.write_repeated_packed_double(VALS_FLOAT64_FIELD_NUMBER, &data.vals_float64),
509 |os| os.write_repeated_packed_double_no_tag(&data.vals_float64),
510 )?;
511
512 write_packed_column(
514 &mut output,
515 &mut columns,
516 SKETCH_NUM_BINS_FIELD_NUMBER,
517 |os| os.write_repeated_packed_uint64(SKETCH_NUM_BINS_FIELD_NUMBER, &data.sketch_num_bins),
518 |os| os.write_repeated_packed_uint64_no_tag(&data.sketch_num_bins),
519 )?;
520 write_packed_column(
521 &mut output,
522 &mut columns,
523 SKETCH_BIN_KEYS_FIELD_NUMBER,
524 |os| os.write_repeated_packed_sint32(SKETCH_BIN_KEYS_FIELD_NUMBER, &data.sketch_bin_keys),
525 |os| os.write_repeated_packed_sint32_no_tag(&data.sketch_bin_keys),
526 )?;
527 write_packed_column(
528 &mut output,
529 &mut columns,
530 SKETCH_BIN_CNTS_FIELD_NUMBER,
531 |os| os.write_repeated_packed_uint32(SKETCH_BIN_CNTS_FIELD_NUMBER, &data.sketch_bin_cnts),
532 |os| os.write_repeated_packed_uint32_no_tag(&data.sketch_bin_cnts),
533 )?;
534
535 Ok(V3EncodedMetrics {
536 payload: output,
537 stats: V3EncoderStats {
538 value_encoding_stats: data.value_encoding_stats,
539 columns,
540 },
541 })
542 }
543
544 fn intern_name(&mut self, name: &str) -> i64 {
547 if name.is_empty() {
548 return 0;
549 }
550 let (id, is_new) = self.name_interner.get_or_insert(name);
551 if is_new {
552 append_len_str(&mut self.dict_name_bytes, name);
553 }
554 id
555 }
556
557 fn intern_tag(&mut self, tag: &str) {
558 if tag.is_empty() {
559 self.tag_ids.push(0);
560 return;
561 }
562
563 let (id, is_new) = self.tag_interner.get_or_insert(tag);
564 if is_new {
565 append_len_str(&mut self.dict_tags_bytes, tag);
566 }
567 self.tag_ids.push(id);
568 }
569
570 fn intern_tagset<I, S>(&mut self, tags: I) -> i64
571 where
572 I: Iterator<Item = S>,
573 S: AsRef<str>,
574 {
575 self.tag_ids.clear();
576 for tag in tags {
577 self.intern_tag(tag.as_ref());
578 }
579
580 if self.tag_ids.is_empty() {
581 return 0;
582 }
583
584 let (id, is_new) = self.tagset_interner.get_or_insert(&self.tag_ids);
585 if is_new {
586 self.encode_tagset();
587 }
588 id
589 }
590
591 fn encode_tagset(&mut self) {
592 self.dict_tagsets.push(self.tag_ids.len() as i64);
594
595 let start = self.dict_tagsets.len();
596
597 self.dict_tagsets.extend_from_slice(&self.tag_ids);
599
600 self.dict_tagsets[start..].sort_unstable();
602 delta_encode(&mut self.dict_tagsets[start..]);
603 }
604
605 fn intern_resource_str(&mut self, s: &str) -> i64 {
606 if s.is_empty() {
607 return 0;
608 }
609 let (id, is_new) = self.resource_str_interner.get_or_insert(s);
610 if is_new {
611 append_len_str(&mut self.dict_resource_str_bytes, s);
612 }
613 id
614 }
615
616 fn intern_resources(&mut self, resources: &[(&str, &str)]) -> i64 {
617 self.resource_ids.clear();
618 for (resource_type, resource_name) in resources {
619 let type_id = self.intern_resource_str(resource_type);
620 let name_id = self.intern_resource_str(resource_name);
621 self.resource_ids.push((type_id, name_id));
622 }
623
624 if self.resource_ids.is_empty() {
625 return 0;
626 }
627
628 let (id, is_new) = self.resource_interner.get_or_insert(&self.resource_ids);
629 if is_new {
630 self.encode_resources();
631 }
632 id
633 }
634
635 fn encode_resources(&mut self) {
636 self.dict_resource_len.push(self.resource_ids.len() as i64);
637
638 let type_start = self.dict_resource_type.len();
639 let name_start = self.dict_resource_name.len();
640
641 for (type_id, name_id) in &self.resource_ids {
642 self.dict_resource_type.push(*type_id);
643 self.dict_resource_name.push(*name_id);
644 }
645
646 delta_encode(&mut self.dict_resource_type[type_start..]);
647 delta_encode(&mut self.dict_resource_name[name_start..]);
648 }
649
650 fn intern_source_type(&mut self, s: &str) -> i64 {
651 if s.is_empty() {
652 return 0;
653 }
654 let (id, is_new) = self.source_type_interner.get_or_insert(s);
655 if is_new {
656 append_len_str(&mut self.dict_source_type_bytes, s);
657 }
658 id
659 }
660
661 fn intern_origin(&mut self, product: i32, category: i32, service: i32) -> i64 {
662 if product == 0 && category == 0 && service == 0 {
663 return 0;
664 }
665
666 let (id, is_new) = self.origin_interner.get_or_insert(&(product, category, service));
667 if is_new {
668 self.dict_origin_info.push(product);
669 self.dict_origin_info.push(category);
670 self.dict_origin_info.push(service);
671 }
672 id
673 }
674
675 fn intern_unit(&mut self, unit: &str) -> i64 {
676 if unit.is_empty() {
677 return 0;
678 }
679 let (id, is_new) = self.unit_interner.get_or_insert(unit);
680 if is_new {
681 append_len_str(&mut self.dict_unit_bytes, unit);
682 }
683 id
684 }
685}
686
687pub struct V3MetricBuilder<'a> {
692 writer: &'a mut V3Writer,
693 point_start_idx: usize,
694 sint64_start_idx: usize,
695 metric_idx: usize,
696 unit_ref_idx: Option<usize>,
697}
698
699impl<'a> V3MetricBuilder<'a> {
700 pub fn set_tags<I, S>(&mut self, tags: I)
704 where
705 I: Iterator<Item = S>,
706 S: AsRef<str>,
707 {
708 let tagset_id = self.writer.intern_tagset(tags);
709 self.writer.tags[self.metric_idx] = tagset_id;
710 }
711
712 pub fn set_resources(&mut self, resources: &[(&str, &str)]) {
716 let res_id = self.writer.intern_resources(resources);
717 self.writer.resources[self.metric_idx] = res_id;
718 }
719
720 pub fn set_interval(&mut self, interval: u64) {
722 self.writer.intervals[self.metric_idx] = interval;
723 }
724
725 pub fn set_source_type(&mut self, source_type: &str) {
727 if source_type.is_empty() {
728 self.writer.source_type_names[self.metric_idx] = 0;
729 return;
730 }
731 let id = self.writer.intern_source_type(source_type);
732 self.writer.source_type_names[self.metric_idx] = id;
733 }
734
735 pub fn set_origin(&mut self, product: u32, category: u32, service: u32, no_index: bool) {
737 let id = self
738 .writer
739 .intern_origin(product as i32, category as i32, service as i32);
740 self.writer.origin_infos[self.metric_idx] = id;
741 if no_index {
742 self.writer.types[self.metric_idx] |= FLAG_NO_INDEX;
743 }
744 }
745
746 pub fn set_unit(&mut self, unit: &str) {
748 if unit.is_empty() {
749 self.writer.types[self.metric_idx] &= !FLAG_HAS_UNIT;
750 if let Some(unit_ref_idx) = self.unit_ref_idx.take() {
751 self.writer.unit_refs.remove(unit_ref_idx);
752 }
753 return;
754 }
755
756 let id = self.writer.intern_unit(unit);
757 if let Some(unit_ref_idx) = self.unit_ref_idx {
758 self.writer.unit_refs[unit_ref_idx] = id;
759 } else {
760 self.unit_ref_idx = Some(self.writer.unit_refs.len());
761 self.writer.unit_refs.push(id);
762 }
763 self.writer.types[self.metric_idx] |= FLAG_HAS_UNIT;
764 }
765
766 pub fn add_point(&mut self, timestamp: i64, value: f64) {
768 self.writer.timestamps.push(timestamp);
769 self.writer.vals_float64.push(value);
770 self.writer.num_points[self.metric_idx] += 1;
771 }
772
773 pub fn add_sketch(
778 &mut self, timestamp: i64, count: i64, sum: f64, min: f64, max: f64, bin_keys: &[i32], bin_counts: &[u32],
779 ) {
780 self.writer.timestamps.push(timestamp);
781
782 self.writer.vals_sint64.push(count);
784 self.writer.vals_float64.push(sum);
785 self.writer.vals_float64.push(min);
786 self.writer.vals_float64.push(max);
787
788 self.writer.sketch_num_bins.push(bin_keys.len() as u64);
790
791 let key_start = self.writer.sketch_bin_keys.len();
792 self.writer.sketch_bin_keys.extend_from_slice(bin_keys);
793 self.writer.sketch_bin_cnts.extend_from_slice(bin_counts);
794
795 delta_encode_i32(&mut self.writer.sketch_bin_keys[key_start..]);
797
798 self.writer.num_points[self.metric_idx] += 1;
799 }
800
801 pub fn close(mut self) {
806 self.compact_values();
807 }
808
809 fn compact_values(&mut self) {
810 let count = self.writer.num_points[self.metric_idx] as usize;
811 if count == 0 {
812 return;
813 }
814
815 let start = self.point_start_idx;
816 let end = self.writer.vals_float64.len();
817
818 let val_ty = value_type_for_values(self.writer.vals_float64[start..end].iter().copied());
820 let is_sketch = (self.writer.types[self.metric_idx] & 0x0F) == V3MetricType::Sketch as u64;
821 let float_values_len = end - start;
822 if is_sketch {
823 self.writer.value_encoding_stats.sint64 += count as u64;
825 }
826
827 self.writer.types[self.metric_idx] |= val_ty.as_u64();
829
830 match val_ty {
832 V3ValueType::Zero => {
833 self.writer.value_encoding_stats.zero += float_values_len as u64;
834 self.writer.vals_float64.truncate(start);
836 }
837 V3ValueType::Sint64 => {
838 self.writer.value_encoding_stats.sint64 += float_values_len as u64;
839 if is_sketch {
840 let counts: Vec<i64> = self.writer.vals_sint64[self.sint64_start_idx..].to_vec();
844 self.writer.vals_sint64.truncate(self.sint64_start_idx);
845 for (i, cnt) in counts.into_iter().enumerate() {
846 let f_off = start + i * 3;
847 self.writer.vals_sint64.push(self.writer.vals_float64[f_off] as i64);
848 self.writer.vals_sint64.push(self.writer.vals_float64[f_off + 1] as i64);
849 self.writer.vals_sint64.push(self.writer.vals_float64[f_off + 2] as i64);
850 self.writer.vals_sint64.push(cnt);
851 }
852 } else {
853 for i in start..end {
854 self.writer.vals_sint64.push(self.writer.vals_float64[i] as i64);
855 }
856 }
857 self.writer.vals_float64.truncate(start);
858 }
859 V3ValueType::Float32 => {
860 self.writer.value_encoding_stats.float32 += float_values_len as u64;
861 for i in start..end {
862 self.writer.vals_float32.push(self.writer.vals_float64[i] as f32);
863 }
864 self.writer.vals_float64.truncate(start);
865 }
866 V3ValueType::Float64 => {
867 self.writer.value_encoding_stats.float64 += float_values_len as u64;
868 }
870 }
871 }
872}
873
874fn write_bytes_column(
875 output: &mut Vec<u8>, columns: &mut Vec<V3ColumnBytes>, field_number: u32, bytes: &[u8],
876) -> Result<(), V3EncodeError> {
877 let start = output.len();
878 {
879 let mut os = CodedOutputStream::vec(output);
880 os.write_bytes(field_number, bytes)?;
881 os.flush()?;
882 }
883
884 if output.len() != start {
885 columns.push(V3ColumnBytes {
886 field_number,
887 bytes: bytes.to_vec(),
888 compressed_len: 0,
889 });
890 }
891
892 Ok(())
893}
894
895fn write_packed_column<F, R>(
896 output: &mut Vec<u8>, columns: &mut Vec<V3ColumnBytes>, field_number: u32, write_framed: F, write_raw: R,
897) -> Result<(), V3EncodeError>
898where
899 F: FnOnce(&mut CodedOutputStream<'_>) -> protobuf::Result<()>,
900 R: FnOnce(&mut CodedOutputStream<'_>) -> protobuf::Result<()>,
901{
902 let start = output.len();
903 {
904 let mut os = CodedOutputStream::vec(output);
905 write_framed(&mut os)?;
906 os.flush()?;
907 }
908
909 if output.len() != start {
910 let mut bytes = Vec::new();
911 {
912 let mut os = CodedOutputStream::vec(&mut bytes);
913 write_raw(&mut os)?;
914 os.flush()?;
915 }
916 columns.push(V3ColumnBytes {
917 field_number,
918 bytes,
919 compressed_len: 0,
920 });
921 }
922
923 Ok(())
924}
925
926fn append_len_str(dst: &mut Vec<u8>, s: &str) {
927 let mut len = s.len() as u64;
928 loop {
929 let mut byte = (len & 0x7F) as u8;
930 len >>= 7;
931 if len != 0 {
932 byte |= 0x80;
933 }
934 dst.push(byte);
935 if len == 0 {
936 break;
937 }
938 }
939 dst.extend_from_slice(s.as_bytes());
940}
941
942fn delta_encode(s: &mut [i64]) {
943 if s.len() < 2 {
944 return;
945 }
946 for i in (1..s.len()).rev() {
947 s[i] -= s[i - 1];
948 }
949}
950
951fn delta_encode_i32(s: &mut [i32]) {
952 if s.len() < 2 {
953 return;
954 }
955 for i in (1..s.len()).rev() {
956 s[i] -= s[i - 1];
957 }
958}
959
960#[cfg(test)]
961mod tests {
962 use super::*;
963
964 #[test]
965 fn test_delta_encode() {
966 let mut data = vec![100, 110, 130, 145];
967 delta_encode(&mut data);
968 assert_eq!(data, vec![100, 10, 20, 15]);
969 }
970
971 #[test]
972 fn estimated_uncompressed_len_grows_and_brackets_finalized_size() {
973 let mut writer = V3Writer::new();
974 assert_eq!(0, writer.metric_count());
975 let empty_estimate = writer.estimated_uncompressed_len();
976
977 for idx in 0..16 {
978 let mut builder = writer.write(V3MetricType::Count, &format!("estimate.metric.{idx}"));
979 builder.set_tags([format!("tag_key_{idx}:tag_value_{idx}")].into_iter());
980 builder.add_point(1_700_000_000 + idx as i64, idx as f64);
981 builder.close();
982 }
983
984 assert_eq!(16, writer.metric_count());
985 let estimate = writer.estimated_uncompressed_len();
986 assert!(
987 estimate > empty_estimate,
988 "estimate should grow as metrics are written: {estimate} vs {empty_estimate}"
989 );
990
991 let encoded = writer.finalize().expect("metrics should encode");
994 assert!(
995 estimate >= encoded.payload.len(),
996 "estimate {estimate} should not undershoot finalized payload {}",
997 encoded.payload.len()
998 );
999 }
1000
1001 #[test]
1002 fn estimated_uncompressed_len_tracks_dictionary_growth() {
1003 let mut writer = V3Writer::new();
1006 let mut builder = writer.write(V3MetricType::Count, "dict.growth");
1007 builder.set_tags(["shared_key:shared_value".to_string()].into_iter());
1008 builder.add_point(1_700_000_000, 1.0);
1009 builder.close();
1010 let after_first = writer.estimated_uncompressed_len();
1011
1012 let mut builder = writer.write(V3MetricType::Count, "dict.growth");
1013 builder.set_tags(["shared_key:shared_value".to_string()].into_iter());
1014 builder.add_point(1_700_000_001, 2.0);
1015 builder.close();
1016 let after_deduped = writer.estimated_uncompressed_len();
1017
1018 let mut builder = writer.write(V3MetricType::Count, "dict.growth.novel");
1019 builder.set_tags(["a_completely_different_key:a_completely_different_value".to_string()].into_iter());
1020 builder.add_point(1_700_000_002, 3.0);
1021 builder.close();
1022 let after_novel = writer.estimated_uncompressed_len();
1023
1024 let deduped_growth = after_deduped - after_first;
1025 let novel_growth = after_novel - after_deduped;
1026 assert!(
1027 novel_growth > deduped_growth,
1028 "new dictionary entries should grow the estimate more than deduplicated ones: {novel_growth} vs {deduped_growth}"
1029 );
1030 }
1031
1032 #[test]
1033 fn test_delta_encode_empty() {
1034 let mut data: Vec<i64> = vec![];
1035 delta_encode(&mut data);
1036 assert!(data.is_empty());
1037 }
1038
1039 #[test]
1040 fn test_delta_encode_single() {
1041 let mut data = vec![42];
1042 delta_encode(&mut data);
1043 assert_eq!(data, vec![42]);
1044 }
1045
1046 #[test]
1047 fn test_append_len_str() {
1048 let mut buf = Vec::new();
1049 append_len_str(&mut buf, "hello");
1050 assert_eq!(buf, vec![5, b'h', b'e', b'l', b'l', b'o']);
1052 }
1053
1054 #[test]
1055 fn test_writer_basic() {
1056 let mut writer = V3Writer::new();
1057
1058 {
1059 let mut metric = writer.write(V3MetricType::Gauge, "test.metric");
1060 metric.set_tags(["env:prod", "service:web"].iter().copied());
1061 metric.add_point(1000, 42.0);
1062 metric.add_point(1010, 43.5);
1063 metric.close();
1064 }
1065
1066 let data = writer.finalize_inner();
1067
1068 assert_eq!(data.types.len(), 1);
1069 assert_eq!(data.names.len(), 1);
1070 assert_eq!(data.timestamps.len(), 2);
1071 }
1072
1073 #[test]
1074 fn test_writer_unit() {
1075 let mut writer = V3Writer::new();
1076
1077 {
1078 let mut metric = writer.write(V3MetricType::Gauge, "has.unit");
1079 metric.set_unit("millisecond");
1080 metric.add_point(1000, 42.0);
1081 metric.close();
1082 }
1083 {
1084 let mut metric = writer.write(V3MetricType::Gauge, "no.unit");
1085 metric.add_point(1000, 43.0);
1086 metric.close();
1087 }
1088 {
1089 let mut metric = writer.write(V3MetricType::Gauge, "same.unit");
1090 metric.set_unit("millisecond");
1091 metric.add_point(1000, 44.0);
1092 metric.close();
1093 }
1094
1095 let data = writer.finalize_inner();
1096
1097 assert_eq!(data.unit_refs, vec![1, 0]);
1098 assert_eq!(data.dict_unit_bytes, b"\x0bmillisecond");
1099 assert_eq!(data.types[0] & FLAG_HAS_UNIT, FLAG_HAS_UNIT);
1100 assert_eq!(data.types[1] & FLAG_HAS_UNIT, 0);
1101 assert_eq!(data.types[2] & FLAG_HAS_UNIT, FLAG_HAS_UNIT);
1102 }
1103
1104 #[test]
1105 fn test_writer_multiple_metrics() {
1106 let mut writer = V3Writer::new();
1107
1108 {
1109 let mut m1 = writer.write(V3MetricType::Count, "metric1");
1110 m1.add_point(1000, 10.0);
1111 m1.close();
1112 }
1113
1114 {
1115 let mut m2 = writer.write(V3MetricType::Rate, "metric2");
1116 m2.set_interval(60);
1117 m2.add_point(2000, 20.0);
1118 m2.close();
1119 }
1120
1121 let data = writer.finalize_inner();
1122
1123 assert_eq!(data.types.len(), 2);
1124 assert_eq!(data.names.len(), 2);
1125 assert_eq!(data.intervals[0], 0);
1126 }
1129
1130 #[test]
1131 fn test_value_compaction_zero() {
1132 let mut writer = V3Writer::new();
1133
1134 {
1135 let mut metric = writer.write(V3MetricType::Gauge, "zero.metric");
1136 metric.add_point(1000, 0.0);
1137 metric.add_point(2000, 0.0);
1138 metric.close();
1139 }
1140
1141 let data = writer.finalize_inner();
1142
1143 assert!(data.vals_float64.is_empty());
1145 assert!(data.vals_sint64.is_empty());
1146 assert!(data.vals_float32.is_empty());
1147 }
1148
1149 #[test]
1150 fn test_value_compaction_int() {
1151 let mut writer = V3Writer::new();
1152
1153 {
1154 let mut metric = writer.write(V3MetricType::Count, "int.metric");
1155 metric.add_point(1000, 100.0);
1156 metric.add_point(2000, 200.0);
1157 metric.close();
1158 }
1159
1160 let data = writer.finalize_inner();
1161
1162 assert!(data.vals_float64.is_empty());
1164 assert_eq!(data.vals_sint64, vec![100, 200]);
1165 assert!(data.vals_float32.is_empty());
1166 }
1167
1168 #[test]
1169 fn test_serialize_empty() {
1170 let writer = V3Writer::new();
1171 let encoded = writer.finalize().unwrap();
1172 assert!(encoded.payload.is_empty());
1173 }
1174
1175 #[test]
1176 fn test_value_compaction_large_int_plus_float32() {
1177 let mut writer = V3Writer::new();
1180
1181 {
1182 let mut metric = writer.write(V3MetricType::Gauge, "mixed.metric");
1183 metric.add_point(1000, (1i64 << 30) as f64); metric.add_point(2000, 1.5); metric.close();
1186 }
1187
1188 let data = writer.finalize_inner();
1189
1190 assert!(
1192 data.vals_float32.is_empty(),
1193 "large int should not be stored as float32"
1194 );
1195 assert_eq!(data.vals_float64, vec![(1i64 << 30) as f64, 1.5]);
1196 assert!(data.vals_sint64.is_empty());
1197 }
1198
1199 #[test]
1200 fn test_value_compaction_small_int_plus_float32() {
1201 let mut writer = V3Writer::new();
1204
1205 {
1206 let mut metric = writer.write(V3MetricType::Gauge, "small.mixed");
1207 metric.add_point(1000, 100.0);
1208 metric.add_point(2000, 1.5);
1209 metric.close();
1210 }
1211
1212 let data = writer.finalize_inner();
1213
1214 assert!(data.vals_float64.is_empty());
1215 assert_eq!(data.vals_float32, vec![100.0, 1.5]);
1216 assert!(data.vals_sint64.is_empty());
1217 }
1218
1219 #[test]
1220 fn test_serialize_basic_metric() {
1221 let mut writer = V3Writer::new();
1222
1223 {
1224 let mut metric = writer.write(V3MetricType::Gauge, "test.metric");
1225 metric.add_point(1000, 42.0);
1226 metric.close();
1227 }
1228
1229 let encoded = writer.finalize().unwrap();
1230
1231 assert!(!encoded.payload.is_empty());
1233 assert_eq!(encoded.stats.value_encoding_stats.sint64, 1);
1234 }
1235
1236 #[test]
1237 fn test_column_stats_for_bytes_column_use_raw_column_stream() {
1238 let mut writer = V3Writer::new();
1239
1240 {
1241 let mut metric = writer.write(V3MetricType::Gauge, "test.metric");
1242 metric.add_point(1000, 42.0);
1243 metric.close();
1244 }
1245
1246 let encoded = writer.finalize().unwrap();
1247 let name_column = encoded
1248 .stats
1249 .columns
1250 .iter()
1251 .find(|column| column.field_number == DICT_NAME_STR_FIELD_NUMBER)
1252 .expect("name dictionary column should be present");
1253
1254 let mut expected = Vec::new();
1255 append_len_str(&mut expected, "test.metric");
1256 assert_eq!(name_column.bytes, expected);
1257 }
1258
1259 #[test]
1260 fn test_column_stats_for_packed_column_use_raw_column_stream() {
1261 let mut writer = V3Writer::new();
1262
1263 {
1264 let mut metric = writer.write(V3MetricType::Gauge, "test.metric");
1265 metric.add_point(1000, 42.0);
1266 metric.close();
1267 }
1268
1269 let encoded = writer.finalize().unwrap();
1270 let timestamps_column = encoded
1271 .stats
1272 .columns
1273 .iter()
1274 .find(|column| column.field_number == TIMESTAMPS_FIELD_NUMBER)
1275 .expect("timestamp column should be present");
1276
1277 let mut expected = Vec::new();
1278 {
1279 let mut os = CodedOutputStream::vec(&mut expected);
1280 os.write_sint64_no_tag(1000).unwrap();
1281 os.flush().unwrap();
1282 }
1283 assert_eq!(timestamps_column.bytes, expected);
1284 }
1285
1286 #[test]
1287 fn test_column_stats_do_not_include_absent_columns() {
1288 let mut writer = V3Writer::new();
1289
1290 {
1291 let mut metric = writer.write(V3MetricType::Gauge, "test.metric");
1292 metric.add_point(1000, 42.0);
1293 metric.close();
1294 }
1295
1296 let encoded = writer.finalize().unwrap();
1297 assert!(!encoded
1298 .stats
1299 .columns
1300 .iter()
1301 .any(|column| column.field_number == UNIT_REFS_FIELD_NUMBER));
1302 }
1303}