@@ -10,40 +10,120 @@ use super::super::Sample;
10
10
use super :: super :: StackTraceId ;
11
11
use crate :: collections:: identifiable:: Id ;
12
12
use crate :: internal:: Timestamp ;
13
+ use crate :: profiles:: SizeRestrictedBuffer ;
13
14
use byteorder:: { NativeEndian , ReadBytesExt } ;
14
- use std:: io:: Write ;
15
- use std:: io:: { BufReader , Cursor } ;
15
+ use std:: io:: { self , Cursor , Read , Write } ;
16
+
17
+ // todo: document
18
+ #[ derive( Clone , Copy , Debug , Default ) ]
19
+ pub enum EncodingType {
20
+ #[ default]
21
+ None ,
22
+ Zstd ,
23
+ }
24
+
25
+ enum ObservationEncoder {
26
+ Noop ( SizeRestrictedBuffer ) ,
27
+ Zstd ( zstd:: Encoder < ' static , SizeRestrictedBuffer > ) ,
28
+ }
16
29
17
30
pub struct TimestampedObservations {
18
- compressed_timestamped_data : zstd:: Encoder < ' static , Vec < u8 > > ,
31
+ compressed_timestamped_data : ObservationEncoder ,
32
+ sample_types_len : usize ,
33
+ }
34
+
35
+ enum ObservationDecoder {
36
+ Noop ( Cursor < SizeRestrictedBuffer > ) ,
37
+ Zstd ( zstd:: Decoder < ' static , Cursor < SizeRestrictedBuffer > > ) ,
38
+ }
39
+
40
+ pub struct TimestampedObservationsIter {
41
+ decoder : ObservationDecoder ,
19
42
sample_types_len : usize ,
20
43
}
21
44
45
+ impl ObservationEncoder {
46
+ pub fn try_new (
47
+ encoding_type : EncodingType ,
48
+ size_hint : usize ,
49
+ max_capacity : usize ,
50
+ ) -> io:: Result < Self > {
51
+ let output_buffer = SizeRestrictedBuffer :: try_new ( size_hint, max_capacity) ?;
52
+ match encoding_type {
53
+ EncodingType :: None => Ok ( ObservationEncoder :: Noop ( output_buffer) ) ,
54
+ EncodingType :: Zstd => Ok ( ObservationEncoder :: Zstd ( zstd:: Encoder :: new (
55
+ output_buffer,
56
+ 1 ,
57
+ ) ?) ) ,
58
+ }
59
+ }
60
+
61
+ pub fn try_into_decoder ( self ) -> io:: Result < ObservationDecoder > {
62
+ match self {
63
+ ObservationEncoder :: Noop ( buffer) => Ok ( ObservationDecoder :: Noop ( Cursor :: new ( buffer) ) ) ,
64
+ ObservationEncoder :: Zstd ( encoder) => match encoder. try_finish ( ) {
65
+ Ok ( buffer) => Ok ( ObservationDecoder :: Zstd ( zstd:: Decoder :: with_buffer (
66
+ Cursor :: new ( buffer) ,
67
+ ) ?) ) ,
68
+ Err ( ( _encoder, error) ) => Err ( error) ,
69
+ } ,
70
+ }
71
+ }
72
+ }
73
+
74
+ impl Write for ObservationEncoder {
75
+ fn write ( & mut self , buf : & [ u8 ] ) -> io:: Result < usize > {
76
+ match self {
77
+ ObservationEncoder :: Noop ( encoder) => encoder. write ( buf) ,
78
+ ObservationEncoder :: Zstd ( encoder) => encoder. write ( buf) ,
79
+ }
80
+ }
81
+
82
+ fn flush ( & mut self ) -> io:: Result < ( ) > {
83
+ match self {
84
+ ObservationEncoder :: Noop ( encoder) => encoder. flush ( ) ,
85
+ ObservationEncoder :: Zstd ( encoder) => encoder. flush ( ) ,
86
+ }
87
+ }
88
+ }
89
+
90
+ impl Read for ObservationDecoder {
91
+ fn read ( & mut self , buf : & mut [ u8 ] ) -> io:: Result < usize > {
92
+ match self {
93
+ ObservationDecoder :: Noop ( decoder) => decoder. read ( buf) ,
94
+ ObservationDecoder :: Zstd ( decoder) => decoder. read ( buf) ,
95
+ }
96
+ }
97
+ }
98
+
22
99
impl TimestampedObservations {
23
100
// As documented in the internal Datadog doc "Ruby timeline memory fragmentation impact
24
101
// investigation", allowing the timeline storage vec to slowly expand creates A LOT of
25
102
// memory fragmentation for apps that employ multiple threads.
26
103
// To avoid this, we've picked a default buffer size of 1MB that very rarely needs to grow, and
27
104
// when it does, is expected to grow in larger steps.
28
- const DEFAULT_BUFFER_SIZE : usize = 1_048_576 ;
29
-
30
- pub fn new ( sample_types_len : usize ) -> Self {
31
- #[ allow( clippy:: expect_used) ] // previous API panic'd implicitly
32
- Self {
33
- compressed_timestamped_data : zstd:: Encoder :: new (
34
- Vec :: with_capacity ( Self :: DEFAULT_BUFFER_SIZE ) ,
35
- 1 ,
36
- )
37
- . expect ( "failed to create zstd encoder" ) ,
105
+ const DEFAULT_BUFFER_SIZE : usize = 1024 * 1024 ;
106
+
107
+ // Protobufs can't exceed 2 GiB, if our observations grow this large, then
108
+ // the profile as a whole would defintely exceed this.
109
+ const MAX_CAPACITY : usize = i32:: MAX as usize ;
110
+
111
+ pub fn try_new ( encoding_type : EncodingType , sample_types_len : usize ) -> io:: Result < Self > {
112
+ Ok ( Self {
113
+ compressed_timestamped_data : ObservationEncoder :: try_new (
114
+ encoding_type,
115
+ Self :: DEFAULT_BUFFER_SIZE ,
116
+ Self :: MAX_CAPACITY ,
117
+ ) ?,
38
118
sample_types_len,
39
- }
119
+ } )
40
120
}
41
121
42
- pub fn with_no_backing_store ( ) -> Self {
43
- #[ allow( clippy:: expect_used) ] // previous API panic'd implicitly
122
+ pub const fn with_no_backing_store ( ) -> Self {
44
123
Self {
45
- compressed_timestamped_data : zstd:: Encoder :: new ( vec ! [ ] , 1 )
46
- . expect ( "failed to create zstd encoder" ) ,
124
+ compressed_timestamped_data : ObservationEncoder :: Noop (
125
+ SizeRestrictedBuffer :: zero_capacity ( ) ,
126
+ ) ,
47
127
sample_types_len : 0 ,
48
128
}
49
129
}
@@ -74,22 +154,17 @@ impl TimestampedObservations {
74
154
}
75
155
76
156
pub fn into_iter ( self ) -> TimestampedObservationsIter {
77
- #[ allow( clippy:: expect_used, clippy :: unwrap_used ) ]
157
+ #[ allow( clippy:: expect_used) ]
78
158
TimestampedObservationsIter {
79
- decoder : zstd :: Decoder :: new ( Cursor :: new (
80
- self . compressed_timestamped_data . finish ( ) . unwrap ( ) ,
81
- ) )
82
- . expect ( "failed to create zstd decoder" ) ,
159
+ decoder : self
160
+ . compressed_timestamped_data
161
+ . try_into_decoder ( )
162
+ . expect ( "failed to initialize timestamped observation decoder" ) ,
83
163
sample_types_len : self . sample_types_len ,
84
164
}
85
165
}
86
166
}
87
167
88
- pub struct TimestampedObservationsIter {
89
- decoder : zstd:: Decoder < ' static , BufReader < Cursor < Vec < u8 > > > > ,
90
- sample_types_len : usize ,
91
- }
92
-
93
168
impl Iterator for TimestampedObservationsIter {
94
169
type Item = ( Sample , Timestamp , Vec < i64 > ) ;
95
170
0 commit comments