3
3
4
4
use crate :: internal:: Profile as InternalProfile ;
5
5
use crate :: iter:: { IntoLendingIterator , LendingIterator } ;
6
+ use crate :: internal:: profile:: otel_emitter:: label:: convert_label_to_key_value;
7
+ use crate :: collections:: identifiable:: Id ;
8
+ use anyhow:: { Result , Context } ;
9
+ use std:: collections:: HashMap ;
6
10
7
- impl From < InternalProfile > for datadog_profiling_otel:: ProfilesData {
8
- fn from ( internal_profile : InternalProfile ) -> Self {
11
+ impl TryFrom < InternalProfile > for datadog_profiling_otel:: ProfilesData {
12
+ type Error = anyhow:: Error ;
13
+
14
+ fn try_from ( internal_profile : InternalProfile ) -> Result < Self > {
15
+ // Convert string table using into_lending_iter
16
+ // Note: We can't use .map().collect() here because LendingIterator doesn't implement
17
+ // the standard Iterator trait. LendingIterator is designed for yielding references
18
+ // with lifetimes tied to the iterator itself, so we need to manually iterate and
19
+ // convert each string reference to an owned String.
20
+ let string_table = {
21
+ let mut strings = Vec :: with_capacity ( internal_profile. strings . len ( ) ) ;
22
+ let mut iter = internal_profile. strings . into_lending_iter ( ) ;
23
+ while let Some ( s) = iter. next ( ) {
24
+ strings. push ( s. to_string ( ) ) ;
25
+ }
26
+ strings
27
+ } ;
28
+
29
+ // Convert labels to KeyValues for the attribute table
30
+ let mut key_to_unit_map = HashMap :: new ( ) ;
31
+ let mut attribute_table = Vec :: with_capacity ( internal_profile. labels . len ( ) ) ;
32
+
33
+ for label in internal_profile. labels . iter ( ) {
34
+ let key_value = convert_label_to_key_value ( label, & string_table, & mut key_to_unit_map)
35
+ . with_context ( || format ! ( "Failed to convert label with key index {}" , label. get_key( ) . to_raw_id( ) ) ) ?;
36
+ attribute_table. push ( key_value) ;
37
+ }
38
+
39
+ // Build attribute units from the key-to-unit mapping
40
+ let attribute_units = key_to_unit_map
41
+ . into_iter ( )
42
+ . map ( |( key_index, unit_index) | datadog_profiling_otel:: AttributeUnit {
43
+ attribute_key_strindex : key_index as i32 ,
44
+ unit_strindex : unit_index as i32 ,
45
+ } )
46
+ . collect ( ) ;
47
+
9
48
// Convert the ProfilesDictionary components
10
49
let dictionary = datadog_profiling_otel:: ProfilesDictionary {
11
50
// Convert mappings
@@ -36,24 +75,12 @@ impl From<InternalProfile> for datadog_profiling_otel::ProfilesData {
36
75
. map ( |stack_trace| stack_trace. into ( ) )
37
76
. collect ( ) ,
38
77
39
- // Convert string table using into_lending_iter
40
- // Note: We can't use .map().collect() here because LendingIterator doesn't implement
41
- // the standard Iterator trait. LendingIterator is designed for yielding references
42
- // with lifetimes tied to the iterator itself, so we need to manually iterate and
43
- // convert each string reference to an owned String.
44
- string_table : {
45
- let mut strings = Vec :: with_capacity ( internal_profile. strings . len ( ) ) ;
46
- let mut iter = internal_profile. strings . into_lending_iter ( ) ;
47
- while let Some ( s) = iter. next ( ) {
48
- strings. push ( s. to_string ( ) ) ;
49
- }
50
- strings
51
- } ,
78
+ string_table,
79
+ attribute_table,
80
+ attribute_units,
52
81
53
- // For now, set empty defaults for fields we're not handling yet
54
- link_table : vec ! [ ] , // TODO: Implement when we handle links
55
- attribute_table : vec ! [ ] , // TODO: Implement when we handle attributes
56
- attribute_units : vec ! [ ] , // TODO: Implement when we handle attribute units
82
+ // For now, set empty default for links (they represent trace span connections)
83
+ link_table : vec ! [ ] , // TODO: Implement when we handle trace links
57
84
} ;
58
85
59
86
// Create a basic ResourceProfiles structure
@@ -68,10 +95,10 @@ impl From<InternalProfile> for datadog_profiling_otel::ProfilesData {
68
95
schema_url: String :: new( ) , // TODO: Implement when we handle schema URLs
69
96
} ] ;
70
97
71
- Self {
98
+ Ok ( Self {
72
99
resource_profiles,
73
100
dictionary : Some ( dictionary) ,
74
- }
101
+ } )
75
102
}
76
103
}
77
104
@@ -86,7 +113,7 @@ mod tests {
86
113
let internal_profile = InternalProfile :: new ( & [ ] , None ) ;
87
114
88
115
// Convert to OpenTelemetry ProfilesData
89
- let otel_profiles_data = ProfilesData :: from ( internal_profile) ;
116
+ let otel_profiles_data = ProfilesData :: try_from ( internal_profile) . unwrap ( ) ;
90
117
91
118
// Verify the conversion
92
119
assert ! ( otel_profiles_data. dictionary. is_some( ) ) ;
@@ -125,7 +152,7 @@ mod tests {
125
152
let _function2_id = internal_profile. add_function ( & function2) ;
126
153
127
154
// Convert to OpenTelemetry ProfilesData
128
- let otel_profiles_data = ProfilesData :: from ( internal_profile) ;
155
+ let otel_profiles_data = ProfilesData :: try_from ( internal_profile) . unwrap ( ) ;
129
156
130
157
// Verify the conversion
131
158
assert ! ( otel_profiles_data. dictionary. is_some( ) ) ;
@@ -146,4 +173,73 @@ mod tests {
146
173
assert_eq ! ( otel_function2. system_name_strindex, 8 ) ;
147
174
assert_eq ! ( otel_function2. filename_strindex, 9 ) ;
148
175
}
176
+
177
+ #[ test]
178
+ fn test_from_internal_profile_with_labels ( ) {
179
+ // Create an internal profile with some data
180
+ let mut internal_profile = InternalProfile :: new ( & [ ] , None ) ;
181
+
182
+ // Add some labels using the API
183
+ let label1 = crate :: api:: Label {
184
+ key : "thread_id" ,
185
+ str : "main" ,
186
+ num : 0 ,
187
+ num_unit : "" ,
188
+ } ;
189
+ let label2 = crate :: api:: Label {
190
+ key : "memory_usage" ,
191
+ str : "" ,
192
+ num : 1024 ,
193
+ num_unit : "bytes" ,
194
+ } ;
195
+
196
+ // Add a sample with these labels
197
+ let sample = crate :: api:: Sample {
198
+ locations : vec ! [ ] ,
199
+ values : & [ 42 ] ,
200
+ labels : vec ! [ label1, label2] ,
201
+ } ;
202
+
203
+ let _ = internal_profile. add_sample ( sample, None ) ;
204
+
205
+ // Convert to OpenTelemetry ProfilesData
206
+ let otel_profiles_data = ProfilesData :: try_from ( internal_profile) . unwrap ( ) ;
207
+
208
+ // Verify the conversion
209
+ assert ! ( otel_profiles_data. dictionary. is_some( ) ) ;
210
+ let dictionary = otel_profiles_data. dictionary . unwrap ( ) ;
211
+
212
+ // Should have 2 labels converted to attributes
213
+ assert_eq ! ( dictionary. attribute_table. len( ) , 2 ) ;
214
+
215
+ // Should have 1 attribute unit (for the numeric label with unit)
216
+ assert_eq ! ( dictionary. attribute_units. len( ) , 1 ) ;
217
+
218
+ // Verify the first attribute (string label)
219
+ let attr1 = & dictionary. attribute_table [ 0 ] ;
220
+ assert_eq ! ( attr1. key, "thread_id" ) ;
221
+ match & attr1. value {
222
+ Some ( datadog_profiling_otel:: key_value:: Value :: StringValue ( s) ) => {
223
+ assert_eq ! ( s, "main" ) ;
224
+ }
225
+ _ => panic ! ( "Expected StringValue" ) ,
226
+ }
227
+
228
+ // Verify the second attribute (numeric label)
229
+ let attr2 = & dictionary. attribute_table [ 1 ] ;
230
+ assert_eq ! ( attr2. key, "memory_usage" ) ;
231
+ match & attr2. value {
232
+ Some ( datadog_profiling_otel:: key_value:: Value :: IntValue ( n) ) => {
233
+ assert_eq ! ( * n, 1024 ) ;
234
+ }
235
+ _ => panic ! ( "Expected IntValue" ) ,
236
+ }
237
+
238
+ // Verify the attribute unit mapping
239
+ let unit = & dictionary. attribute_units [ 0 ] ;
240
+ // The key should map to the memory_usage string index
241
+ // and the unit should map to the "bytes" string index
242
+ assert ! ( unit. attribute_key_strindex > 0 ) ;
243
+ assert ! ( unit. unit_strindex > 0 ) ;
244
+ }
149
245
}
0 commit comments