Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{USIZE_BITS, USIZE_BITS_MASK};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Bitset<const N: usize>(pub(crate) [usize; N]);
|
||||
|
||||
impl<const N: usize> Bitset<N> {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub const fn new() -> Self {
|
||||
Self([0; N])
|
||||
}
|
||||
|
||||
pub const fn all() -> Self {
|
||||
Self([usize::MAX; N])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set(&mut self, index: impl Into<usize>) {
|
||||
let index = index.into();
|
||||
self.0[index / USIZE_BITS] |= 1 << (index & USIZE_BITS_MASK);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn clear(&mut self, index: impl Into<usize>) {
|
||||
let index = index.into();
|
||||
self.0[index / USIZE_BITS] &= !(1 << (index & USIZE_BITS_MASK));
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get(&self, index: impl Into<usize>) -> bool {
|
||||
let index = index.into();
|
||||
self.0[index / USIZE_BITS] & (1 << (index & USIZE_BITS_MASK)) != 0
|
||||
}
|
||||
|
||||
pub fn union(&mut self, other: &Self) {
|
||||
for i in 0..N {
|
||||
self.0[i] |= other.0[i];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intersection(&mut self, other: &Self) {
|
||||
for i in 0..N {
|
||||
self.0[i] &= other.0[i];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn difference(&mut self, other: &Self) {
|
||||
for i in 0..N {
|
||||
self.0[i] &= !other.0[i];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_many(&mut self, other: &Self) {
|
||||
for i in 0..N {
|
||||
self.0[i] &= !other.0[i];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_all(&mut self) {
|
||||
for i in 0..N {
|
||||
self.0[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
for i in 0..N {
|
||||
if self.0[i] != 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &[usize; N] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Default for Bitset<N> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::{
|
||||
cell::UnsafeCell,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU64, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
use rtrb::{Consumer, Producer, PushError, RingBuffer};
|
||||
|
||||
use crate::{
|
||||
Error, Event, EventType,
|
||||
ipc::collector::{COLLECTOR_THREAD, COLLECTOR_UPDATES, Update},
|
||||
};
|
||||
|
||||
use super::collector::{Collector, CollectorThread};
|
||||
|
||||
pub(crate) static CHANNEL_FLAGS: AtomicU64 = AtomicU64::new(0);
|
||||
pub(crate) const CHANNEL_SIZE: usize = 10240;
|
||||
pub(crate) const CHANNEL_UPDATE_MARKER: u64 = 1 << 63;
|
||||
|
||||
thread_local! {
|
||||
static EVENT_TX: UnsafeCell<Sender> = {
|
||||
// Create channel.
|
||||
let (tx, rx) = RingBuffer::new(CHANNEL_SIZE);
|
||||
|
||||
// Register receiver with collector.
|
||||
COLLECTOR_UPDATES.lock().push(Update::RegisterReceiver { receiver: Receiver { rx } });
|
||||
|
||||
// Spawn collector thread.
|
||||
let collector = COLLECTOR_THREAD.clone();
|
||||
CHANNEL_FLAGS.fetch_or(CHANNEL_UPDATE_MARKER, Ordering::Relaxed);
|
||||
collector.thread().unpark();
|
||||
|
||||
// Return sender.
|
||||
UnsafeCell::new(Sender {
|
||||
tx,
|
||||
collector,
|
||||
overflow: Vec::with_capacity(0),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Sender {
|
||||
tx: Producer<Event<EventType>>,
|
||||
collector: Arc<CollectorThread>,
|
||||
overflow: Vec<Event<EventType>>,
|
||||
}
|
||||
|
||||
pub struct Receiver {
|
||||
rx: Consumer<Event<EventType>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelError;
|
||||
|
||||
impl Sender {
|
||||
pub fn send(&mut self, event: Event<EventType>) -> Result<(), ChannelError> {
|
||||
while let Some(event) = self.overflow.pop() {
|
||||
if let Err(PushError::Full(event)) = self.tx.push(event) {
|
||||
self.overflow.push(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(PushError::Full(event)) = self.tx.push(event) {
|
||||
if self.overflow.len() <= CHANNEL_SIZE * 2 {
|
||||
self.overflow.push(event);
|
||||
} else {
|
||||
return Err(ChannelError);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Receiver {
|
||||
pub fn try_recv(&mut self) -> Result<Option<Event<EventType>>, ChannelError> {
|
||||
match self.rx.pop() {
|
||||
Ok(event) => Ok(Some(event)),
|
||||
Err(_) => {
|
||||
if !self.rx.is_abandoned() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(ChannelError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Event<EventType> {
|
||||
pub fn send(self) {
|
||||
// SAFETY: EVENT_TX is thread-local.
|
||||
let _ = EVENT_TX.try_with(|tx| unsafe {
|
||||
let tx = &mut *tx.get();
|
||||
if tx.send(self).is_ok() {
|
||||
CHANNEL_FLAGS.fetch_add(1, Ordering::Relaxed);
|
||||
tx.collector.thread().unpark();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_with_metrics(self) {
|
||||
Collector::record_metric(self.inner, self.inner.to_id() as usize, &self.keys);
|
||||
self.send();
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn send(self) {
|
||||
self.0.send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::{
|
||||
sync::{Arc, LazyLock, atomic::Ordering},
|
||||
thread::{Builder, JoinHandle, park},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use ahash::AHashMap;
|
||||
use atomics::bitset::AtomicBitset;
|
||||
use ipc::{
|
||||
USIZE_BITS,
|
||||
channel::{CHANNEL_FLAGS, CHANNEL_UPDATE_MARKER, Receiver},
|
||||
subscriber::{Interests, Subscriber},
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub(crate) type GlobalInterests = AtomicBitset<{ TOTAL_EVENT_COUNT.div_ceil(USIZE_BITS) }>;
|
||||
|
||||
pub(crate) static TRACE_INTERESTS: GlobalInterests = GlobalInterests::new();
|
||||
pub(crate) type CollectorThread = JoinHandle<()>;
|
||||
pub(crate) static ACTIVE_SUBSCRIBERS: Mutex<Vec<String>> = Mutex::new(Vec::new());
|
||||
pub(crate) static COLLECTOR_UPDATES: Mutex<Vec<Update>> = Mutex::new(Vec::new());
|
||||
|
||||
pub(crate) static EVENT_TYPES: &[EventType] = EventType::variants();
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub(crate) enum Update {
|
||||
RegisterReceiver {
|
||||
receiver: Receiver,
|
||||
},
|
||||
RegisterSubscriber {
|
||||
subscriber: Subscriber,
|
||||
},
|
||||
UnregisterSubscriber {
|
||||
id: String,
|
||||
},
|
||||
UpdateSubscriber {
|
||||
id: String,
|
||||
interests: Interests,
|
||||
lossy: bool,
|
||||
},
|
||||
UpdateLevels {
|
||||
levels: AHashMap<EventType, Level>,
|
||||
},
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
pub struct Collector {
|
||||
receivers: Vec<Receiver>,
|
||||
subscribers: Vec<Subscriber>,
|
||||
levels: [Level; TOTAL_EVENT_COUNT],
|
||||
active_spans: AHashMap<u64, Arc<Event<EventDetails>>>,
|
||||
}
|
||||
|
||||
const HTTP_CONN_START: usize = EventType::Http(HttpEvent::ConnectionStart).to_id() as usize;
|
||||
const HTTP_CONN_END: usize = EventType::Http(HttpEvent::ConnectionEnd).to_id() as usize;
|
||||
const IMAP_CONN_START: usize = EventType::Imap(ImapEvent::ConnectionStart).to_id() as usize;
|
||||
const IMAP_CONN_END: usize = EventType::Imap(ImapEvent::ConnectionEnd).to_id() as usize;
|
||||
const POP3_CONN_START: usize = EventType::Pop3(Pop3Event::ConnectionStart).to_id() as usize;
|
||||
const POP3_CONN_END: usize = EventType::Pop3(Pop3Event::ConnectionEnd).to_id() as usize;
|
||||
const SMTP_CONN_START: usize = EventType::Smtp(SmtpEvent::ConnectionStart).to_id() as usize;
|
||||
const SMTP_CONN_END: usize = EventType::Smtp(SmtpEvent::ConnectionEnd).to_id() as usize;
|
||||
const MANAGE_SIEVE_CONN_START: usize =
|
||||
EventType::ManageSieve(ManageSieveEvent::ConnectionStart).to_id() as usize;
|
||||
const MANAGE_SIEVE_CONN_END: usize =
|
||||
EventType::ManageSieve(ManageSieveEvent::ConnectionEnd).to_id() as usize;
|
||||
const EV_ATTEMPT_START: usize = EventType::Delivery(DeliveryEvent::AttemptStart).to_id() as usize;
|
||||
const EV_ATTEMPT_END: usize = EventType::Delivery(DeliveryEvent::AttemptEnd).to_id() as usize;
|
||||
|
||||
const STALE_SPAN_CHECK_WATERMARK: usize = 8000;
|
||||
const SPAN_MAX_HOLD: u64 = 60 * 60 * 24; // 1 day
|
||||
|
||||
pub(crate) static COLLECTOR_THREAD: LazyLock<Arc<CollectorThread>> = LazyLock::new(|| {
|
||||
Arc::new(
|
||||
Builder::new()
|
||||
.name("stalwart-collector".to_string())
|
||||
.spawn(move || {
|
||||
Collector::default().collect();
|
||||
})
|
||||
.expect("Failed to start event collector"),
|
||||
)
|
||||
});
|
||||
|
||||
impl Collector {
|
||||
fn collect(&mut self) {
|
||||
let mut do_continue = true;
|
||||
|
||||
// Update
|
||||
self.update();
|
||||
|
||||
while do_continue {
|
||||
match CHANNEL_FLAGS.swap(0, Ordering::Relaxed) {
|
||||
0 => {
|
||||
park();
|
||||
}
|
||||
CHANNEL_UPDATE_MARKER..=u64::MAX => {
|
||||
do_continue = self.update();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Collect all events
|
||||
let mut closed_rxs = Vec::new();
|
||||
for (rx_idx, rx) in self.receivers.iter_mut().enumerate() {
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map_or(0, |d| d.as_secs());
|
||||
|
||||
loop {
|
||||
match rx.try_recv() {
|
||||
Ok(Some(event)) => {
|
||||
// Build event
|
||||
let event_id = event.inner.to_id() as usize;
|
||||
let mut event = Event {
|
||||
inner: EventDetails {
|
||||
level: self.levels[event_id],
|
||||
typ: event.inner,
|
||||
timestamp,
|
||||
span: None,
|
||||
},
|
||||
keys: event.keys,
|
||||
};
|
||||
|
||||
// Track spans
|
||||
let event = match event_id {
|
||||
HTTP_CONN_START
|
||||
| IMAP_CONN_START
|
||||
| POP3_CONN_START
|
||||
| SMTP_CONN_START
|
||||
| MANAGE_SIEVE_CONN_START
|
||||
| EV_ATTEMPT_START => {
|
||||
let event = Arc::new(event);
|
||||
self.active_spans.insert(
|
||||
event.span_id().unwrap_or_else(|| {
|
||||
panic!("Missing span ID: {event:?}")
|
||||
}),
|
||||
event.clone(),
|
||||
);
|
||||
|
||||
if self.active_spans.len() > STALE_SPAN_CHECK_WATERMARK {
|
||||
self.active_spans.retain(|_, span| {
|
||||
timestamp.saturating_sub(span.inner.timestamp)
|
||||
< SPAN_MAX_HOLD
|
||||
});
|
||||
}
|
||||
event
|
||||
}
|
||||
|
||||
HTTP_CONN_END
|
||||
| IMAP_CONN_END
|
||||
| POP3_CONN_END
|
||||
| SMTP_CONN_END
|
||||
| MANAGE_SIEVE_CONN_END
|
||||
| EV_ATTEMPT_END => {
|
||||
if let Some(span) = self
|
||||
.active_spans
|
||||
.remove(&event.span_id().expect("Missing span ID"))
|
||||
{
|
||||
event.inner.span = Some(span.clone());
|
||||
} else {
|
||||
#[cfg(any(feature = "dev_mode", feature = "test_mode"))]
|
||||
{
|
||||
if event.span_id().unwrap() != 0 {
|
||||
eprintln!("Unregistered span ID: {event:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Arc::new(event)
|
||||
}
|
||||
_ => {
|
||||
if let Some(span_id) = event.span_id() {
|
||||
if let Some(span) = self.active_spans.get(&span_id) {
|
||||
event.inner.span = Some(span.clone());
|
||||
} else {
|
||||
#[cfg(any(
|
||||
feature = "dev_mode",
|
||||
feature = "test_mode"
|
||||
))]
|
||||
{
|
||||
if span_id != 0 {
|
||||
eprintln!("Unregistered span ID: {event:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Arc::new(event)
|
||||
}
|
||||
};
|
||||
|
||||
// Send to subscribers
|
||||
for subscriber in self.subscribers.iter_mut() {
|
||||
subscriber.push_event(event_id, event.clone());
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
closed_rxs.push(rx_idx); // Channel is closed, remove.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if do_continue {
|
||||
// Remove closed receivers (should be rare in Tokio)
|
||||
if !closed_rxs.is_empty() {
|
||||
let mut receivers = Vec::with_capacity(self.receivers.len() - closed_rxs.len());
|
||||
for (rx_idx, rx) in self.receivers.drain(..).enumerate() {
|
||||
if !closed_rxs.contains(&rx_idx) {
|
||||
receivers.push(rx);
|
||||
}
|
||||
}
|
||||
self.receivers = receivers;
|
||||
}
|
||||
|
||||
// Send batched events
|
||||
if !self.subscribers.is_empty() {
|
||||
self.subscribers
|
||||
.retain_mut(|subscriber| subscriber.send_batch().is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send remaining events
|
||||
for mut subscriber in self.subscribers.drain(..) {
|
||||
let _ = subscriber.send_batch();
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self) -> bool {
|
||||
for update in COLLECTOR_UPDATES.lock().drain(..) {
|
||||
match update {
|
||||
Update::RegisterReceiver { receiver } => {
|
||||
self.receivers.push(receiver);
|
||||
}
|
||||
Update::RegisterSubscriber { subscriber } => {
|
||||
ACTIVE_SUBSCRIBERS.lock().push(subscriber.id.clone());
|
||||
self.subscribers.push(subscriber);
|
||||
}
|
||||
Update::UnregisterSubscriber { id } => {
|
||||
ACTIVE_SUBSCRIBERS.lock().retain(|s| s != &id);
|
||||
self.subscribers.retain(|s| s.id != id);
|
||||
}
|
||||
Update::UpdateSubscriber {
|
||||
id,
|
||||
interests,
|
||||
lossy,
|
||||
} => {
|
||||
for subscriber in self.subscribers.iter_mut() {
|
||||
if subscriber.id == id {
|
||||
subscriber.interests = interests;
|
||||
subscriber.lossy = lossy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Update::UpdateLevels { levels } => {
|
||||
for event in EVENT_TYPES.iter() {
|
||||
let event_id = event.to_id() as usize;
|
||||
if let Some(level) = levels.get(event) {
|
||||
self.levels[event_id] = *level;
|
||||
} else {
|
||||
self.levels[event_id] = event.level();
|
||||
}
|
||||
}
|
||||
}
|
||||
Update::Shutdown => return false,
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_interests(mut interests: Interests) {
|
||||
if !interests.is_empty() {
|
||||
for event_type in EVENT_TYPES.iter() {
|
||||
if event_type.is_span_start() || event_type.is_span_end() {
|
||||
interests.set(*event_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TRACE_INTERESTS.update(interests);
|
||||
}
|
||||
|
||||
pub fn union_interests(interests: Interests) {
|
||||
TRACE_INTERESTS.union(interests);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn has_interest(event: impl Into<usize>) -> bool {
|
||||
TRACE_INTERESTS.get(event)
|
||||
}
|
||||
|
||||
pub fn get_subscribers() -> Vec<String> {
|
||||
ACTIVE_SUBSCRIBERS.lock().clone()
|
||||
}
|
||||
|
||||
pub fn update_custom_levels(levels: AHashMap<EventType, Level>) {
|
||||
COLLECTOR_UPDATES
|
||||
.lock()
|
||||
.push(Update::UpdateLevels { levels });
|
||||
}
|
||||
|
||||
pub fn update_subscriber(id: String, interests: Interests, lossy: bool) {
|
||||
COLLECTOR_UPDATES.lock().push(Update::UpdateSubscriber {
|
||||
id,
|
||||
interests,
|
||||
lossy,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn remove_subscriber(id: String) {
|
||||
COLLECTOR_UPDATES
|
||||
.lock()
|
||||
.push(Update::UnregisterSubscriber { id });
|
||||
}
|
||||
|
||||
pub fn shutdown() {
|
||||
COLLECTOR_UPDATES.lock().push(Update::Shutdown);
|
||||
Collector::reload();
|
||||
}
|
||||
|
||||
pub fn is_enabled() -> bool {
|
||||
!TRACE_INTERESTS.is_empty()
|
||||
}
|
||||
|
||||
pub fn reload() {
|
||||
CHANNEL_FLAGS.fetch_or(CHANNEL_UPDATE_MARKER, Ordering::Relaxed);
|
||||
COLLECTOR_THREAD.thread().unpark();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Collector {
|
||||
fn default() -> Self {
|
||||
let mut c = Collector {
|
||||
subscribers: Vec::new(),
|
||||
levels: [Level::Disable; TOTAL_EVENT_COUNT],
|
||||
active_spans: AHashMap::new(),
|
||||
receivers: Vec::new(),
|
||||
};
|
||||
|
||||
for event in EVENT_TYPES.iter() {
|
||||
let event_id = event.to_id() as usize;
|
||||
c.levels[event_id] = event.level();
|
||||
}
|
||||
|
||||
c
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use atomics::{array::AtomicU32Array, gauge::AtomicGauge, histogram::AtomicHistogram};
|
||||
use ipc::{
|
||||
collector::{Collector, GlobalInterests},
|
||||
subscriber::Interests,
|
||||
};
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub(crate) static METRIC_INTERESTS: GlobalInterests = GlobalInterests::new();
|
||||
|
||||
static EVENT_COUNTERS: AtomicU32Array<TOTAL_EVENT_COUNT> = AtomicU32Array::new();
|
||||
static CONNECTION_METRICS: [ConnectionMetrics; TOTAL_CONN_TYPES] = init_conn_metrics();
|
||||
|
||||
static MESSAGE_INGESTION_TIME: AtomicHistogram<12> =
|
||||
AtomicHistogram::<10>::new_short_durations(MetricType::MessageIngestTime);
|
||||
static MESSAGE_INDEX_TIME: AtomicHistogram<12> =
|
||||
AtomicHistogram::<10>::new_short_durations(MetricType::MessageIngestIndexTime);
|
||||
static MESSAGE_DELIVERY_TIME: AtomicHistogram<12> =
|
||||
AtomicHistogram::<18>::new_long_durations(MetricType::DeliveryTotalTime);
|
||||
|
||||
static MESSAGE_INCOMING_SIZE: AtomicHistogram<12> =
|
||||
AtomicHistogram::<12>::new_message_sizes(MetricType::MessageSize);
|
||||
static MESSAGE_SUBMISSION_SIZE: AtomicHistogram<12> =
|
||||
AtomicHistogram::<12>::new_message_sizes(MetricType::MessageAuthenticatedSize);
|
||||
static MESSAGE_OUT_REPORT_SIZE: AtomicHistogram<12> =
|
||||
AtomicHistogram::<12>::new_message_sizes(MetricType::OutgoingReportSize);
|
||||
|
||||
static STORE_DATA_READ_TIME: AtomicHistogram<12> =
|
||||
AtomicHistogram::<10>::new_short_durations(MetricType::StoreDataReadTime);
|
||||
static STORE_DATA_WRITE_TIME: AtomicHistogram<12> =
|
||||
AtomicHistogram::<10>::new_short_durations(MetricType::StoreDataWriteTime);
|
||||
static STORE_BLOB_READ_TIME: AtomicHistogram<12> =
|
||||
AtomicHistogram::<10>::new_short_durations(MetricType::StoreBlobReadTime);
|
||||
static STORE_BLOB_WRITE_TIME: AtomicHistogram<12> =
|
||||
AtomicHistogram::<10>::new_short_durations(MetricType::StoreBlobWriteTime);
|
||||
|
||||
static DNS_LOOKUP_TIME: AtomicHistogram<12> =
|
||||
AtomicHistogram::<10>::new_short_durations(MetricType::DnsLookupTime);
|
||||
|
||||
static SERVER_MEMORY: AtomicGauge = AtomicGauge::new(MetricType::ServerMemory);
|
||||
static QUEUE_COUNT: AtomicGauge = AtomicGauge::new(MetricType::QueueCount);
|
||||
static USER_COUNT: AtomicGauge = AtomicGauge::new(MetricType::UserCount);
|
||||
static DOMAIN_COUNT: AtomicGauge = AtomicGauge::new(MetricType::DomainCount);
|
||||
|
||||
const CONN_SMTP_IN: usize = 0;
|
||||
const CONN_SMTP_OUT: usize = 1;
|
||||
const CONN_IMAP: usize = 2;
|
||||
const CONN_POP3: usize = 3;
|
||||
const CONN_HTTP: usize = 4;
|
||||
const CONN_SIEVE: usize = 5;
|
||||
const TOTAL_CONN_TYPES: usize = 6;
|
||||
|
||||
pub struct ConnectionMetrics {
|
||||
pub active_connections: AtomicGauge,
|
||||
pub elapsed: AtomicHistogram<12>,
|
||||
}
|
||||
|
||||
pub struct EventCounter {
|
||||
id: EventType,
|
||||
value: u32,
|
||||
}
|
||||
|
||||
impl Collector {
|
||||
pub fn record_metric(event: EventType, event_id: usize, keys: &[(Key, Value)]) {
|
||||
// Increment the event counter
|
||||
if !event.is_span_end() && !event.is_raw_io() {
|
||||
EVENT_COUNTERS.add(event_id, 1);
|
||||
}
|
||||
|
||||
// Extract variables
|
||||
let mut elapsed = 0;
|
||||
let mut size = 0;
|
||||
for (key, value) in keys {
|
||||
match (key, value) {
|
||||
(Key::Elapsed, Value::Duration(d)) => elapsed = *d,
|
||||
(Key::Size, Value::UInt(s)) => size = *s,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match event {
|
||||
EventType::Smtp(SmtpEvent::ConnectionStart) => {
|
||||
let conn = &CONNECTION_METRICS[CONN_SMTP_IN];
|
||||
conn.active_connections.increment();
|
||||
}
|
||||
EventType::Smtp(SmtpEvent::ConnectionEnd) => {
|
||||
let conn = &CONNECTION_METRICS[CONN_SMTP_IN];
|
||||
conn.active_connections.decrement();
|
||||
conn.elapsed.observe(elapsed);
|
||||
}
|
||||
EventType::Imap(ImapEvent::ConnectionStart) => {
|
||||
let conn = &CONNECTION_METRICS[CONN_IMAP];
|
||||
conn.active_connections.increment();
|
||||
}
|
||||
EventType::Imap(ImapEvent::ConnectionEnd) => {
|
||||
let conn = &CONNECTION_METRICS[CONN_IMAP];
|
||||
conn.active_connections.decrement();
|
||||
conn.elapsed.observe(elapsed);
|
||||
}
|
||||
EventType::Pop3(Pop3Event::ConnectionStart) => {
|
||||
let conn = &CONNECTION_METRICS[CONN_POP3];
|
||||
conn.active_connections.increment();
|
||||
}
|
||||
EventType::Pop3(Pop3Event::ConnectionEnd) => {
|
||||
let conn = &CONNECTION_METRICS[CONN_POP3];
|
||||
conn.active_connections.decrement();
|
||||
conn.elapsed.observe(elapsed);
|
||||
}
|
||||
EventType::Http(HttpEvent::ConnectionStart) => {
|
||||
let conn = &CONNECTION_METRICS[CONN_HTTP];
|
||||
conn.active_connections.increment();
|
||||
}
|
||||
EventType::Http(HttpEvent::ConnectionEnd) => {
|
||||
let conn = &CONNECTION_METRICS[CONN_HTTP];
|
||||
conn.active_connections.decrement();
|
||||
conn.elapsed.observe(elapsed);
|
||||
}
|
||||
EventType::ManageSieve(ManageSieveEvent::ConnectionStart) => {
|
||||
let conn = &CONNECTION_METRICS[CONN_SIEVE];
|
||||
conn.active_connections.increment();
|
||||
}
|
||||
EventType::ManageSieve(ManageSieveEvent::ConnectionEnd) => {
|
||||
let conn = &CONNECTION_METRICS[CONN_SIEVE];
|
||||
conn.active_connections.decrement();
|
||||
conn.elapsed.observe(elapsed);
|
||||
}
|
||||
EventType::Delivery(DeliveryEvent::AttemptStart) => {
|
||||
let conn = &CONNECTION_METRICS[CONN_SMTP_OUT];
|
||||
conn.active_connections.increment();
|
||||
}
|
||||
EventType::Delivery(DeliveryEvent::AttemptEnd) => {
|
||||
let conn = &CONNECTION_METRICS[CONN_SMTP_OUT];
|
||||
conn.active_connections.decrement();
|
||||
conn.elapsed.observe(elapsed);
|
||||
}
|
||||
EventType::Delivery(DeliveryEvent::Completed) => {
|
||||
QUEUE_COUNT.decrement();
|
||||
MESSAGE_DELIVERY_TIME.observe(elapsed);
|
||||
}
|
||||
EventType::Delivery(
|
||||
DeliveryEvent::MxLookup | DeliveryEvent::IpLookup | DeliveryEvent::NullMx,
|
||||
)
|
||||
| EventType::TlsRpt(_)
|
||||
| EventType::MtaSts(_)
|
||||
| EventType::Dane(_)
|
||||
if elapsed > 0 =>
|
||||
{
|
||||
DNS_LOOKUP_TIME.observe(elapsed);
|
||||
}
|
||||
EventType::MessageIngest(
|
||||
MessageIngestEvent::Ham
|
||||
| MessageIngestEvent::Spam
|
||||
| MessageIngestEvent::ImapAppend
|
||||
| MessageIngestEvent::JmapAppend,
|
||||
) => {
|
||||
MESSAGE_INGESTION_TIME.observe(elapsed);
|
||||
}
|
||||
EventType::Queue(QueueEvent::MessageQueued) => {
|
||||
MESSAGE_INCOMING_SIZE.observe(size);
|
||||
QUEUE_COUNT.increment();
|
||||
}
|
||||
EventType::Queue(QueueEvent::AuthenticatedMessageQueued) => {
|
||||
MESSAGE_SUBMISSION_SIZE.observe(size);
|
||||
QUEUE_COUNT.increment();
|
||||
}
|
||||
EventType::Queue(QueueEvent::ReportQueued) => {
|
||||
MESSAGE_OUT_REPORT_SIZE.observe(size);
|
||||
QUEUE_COUNT.increment();
|
||||
}
|
||||
EventType::Queue(QueueEvent::AutogeneratedQueued | QueueEvent::DsnQueued) => {
|
||||
QUEUE_COUNT.increment();
|
||||
}
|
||||
EventType::MessageIngest(MessageIngestEvent::SearchIndex) => {
|
||||
MESSAGE_INDEX_TIME.observe(elapsed);
|
||||
}
|
||||
EventType::Store(StoreEvent::BlobWrite) => {
|
||||
STORE_BLOB_WRITE_TIME.observe(elapsed);
|
||||
}
|
||||
EventType::Store(StoreEvent::BlobRead) => {
|
||||
STORE_BLOB_READ_TIME.observe(elapsed);
|
||||
}
|
||||
EventType::Store(StoreEvent::DataWrite) => {
|
||||
STORE_DATA_WRITE_TIME.observe(elapsed);
|
||||
}
|
||||
EventType::Store(StoreEvent::DataIterate) => {
|
||||
STORE_DATA_READ_TIME.observe(elapsed);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_metric(event: impl Into<usize>) -> bool {
|
||||
METRIC_INTERESTS.get(event)
|
||||
}
|
||||
|
||||
pub fn set_metrics(interests: Interests) {
|
||||
METRIC_INTERESTS.update(interests);
|
||||
}
|
||||
|
||||
pub fn collect_counters(_is_enterprise: bool) -> impl Iterator<Item = EventCounter> {
|
||||
EVENT_COUNTERS
|
||||
.inner()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(event_id, value)| {
|
||||
let value = value.load(Ordering::Relaxed);
|
||||
if value > 0 {
|
||||
Some(EventCounter {
|
||||
id: EventType::from_id(event_id as u16)?,
|
||||
value,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn collect_gauges(is_enterprise: bool) -> impl Iterator<Item = &'static AtomicGauge> {
|
||||
static E_GAUGES: &[&AtomicGauge] =
|
||||
&[&SERVER_MEMORY, &QUEUE_COUNT, &USER_COUNT, &DOMAIN_COUNT];
|
||||
static C_GAUGES: &[&AtomicGauge] = &[&SERVER_MEMORY, &USER_COUNT, &DOMAIN_COUNT];
|
||||
|
||||
if is_enterprise { E_GAUGES } else { C_GAUGES }
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(CONNECTION_METRICS.iter().map(|m| &m.active_connections))
|
||||
}
|
||||
|
||||
pub fn collect_histograms(
|
||||
is_enterprise: bool,
|
||||
) -> impl Iterator<Item = &'static AtomicHistogram<12>> {
|
||||
static E_HISTOGRAMS: &[&AtomicHistogram<12>] = &[
|
||||
&MESSAGE_INGESTION_TIME,
|
||||
&MESSAGE_INDEX_TIME,
|
||||
&MESSAGE_DELIVERY_TIME,
|
||||
&MESSAGE_INCOMING_SIZE,
|
||||
&MESSAGE_SUBMISSION_SIZE,
|
||||
&MESSAGE_OUT_REPORT_SIZE,
|
||||
&STORE_DATA_READ_TIME,
|
||||
&STORE_DATA_WRITE_TIME,
|
||||
&STORE_BLOB_READ_TIME,
|
||||
&STORE_BLOB_WRITE_TIME,
|
||||
&DNS_LOOKUP_TIME,
|
||||
];
|
||||
static C_HISTOGRAMS: &[&AtomicHistogram<12>] = &[
|
||||
&MESSAGE_DELIVERY_TIME,
|
||||
&MESSAGE_INCOMING_SIZE,
|
||||
&MESSAGE_SUBMISSION_SIZE,
|
||||
];
|
||||
|
||||
if is_enterprise {
|
||||
E_HISTOGRAMS
|
||||
} else {
|
||||
C_HISTOGRAMS
|
||||
}
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(CONNECTION_METRICS.iter().map(|m| &m.elapsed))
|
||||
.filter(|h| h.is_active())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_metric_counter(metric_id: usize) -> u32 {
|
||||
EVENT_COUNTERS.get(metric_id)
|
||||
}
|
||||
|
||||
pub fn read_metric(metric_type: MetricType) -> f64 {
|
||||
match metric_type {
|
||||
MetricType::ServerMemory => SERVER_MEMORY.get() as f64,
|
||||
MetricType::MessageIngestTime => MESSAGE_INGESTION_TIME.average(),
|
||||
MetricType::MessageIngestIndexTime => MESSAGE_INDEX_TIME.average(),
|
||||
MetricType::MessageSize => MESSAGE_INCOMING_SIZE.average(),
|
||||
MetricType::MessageAuthenticatedSize => MESSAGE_SUBMISSION_SIZE.average(),
|
||||
MetricType::DeliveryTotalTime => MESSAGE_DELIVERY_TIME.average(),
|
||||
MetricType::DeliveryAttemptTime => CONNECTION_METRICS[CONN_SMTP_OUT].elapsed.average(),
|
||||
MetricType::DeliveryActiveConnections => {
|
||||
CONNECTION_METRICS[CONN_SMTP_OUT].active_connections.get() as f64
|
||||
}
|
||||
MetricType::QueueCount => QUEUE_COUNT.get() as f64,
|
||||
MetricType::OutgoingReportSize => MESSAGE_OUT_REPORT_SIZE.average(),
|
||||
MetricType::StoreDataReadTime => STORE_DATA_READ_TIME.average(),
|
||||
MetricType::StoreDataWriteTime => STORE_DATA_WRITE_TIME.average(),
|
||||
MetricType::StoreBlobReadTime => STORE_BLOB_READ_TIME.average(),
|
||||
MetricType::StoreBlobWriteTime => STORE_BLOB_WRITE_TIME.average(),
|
||||
MetricType::DnsLookupTime => DNS_LOOKUP_TIME.average(),
|
||||
MetricType::HttpActiveConnections => {
|
||||
CONNECTION_METRICS[CONN_HTTP].active_connections.get() as f64
|
||||
}
|
||||
MetricType::HttpRequestTime => CONNECTION_METRICS[CONN_HTTP].elapsed.average(),
|
||||
MetricType::ImapActiveConnections => {
|
||||
CONNECTION_METRICS[CONN_IMAP].active_connections.get() as f64
|
||||
}
|
||||
MetricType::ImapRequestTime => CONNECTION_METRICS[CONN_IMAP].elapsed.average(),
|
||||
MetricType::Pop3ActiveConnections => {
|
||||
CONNECTION_METRICS[CONN_POP3].active_connections.get() as f64
|
||||
}
|
||||
MetricType::Pop3RequestTime => CONNECTION_METRICS[CONN_POP3].elapsed.average(),
|
||||
MetricType::SmtpActiveConnections => {
|
||||
CONNECTION_METRICS[CONN_SMTP_IN].active_connections.get() as f64
|
||||
}
|
||||
MetricType::SmtpRequestTime => CONNECTION_METRICS[CONN_SMTP_IN].elapsed.average(),
|
||||
MetricType::SieveActiveConnections => {
|
||||
CONNECTION_METRICS[CONN_SIEVE].active_connections.get() as f64
|
||||
}
|
||||
MetricType::SieveRequestTime => CONNECTION_METRICS[CONN_SIEVE].elapsed.average(),
|
||||
MetricType::UserCount => USER_COUNT.get() as f64,
|
||||
MetricType::DomainCount => DOMAIN_COUNT.get() as f64,
|
||||
_ => EVENT_COUNTERS.get(metric_type.event_id()) as f64,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_gauge(metric_type: MetricType, value: u64) {
|
||||
match metric_type {
|
||||
MetricType::ServerMemory => SERVER_MEMORY.set(value),
|
||||
MetricType::QueueCount => QUEUE_COUNT.set(value),
|
||||
MetricType::UserCount => USER_COUNT.set(value),
|
||||
MetricType::DomainCount => DOMAIN_COUNT.set(value),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_event_counter(event_type: EventType, value: u32) {
|
||||
EVENT_COUNTERS.add(event_type.into(), value);
|
||||
}
|
||||
|
||||
pub fn update_histogram(metric_type: MetricType, value: u64) {
|
||||
match metric_type {
|
||||
MetricType::MessageIngestTime => MESSAGE_INGESTION_TIME.observe(value),
|
||||
MetricType::MessageIngestIndexTime => MESSAGE_INDEX_TIME.observe(value),
|
||||
MetricType::DeliveryTotalTime => MESSAGE_DELIVERY_TIME.observe(value),
|
||||
MetricType::DeliveryAttemptTime => {
|
||||
CONNECTION_METRICS[CONN_SMTP_OUT].elapsed.observe(value)
|
||||
}
|
||||
MetricType::DnsLookupTime => DNS_LOOKUP_TIME.observe(value),
|
||||
MetricType::StoreDataReadTime => STORE_DATA_READ_TIME.observe(value),
|
||||
MetricType::StoreDataWriteTime => STORE_DATA_WRITE_TIME.observe(value),
|
||||
MetricType::StoreBlobReadTime => STORE_BLOB_READ_TIME.observe(value),
|
||||
MetricType::StoreBlobWriteTime => STORE_BLOB_WRITE_TIME.observe(value),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventCounter {
|
||||
pub fn id(&self) -> EventType {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn value(&self) -> u64 {
|
||||
self.value as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionMetrics {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
active_connections: AtomicGauge::new(MetricType::StoreBlobReadTime),
|
||||
elapsed: AtomicHistogram::<18>::new_medium_durations(MetricType::StoreBlobReadTime),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::declare_interior_mutable_const)]
|
||||
const fn init_conn_metrics() -> [ConnectionMetrics; TOTAL_CONN_TYPES] {
|
||||
const INIT: ConnectionMetrics = ConnectionMetrics::new();
|
||||
let mut array = [INIT; TOTAL_CONN_TYPES];
|
||||
let mut i = 0;
|
||||
while i < TOTAL_CONN_TYPES {
|
||||
let metric = match i {
|
||||
CONN_HTTP => &[
|
||||
MetricType::HttpRequestTime,
|
||||
MetricType::HttpActiveConnections,
|
||||
],
|
||||
CONN_IMAP => &[
|
||||
MetricType::ImapRequestTime,
|
||||
MetricType::ImapActiveConnections,
|
||||
],
|
||||
CONN_POP3 => &[
|
||||
MetricType::Pop3RequestTime,
|
||||
MetricType::Pop3ActiveConnections,
|
||||
],
|
||||
CONN_SMTP_IN => &[
|
||||
MetricType::SmtpRequestTime,
|
||||
MetricType::SmtpActiveConnections,
|
||||
],
|
||||
CONN_SMTP_OUT => &[
|
||||
MetricType::DeliveryAttemptTime,
|
||||
MetricType::DeliveryActiveConnections,
|
||||
],
|
||||
CONN_SIEVE => &[
|
||||
MetricType::SieveRequestTime,
|
||||
MetricType::SieveActiveConnections,
|
||||
],
|
||||
_ => &[MetricType::StoreBlobReadTime, MetricType::StoreBlobReadTime],
|
||||
};
|
||||
|
||||
array[i] = ConnectionMetrics {
|
||||
elapsed: AtomicHistogram::<18>::new_medium_durations(metric[0]),
|
||||
active_connections: AtomicGauge::new(metric[1]),
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
array
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod bitset;
|
||||
pub mod channel;
|
||||
pub mod collector;
|
||||
pub mod metrics;
|
||||
pub mod subscriber;
|
||||
|
||||
pub(crate) const USIZE_BITS: usize = std::mem::size_of::<usize>() * 8;
|
||||
pub(crate) const USIZE_BITS_MASK: usize = USIZE_BITS - 1;
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::mpsc::{self, error::TrySendError};
|
||||
|
||||
use crate::{Event, EventDetails, EventType, Level, TOTAL_EVENT_COUNT};
|
||||
|
||||
use super::{
|
||||
USIZE_BITS,
|
||||
bitset::Bitset,
|
||||
channel::ChannelError,
|
||||
collector::{COLLECTOR_UPDATES, Collector, Update},
|
||||
};
|
||||
|
||||
const MAX_BATCH_SIZE: usize = 32768;
|
||||
|
||||
pub type Interests = Box<Bitset<{ TOTAL_EVENT_COUNT.div_ceil(USIZE_BITS) }>>;
|
||||
pub type EventBatch = Vec<Arc<Event<EventDetails>>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Subscriber {
|
||||
pub id: String,
|
||||
pub interests: Interests,
|
||||
pub tx: mpsc::Sender<EventBatch>,
|
||||
pub lossy: bool,
|
||||
pub batch: EventBatch,
|
||||
}
|
||||
|
||||
pub struct SubscriberBuilder {
|
||||
pub id: String,
|
||||
pub interests: Interests,
|
||||
pub lossy: bool,
|
||||
}
|
||||
|
||||
impl Subscriber {
|
||||
#[inline(always)]
|
||||
pub fn push_event(&mut self, event_id: usize, trace: Arc<Event<EventDetails>>) {
|
||||
if self.interests.get(event_id) {
|
||||
self.batch.push(trace);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_batch(&mut self) -> Result<(), ChannelError> {
|
||||
if !self.batch.is_empty() {
|
||||
match self
|
||||
.tx
|
||||
.try_send(std::mem::replace(&mut self.batch, Vec::with_capacity(128)))
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(TrySendError::Full(mut events)) => {
|
||||
if self.lossy && events.len() > MAX_BATCH_SIZE {
|
||||
events.retain(|e| e.inner.level == Level::Error);
|
||||
if events.len() > MAX_BATCH_SIZE {
|
||||
events.truncate(MAX_BATCH_SIZE);
|
||||
}
|
||||
}
|
||||
self.batch = events;
|
||||
Ok(())
|
||||
}
|
||||
Err(TrySendError::Closed(_)) => Err(ChannelError),
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubscriberBuilder {
|
||||
pub fn new(id: String) -> Self {
|
||||
Self {
|
||||
id,
|
||||
interests: Default::default(),
|
||||
lossy: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_default_interests(mut self, level: Level) -> Self {
|
||||
for event in EventType::variants() {
|
||||
if event.level() >= level {
|
||||
self.interests.set(*event);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_interests(mut self, interests: Interests) -> Self {
|
||||
self.interests = interests;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_interests(mut self, interest: impl IntoIterator<Item = impl Into<usize>>) -> Self {
|
||||
for level in interest {
|
||||
self.interests.set(level);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_lossy(mut self, lossy: bool) -> Self {
|
||||
self.lossy = lossy;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn register(self) -> (mpsc::Sender<EventBatch>, mpsc::Receiver<EventBatch>) {
|
||||
let (tx, rx) = mpsc::channel(8192);
|
||||
|
||||
COLLECTOR_UPDATES.lock().push(Update::RegisterSubscriber {
|
||||
subscriber: Subscriber {
|
||||
id: self.id,
|
||||
interests: self.interests,
|
||||
tx: tx.clone(),
|
||||
lossy: self.lossy,
|
||||
batch: Vec::new(),
|
||||
},
|
||||
});
|
||||
|
||||
// Notify collector
|
||||
Collector::reload();
|
||||
|
||||
(tx, rx)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user