1use crate::{ConfigError, EventKind, Notification, NotifyErrorKind};
2use nametrics_core::atomic::{add, AtomicHistogram, LATENCY_SECONDS};
3use nametrics_core::{
4 LegacyMetricsSource, MetricDescriptor, MetricKind, MetricSample, MetricValue,
5};
6use std::collections::BTreeSet;
7use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
8use std::sync::Arc;
9use std::time::Duration;
10use tokio::sync::mpsc;
11
12static ENQUEUED: MetricDescriptor = MetricDescriptor {
13 name: "nanotify_enqueued_total",
14 help: "进入通知队列的离散事件累计数。",
15 unit: "",
16 kind: MetricKind::Counter,
17 label_names: &["provider", "event"],
18 histogram_bounds: &[],
19};
20static DROPPED: MetricDescriptor = MetricDescriptor {
21 name: "nanotify_dropped_total",
22 help: "因队列或停机门禁丢弃的通知累计数。",
23 unit: "",
24 kind: MetricKind::Counter,
25 label_names: &["provider", "event", "reason"],
26 histogram_bounds: &[],
27};
28static DELIVERIES: MetricDescriptor = MetricDescriptor {
29 name: "nanotify_deliveries_total",
30 help: "以最终结果分类的通知投递累计数。",
31 unit: "",
32 kind: MetricKind::Counter,
33 label_names: &["provider", "event", "outcome"],
34 histogram_bounds: &[],
35};
36static DURATION: MetricDescriptor = MetricDescriptor {
37 name: "nanotify_delivery_duration_seconds",
38 help: "包含有限重试和等待的通知投递时长。",
39 unit: "seconds",
40 kind: MetricKind::Histogram,
41 label_names: &["provider", "outcome"],
42 histogram_bounds: LATENCY_SECONDS,
43};
44static DEPTH: MetricDescriptor = MetricDescriptor {
45 name: "nanotify_queue_depth",
46 help: "等待 worker 领取的通知数量。",
47 unit: "",
48 kind: MetricKind::Gauge,
49 label_names: &[],
50 histogram_bounds: &[],
51};
52static CAPACITY: MetricDescriptor = MetricDescriptor {
53 name: "nanotify_queue_capacity",
54 help: "当前进程通知队列的固定容量。",
55 unit: "",
56 kind: MetricKind::Gauge,
57 label_names: &[],
58 histogram_bounds: &[],
59};
60static DESCRIPTORS: &[&MetricDescriptor] = &[
61 &ENQUEUED,
62 &DROPPED,
63 &DELIVERIES,
64 &DURATION,
65 &DEPTH,
66 &CAPACITY,
67];
68const EVENTS: [EventKind; 3] = [
69 EventKind::SlowSql,
70 EventKind::ExecutionError,
71 EventKind::AcquireTimeout,
72];
73const DROP_REASONS: [&str; 3] = ["queue_full", "stopping", "shutdown_timeout"];
74const OUTCOMES: [&str; 10] = [
75 "accepted",
76 "timeout",
77 "unavailable",
78 "rate_limited",
79 "rejected",
80 "authentication",
81 "invalid_request",
82 "other",
83 "panic",
84 "shutdown",
85];
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89#[repr(usize)]
90pub enum DeliveryOutcome {
91 Accepted,
93 Timeout,
95 Unavailable,
97 RateLimited,
99 Rejected,
101 Authentication,
103 InvalidRequest,
105 Other,
107 Panic,
109 Shutdown,
111}
112
113impl From<NotifyErrorKind> for DeliveryOutcome {
114 fn from(kind: NotifyErrorKind) -> Self {
121 match kind {
122 NotifyErrorKind::Timeout => Self::Timeout,
123 NotifyErrorKind::Unavailable => Self::Unavailable,
124 NotifyErrorKind::RateLimited => Self::RateLimited,
125 NotifyErrorKind::Rejected => Self::Rejected,
126 NotifyErrorKind::Authentication => Self::Authentication,
127 NotifyErrorKind::InvalidRequest => Self::InvalidRequest,
128 NotifyErrorKind::Other => Self::Other,
129 }
130 }
131}
132
133struct ProviderMetrics {
134 id: String,
135 enqueued: [AtomicU64; 3],
136 dropped: [[AtomicU64; 3]; 3],
137 deliveries: [[AtomicU64; 10]; 3],
138 duration: [AtomicHistogram; 10],
139}
140
141pub struct QueueMetrics {
143 providers: Vec<ProviderMetrics>,
144 capacity: usize,
145 depth: AtomicU64,
146}
147
148impl QueueMetrics {
149 pub fn series_budget(&self) -> usize {
155 self.providers.len() * (3 + 9 + 30 + 10 * (LATENCY_SECONDS.len() + 3)) + 2
156 }
157
158 pub fn record_delivery(
168 &self,
169 provider: usize,
170 event: EventKind,
171 outcome: DeliveryOutcome,
172 elapsed: Duration,
173 ) {
174 if let Some(slot) = self.providers.get(provider) {
175 add(&slot.deliveries[event as usize][outcome as usize], 1);
176 slot.duration[outcome as usize].observe(elapsed);
177 }
178 }
179}
180
181impl LegacyMetricsSource for QueueMetrics {
182 fn descriptors(&self) -> &'static [&'static MetricDescriptor] {
188 DESCRIPTORS
189 }
190
191 fn snapshot(&self) -> Option<Vec<MetricSample>> {
197 let mut samples = Vec::new();
198 for provider in &self.providers {
199 for event in EVENTS {
200 let labels = vec![
201 ("provider", provider.id.clone()),
202 ("event", event.as_str().into()),
203 ];
204 samples.push(MetricSample {
205 name: ENQUEUED.name,
206 labels: labels.clone(),
207 value: MetricValue::Counter(
208 provider.enqueued[event as usize].load(Ordering::Relaxed),
209 ),
210 });
211 for (index, reason) in DROP_REASONS.iter().enumerate() {
212 let mut labels = labels.clone();
213 labels.push(("reason", (*reason).into()));
214 samples.push(MetricSample {
215 name: DROPPED.name,
216 labels,
217 value: MetricValue::Counter(
218 provider.dropped[event as usize][index].load(Ordering::Relaxed),
219 ),
220 });
221 }
222 for (index, outcome) in OUTCOMES.iter().enumerate() {
223 let mut labels = labels.clone();
224 labels.push(("outcome", (*outcome).into()));
225 samples.push(MetricSample {
226 name: DELIVERIES.name,
227 labels,
228 value: MetricValue::Counter(
229 provider.deliveries[event as usize][index].load(Ordering::Relaxed),
230 ),
231 });
232 }
233 }
234 for (index, outcome) in OUTCOMES.iter().enumerate() {
235 samples.push(MetricSample {
236 name: DURATION.name,
237 labels: vec![
238 ("provider", provider.id.clone()),
239 ("outcome", (*outcome).into()),
240 ],
241 value: provider.duration[index].snapshot(),
242 });
243 }
244 }
245 samples.push(MetricSample {
246 name: DEPTH.name,
247 labels: vec![],
248 value: MetricValue::Gauge(self.depth.load(Ordering::Relaxed) as f64),
249 });
250 samples.push(MetricSample {
251 name: CAPACITY.name,
252 labels: vec![],
253 value: MetricValue::Gauge(self.capacity as f64),
254 });
255 Some(samples)
256 }
257
258 fn render_prometheus(&self, output: &mut String) {
265 nametrics_core::atomic::render_snapshot(
266 DESCRIPTORS,
267 &self.snapshot().unwrap_or_default(),
268 output,
269 );
270 }
271}
272
273struct QueueState {
274 accepting: AtomicBool,
275 metrics: Arc<QueueMetrics>,
276}
277
278#[derive(Clone)]
280pub struct AlertRoute {
281 sender: mpsc::Sender<Envelope>,
282 state: Arc<QueueState>,
283 provider: usize,
284 requires_initialized_notify: bool,
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub enum EnqueueOutcome {
290 Enqueued,
292 Full,
294 Stopping,
296 Ignored,
298}
299
300impl AlertRoute {
301 pub fn try_send(&self, notification: Notification) -> EnqueueOutcome {
308 if self.requires_initialized_notify && crate::get().is_none() {
310 return EnqueueOutcome::Ignored;
311 }
312 let event = notification.fields().event;
313 let slot = &self.state.metrics.providers[self.provider];
314 if !self.state.accepting.load(Ordering::Acquire) {
316 add(&slot.dropped[event as usize][1], 1);
317 return EnqueueOutcome::Stopping;
318 }
319 match self.sender.try_reserve() {
320 Ok(permit) => {
321 if !self.state.accepting.load(Ordering::Acquire) {
322 add(&slot.dropped[event as usize][1], 1);
323 return EnqueueOutcome::Stopping;
324 }
325 add(&self.state.metrics.depth, 1);
326 add(&slot.enqueued[event as usize], 1);
327 permit.send(Envelope {
328 provider: self.provider,
329 notification,
330 queued: true,
331 metrics: self.state.metrics.clone(),
332 });
333 EnqueueOutcome::Enqueued
334 }
335 Err(mpsc::error::TrySendError::Full(_)) => {
336 add(&slot.dropped[event as usize][0], 1);
337 EnqueueOutcome::Full
338 }
339 Err(mpsc::error::TrySendError::Closed(_)) => {
340 add(&slot.dropped[event as usize][1], 1);
341 EnqueueOutcome::Stopping
342 }
343 }
344 }
345}
346
347#[derive(Clone)]
349pub struct NotificationProducer {
350 sender: mpsc::Sender<Envelope>,
351 state: Arc<QueueState>,
352}
353
354impl NotificationProducer {
355 pub fn route(&self, provider_ref: &str) -> Option<AlertRoute> {
362 self.state
363 .metrics
364 .providers
365 .iter()
366 .position(|slot| slot.id == provider_ref)
367 .map(|provider| AlertRoute {
368 sender: self.sender.clone(),
369 state: self.state.clone(),
370 provider,
371 requires_initialized_notify: false,
372 })
373 }
374
375 pub fn default_route(&self) -> Option<AlertRoute> {
381 self.route(crate::DEFAULT_PROVIDER_ID).map(|mut route| {
382 route.requires_initialized_notify = true;
383 route
384 })
385 }
386 pub fn close(&self) {
392 self.state.accepting.store(false, Ordering::Release);
393 }
394 pub fn metrics(&self) -> Arc<QueueMetrics> {
400 self.state.metrics.clone()
401 }
402}
403
404pub struct Envelope {
406 pub provider: usize,
408 pub notification: Notification,
410 queued: bool,
411 metrics: Arc<QueueMetrics>,
412}
413
414impl Drop for Envelope {
415 fn drop(&mut self) {
421 if self.queued {
422 self.metrics.depth.fetch_sub(1, Ordering::Relaxed);
423 add(
424 &self.metrics.providers[self.provider].dropped
425 [self.notification.fields().event as usize][2],
426 1,
427 );
428 }
429 }
430}
431
432pub struct NotificationReceiver {
434 receiver: mpsc::Receiver<Envelope>,
435 state: Arc<QueueState>,
436}
437
438impl NotificationReceiver {
439 pub async fn recv(&mut self) -> Option<Envelope> {
445 let mut message = self.receiver.recv().await;
446 if let Some(message) = &mut message {
447 message.queued = false;
448 self.state.metrics.depth.fetch_sub(1, Ordering::Relaxed);
449 }
450 message
451 }
452 pub fn close(&mut self) {
458 self.state.accepting.store(false, Ordering::Release);
459 self.receiver.close();
460 }
461 pub fn discard_remaining(&mut self) -> usize {
467 self.close();
468 let mut count = 0;
469 while let Ok(message) = self.receiver.try_recv() {
470 drop(message);
471 count += 1;
472 }
473 count
474 }
475}
476
477impl Drop for NotificationReceiver {
478 fn drop(&mut self) {
484 self.discard_remaining();
485 }
486}
487
488pub struct NotificationQueue;
490
491impl NotificationQueue {
492 pub fn bounded(
500 provider_ids: Vec<String>,
501 capacity: usize,
502 ) -> Result<(NotificationProducer, NotificationReceiver), ConfigError> {
503 if !(1..=65536).contains(&capacity) || provider_ids.is_empty() || provider_ids.len() > 32 {
504 return Err(ConfigError("queue"));
505 }
506 let mut seen = BTreeSet::new();
507 if provider_ids
508 .iter()
509 .any(|id| !valid_provider_id(id) || !seen.insert(id))
510 {
511 return Err(ConfigError("providers"));
512 }
513 let providers = provider_ids
514 .into_iter()
515 .map(|id| ProviderMetrics {
516 id,
517 enqueued: std::array::from_fn(|_| AtomicU64::new(0)),
518 dropped: std::array::from_fn(|_| std::array::from_fn(|_| AtomicU64::new(0))),
519 deliveries: std::array::from_fn(|_| std::array::from_fn(|_| AtomicU64::new(0))),
520 duration: std::array::from_fn(|_| AtomicHistogram::new(LATENCY_SECONDS)),
521 })
522 .collect();
523 let state = Arc::new(QueueState {
524 accepting: AtomicBool::new(true),
525 metrics: Arc::new(QueueMetrics {
526 providers,
527 capacity,
528 depth: AtomicU64::new(0),
529 }),
530 });
531 let (sender, receiver) = mpsc::channel(capacity);
532 Ok((
533 NotificationProducer {
534 sender,
535 state: state.clone(),
536 },
537 NotificationReceiver {
538 receiver,
539 state,
540 },
541 ))
542 }
543}
544
545pub fn valid_provider_id(id: &str) -> bool {
552 (1..=64).contains(&id.len())
553 && id.as_bytes()[0].is_ascii_alphabetic()
554 && id
555 .bytes()
556 .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, b'_' | b'.' | b'-'))
557}