1use crate::{
2 measure::Measure, metadata::ProcessMetadata,
3 properties::property_set_jsonb_dictionary_builder::PropertySetJsonbDictionaryBuilder,
4 time::TimeRange,
5};
6use anyhow::{Context, Result};
7use chrono::DateTime;
8use datafusion::arrow::{
9 array::{ArrayBuilder, BinaryDictionaryBuilder, PrimitiveBuilder, StringDictionaryBuilder},
10 datatypes::{
11 DataType, Field, Float64Type, Int32Type, Schema, TimeUnit, TimestampNanosecondType,
12 },
13 record_batch::RecordBatch,
14};
15use std::sync::Arc;
16
17pub fn metrics_table_schema() -> Schema {
19 Schema::new(vec![
20 Field::new(
21 "process_id",
22 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
23 false,
24 ),
25 Field::new(
26 "stream_id",
27 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
28 false,
29 ),
30 Field::new(
31 "block_id",
32 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
33 false,
34 ),
35 Field::new(
36 "insert_time",
37 DataType::Timestamp(TimeUnit::Nanosecond, Some("+00:00".into())),
38 false,
39 ),
40 Field::new(
41 "exe",
42 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
43 false,
44 ),
45 Field::new(
46 "username",
47 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
48 false,
49 ),
50 Field::new(
51 "computer",
52 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
53 false,
54 ),
55 Field::new(
56 "time",
57 DataType::Timestamp(TimeUnit::Nanosecond, Some("+00:00".into())),
58 false,
59 ),
60 Field::new(
61 "target",
62 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
63 false,
64 ),
65 Field::new(
66 "name",
67 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
68 false,
69 ),
70 Field::new(
71 "unit",
72 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
73 false,
74 ),
75 Field::new("value", DataType::Float64, false),
76 Field::new(
77 "properties",
78 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Binary)),
79 false,
80 ),
81 Field::new(
82 "process_properties",
83 DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Binary)),
84 false,
85 ),
86 ])
87}
88
89pub struct MetricsRecordBuilder {
91 pub process_ids: StringDictionaryBuilder<Int32Type>,
92 pub stream_ids: StringDictionaryBuilder<Int32Type>,
93 pub block_ids: StringDictionaryBuilder<Int32Type>,
94 pub insert_times: PrimitiveBuilder<TimestampNanosecondType>,
95 pub exes: StringDictionaryBuilder<Int32Type>,
96 pub usernames: StringDictionaryBuilder<Int32Type>,
97 pub computers: StringDictionaryBuilder<Int32Type>,
98 pub times: PrimitiveBuilder<TimestampNanosecondType>,
99 pub targets: StringDictionaryBuilder<Int32Type>,
100 pub names: StringDictionaryBuilder<Int32Type>,
101 pub units: StringDictionaryBuilder<Int32Type>,
102 pub values: PrimitiveBuilder<Float64Type>,
103 pub properties: PropertySetJsonbDictionaryBuilder,
104 pub process_properties: BinaryDictionaryBuilder<Int32Type>,
105}
106
107impl MetricsRecordBuilder {
108 pub fn with_capacity(capacity: usize) -> Self {
109 Self {
110 process_ids: StringDictionaryBuilder::new(),
111 stream_ids: StringDictionaryBuilder::new(),
112 block_ids: StringDictionaryBuilder::new(),
113 insert_times: PrimitiveBuilder::with_capacity(capacity),
114 exes: StringDictionaryBuilder::new(),
115 usernames: StringDictionaryBuilder::new(),
116 computers: StringDictionaryBuilder::new(),
117 times: PrimitiveBuilder::with_capacity(capacity),
118 targets: StringDictionaryBuilder::new(),
119 names: StringDictionaryBuilder::new(),
120 units: StringDictionaryBuilder::new(),
121 values: PrimitiveBuilder::with_capacity(capacity),
122 properties: PropertySetJsonbDictionaryBuilder::new(capacity),
123 process_properties: BinaryDictionaryBuilder::new(),
124 }
125 }
126
127 pub fn len(&self) -> i64 {
128 self.times.len() as i64
129 }
130
131 pub fn is_empty(&self) -> bool {
132 self.times.len() == 0
133 }
134
135 pub fn get_time_range(&self) -> Option<TimeRange> {
136 if self.is_empty() {
137 return None;
138 }
139 let slice = self.times.values_slice();
141 Some(TimeRange::new(
142 DateTime::from_timestamp_nanos(slice[0]),
143 DateTime::from_timestamp_nanos(slice[slice.len() - 1]),
144 ))
145 }
146
147 pub fn append(&mut self, row: &Measure) -> Result<()> {
148 self.process_ids
149 .append(format!("{}", row.process.process_id))?;
150 self.stream_ids.append(&*row.stream_id)?;
151 self.block_ids.append(&*row.block_id)?;
152 self.insert_times.append_value(row.insert_time);
153 self.exes.append(&row.process.exe)?;
154 self.usernames.append(&row.process.username)?;
155 self.computers.append(&row.process.computer)?;
156 self.times.append_value(row.time);
157 self.targets.append(row.target)?;
158 self.names.append(row.name)?;
159 self.units.append(row.unit)?;
160 self.values.append_value(row.value);
161 self.properties.append_property_set(&row.properties)?;
162 self.process_properties.append(&*row.process.properties)?;
163 Ok(())
164 }
165
166 pub fn append_entry_only(&mut self, row: &Measure) -> Result<()> {
168 self.times.append_value(row.time);
170 self.targets.append(row.target)?;
171 self.names.append(row.name)?;
172 self.units.append(row.unit)?;
173 self.values.append_value(row.value);
174 self.properties.append_property_set(&row.properties)?;
175 Ok(())
176 }
177
178 pub fn fill_constant_columns(
180 &mut self,
181 process: &ProcessMetadata,
182 stream_id: &str,
183 block_id: &str,
184 insert_time: i64,
185 entry_count: usize,
186 ) -> Result<()> {
187 let process_id_str = format!("{}", process.process_id);
188
189 let insert_times_slice = vec![insert_time; entry_count];
191 self.insert_times.append_slice(&insert_times_slice);
192
193 self.process_properties
195 .append_n(&**process.properties, entry_count)?;
196
197 self.process_ids.append_n(&process_id_str, entry_count)?;
199 self.stream_ids.append_n(stream_id, entry_count)?;
200 self.block_ids.append_n(block_id, entry_count)?;
201 self.exes.append_n(&process.exe, entry_count)?;
202 self.usernames.append_n(&process.username, entry_count)?;
203 self.computers.append_n(&process.computer, entry_count)?;
204
205 Ok(())
206 }
207
208 pub fn finish(mut self) -> Result<RecordBatch> {
209 RecordBatch::try_new(
210 Arc::new(metrics_table_schema()),
211 vec![
212 Arc::new(self.process_ids.finish()),
213 Arc::new(self.stream_ids.finish()),
214 Arc::new(self.block_ids.finish()),
215 Arc::new(self.insert_times.finish().with_timezone_utc()),
216 Arc::new(self.exes.finish()),
217 Arc::new(self.usernames.finish()),
218 Arc::new(self.computers.finish()),
219 Arc::new(self.times.finish().with_timezone_utc()),
220 Arc::new(self.targets.finish()),
221 Arc::new(self.names.finish()),
222 Arc::new(self.units.finish()),
223 Arc::new(self.values.finish()),
224 Arc::new(self.properties.finish()?),
225 Arc::new(self.process_properties.finish()),
226 ],
227 )
228 .with_context(|| "building record batch")
229 }
230}