JMAP Registry API implementation - part 4
This commit is contained in:
33
crates/common/src/cache/principals.rs
vendored
33
crates/common/src/cache/principals.rs
vendored
@@ -27,15 +27,13 @@ use registry::{
|
||||
Role, SubAddressing, Tenant,
|
||||
},
|
||||
},
|
||||
types::{
|
||||
id::ObjectId,
|
||||
index::{IndexKey, IndexValue},
|
||||
},
|
||||
types::{EnumImpl, id::ObjectId},
|
||||
};
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
use store::{
|
||||
U64_LEN, ValueKey,
|
||||
registry::{RegistryQuery, bootstrap::Bootstrap},
|
||||
write::{RegistryClass, now},
|
||||
write::{RegistryClass, ValueClass, key::KeySerializer, now},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use types::id::Id;
|
||||
@@ -161,19 +159,20 @@ impl Server {
|
||||
.get(&EmailAddressRef::new(local_part, domain_id))
|
||||
.is_none()
|
||||
{
|
||||
let key = IndexKey::Global {
|
||||
property: Property::Email,
|
||||
value_1: IndexValue::Text(local_part.into()),
|
||||
value_2: IndexValue::U64(domain_id.into()),
|
||||
};
|
||||
let key = ValueKey::from(ValueClass::Registry(RegistryClass::PrimaryKey {
|
||||
object_id: None,
|
||||
index_id: Property::Email.to_id(),
|
||||
key: KeySerializer::new(local_part.len() + U64_LEN)
|
||||
.write(local_part.as_bytes())
|
||||
.write(domain_id as u64)
|
||||
.finalize(),
|
||||
}));
|
||||
|
||||
if let Some(object) = self
|
||||
.registry()
|
||||
.validate_primary_key(
|
||||
RegistryClass::from_index_key(&key, 0, 0),
|
||||
RegistryClass::from_index_key(&key, u16::MAX, u64::MAX),
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.store()
|
||||
.get_value::<ObjectId>(key)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
let item_id = object.id().document_id();
|
||||
let result = match object.object() {
|
||||
|
||||
@@ -508,7 +508,7 @@ impl Tracers {
|
||||
.copied()
|
||||
.unwrap_or(event_type.level());
|
||||
if Level::Info.is_contained(event_level) {
|
||||
global_interests.set(event_type);
|
||||
global_interests.set(event_type.to_id() as usize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -154,6 +154,7 @@ pub struct DmarcEvent {
|
||||
pub report_record: Record,
|
||||
pub dmarc_record: Arc<Dmarc>,
|
||||
pub interval: AggregateFrequency,
|
||||
pub span_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -163,6 +164,7 @@ pub struct TlsEvent {
|
||||
pub failure: Option<FailureDetails>,
|
||||
pub tls_record: Arc<TlsRpt>,
|
||||
pub interval: AggregateFrequency,
|
||||
pub span_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
|
||||
@@ -316,7 +316,7 @@ impl Family {
|
||||
Family::Registry => &[
|
||||
SUBSPACE_REGISTRY,
|
||||
SUBSPACE_REGISTRY_IDX,
|
||||
SUBSPACE_REGISTRY_IDX_GLOBAL,
|
||||
SUBSPACE_REGISTRY_PK,
|
||||
SUBSPACE_DIRECTORY,
|
||||
],
|
||||
Family::Changelog => &[SUBSPACE_LOGS],
|
||||
|
||||
@@ -9,10 +9,7 @@ use base64::engine::general_purpose;
|
||||
use std::env;
|
||||
use std::io::{self, Write};
|
||||
use store::write::{AnyClass, AnyKey, BatchBuilder, ValueClass};
|
||||
use store::{
|
||||
Deserialize, IterateParams, SUBSPACE_INDEXES, SUBSPACE_REGISTRY_IDX,
|
||||
SUBSPACE_REGISTRY_IDX_GLOBAL, Store,
|
||||
};
|
||||
use store::{Deserialize, IterateParams, SUBSPACE_INDEXES, SUBSPACE_REGISTRY_IDX, Store};
|
||||
|
||||
const HELP: &str = concat!(
|
||||
"Stalwart Server v",
|
||||
@@ -76,12 +73,7 @@ pub async fn store_console(store: Store) {
|
||||
},
|
||||
)
|
||||
.set_values(
|
||||
![
|
||||
SUBSPACE_INDEXES,
|
||||
SUBSPACE_REGISTRY_IDX,
|
||||
SUBSPACE_REGISTRY_IDX_GLOBAL,
|
||||
]
|
||||
.contains(&from_subspace),
|
||||
![SUBSPACE_INDEXES, SUBSPACE_REGISTRY_IDX].contains(&from_subspace),
|
||||
),
|
||||
|key, value| {
|
||||
print!("{}", char::from(from_subspace));
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::{
|
||||
};
|
||||
use store::{
|
||||
BlobStore, SUBSPACE_BLOBS, SUBSPACE_COUNTER, SUBSPACE_INDEXES, SUBSPACE_QUOTA,
|
||||
SUBSPACE_REGISTRY_IDX, SUBSPACE_REGISTRY_IDX_GLOBAL, Store, U32_LEN,
|
||||
SUBSPACE_REGISTRY_IDX, Store, U32_LEN,
|
||||
write::{AnyClass, BatchBuilder, ValueClass, key::DeserializeBigEndian},
|
||||
};
|
||||
use types::{collection::Collection, field::Field};
|
||||
@@ -85,7 +85,7 @@ async fn restore_file(store: Store, blob_store: BlobStore, path: &Path) {
|
||||
}
|
||||
}
|
||||
}
|
||||
SUBSPACE_INDEXES | SUBSPACE_REGISTRY_IDX | SUBSPACE_REGISTRY_IDX_GLOBAL => {
|
||||
SUBSPACE_INDEXES | SUBSPACE_REGISTRY_IDX => {
|
||||
while let Some((key, _)) = reader.next() {
|
||||
let account_id = key
|
||||
.as_slice()
|
||||
|
||||
@@ -8,40 +8,32 @@
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::Core;
|
||||
use ahash::AHashMap;
|
||||
use parking_lot::Mutex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use registry::{
|
||||
pickle::Pickle,
|
||||
schema::structs::{Metric, MetricCount, MetricSum},
|
||||
};
|
||||
use std::{future::Future, sync::Arc, time::Duration};
|
||||
use store::{
|
||||
IterateParams, Store, U32_LEN, U64_LEN, ValueKey,
|
||||
write::{
|
||||
BatchBuilder, TelemetryClass, ValueClass,
|
||||
key::{DeserializeBigEndian, KeySerializer},
|
||||
now,
|
||||
},
|
||||
Store, ValueKey,
|
||||
write::{BatchBuilder, TelemetryClass, ValueClass},
|
||||
};
|
||||
use trc::*;
|
||||
use utils::codec::leb128::Leb128Reader;
|
||||
use utils::snowflake::SnowflakeIdGenerator;
|
||||
|
||||
pub trait MetricsStore: Sync + Send {
|
||||
fn write_metrics(
|
||||
&self,
|
||||
core: Arc<Core>,
|
||||
timestamp: u64,
|
||||
timestamp: Option<u64>,
|
||||
history: SharedMetricHistory,
|
||||
) -> impl Future<Output = trc::Result<()>> + Send;
|
||||
fn query_metrics(
|
||||
&self,
|
||||
from_timestamp: u64,
|
||||
to_timestamp: u64,
|
||||
) -> impl Future<Output = trc::Result<Vec<Metric<MetricType, MetricType, u64>>>> + Send;
|
||||
fn purge_metrics(&self, period: Duration) -> impl Future<Output = trc::Result<()>> + Send;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MetricsHistory {
|
||||
events: AHashMap<EventType, u32>,
|
||||
events: AHashMap<MetricType, u32>,
|
||||
histograms: AHashMap<MetricType, HistogramHistory>,
|
||||
}
|
||||
|
||||
@@ -51,81 +43,71 @@ struct HistogramHistory {
|
||||
count: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Metric<CI, MI, T> {
|
||||
Counter {
|
||||
id: CI,
|
||||
timestamp: T,
|
||||
value: u64,
|
||||
},
|
||||
Gauge {
|
||||
id: MI,
|
||||
timestamp: T,
|
||||
value: u64,
|
||||
},
|
||||
Histogram {
|
||||
id: MI,
|
||||
timestamp: T,
|
||||
count: u64,
|
||||
sum: u64,
|
||||
},
|
||||
}
|
||||
|
||||
pub type SharedMetricHistory = Arc<Mutex<MetricsHistory>>;
|
||||
|
||||
const TYPE_COUNTER: u64 = 0x00;
|
||||
const TYPE_HISTOGRAM: u64 = 0x01;
|
||||
const TYPE_GAUGE: u64 = 0x02;
|
||||
|
||||
impl MetricsStore for Store {
|
||||
async fn write_metrics(
|
||||
&self,
|
||||
core: Arc<Core>,
|
||||
timestamp: u64,
|
||||
_timestamp: Option<u64>,
|
||||
history_: SharedMetricHistory,
|
||||
) -> trc::Result<()> {
|
||||
let mut batch = BatchBuilder::new();
|
||||
{
|
||||
let node_id = core.network.node_id;
|
||||
let mut history = history_.lock();
|
||||
for event in [
|
||||
EventType::Smtp(SmtpEvent::ConnectionStart),
|
||||
EventType::Imap(ImapEvent::ConnectionStart),
|
||||
EventType::Pop3(Pop3Event::ConnectionStart),
|
||||
EventType::ManageSieve(ManageSieveEvent::ConnectionStart),
|
||||
EventType::Http(HttpEvent::ConnectionStart),
|
||||
EventType::Delivery(DeliveryEvent::AttemptStart),
|
||||
EventType::Queue(QueueEvent::QueueMessage),
|
||||
EventType::Queue(QueueEvent::QueueMessageAuthenticated),
|
||||
EventType::Queue(QueueEvent::QueueDsn),
|
||||
EventType::Queue(QueueEvent::QueueReport),
|
||||
EventType::MessageIngest(MessageIngestEvent::Ham),
|
||||
EventType::MessageIngest(MessageIngestEvent::Spam),
|
||||
EventType::Auth(AuthEvent::Failed),
|
||||
EventType::Security(SecurityEvent::AuthenticationBan),
|
||||
EventType::Security(SecurityEvent::ScanBan),
|
||||
EventType::Security(SecurityEvent::AbuseBan),
|
||||
EventType::Security(SecurityEvent::LoiterBan),
|
||||
EventType::Security(SecurityEvent::IpBlocked),
|
||||
EventType::IncomingReport(IncomingReportEvent::DmarcReport),
|
||||
EventType::IncomingReport(IncomingReportEvent::DmarcReportWithWarnings),
|
||||
EventType::IncomingReport(IncomingReportEvent::TlsReport),
|
||||
EventType::IncomingReport(IncomingReportEvent::TlsReportWithWarnings),
|
||||
MetricType::SmtpConnectionStart,
|
||||
MetricType::ImapConnectionStart,
|
||||
MetricType::Pop3ConnectionStart,
|
||||
MetricType::ManageSieveConnectionStart,
|
||||
MetricType::HttpConnectionStart,
|
||||
MetricType::DeliveryAttemptStart,
|
||||
MetricType::QueueQueueMessage,
|
||||
MetricType::QueueQueueMessageAuthenticated,
|
||||
MetricType::QueueQueueDsn,
|
||||
MetricType::QueueQueueReport,
|
||||
MetricType::MessageIngestHam,
|
||||
MetricType::MessageIngestSpam,
|
||||
MetricType::AuthFailed,
|
||||
MetricType::SecurityAuthenticationBan,
|
||||
MetricType::SecurityScanBan,
|
||||
MetricType::SecurityAbuseBan,
|
||||
MetricType::SecurityLoiterBan,
|
||||
MetricType::SecurityIpBlocked,
|
||||
MetricType::IncomingReportDmarcReport,
|
||||
MetricType::IncomingReportDmarcReportWithWarnings,
|
||||
MetricType::IncomingReportTlsReport,
|
||||
MetricType::IncomingReportTlsReportWithWarnings,
|
||||
] {
|
||||
let reading = Collector::read_metric_counter(event.to_id() as usize);
|
||||
let reading = Collector::read_metric_counter(event.event_id());
|
||||
if reading > 0 {
|
||||
let history = history.events.entry(event).or_insert(0);
|
||||
let diff = reading - *history;
|
||||
|
||||
#[cfg(not(feature = "test_mode"))]
|
||||
let metric_id = SnowflakeIdGenerator::from_sequence_id(event.to_id() as u64)
|
||||
.unwrap_or_default();
|
||||
|
||||
#[cfg(feature = "test_mode")]
|
||||
let metric_id = _timestamp
|
||||
.map(|timestamp| {
|
||||
SnowflakeIdGenerator::from_timestamp_and_sequence_id(
|
||||
timestamp,
|
||||
event.to_id() as u64,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
SnowflakeIdGenerator::from_sequence_id(event.to_id() as u64)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if diff > 0 {
|
||||
batch.set(
|
||||
ValueClass::Telemetry(TelemetryClass::Metric {
|
||||
timestamp,
|
||||
metric_id: (event.to_id() << 2) as u64 | TYPE_COUNTER,
|
||||
node_id,
|
||||
}),
|
||||
KeySerializer::new(U32_LEN).write_leb128(diff).finalize(),
|
||||
ValueClass::Telemetry(TelemetryClass::Metric(metric_id)),
|
||||
Metric::Counter(MetricCount {
|
||||
count: diff as u64,
|
||||
metric: event,
|
||||
})
|
||||
.to_pickled_vec(),
|
||||
);
|
||||
}
|
||||
*history = reading;
|
||||
@@ -133,48 +115,77 @@ impl MetricsStore for Store {
|
||||
}
|
||||
|
||||
for gauge in Collector::collect_gauges(true) {
|
||||
let gauge_id = gauge.id();
|
||||
if matches!(gauge_id, MetricType::QueueCount | MetricType::ServerMemory) {
|
||||
let metric = gauge.id();
|
||||
if matches!(metric, MetricType::QueueCount | MetricType::ServerMemory) {
|
||||
let value = gauge.get();
|
||||
if value > 0 {
|
||||
#[cfg(not(feature = "test_mode"))]
|
||||
let metric_id =
|
||||
SnowflakeIdGenerator::from_sequence_id(metric.to_id() as u64)
|
||||
.unwrap_or_default();
|
||||
|
||||
#[cfg(feature = "test_mode")]
|
||||
let metric_id = _timestamp
|
||||
.map(|timestamp| {
|
||||
SnowflakeIdGenerator::from_timestamp_and_sequence_id(
|
||||
timestamp,
|
||||
metric.to_id() as u64,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
SnowflakeIdGenerator::from_sequence_id(metric.to_id() as u64)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
batch.set(
|
||||
ValueClass::Telemetry(TelemetryClass::Metric {
|
||||
timestamp,
|
||||
metric_id: (gauge_id.to_id() << 2) as u64 | TYPE_GAUGE,
|
||||
node_id,
|
||||
}),
|
||||
KeySerializer::new(U32_LEN).write_leb128(value).finalize(),
|
||||
ValueClass::Telemetry(TelemetryClass::Metric(metric_id)),
|
||||
Metric::Gauge(MetricCount {
|
||||
count: value,
|
||||
metric,
|
||||
})
|
||||
.to_pickled_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for histogram in Collector::collect_histograms(true) {
|
||||
let histogram_id = histogram.id();
|
||||
let metric = histogram.id();
|
||||
if matches!(
|
||||
histogram_id,
|
||||
metric,
|
||||
MetricType::MessageIngestTime
|
||||
| MetricType::MessageIngestIndexTime
|
||||
| MetricType::DeliveryTotalTime
|
||||
| MetricType::DeliveryAttemptTime
|
||||
| MetricType::DnsLookupTime
|
||||
) {
|
||||
let history = history.histograms.entry(histogram_id).or_default();
|
||||
let history = history.histograms.entry(metric).or_default();
|
||||
let sum = histogram.sum();
|
||||
let count = histogram.count();
|
||||
let diff_sum = sum - history.sum;
|
||||
let diff_count = count - history.count;
|
||||
if diff_sum > 0 || diff_count > 0 {
|
||||
#[cfg(not(feature = "test_mode"))]
|
||||
let metric_id =
|
||||
SnowflakeIdGenerator::from_sequence_id(metric.to_id() as u64)
|
||||
.unwrap_or_default();
|
||||
|
||||
#[cfg(feature = "test_mode")]
|
||||
let metric_id = _timestamp
|
||||
.map(|timestamp| {
|
||||
SnowflakeIdGenerator::from_timestamp_and_sequence_id(
|
||||
timestamp,
|
||||
metric.to_id() as u64,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
SnowflakeIdGenerator::from_sequence_id(metric.to_id() as u64)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
batch.set(
|
||||
ValueClass::Telemetry(TelemetryClass::Metric {
|
||||
timestamp,
|
||||
metric_id: (histogram_id.to_id() << 2) as u64 | TYPE_HISTOGRAM,
|
||||
node_id,
|
||||
}),
|
||||
KeySerializer::new(U32_LEN)
|
||||
.write_leb128(diff_count)
|
||||
.write_leb128(diff_sum)
|
||||
.finalize(),
|
||||
ValueClass::Telemetry(TelemetryClass::Metric(metric_id)),
|
||||
Metric::Histogram(MetricSum { count, metric, sum }).to_pickled_vec(),
|
||||
);
|
||||
}
|
||||
history.sum = sum;
|
||||
@@ -192,105 +203,16 @@ impl MetricsStore for Store {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn query_metrics(
|
||||
&self,
|
||||
from_timestamp: u64,
|
||||
to_timestamp: u64,
|
||||
) -> trc::Result<Vec<Metric<MetricType, MetricType, u64>>> {
|
||||
let mut metrics = Vec::new();
|
||||
self.iterate(
|
||||
IterateParams::new(
|
||||
ValueKey::from(ValueClass::Telemetry(TelemetryClass::Metric {
|
||||
timestamp: from_timestamp,
|
||||
metric_id: 0,
|
||||
node_id: 0,
|
||||
})),
|
||||
ValueKey::from(ValueClass::Telemetry(TelemetryClass::Metric {
|
||||
timestamp: to_timestamp,
|
||||
metric_id: 0,
|
||||
node_id: 0,
|
||||
})),
|
||||
),
|
||||
|key, value| {
|
||||
let timestamp = key.deserialize_be_u64(0).caused_by(trc::location!())?;
|
||||
let (metric_type, _) = key
|
||||
.get(U64_LEN..)
|
||||
.and_then(|bytes| bytes.read_leb128::<u64>())
|
||||
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))?;
|
||||
match metric_type & 0x03 {
|
||||
TYPE_COUNTER => {
|
||||
let id =
|
||||
MetricType::from_id((metric_type >> 2) as u16).ok_or_else(|| {
|
||||
trc::Error::corrupted_key(key, None, trc::location!())
|
||||
})?;
|
||||
let (value, _) = value.read_leb128::<u64>().ok_or_else(|| {
|
||||
trc::Error::corrupted_key(key, value.into(), trc::location!())
|
||||
})?;
|
||||
metrics.push(Metric::Counter {
|
||||
id,
|
||||
timestamp,
|
||||
value,
|
||||
});
|
||||
}
|
||||
TYPE_HISTOGRAM => {
|
||||
let id =
|
||||
MetricType::from_id((metric_type >> 2) as u16).ok_or_else(|| {
|
||||
trc::Error::corrupted_key(key, None, trc::location!())
|
||||
})?;
|
||||
let (count, bytes_read) = value.read_leb128::<u64>().ok_or_else(|| {
|
||||
trc::Error::corrupted_key(key, value.into(), trc::location!())
|
||||
})?;
|
||||
let (sum, _) = value
|
||||
.get(bytes_read..)
|
||||
.and_then(|bytes| bytes.read_leb128::<u64>())
|
||||
.ok_or_else(|| {
|
||||
trc::Error::corrupted_key(key, value.into(), trc::location!())
|
||||
})?;
|
||||
metrics.push(Metric::Histogram {
|
||||
id,
|
||||
timestamp,
|
||||
count,
|
||||
sum,
|
||||
});
|
||||
}
|
||||
TYPE_GAUGE => {
|
||||
let id =
|
||||
MetricType::from_id((metric_type >> 2) as u16).ok_or_else(|| {
|
||||
trc::Error::corrupted_key(key, None, trc::location!())
|
||||
})?;
|
||||
let (value, _) = value.read_leb128::<u64>().ok_or_else(|| {
|
||||
trc::Error::corrupted_key(key, value.into(), trc::location!())
|
||||
})?;
|
||||
metrics.push(Metric::Gauge {
|
||||
id,
|
||||
timestamp,
|
||||
value,
|
||||
});
|
||||
}
|
||||
_ => return Err(trc::Error::corrupted_key(key, None, trc::location!())),
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
Ok(metrics)
|
||||
}
|
||||
|
||||
async fn purge_metrics(&self, period: Duration) -> trc::Result<()> {
|
||||
let until_span_id = SnowflakeIdGenerator::from_duration(period).ok_or_else(|| {
|
||||
trc::StoreEvent::UnexpectedError
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Reason, "Failed to generate reference metric id.")
|
||||
})?;
|
||||
|
||||
self.delete_range(
|
||||
ValueKey::from(ValueClass::Telemetry(TelemetryClass::Metric {
|
||||
timestamp: 0,
|
||||
metric_id: 0,
|
||||
node_id: 0,
|
||||
})),
|
||||
ValueKey::from(ValueClass::Telemetry(TelemetryClass::Metric {
|
||||
timestamp: now() - period.as_secs(),
|
||||
metric_id: 0,
|
||||
node_id: 0,
|
||||
})),
|
||||
ValueKey::from(ValueClass::Telemetry(TelemetryClass::Metric(0))),
|
||||
ValueKey::from(ValueClass::Telemetry(TelemetryClass::Metric(until_span_id))),
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
|
||||
@@ -10,18 +10,25 @@
|
||||
|
||||
use crate::config::telemetry::StoreTracer;
|
||||
use ahash::{AHashMap, AHashSet};
|
||||
use registry::schema::structs::{Task, TaskIndexTrace, TaskStatus};
|
||||
use registry::{
|
||||
pickle::Pickle,
|
||||
schema::structs::{
|
||||
Task, TaskIndexTrace, TaskStatus, Trace, TraceEvent, TraceKeyValue, TraceValue,
|
||||
TraceValueBoolean, TraceValueDuration, TraceValueEvent, TraceValueFloat, TraceValueInteger,
|
||||
TraceValueIpAddr, TraceValueList, TraceValueString, TraceValueUTCDateTime,
|
||||
TraceValueUnsignedInt,
|
||||
},
|
||||
types::{datetime::UTCDateTime, ipaddr::IpAddr},
|
||||
};
|
||||
use std::{collections::HashSet, future::Future, time::Duration};
|
||||
use store::{
|
||||
Deserialize, SearchStore, Store, ValueKey,
|
||||
SearchStore, Store, ValueKey,
|
||||
search::{IndexDocument, SearchField, SearchFilter, SearchQuery, TracingSearchField},
|
||||
write::{BatchBuilder, SearchIndex, TelemetryClass, ValueClass},
|
||||
};
|
||||
use trc::{
|
||||
AddContext, AuthEvent, Event, EventDetails, EventType, Key, MessageIngestEvent,
|
||||
OutgoingReportEvent, QueueEvent, Value,
|
||||
ipc::subscriber::SubscriberBuilder,
|
||||
serializers::binary::{deserialize_events, serialize_events},
|
||||
OutgoingReportEvent, QueueEvent, Value, ipc::subscriber::SubscriberBuilder,
|
||||
};
|
||||
use utils::snowflake::SnowflakeIdGenerator;
|
||||
|
||||
@@ -53,14 +60,15 @@ pub(crate) fn spawn_store_tracer(builder: SubscriberBuilder, settings: StoreTrac
|
||||
// Serialize events
|
||||
batch
|
||||
.set(
|
||||
ValueClass::Telemetry(TelemetryClass::Span { span_id }),
|
||||
serialize_events(
|
||||
ValueClass::Telemetry(TelemetryClass::Span(span_id)),
|
||||
map_events(
|
||||
[span.as_ref()]
|
||||
.into_iter()
|
||||
.chain(events.iter().map(|event| event.as_ref()))
|
||||
.chain([event.as_ref()].into_iter()),
|
||||
events.len() + 2,
|
||||
),
|
||||
)
|
||||
.to_pickled_vec(),
|
||||
)
|
||||
.schedule_task(Task::IndexTrace(TaskIndexTrace {
|
||||
status: TaskStatus::now(),
|
||||
@@ -80,15 +88,72 @@ pub(crate) fn spawn_store_tracer(builder: SubscriberBuilder, settings: StoreTrac
|
||||
});
|
||||
}
|
||||
|
||||
fn map_events<'x>(
|
||||
span_events: impl IntoIterator<Item = &'x Event<EventDetails>>,
|
||||
num_events: usize,
|
||||
) -> Trace {
|
||||
let mut events = Vec::with_capacity(num_events);
|
||||
|
||||
for event in span_events {
|
||||
let mut key_values = Vec::with_capacity(event.keys.len());
|
||||
for (key, value) in &event.keys {
|
||||
key_values.push(TraceKeyValue {
|
||||
key: *key,
|
||||
value: map_value(value),
|
||||
});
|
||||
}
|
||||
|
||||
events.push(TraceEvent {
|
||||
event: event.inner.typ,
|
||||
timestamp: UTCDateTime::from_timestamp(event.inner.timestamp as i64),
|
||||
key_values,
|
||||
});
|
||||
}
|
||||
|
||||
Trace { events }
|
||||
}
|
||||
|
||||
fn map_value(value: &Value) -> TraceValue {
|
||||
match value {
|
||||
Value::String(value) => TraceValue::String(TraceValueString {
|
||||
value: value.to_string(),
|
||||
}),
|
||||
Value::UInt(value) => TraceValue::UnsignedInt(TraceValueUnsignedInt { value: *value }),
|
||||
Value::Int(value) => TraceValue::Integer(TraceValueInteger { value: *value }),
|
||||
Value::Float(value) => TraceValue::Float(TraceValueFloat { value: *value }),
|
||||
Value::Timestamp(value) => TraceValue::UTCDateTime(TraceValueUTCDateTime {
|
||||
value: UTCDateTime::from_timestamp(*value as i64),
|
||||
}),
|
||||
Value::Duration(value) => TraceValue::Duration(TraceValueDuration { value: *value }),
|
||||
Value::Bytes(items) => TraceValue::String(TraceValueString {
|
||||
value: String::from_utf8_lossy(items).to_string(),
|
||||
}),
|
||||
Value::Bool(value) => TraceValue::Boolean(TraceValueBoolean { value: *value }),
|
||||
Value::Ipv4(ipv4_addr) => TraceValue::IpAddr(TraceValueIpAddr {
|
||||
value: IpAddr((*ipv4_addr).into()),
|
||||
}),
|
||||
Value::Ipv6(ipv6_addr) => TraceValue::IpAddr(TraceValueIpAddr {
|
||||
value: IpAddr((*ipv6_addr).into()),
|
||||
}),
|
||||
Value::Event(event) => TraceValue::Event(TraceValueEvent {
|
||||
value: event
|
||||
.keys()
|
||||
.iter()
|
||||
.map(|(k, v)| TraceKeyValue {
|
||||
key: *k,
|
||||
value: map_value(v),
|
||||
})
|
||||
.collect(),
|
||||
event: event.event_type(),
|
||||
}),
|
||||
Value::Array(values) => TraceValue::List(TraceValueList {
|
||||
value: values.iter().map(map_value).collect::<Vec<_>>(),
|
||||
}),
|
||||
Value::None => TraceValue::Null,
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TracingStore: Sync + Send {
|
||||
fn get_span(
|
||||
&self,
|
||||
span_id: u64,
|
||||
) -> impl Future<Output = trc::Result<Vec<Event<EventDetails>>>> + Send;
|
||||
fn get_raw_span(
|
||||
&self,
|
||||
span_id: u64,
|
||||
) -> impl Future<Output = trc::Result<Option<Vec<u8>>>> + Send;
|
||||
fn purge_spans(
|
||||
&self,
|
||||
period: Duration,
|
||||
@@ -97,24 +162,6 @@ pub trait TracingStore: Sync + Send {
|
||||
}
|
||||
|
||||
impl TracingStore for Store {
|
||||
async fn get_span(&self, span_id: u64) -> trc::Result<Vec<Event<EventDetails>>> {
|
||||
self.get_value::<Span>(ValueKey::from(ValueClass::Telemetry(
|
||||
TelemetryClass::Span { span_id },
|
||||
)))
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.map(|span| span.map(|span| span.0).unwrap_or_default())
|
||||
}
|
||||
|
||||
async fn get_raw_span(&self, span_id: u64) -> trc::Result<Option<Vec<u8>>> {
|
||||
self.get_value::<RawSpan>(ValueKey::from(ValueClass::Telemetry(
|
||||
TelemetryClass::Span { span_id },
|
||||
)))
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.map(|span| span.map(|span| span.0))
|
||||
}
|
||||
|
||||
async fn purge_spans(
|
||||
&self,
|
||||
period: Duration,
|
||||
@@ -127,10 +174,8 @@ impl TracingStore for Store {
|
||||
})?;
|
||||
|
||||
self.delete_range(
|
||||
ValueKey::from(ValueClass::Telemetry(TelemetryClass::Span { span_id: 0 })),
|
||||
ValueKey::from(ValueClass::Telemetry(TelemetryClass::Span {
|
||||
span_id: until_span_id,
|
||||
})),
|
||||
ValueKey::from(ValueClass::Telemetry(TelemetryClass::Span(0))),
|
||||
ValueKey::from(ValueClass::Telemetry(TelemetryClass::Span(until_span_id))),
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
@@ -218,76 +263,57 @@ impl StoreTracer {
|
||||
}
|
||||
}
|
||||
|
||||
struct RawSpan(Vec<u8>);
|
||||
struct Span(Vec<Event<EventDetails>>);
|
||||
|
||||
impl Deserialize for Span {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
deserialize_events(bytes).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for RawSpan {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
Ok(Self(bytes.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_span_document(
|
||||
span_id: u64,
|
||||
events: Vec<Event<EventDetails>>,
|
||||
trace: Trace,
|
||||
index_fields: &AHashSet<SearchField>,
|
||||
) -> IndexDocument {
|
||||
let mut document = IndexDocument::new(SearchIndex::Tracing).with_id(span_id);
|
||||
let mut keywords = HashSet::new();
|
||||
|
||||
for (idx, event) in events.into_iter().enumerate() {
|
||||
for (idx, event) in trace.events.into_iter().enumerate() {
|
||||
if idx == 0
|
||||
&& (index_fields.is_empty()
|
||||
|| index_fields.contains(&TracingSearchField::EventType.into()))
|
||||
{
|
||||
document.index_unsigned(TracingSearchField::EventType, event.inner.typ.to_id());
|
||||
document.index_unsigned(TracingSearchField::EventType, event.event.to_id());
|
||||
}
|
||||
|
||||
for (key, value) in event.keys {
|
||||
for TraceKeyValue { key, value } in event.key_values {
|
||||
match (key, value) {
|
||||
(Key::QueueId, Value::UInt(queue_id)) => {
|
||||
(Key::QueueId, TraceValue::UnsignedInt(TraceValueUnsignedInt { value })) => {
|
||||
if index_fields.is_empty()
|
||||
|| index_fields.contains(&TracingSearchField::QueueId.into())
|
||||
{
|
||||
document.index_unsigned(TracingSearchField::QueueId, queue_id);
|
||||
document.index_unsigned(TracingSearchField::QueueId, value);
|
||||
}
|
||||
}
|
||||
(Key::From | Key::To | Key::Domain | Key::Hostname, Value::String(address)) => {
|
||||
(
|
||||
Key::From | Key::To | Key::Domain | Key::Hostname,
|
||||
TraceValue::String(TraceValueString { value }),
|
||||
) => {
|
||||
if index_fields.is_empty()
|
||||
|| index_fields.contains(&TracingSearchField::Keywords.into())
|
||||
{
|
||||
keywords.insert(address.to_string());
|
||||
keywords.insert(value);
|
||||
}
|
||||
}
|
||||
(Key::To, Value::Array(value)) => {
|
||||
(Key::To, TraceValue::List(TraceValueList { value })) => {
|
||||
if index_fields.is_empty()
|
||||
|| index_fields.contains(&TracingSearchField::Keywords.into())
|
||||
{
|
||||
for value in value {
|
||||
if let Value::String(address) = value {
|
||||
keywords.insert(address.to_string());
|
||||
if let TraceValue::String(TraceValueString { value }) = value {
|
||||
keywords.insert(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(Key::RemoteIp, Value::Ipv4(ip)) => {
|
||||
(Key::RemoteIp, TraceValue::IpAddr(TraceValueIpAddr { value })) => {
|
||||
if index_fields.is_empty()
|
||||
|| index_fields.contains(&TracingSearchField::Keywords.into())
|
||||
{
|
||||
keywords.insert(ip.to_string());
|
||||
}
|
||||
}
|
||||
(Key::RemoteIp, Value::Ipv6(ip)) => {
|
||||
if index_fields.is_empty()
|
||||
|| index_fields.contains(&TracingSearchField::Keywords.into())
|
||||
{
|
||||
keywords.insert(ip.to_string());
|
||||
keywords.insert(value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user