1#![allow(unsafe_op_in_unsafe_fn)] use std::{
4 num::NonZeroU64,
5 pin::Pin,
6 sync::{Arc, Mutex, MutexGuard},
7 time::{Duration, Instant},
8};
9
10use async_trait::async_trait;
11use bytes::Bytes;
12use evmap::{
13 shallow_copy::CopyValue,
14 {self},
15};
16use evmap_derive::ShallowCopy;
17use futures::{
18 Stream, StreamExt,
19 stream::{self, BoxStream},
20};
21use thread_local::ThreadLocal;
22use tokio::{
23 sync::broadcast::{Receiver, Sender},
24 time::interval,
25};
26use tokio_stream::wrappers::IntervalStream;
27use vector_lib::{
28 ByteSizeOf, EstimatedJsonEncodedSizeOf,
29 config::LogNamespace,
30 enrichment::{Case, Condition, Error, IndexHandle, InternalError, Table},
31 event::{Event, EventStatus, Finalizable},
32 internal_event::{
33 ByteSize, BytesSent, CountByteSize, EventsSent, InternalEventHandle, Output, Protocol,
34 },
35 shutdown::ShutdownSignal,
36 sink::StreamSink,
37};
38use vrl::value::{KeyString, ObjectMap, Value};
39
40use super::source::MemorySource;
41use crate::{
42 SourceSender,
43 enrichment_tables::memory::{
44 MemoryConfig,
45 internal_events::{
46 MemoryEnrichmentTableFlushed, MemoryEnrichmentTableInsertFailed,
47 MemoryEnrichmentTableInserted, MemoryEnrichmentTableRead,
48 MemoryEnrichmentTableReadFailed, MemoryEnrichmentTableTtlExpired,
49 },
50 },
51};
52
53#[derive(Clone, Eq, PartialEq, Hash, ShallowCopy)]
55pub struct MemoryEntry {
56 value: String,
57 update_time: CopyValue<Instant>,
58 ttl: u64,
59}
60
61impl ByteSizeOf for MemoryEntry {
62 fn allocated_bytes(&self) -> usize {
63 self.value.size_of()
64 }
65}
66
67impl MemoryEntry {
68 pub(super) fn as_object_map(&self, now: Instant, key: &str) -> Result<ObjectMap, Error> {
69 let ttl = self
70 .ttl
71 .saturating_sub(now.duration_since(*self.update_time).as_secs());
72 Ok(ObjectMap::from([
73 (
74 KeyString::from("key"),
75 Value::Bytes(Bytes::copy_from_slice(key.as_bytes())),
76 ),
77 (
78 KeyString::from("value"),
79 serde_json::from_str::<Value>(&self.value).map_err(|source| Error::Internal {
81 source: InternalError::FailedToDecode {
82 details: source.to_string(),
83 },
84 })?,
85 ),
86 (
87 KeyString::from("ttl"),
88 Value::Integer(ttl.try_into().unwrap_or(i64::MAX)),
89 ),
90 ]))
91 }
92
93 fn expired(&self, now: Instant) -> bool {
94 now.duration_since(*self.update_time).as_secs() > self.ttl
95 }
96}
97
98#[derive(Default)]
99struct MemoryMetadata {
100 byte_size: u64,
101}
102
103#[derive(Clone)]
105pub(super) struct MemoryEntryPair {
106 pub(super) key: String,
108 pub(super) entry: MemoryEntry,
110}
111
112pub(super) struct MemoryWriter {
114 pub(super) write_handle: evmap::WriteHandle<String, MemoryEntry>,
115 metadata: MemoryMetadata,
116}
117
118pub struct Memory {
120 read_handle_factory: evmap::ReadHandleFactory<String, MemoryEntry>,
121 read_handle: ThreadLocal<evmap::ReadHandle<String, MemoryEntry>>,
122 pub(super) write_handle: Arc<Mutex<MemoryWriter>>,
123 pub(super) config: MemoryConfig,
124 #[allow(dead_code)]
125 expired_items_receiver: Receiver<Vec<MemoryEntryPair>>,
126 expired_items_sender: Sender<Vec<MemoryEntryPair>>,
127}
128
129impl Memory {
130 pub fn new(config: MemoryConfig) -> Self {
132 let (read_handle, write_handle) = evmap::new();
133 let (expired_tx, expired_rx) = tokio::sync::broadcast::channel(5);
137 Self {
138 config,
139 read_handle_factory: read_handle.factory(),
140 read_handle: ThreadLocal::new(),
141 write_handle: Arc::new(Mutex::new(MemoryWriter {
142 write_handle,
143 metadata: MemoryMetadata::default(),
144 })),
145 expired_items_sender: expired_tx,
146 expired_items_receiver: expired_rx,
147 }
148 }
149
150 pub fn from_previous_state(
152 config: MemoryConfig,
153 prev_state: Box<dyn std::any::Any + Send + Sync>,
154 ) -> Self {
155 if let Ok(prev_memory) = prev_state.downcast::<Memory>() {
156 Self {
157 config,
158 read_handle_factory: prev_memory.read_handle_factory,
159 read_handle: prev_memory.read_handle,
160 write_handle: prev_memory.write_handle,
161 expired_items_sender: prev_memory.expired_items_sender,
162 expired_items_receiver: prev_memory.expired_items_receiver,
163 }
164 } else {
165 Self::new(config)
166 }
167 }
168
169 pub(super) fn get_read_handle(&self) -> &evmap::ReadHandle<String, MemoryEntry> {
170 self.read_handle
171 .get_or(|| self.read_handle_factory.handle())
172 }
173
174 pub(super) fn subscribe_to_expired_items(&self) -> Receiver<Vec<MemoryEntryPair>> {
175 self.expired_items_sender.subscribe()
176 }
177
178 fn handle_value(&self, value: ObjectMap) {
179 let mut writer = self.write_handle.lock().expect("mutex poisoned");
180 let now = Instant::now();
181
182 for (k, value) in value.into_iter() {
183 let new_entry_key = String::from(k);
184 let Ok(v) = serde_json::to_string(&value) else {
185 emit!(MemoryEnrichmentTableInsertFailed {
186 key: &new_entry_key,
187 include_key_metric_tag: self.config.internal_metrics.include_key_tag
188 });
189 continue;
190 };
191 let new_entry = MemoryEntry {
192 value: v,
193 update_time: now.into(),
194 ttl: self
195 .config
196 .ttl_field
197 .path
198 .as_ref()
199 .and_then(|p| value.get(p))
200 .and_then(|v| v.as_integer())
201 .map(|v| v as u64)
202 .unwrap_or(self.config.ttl),
203 };
204 let new_entry_size = new_entry_key.size_of() + new_entry.size_of();
205 if let Some(max_byte_size) = self.config.max_byte_size
206 && writer
207 .metadata
208 .byte_size
209 .saturating_add(new_entry_size as u64)
210 > max_byte_size
211 {
212 emit!(MemoryEnrichmentTableInsertFailed {
214 key: &new_entry_key,
215 include_key_metric_tag: self.config.internal_metrics.include_key_tag
216 });
217 continue;
218 }
219 writer.metadata.byte_size = writer
220 .metadata
221 .byte_size
222 .saturating_add(new_entry_size as u64);
223 emit!(MemoryEnrichmentTableInserted {
224 key: &new_entry_key,
225 include_key_metric_tag: self.config.internal_metrics.include_key_tag
226 });
227 writer.write_handle.update(new_entry_key, new_entry);
228 }
229
230 if self.config.flush_interval.is_none() {
231 self.flush(writer);
232 }
233 }
234
235 fn scan_and_mark_for_deletion(&self, writer: &mut MutexGuard<'_, MemoryWriter>) -> bool {
236 let now = Instant::now();
237
238 let mut needs_flush = false;
239 if let Some(reader) = self.get_read_handle().read() {
243 for (k, v) in reader.iter() {
244 if let Some(entry) = v.get_one()
245 && entry.expired(now)
246 {
247 writer.write_handle.empty(k.clone());
250 emit!(MemoryEnrichmentTableTtlExpired {
251 key: k,
252 include_key_metric_tag: self.config.internal_metrics.include_key_tag
253 });
254 needs_flush = true;
255 }
256 }
257 };
258
259 needs_flush
260 }
261
262 fn scan(&self, mut writer: MutexGuard<'_, MemoryWriter>) {
263 let needs_flush = self.scan_and_mark_for_deletion(&mut writer);
264 if needs_flush {
265 self.flush(writer);
266 }
267 }
268
269 fn flush(&self, mut writer: MutexGuard<'_, MemoryWriter>) {
270 if self
272 .config
273 .source_config
274 .as_ref()
275 .map(|c| c.export_expired_items)
276 .unwrap_or_default()
277 {
278 let pending_removal = writer
279 .write_handle
280 .pending()
281 .iter()
282 .filter_map(|o| match o {
284 evmap::Operation::Empty(k) => Some(k),
285 _ => None,
286 })
287 .filter_map(|key| {
288 writer.write_handle.get_one(key).map(|v| MemoryEntryPair {
289 key: key.to_string(),
290 entry: v.clone(),
291 })
292 })
293 .collect::<Vec<_>>();
294 if let Err(error) = self.expired_items_sender.send(pending_removal) {
295 error!(
296 message = "Error exporting expired items from memory enrichment table.",
297 error = %error,
298 );
299 }
300 }
301
302 writer.write_handle.refresh();
303 if let Some(reader) = self.get_read_handle().read() {
304 let mut byte_size = 0;
305 for (k, v) in reader.iter() {
306 byte_size += k.size_of() + v.get_one().size_of();
307 }
308 writer.metadata.byte_size = byte_size as u64;
309 emit!(MemoryEnrichmentTableFlushed {
310 new_objects_count: reader.len(),
311 new_byte_size: byte_size
312 });
313 }
314 }
315
316 pub(crate) fn as_source(
317 &self,
318 shutdown: ShutdownSignal,
319 out: SourceSender,
320 log_namespace: LogNamespace,
321 ) -> MemorySource {
322 MemorySource {
323 memory: self.clone(),
324 shutdown,
325 out,
326 log_namespace,
327 }
328 }
329}
330
331impl Clone for Memory {
332 fn clone(&self) -> Self {
333 Self {
334 read_handle_factory: self.read_handle_factory.clone(),
335 read_handle: ThreadLocal::new(),
336 write_handle: Arc::clone(&self.write_handle),
337 config: self.config.clone(),
338 expired_items_sender: self.expired_items_sender.clone(),
339 expired_items_receiver: self.expired_items_sender.subscribe(),
340 }
341 }
342}
343
344impl Table for Memory {
345 fn find_table_row<'a>(
346 &self,
347 case: Case,
348 condition: &'a [Condition<'a>],
349 select: Option<&'a [String]>,
350 wildcard: Option<&Value>,
351 index: Option<IndexHandle>,
352 ) -> Result<ObjectMap, Error> {
353 let mut rows = self.find_table_rows(case, condition, select, wildcard, index)?;
354
355 match rows.pop() {
356 Some(row) if rows.is_empty() => Ok(row),
357 Some(_) => Err(Error::MoreThanOneRowFound),
358 None => Err(Error::NoRowsFound),
359 }
360 }
361
362 fn find_table_rows<'a>(
363 &self,
364 _case: Case,
365 condition: &'a [Condition<'a>],
366 _select: Option<&'a [String]>,
367 _wildcard: Option<&Value>,
368 _index: Option<IndexHandle>,
369 ) -> Result<Vec<ObjectMap>, Error> {
370 match condition.first() {
371 Some(_) if condition.len() > 1 => Err(Error::OnlyOneConditionAllowed),
372 Some(Condition::Equals { value, .. }) => {
373 let key = value.to_string_lossy();
374 match self.get_read_handle().get_one(key.as_ref()) {
375 Some(row) => {
376 emit!(MemoryEnrichmentTableRead {
377 key: &key,
378 include_key_metric_tag: self.config.internal_metrics.include_key_tag
379 });
380 row.as_object_map(Instant::now(), &key).map(|r| vec![r])
381 }
382 None => {
383 emit!(MemoryEnrichmentTableReadFailed {
384 key: &key,
385 include_key_metric_tag: self.config.internal_metrics.include_key_tag
386 });
387 Ok(Default::default())
388 }
389 }
390 }
391 Some(_) => Err(Error::OnlyEqualityConditionAllowed),
392 None => Err(Error::MissingCondition { kind: "Key" }),
393 }
394 }
395
396 fn add_index(&mut self, _case: Case, fields: &[&str]) -> Result<IndexHandle, Error> {
397 match fields.len() {
398 0 => Err(Error::MissingRequiredField { field: "Key" }),
399 1 => Ok(IndexHandle(0)),
400 _ => Err(Error::OnlyOneFieldAllowed),
401 }
402 }
403
404 fn index_fields(&self) -> Vec<(Case, Vec<String>)> {
406 Vec::new()
407 }
408
409 fn needs_reload(&self) -> bool {
411 false
412 }
413
414 fn extract_state(&self) -> Option<Box<dyn std::any::Any + Send + Sync>> {
415 let writer = self.write_handle.lock().expect("mutex poisoned");
416 self.flush(writer);
417 Some(Box::new(self.clone()))
418 }
419}
420
421impl std::fmt::Debug for Memory {
422 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423 write!(f, "Memory {} row(s)", self.get_read_handle().len())
424 }
425}
426
427#[async_trait]
428impl StreamSink<Event> for Memory {
429 async fn run(mut self: Box<Self>, mut input: BoxStream<'_, Event>) -> Result<(), ()> {
430 let events_sent = register!(EventsSent::from(Output(None)));
431 let bytes_sent = register!(BytesSent::from(Protocol("memory_enrichment_table".into(),)));
432 let mut flush_interval: Pin<Box<dyn Stream<Item = tokio::time::Instant> + Send>> = self
433 .config
434 .flush_interval
435 .map(NonZeroU64::get)
436 .map(Duration::from_secs)
437 .map::<Pin<Box<dyn Stream<Item = tokio::time::Instant> + Send>>, _>(|d| {
438 Box::pin(IntervalStream::new(interval(d)))
439 })
440 .unwrap_or(Box::pin(stream::empty()));
441 let mut scan_interval = IntervalStream::new(interval(Duration::from_secs(
442 self.config.scan_interval.into(),
443 )));
444
445 loop {
446 tokio::select! {
447 event = input.next() => {
448 let mut event = if let Some(event) = event {
449 event
450 } else {
451 break;
452 };
453 let event_byte_size = event.estimated_json_encoded_size_of();
454
455 let finalizers = event.take_finalizers();
456
457 let log = event.into_log();
459
460 if let (Value::Object(map), _) = log.into_parts() {
461 self.handle_value(map)
462 };
463
464 finalizers.update_status(EventStatus::Delivered);
465 events_sent.emit(CountByteSize(1, event_byte_size));
466 bytes_sent.emit(ByteSize(event_byte_size.get()));
467 }
468
469 Some(_) = flush_interval.next() => {
470 let writer = self.write_handle.lock().expect("mutex poisoned");
471 self.flush(writer);
472 }
473
474 Some(_) = scan_interval.next() => {
475 let writer = self.write_handle.lock().expect("mutex poisoned");
476 self.scan(writer);
477 }
478 }
479 }
480 Ok(())
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use std::{num::NonZeroU64, slice::from_ref, time::Duration};
487
488 use futures::{StreamExt, future::ready};
489 use futures_util::stream;
490 use tokio::time;
491
492 use vector_lib::{
493 event::{EventContainer, MetricValue},
494 lookup::lookup_v2::OptionalValuePath,
495 metrics::Controller,
496 sink::VectorSink,
497 };
498
499 use super::*;
500 use crate::{
501 config::EnrichmentTableConfig,
502 enrichment_tables::memory::{
503 config::MemorySourceConfig, internal_events::InternalMetricsConfig,
504 },
505 event::{Event, LogEvent},
506 test_util::components::{
507 SINK_TAGS, SOURCE_TAGS, run_and_assert_sink_compliance,
508 run_and_assert_source_compliance,
509 },
510 };
511
512 fn build_memory_config(modfn: impl Fn(&mut MemoryConfig)) -> MemoryConfig {
513 let mut config = MemoryConfig::default();
514 modfn(&mut config);
515 config
516 }
517
518 #[test]
519 fn finds_row() {
520 let memory = Memory::new(Default::default());
521 memory.handle_value(ObjectMap::from([("test_key".into(), Value::from(5))]));
522
523 let condition = Condition::Equals {
524 field: "key",
525 value: Value::from("test_key"),
526 };
527
528 assert_eq!(
529 Ok(ObjectMap::from([
530 ("key".into(), Value::from("test_key")),
531 ("ttl".into(), Value::from(memory.config.ttl)),
532 ("value".into(), Value::from(5)),
533 ])),
534 memory.find_table_row(Case::Sensitive, &[condition], None, None, None)
535 );
536 }
537
538 #[tokio::test]
539 async fn extract_state_preserves_data() {
540 let memory = Memory::new(Default::default());
541 memory.handle_value(ObjectMap::from([("test_key".into(), Value::from(5))]));
542
543 let condition = Condition::Equals {
544 field: "key",
545 value: Value::from("test_key"),
546 };
547
548 let expected = ObjectMap::from([
549 ("key".into(), Value::from("test_key")),
550 ("ttl".into(), Value::from(memory.config.ttl)),
551 ("value".into(), Value::from(5)),
552 ]);
553 assert_eq!(
554 Ok(expected.clone()),
555 memory.find_table_row(
556 Case::Sensitive,
557 std::slice::from_ref(&condition),
558 None,
559 None,
560 None
561 )
562 );
563
564 let new_memory = MemoryConfig::default()
566 .build(&Default::default(), memory.extract_state())
567 .await
568 .unwrap();
569 assert_eq!(
570 Ok(expected),
571 new_memory.find_table_row(Case::Sensitive, &[condition], None, None, None)
572 );
573 }
574
575 #[test]
576 fn calculates_ttl() {
577 let ttl = 100;
578 let secs_to_subtract = 10;
579 let memory = Memory::new(build_memory_config(|c| c.ttl = ttl));
580 {
581 let mut handle = memory.write_handle.lock().unwrap();
582 handle.write_handle.update(
583 "test_key".to_string(),
584 MemoryEntry {
585 value: "5".to_string(),
586 update_time: (Instant::now() - Duration::from_secs(secs_to_subtract)).into(),
587 ttl,
588 },
589 );
590 handle.write_handle.refresh();
591 }
592
593 let condition = Condition::Equals {
594 field: "key",
595 value: Value::from("test_key"),
596 };
597
598 assert_eq!(
599 Ok(ObjectMap::from([
600 ("key".into(), Value::from("test_key")),
601 ("ttl".into(), Value::from(ttl - secs_to_subtract)),
602 ("value".into(), Value::from(5)),
603 ])),
604 memory.find_table_row(Case::Sensitive, &[condition], None, None, None)
605 );
606 }
607
608 #[test]
609 fn calculates_ttl_override() {
610 let global_ttl = 100;
611 let ttl_override = 10;
612 let memory = Memory::new(build_memory_config(|c| {
613 c.ttl = global_ttl;
614 c.ttl_field = OptionalValuePath::new("ttl");
615 }));
616 memory.handle_value(ObjectMap::from([
617 (
618 "ttl_override".into(),
619 Value::from(ObjectMap::from([
620 ("val".into(), Value::from(5)),
621 ("ttl".into(), Value::from(ttl_override)),
622 ])),
623 ),
624 (
625 "default_ttl".into(),
626 Value::from(ObjectMap::from([("val".into(), Value::from(5))])),
627 ),
628 ]));
629
630 let default_condition = Condition::Equals {
631 field: "key",
632 value: Value::from("default_ttl"),
633 };
634 let override_condition = Condition::Equals {
635 field: "key",
636 value: Value::from("ttl_override"),
637 };
638
639 assert_eq!(
640 Ok(ObjectMap::from([
641 ("key".into(), Value::from("default_ttl")),
642 ("ttl".into(), Value::from(global_ttl)),
643 (
644 "value".into(),
645 Value::from(ObjectMap::from([("val".into(), Value::from(5))]))
646 ),
647 ])),
648 memory.find_table_row(Case::Sensitive, &[default_condition], None, None, None)
649 );
650 assert_eq!(
651 Ok(ObjectMap::from([
652 ("key".into(), Value::from("ttl_override")),
653 ("ttl".into(), Value::from(ttl_override)),
654 (
655 "value".into(),
656 Value::from(ObjectMap::from([
657 ("val".into(), Value::from(5)),
658 ("ttl".into(), Value::from(ttl_override))
659 ]))
660 ),
661 ])),
662 memory.find_table_row(Case::Sensitive, &[override_condition], None, None, None)
663 );
664 }
665
666 #[test]
667 fn removes_expired_records_on_scan_interval() {
668 let ttl = 100;
669 let memory = Memory::new(build_memory_config(|c| {
670 c.ttl = ttl;
671 }));
672 {
673 let mut handle = memory.write_handle.lock().unwrap();
674 handle.write_handle.update(
675 "test_key".to_string(),
676 MemoryEntry {
677 value: "5".to_string(),
678 update_time: (Instant::now() - Duration::from_secs(ttl + 10)).into(),
679 ttl,
680 },
681 );
682 handle.write_handle.refresh();
683 }
684
685 let condition = Condition::Equals {
687 field: "key",
688 value: Value::from("test_key"),
689 };
690 assert_eq!(
691 Ok(ObjectMap::from([
692 ("key".into(), Value::from("test_key")),
693 ("ttl".into(), Value::from(0)),
694 ("value".into(), Value::from(5)),
695 ])),
696 memory.find_table_row(Case::Sensitive, from_ref(&condition), None, None, None)
697 );
698
699 let writer = memory.write_handle.lock().unwrap();
701 memory.scan(writer);
702
703 assert!(
705 memory
706 .find_table_rows(Case::Sensitive, &[condition], None, None, None)
707 .unwrap()
708 .pop()
709 .is_none()
710 );
711 }
712
713 #[test]
714 fn does_not_show_values_before_flush_interval() {
715 let ttl = 100;
716 let memory = Memory::new(build_memory_config(|c| {
717 c.ttl = ttl;
718 c.flush_interval = NonZeroU64::new(10);
719 }));
720 memory.handle_value(ObjectMap::from([("test_key".into(), Value::from(5))]));
721
722 let condition = Condition::Equals {
723 field: "key",
724 value: Value::from("test_key"),
725 };
726
727 assert!(
728 memory
729 .find_table_rows(Case::Sensitive, &[condition], None, None, None)
730 .unwrap()
731 .pop()
732 .is_none()
733 );
734 }
735
736 #[test]
737 fn updates_ttl_on_value_replacement() {
738 let ttl = 100;
739 let memory = Memory::new(build_memory_config(|c| c.ttl = ttl));
740 {
741 let mut handle = memory.write_handle.lock().unwrap();
742 handle.write_handle.update(
743 "test_key".to_string(),
744 MemoryEntry {
745 value: "5".to_string(),
746 update_time: (Instant::now() - Duration::from_secs(ttl / 2)).into(),
747 ttl,
748 },
749 );
750 handle.write_handle.refresh();
751 }
752 let condition = Condition::Equals {
753 field: "key",
754 value: Value::from("test_key"),
755 };
756
757 assert_eq!(
758 Ok(ObjectMap::from([
759 ("key".into(), Value::from("test_key")),
760 ("ttl".into(), Value::from(ttl / 2)),
761 ("value".into(), Value::from(5)),
762 ])),
763 memory.find_table_row(Case::Sensitive, from_ref(&condition), None, None, None)
764 );
765
766 memory.handle_value(ObjectMap::from([("test_key".into(), Value::from(5))]));
767
768 assert_eq!(
769 Ok(ObjectMap::from([
770 ("key".into(), Value::from("test_key")),
771 ("ttl".into(), Value::from(ttl)),
772 ("value".into(), Value::from(5)),
773 ])),
774 memory.find_table_row(Case::Sensitive, &[condition], None, None, None)
775 );
776 }
777
778 #[test]
779 fn ignores_all_values_over_byte_size_limit() {
780 let memory = Memory::new(build_memory_config(|c| {
781 c.max_byte_size = Some(1);
782 }));
783 memory.handle_value(ObjectMap::from([("test_key".into(), Value::from(5))]));
784
785 let condition = Condition::Equals {
786 field: "key",
787 value: Value::from("test_key"),
788 };
789
790 assert!(
791 memory
792 .find_table_rows(Case::Sensitive, &[condition], None, None, None)
793 .unwrap()
794 .pop()
795 .is_none()
796 );
797 }
798
799 #[test]
800 fn ignores_values_when_byte_size_limit_is_reached() {
801 let ttl = 100;
802 let memory = Memory::new(build_memory_config(|c| {
803 c.ttl = ttl;
804 c.max_byte_size = Some(150);
805 }));
806 memory.handle_value(ObjectMap::from([("test_key".into(), Value::from(5))]));
807 memory.handle_value(ObjectMap::from([("rejected_key".into(), Value::from(5))]));
808
809 assert_eq!(
810 Ok(ObjectMap::from([
811 ("key".into(), Value::from("test_key")),
812 ("ttl".into(), Value::from(ttl)),
813 ("value".into(), Value::from(5)),
814 ])),
815 memory.find_table_row(
816 Case::Sensitive,
817 &[Condition::Equals {
818 field: "key",
819 value: Value::from("test_key")
820 }],
821 None,
822 None,
823 None
824 )
825 );
826
827 assert!(
828 memory
829 .find_table_rows(
830 Case::Sensitive,
831 &[Condition::Equals {
832 field: "key",
833 value: Value::from("rejected_key")
834 }],
835 None,
836 None,
837 None
838 )
839 .unwrap()
840 .pop()
841 .is_none()
842 );
843 }
844
845 #[test]
846 fn missing_key() {
847 let memory = Memory::new(Default::default());
848
849 let condition = Condition::Equals {
850 field: "key",
851 value: Value::from("test_key"),
852 };
853
854 assert!(
855 memory
856 .find_table_rows(Case::Sensitive, &[condition], None, None, None)
857 .unwrap()
858 .pop()
859 .is_none()
860 );
861 }
862
863 #[tokio::test]
864 async fn sink_spec_compliance() {
865 let event = Event::Log(LogEvent::from(ObjectMap::from([(
866 "test_key".into(),
867 Value::from(5),
868 )])));
869
870 let memory = Memory::new(Default::default());
871
872 run_and_assert_sink_compliance(
873 VectorSink::from_event_streamsink(memory),
874 stream::once(ready(event)),
875 &SINK_TAGS,
876 )
877 .await;
878 }
879
880 #[tokio::test]
881 async fn flush_metrics_without_interval() {
882 let event = Event::Log(LogEvent::from(ObjectMap::from([(
883 "test_key".into(),
884 Value::from(5),
885 )])));
886
887 let memory = Memory::new(Default::default());
888
889 run_and_assert_sink_compliance(
890 VectorSink::from_event_streamsink(memory),
891 stream::once(ready(event)),
892 &SINK_TAGS,
893 )
894 .await;
895
896 let metrics = Controller::get().unwrap().capture_metrics();
897 let insertions_counter = metrics
898 .iter()
899 .find(|m| {
900 matches!(m.value(), MetricValue::Counter { .. })
901 && m.name() == "memory_enrichment_table_insertions_total"
902 })
903 .expect("Insertions metric is missing!");
904 let MetricValue::Counter {
905 value: insertions_count,
906 } = insertions_counter.value()
907 else {
908 unreachable!();
909 };
910 let flushes_counter = metrics
911 .iter()
912 .find(|m| {
913 matches!(m.value(), MetricValue::Counter { .. })
914 && m.name() == "memory_enrichment_table_flushes_total"
915 })
916 .expect("Flushes metric is missing!");
917 let MetricValue::Counter {
918 value: flushes_count,
919 } = flushes_counter.value()
920 else {
921 unreachable!();
922 };
923 let object_count_gauge = metrics
924 .iter()
925 .find(|m| {
926 matches!(m.value(), MetricValue::Gauge { .. })
927 && m.name() == "memory_enrichment_table_objects_count"
928 })
929 .expect("Object count metric is missing!");
930 let MetricValue::Gauge {
931 value: object_count,
932 } = object_count_gauge.value()
933 else {
934 unreachable!();
935 };
936 let byte_size_gauge = metrics
937 .iter()
938 .find(|m| {
939 matches!(m.value(), MetricValue::Gauge { .. })
940 && m.name() == "memory_enrichment_table_byte_size"
941 })
942 .expect("Byte size metric is missing!");
943 assert_eq!(*insertions_count, 1.0);
944 assert_eq!(*flushes_count, 1.0);
945 assert_eq!(*object_count, 1.0);
946 assert!(!byte_size_gauge.is_empty());
947 }
948
949 #[tokio::test]
950 async fn flush_metrics_with_interval() {
951 let event = Event::Log(LogEvent::from(ObjectMap::from([(
952 "test_key".into(),
953 Value::from(5),
954 )])));
955
956 let memory = Memory::new(build_memory_config(|c| {
957 c.flush_interval = NonZeroU64::new(1);
958 }));
959
960 run_and_assert_sink_compliance(
961 VectorSink::from_event_streamsink(memory),
962 stream::iter(vec![event.clone(), event]).flat_map(|e| {
963 stream::once(async move {
964 tokio::time::sleep(Duration::from_millis(600)).await;
965 e
966 })
967 }),
968 &SINK_TAGS,
969 )
970 .await;
971
972 let metrics = Controller::get().unwrap().capture_metrics();
973 let insertions_counter = metrics
974 .iter()
975 .find(|m| {
976 matches!(m.value(), MetricValue::Counter { .. })
977 && m.name() == "memory_enrichment_table_insertions_total"
978 })
979 .expect("Insertions metric is missing!");
980 let MetricValue::Counter {
981 value: insertions_count,
982 } = insertions_counter.value()
983 else {
984 unreachable!();
985 };
986 let flushes_counter = metrics
987 .iter()
988 .find(|m| {
989 matches!(m.value(), MetricValue::Counter { .. })
990 && m.name() == "memory_enrichment_table_flushes_total"
991 })
992 .expect("Flushes metric is missing!");
993 let MetricValue::Counter {
994 value: flushes_count,
995 } = flushes_counter.value()
996 else {
997 unreachable!();
998 };
999 let object_count_gauge = metrics
1000 .iter()
1001 .find(|m| {
1002 matches!(m.value(), MetricValue::Gauge { .. })
1003 && m.name() == "memory_enrichment_table_objects_count"
1004 })
1005 .expect("Object count metric is missing!");
1006 let MetricValue::Gauge {
1007 value: object_count,
1008 } = object_count_gauge.value()
1009 else {
1010 unreachable!();
1011 };
1012 let byte_size_gauge = metrics
1013 .iter()
1014 .find(|m| {
1015 matches!(m.value(), MetricValue::Gauge { .. })
1016 && m.name() == "memory_enrichment_table_byte_size"
1017 })
1018 .expect("Byte size metric is missing!");
1019
1020 assert_eq!(*insertions_count, 2.0);
1021 assert_eq!(*flushes_count, 2.0);
1023 assert_eq!(*object_count, 1.0);
1024 assert!(!byte_size_gauge.is_empty());
1025 }
1026
1027 #[tokio::test]
1028 async fn flush_metrics_with_key() {
1029 let event = Event::Log(LogEvent::from(ObjectMap::from([(
1030 "test_key".into(),
1031 Value::from(5),
1032 )])));
1033
1034 let memory = Memory::new(build_memory_config(|c| {
1035 c.internal_metrics = InternalMetricsConfig {
1036 include_key_tag: true,
1037 };
1038 }));
1039
1040 run_and_assert_sink_compliance(
1041 VectorSink::from_event_streamsink(memory),
1042 stream::once(ready(event)),
1043 &SINK_TAGS,
1044 )
1045 .await;
1046
1047 let metrics = Controller::get().unwrap().capture_metrics();
1048 let insertions_counter = metrics
1049 .iter()
1050 .find(|m| {
1051 matches!(m.value(), MetricValue::Counter { .. })
1052 && m.name() == "memory_enrichment_table_insertions_total"
1053 })
1054 .expect("Insertions metric is missing!");
1055
1056 assert!(insertions_counter.tag_matches("key", "test_key"));
1057 }
1058
1059 #[tokio::test]
1060 async fn flush_metrics_without_key() {
1061 let event = Event::Log(LogEvent::from(ObjectMap::from([(
1062 "test_key".into(),
1063 Value::from(5),
1064 )])));
1065
1066 let memory = Memory::new(Default::default());
1067
1068 run_and_assert_sink_compliance(
1069 VectorSink::from_event_streamsink(memory),
1070 stream::once(ready(event)),
1071 &SINK_TAGS,
1072 )
1073 .await;
1074
1075 let metrics = Controller::get().unwrap().capture_metrics();
1076 let insertions_counter = metrics
1077 .iter()
1078 .find(|m| {
1079 matches!(m.value(), MetricValue::Counter { .. })
1080 && m.name() == "memory_enrichment_table_insertions_total"
1081 })
1082 .expect("Insertions metric is missing!");
1083
1084 assert!(insertions_counter.tag_value("key").is_none());
1085 }
1086
1087 #[tokio::test]
1088 async fn source_spec_compliance() {
1089 let mut memory_config = MemoryConfig::default();
1090 memory_config.source_config = Some(MemorySourceConfig {
1091 export_interval: Some(NonZeroU64::try_from(1).unwrap()),
1092 export_batch_size: None,
1093 remove_after_export: false,
1094 export_expired_items: false,
1095 source_key: "test".to_string(),
1096 });
1097 let memory = memory_config.get_or_build_memory(None).await;
1098 memory.handle_value(ObjectMap::from([("test_key".into(), Value::from(5))]));
1099
1100 let mut events: Vec<Event> = run_and_assert_source_compliance(
1101 memory_config,
1102 time::Duration::from_secs(5),
1103 &SOURCE_TAGS,
1104 )
1105 .await;
1106
1107 assert!(!events.is_empty());
1108 let event = events.remove(0);
1109 let log = event.as_log();
1110
1111 assert!(!log.value().is_empty());
1112 }
1113}