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:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+88
View File
@@ -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()
}
}
+121
View File
@@ -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();
}
}
+360
View File
@@ -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
}
}
+415
View File
@@ -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
}
+14
View File
@@ -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;
+126
View File
@@ -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)
}
}