1use crate::{Plugin, now_ms};
16use ahash::AHashMap;
17use bytes::BytesMut;
18use http::StatusCode;
19use http::Uri;
20use http::{HeaderName, HeaderValue};
21#[cfg(feature = "tracing")]
22use opentelemetry::{
23 Context,
24 global::{BoxedSpan, BoxedTracer, ObjectSafeSpan},
25 trace::{SpanKind, TraceContextExt, Tracer},
26};
27use pingora::cache::CacheKey;
28use pingora::http::RequestHeader;
29use pingora::protocols::Digest;
30use pingora::protocols::TimingDigest;
31use pingora::proxy::Session;
32use pingora_limits::inflight::Guard;
33use std::borrow::Cow;
34use std::fmt::Write;
35use std::sync::Arc;
36use std::time::{Duration, Instant, SystemTime};
37
38const SECOND: u64 = 1_000;
40const MINUTE: u64 = 60 * SECOND;
41const HOUR: u64 = 60 * MINUTE;
42
43#[inline]
44pub fn format_duration(buf: &mut BytesMut, ms: u64) {
47 if ms < SECOND {
48 buf.extend_from_slice(itoa::Buffer::new().format(ms).as_bytes());
50 buf.extend_from_slice(b"ms");
51 } else if ms < MINUTE {
52 buf.extend_from_slice(
54 itoa::Buffer::new().format(ms / SECOND).as_bytes(),
55 );
56 let value = (ms % SECOND) / 100;
57 if value != 0 {
58 buf.extend_from_slice(b".");
59 buf.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
60 }
61 buf.extend_from_slice(b"s");
62 } else if ms < HOUR {
63 buf.extend_from_slice(
65 itoa::Buffer::new().format(ms / MINUTE).as_bytes(),
66 );
67 let value = ms % MINUTE * 10 / MINUTE;
68 if value != 0 {
69 buf.extend_from_slice(b".");
70 buf.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
71 }
72 buf.extend_from_slice(b"m");
73 } else {
74 buf.extend_from_slice(itoa::Buffer::new().format(ms / HOUR).as_bytes());
76 let value = ms % HOUR * 10 / HOUR;
77 if value != 0 {
78 buf.extend_from_slice(b".");
79 buf.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
80 }
81 buf.extend_from_slice(b"h");
82 }
83}
84
85#[derive(PartialEq)]
86pub enum ModifiedMode {
87 Upstream,
88 Response,
89}
90
91impl From<&str> for ModifiedMode {
92 fn from(value: &str) -> Self {
93 match value {
94 "upstream" => ModifiedMode::Upstream,
95 _ => ModifiedMode::Response,
96 }
97 }
98}
99
100pub trait ModifyResponseBody: Sync + Send {
102 fn handle(
104 &mut self,
105 session: &Session,
106 body: &mut Option<bytes::Bytes>,
107 end_of_stream: bool,
108 ) -> pingora::Result<()>;
109 fn name(&self) -> String {
111 "unknown".to_string()
112 }
113}
114
115#[derive(Default)]
117pub struct ConnectionInfo {
118 pub id: usize,
120 pub client_ip: Option<String>,
122 pub remote_addr: Option<String>,
124 pub remote_port: Option<u16>,
126 pub server_addr: Option<String>,
128 pub server_port: Option<u16>,
130 pub tls_version: Option<Cow<'static, str>>,
135 pub tls_cipher: Option<Cow<'static, str>>,
137 pub reused: bool,
139}
140
141pub struct Timing {
144 pub created_at: Instant,
146 pub connection_duration: u64,
149 pub tls_handshake: Option<i32>,
151 pub upstream_connect: Option<i32>,
153 pub upstream_tcp_connect: Option<i32>,
155 pub upstream_tls_handshake: Option<i32>,
157 pub upstream_connect_offload_wait: Option<i32>,
162 pub upstream_processing: Option<i32>,
164 pub upstream_response: Option<i32>,
166 pub upstream_connection_duration: Option<u64>,
168 pub cache_lookup: Option<i32>,
170 pub cache_lock: Option<i32>,
172}
173
174impl Default for Timing {
175 fn default() -> Self {
176 Self {
177 created_at: Instant::now(),
178 connection_duration: 0,
179 tls_handshake: None,
180 upstream_connect: None,
181 upstream_tcp_connect: None,
182 upstream_tls_handshake: None,
183 upstream_connect_offload_wait: None,
184 upstream_processing: None,
185 upstream_response: None,
186 upstream_connection_duration: None,
187 cache_lookup: None,
188 cache_lock: None,
189 }
190 }
191}
192
193pub trait UpstreamInstance: Send + Sync {
195 fn on_transport_failure(&self, address: &str);
196 fn on_response(&self, address: &str, status: StatusCode);
197 fn completed(&self) -> i32;
202}
203
204pub trait LocationInstance: Send + Sync {
206 fn name(&self) -> &str;
208 fn upstream(&self) -> &str;
210 fn rewrite(
212 &self,
213 header: &mut RequestHeader,
214 variables: Option<AHashMap<String, String>>,
215 ) -> (bool, Option<AHashMap<String, String>>);
216 fn headers(&self) -> Option<&Vec<(HeaderName, HeaderValue, bool)>>;
218 fn client_body_size_limit(&self) -> usize;
220 fn on_request(&self) -> pingora::Result<(u64, i32)>;
226 fn on_response(&self);
228}
229
230#[derive(Default)]
232pub struct UpstreamInfo {
233 pub upstream_instance: Option<Arc<dyn UpstreamInstance>>,
235 pub location_instance: Option<Arc<dyn LocationInstance>>,
237 pub location: Arc<str>,
239 pub name: Arc<str>,
241 pub address: String,
243 pub reused: bool,
245 pub processing_count: Option<i32>,
247 pub connected_count: Option<i32>,
249 pub status: Option<StatusCode>,
251 pub retries: u8,
253 pub max_retries: Option<u8>,
255 pub max_retry_window: Option<Duration>,
263}
264
265#[derive(Default)]
267pub struct RequestState {
268 pub request_id: Option<String>,
270 pub status: Option<StatusCode>,
272 pub payload_size: usize,
274 pub guard: Option<Guard>,
276 pub processing_count: i32,
278 pub accepted_count: u64,
280 pub location_processing_count: i32,
282 pub location_accepted_count: u64,
284}
285
286#[derive(Default)]
288pub struct CacheInfo {
289 pub namespace: Option<String>,
291 pub keys: Option<Vec<String>>,
293 pub check_cache_control: bool,
295 pub max_ttl: Option<Duration>,
297 pub vary_headers: Option<Arc<Vec<String>>>,
300 pub reading_count: Option<u32>,
302 pub writing_count: Option<u32>,
304}
305
306#[derive(Default)]
308pub struct Features {
309 pub variables: Option<AHashMap<String, String>>,
311 pub plugin_processing_times: Option<Vec<(Arc<str>, u32)>>,
313 pub compression_stat: Option<CompressionStat>,
315 pub modify_body_handlers:
317 Option<AHashMap<String, Box<dyn ModifyResponseBody>>>,
318 #[cfg(feature = "tracing")]
320 pub otel_tracer: Option<OtelTracer>,
321 #[cfg(feature = "tracing")]
323 pub upstream_span: Option<BoxedSpan>,
324}
325
326#[derive(Default)]
327pub struct CompressionStat {
329 pub algorithm: String,
331 pub in_bytes: usize,
333 pub out_bytes: usize,
335 pub duration: Duration,
337}
338
339impl CompressionStat {
340 pub fn ratio(&self) -> f64 {
342 if self.out_bytes == 0 {
343 return 0.0;
344 }
345 (self.in_bytes as f64) / (self.out_bytes as f64)
346 }
347}
348
349#[cfg(feature = "tracing")]
351pub struct OtelTracer {
352 pub tracer: BoxedTracer,
354 pub http_request_span: BoxedSpan,
356}
357
358#[cfg(feature = "tracing")]
359impl OtelTracer {
360 #[inline]
362 pub fn new_upstream_span(&self, name: &str) -> BoxedSpan {
363 self.tracer
364 .span_builder(name.to_string())
365 .with_kind(SpanKind::Client)
366 .start_with_context(
367 &self.tracer,
368 &Context::current().with_remote_span_context(
370 self.http_request_span.span_context().clone(),
371 ),
372 )
373 }
374}
375
376pub type NamedPlugin = (Arc<str>, Arc<dyn Plugin>);
378
379#[derive(Default)]
382pub struct Ctx {
383 pub conn: ConnectionInfo,
385 pub upstream: UpstreamInfo,
387 pub timing: Timing,
389 pub state: RequestState,
391 pub cache: Option<CacheInfo>,
393 pub features: Option<Features>,
395 pub plugins: Option<Vec<NamedPlugin>>,
397}
398
399#[derive(Debug, Default)]
401pub struct DigestDetail {
402 pub connection_reused: bool,
408 pub connection_time: u64,
410 pub tcp_established: u64,
412 pub tls_established: u64,
414 pub tcp_connect: Option<u64>,
418 pub tls_handshake: Option<u64>,
422 pub connect_offload_wait: Option<u64>,
425 pub tls_version: Option<Cow<'static, str>>,
427 pub tls_cipher: Option<Cow<'static, str>>,
429}
430
431#[inline]
432pub(crate) fn timing_to_ms(timing: Option<&Option<TimingDigest>>) -> u64 {
433 match timing {
434 Some(Some(item)) => item
435 .established_ts
436 .duration_since(SystemTime::UNIX_EPOCH)
437 .unwrap_or_default()
438 .as_millis() as u64,
439 _ => 0,
440 }
441}
442
443#[inline]
446pub fn get_digest_detail(digest: &Digest) -> DigestDetail {
447 let tcp_established = timing_to_ms(digest.timing_digest.first());
448 let mut connection_time = 0;
449 let now = now_ms();
450 if tcp_established > 0 && tcp_established < now {
451 connection_time = now - tcp_established;
452 }
453 let connection_reused = connection_time > 100;
454 let tcp_connect = establishment_ms(digest.timing_digest.first());
458 let connect_offload_wait = offload_wait_ms(digest.timing_digest.first());
459
460 let Some(ssl_digest) = &digest.ssl_digest else {
461 return DigestDetail {
462 connection_reused,
463 tcp_established,
464 connection_time,
465 tcp_connect,
466 connect_offload_wait,
467 ..Default::default()
468 };
469 };
470
471 DigestDetail {
472 connection_reused,
473 tcp_established,
474 connection_time,
475 tcp_connect,
476 connect_offload_wait,
477 tls_established: timing_to_ms(digest.timing_digest.last()),
478 tls_handshake: establishment_ms(digest.timing_digest.last()),
479 tls_version: Some(ssl_digest.version.clone()),
481 tls_cipher: Some(ssl_digest.cipher.clone()),
482 }
483}
484
485fn establishment_ms(timing: Option<&Option<TimingDigest>>) -> Option<u64> {
487 timing
488 .and_then(|item| item.as_ref())
489 .and_then(|item| item.establishment_duration)
490 .map(|duration| duration.as_millis() as u64)
491}
492
493fn offload_wait_ms(timing: Option<&Option<TimingDigest>>) -> Option<u64> {
496 timing
497 .and_then(|item| item.as_ref())
498 .and_then(|item| item.offload_wait_duration)
499 .map(|duration| duration.as_millis() as u64)
500}
501
502impl Ctx {
503 pub fn new() -> Self {
508 Self {
509 ..Default::default()
510 }
511 }
512
513 #[inline]
519 pub fn add_variable(&mut self, key: &str, value: &str) {
520 let features = self.features.get_or_insert_default();
522 let variables = features.variables.get_or_insert_with(AHashMap::new);
523 variables.insert(key.to_string(), value.to_string());
524 }
525
526 #[inline]
531 pub fn extend_variables(&mut self, values: AHashMap<String, String>) {
532 let features = self.features.get_or_insert_default();
533 if let Some(variables) = features.variables.as_mut() {
534 variables.extend(values);
535 } else {
536 features.variables = Some(values);
537 }
538 }
539
540 #[inline]
547 pub fn get_variable(&self, key: &str) -> Option<&str> {
548 self.features
549 .as_ref()?
550 .variables
551 .as_ref()?
552 .get(key)
553 .map(|v| v.as_str())
554 }
555
556 #[inline]
562 pub fn add_modify_body_handler(
563 &mut self,
564 name: &str,
565 handler: Box<dyn ModifyResponseBody>,
566 ) {
567 let features = self.features.get_or_insert_default();
568 let handlers = features
569 .modify_body_handlers
570 .get_or_insert_with(AHashMap::new);
571 handlers.insert(name.to_string(), handler);
572 }
573
574 #[inline]
576 pub fn get_modify_body_handler(
577 &mut self,
578 name: &str,
579 ) -> Option<&mut Box<dyn ModifyResponseBody>> {
580 self.features
581 .as_mut()
582 .and_then(|f| f.modify_body_handlers.as_mut())
583 .and_then(|h| h.get_mut(name))
584 }
585
586 #[inline]
589 fn get_time_field(&self, field: Option<i32>) -> Option<u32> {
590 if let Some(value) = field
591 && value >= 0
592 {
593 return Some(value as u32);
594 }
595 None
596 }
597
598 #[inline]
603 pub fn get_upstream_response_time(&self) -> Option<u32> {
604 self.get_time_field(self.timing.upstream_response)
605 }
606
607 #[inline]
612 pub fn get_upstream_connect_time(&self) -> Option<u32> {
613 self.get_time_field(self.timing.upstream_connect)
614 }
615
616 #[inline]
621 pub fn get_upstream_processing_time(&self) -> Option<u32> {
622 self.get_time_field(self.timing.upstream_processing)
623 }
624
625 #[inline]
631 pub fn add_plugin_processing_time(&mut self, name: &Arc<str>, time: u32) {
632 let features = self.features.get_or_insert_default();
634 let times = features
635 .plugin_processing_times
636 .get_or_insert_with(|| Vec::with_capacity(5));
637 if let Some(item) = times.iter_mut().find(|item| &item.0 == name) {
638 item.1 += time;
639 } else {
640 times.push((Arc::clone(name), time));
641 }
642 }
643
644 #[inline]
653 pub fn append_log_value(&self, buf: &mut BytesMut, key: &str) {
654 macro_rules! append_time {
656 ($val:expr) => {
658 if let Some(ms) = $val {
659 buf.extend(itoa::Buffer::new().format(ms).as_bytes());
660 }
661 };
662 ($val:expr, human) => {
664 if let Some(ms) = $val {
665 format_duration(buf, ms as u64);
666 }
667 };
668 }
669
670 match key {
671 "connection_id" => {
672 buf.extend(itoa::Buffer::new().format(self.conn.id).as_bytes());
673 },
674 "upstream_reused" => {
675 if self.upstream.reused {
676 buf.extend(b"true");
677 } else {
678 buf.extend(b"false");
679 }
680 },
681 "upstream_status" => {
682 if let Some(status) = &self.upstream.status {
683 buf.extend_from_slice(status.as_str().as_bytes());
684 } else {
685 buf.extend_from_slice(b"-");
686 }
687 },
688 "upstream_addr" => buf.extend(self.upstream.address.as_bytes()),
689 "processing" => buf.extend(
690 itoa::Buffer::new()
691 .format(self.state.processing_count)
692 .as_bytes(),
693 ),
694 "upstream_connected" => {
695 if let Some(value) = self.upstream.connected_count {
696 buf.extend(itoa::Buffer::new().format(value).as_bytes());
697 }
698 },
699
700 "upstream_connect_time" => {
702 append_time!(self.get_upstream_connect_time())
703 },
704 "upstream_connect_time_human" => {
705 append_time!(self.get_upstream_connect_time(), human)
706 },
707
708 "upstream_processing_time" => {
709 append_time!(self.get_upstream_processing_time())
710 },
711 "upstream_processing_time_human" => {
712 append_time!(self.get_upstream_processing_time(), human)
713 },
714 "upstream_response_time" => {
715 append_time!(self.get_upstream_response_time())
716 },
717 "upstream_response_time_human" => {
718 append_time!(self.get_upstream_response_time(), human)
719 },
720 "upstream_tcp_connect_time" => {
721 append_time!(self.timing.upstream_tcp_connect)
722 },
723 "upstream_tcp_connect_time_human" => {
724 append_time!(self.timing.upstream_tcp_connect, human)
725 },
726 "upstream_tls_handshake_time" => {
727 append_time!(self.timing.upstream_tls_handshake)
728 },
729 "upstream_tls_handshake_time_human" => {
730 append_time!(self.timing.upstream_tls_handshake, human)
731 },
732 "upstream_connect_offload_wait_time" => {
733 append_time!(self.timing.upstream_connect_offload_wait)
734 },
735 "upstream_connect_offload_wait_time_human" => {
736 append_time!(self.timing.upstream_connect_offload_wait, human)
737 },
738 "upstream_connection_time" => {
739 append_time!(self.timing.upstream_connection_duration)
740 },
741 "upstream_connection_time_human" => {
742 append_time!(self.timing.upstream_connection_duration, human)
743 },
744 "connection_time" => {
745 append_time!(Some(self.timing.connection_duration))
746 },
747 "connection_time_human" => {
748 append_time!(Some(self.timing.connection_duration), human)
749 },
750
751 "location" if !self.upstream.location.is_empty() => {
753 buf.extend(self.upstream.location.as_bytes())
754 },
755 "connection_reused" => {
756 if self.conn.reused {
757 buf.extend(b"true");
758 } else {
759 buf.extend(b"false");
760 }
761 },
762 "tls_version" => {
763 if let Some(value) = &self.conn.tls_version {
764 buf.extend(value.as_bytes());
765 }
766 },
767 "tls_cipher" => {
768 if let Some(value) = &self.conn.tls_cipher {
769 buf.extend(value.as_bytes());
770 }
771 },
772 "tls_handshake_time" => append_time!(self.timing.tls_handshake),
773 "tls_handshake_time_human" => {
774 append_time!(self.timing.tls_handshake, human)
775 },
776 "compression_time" => {
777 if let Some(feature) = &self.features
778 && let Some(value) = &feature.compression_stat
779 {
780 append_time!(Some(value.duration.as_millis() as u64))
781 }
782 },
783 "compression_time_human" => {
784 if let Some(feature) = &self.features
785 && let Some(value) = &feature.compression_stat
786 {
787 append_time!(Some(value.duration.as_millis() as u64), human)
788 }
789 },
790 "compression_ratio" => {
791 if let Some(feature) = &self.features
792 && let Some(value) = &feature.compression_stat
793 {
794 let tenths = (value.ratio() * 10.0).round() as u64;
796 buf.extend(
797 itoa::Buffer::new().format(tenths / 10).as_bytes(),
798 );
799 buf.extend_from_slice(b".");
800 buf.extend(
801 itoa::Buffer::new().format(tenths % 10).as_bytes(),
802 );
803 }
804 },
805 "cache_lookup_time" => {
806 append_time!(self.timing.cache_lookup)
807 },
808 "cache_lookup_time_human" => {
809 append_time!(self.timing.cache_lookup, human)
810 },
811 "cache_lock_time" => {
812 append_time!(self.timing.cache_lock)
813 },
814 "cache_lock_time_human" => {
815 append_time!(self.timing.cache_lock, human)
816 },
817 "service_time" => {
818 append_time!(Some(self.timing.created_at.elapsed().as_millis()))
819 },
820 "service_time_human" => {
821 append_time!(
822 Some(self.timing.created_at.elapsed().as_millis()),
823 human
824 )
825 },
826 _ => {},
828 }
829 }
830
831 pub fn generate_server_timing(&self) -> String {
839 let mut timing_str = String::with_capacity(200);
840 let mut first = true;
842
843 macro_rules! add_timing {
845 ($name:expr, $dur:expr) => {
846 if !first {
847 timing_str.push_str(", ");
848 }
849 let _ = write!(&mut timing_str, "{};dur={}", $name, $dur);
851 first = false;
852 };
853 }
854
855 let mut upstream_time = 0;
857 if let Some(time) = self.get_upstream_connect_time() {
858 upstream_time += time;
859 add_timing!("upstream.connect", time);
860 }
861 if let Some(time) = self.get_upstream_processing_time() {
862 upstream_time += time;
863 add_timing!("upstream.processing", time);
864 }
865 if upstream_time > 0 {
866 add_timing!("upstream", upstream_time);
867 }
868
869 let mut cache_time = 0;
871 if let Some(time) = self.timing.cache_lookup {
872 cache_time += time;
873 add_timing!("cache.lookup", time);
874 }
875 if let Some(time) = self.timing.cache_lock {
876 cache_time += time;
877 add_timing!("cache.lock", time);
878 }
879 if cache_time > 0 {
880 add_timing!("cache", cache_time);
881 }
882
883 if let Some(features) = &self.features
885 && let Some(times) = &features.plugin_processing_times
886 {
887 let mut plugin_time: u32 = 0;
888 for (name, time) in times {
889 if *time == 0 {
890 continue;
891 }
892 plugin_time += time;
893 if !first {
896 timing_str.push_str(", ");
897 }
898 let _ = write!(&mut timing_str, "plugin.{name};dur={time}");
899 first = false;
900 }
901 if plugin_time > 0 {
902 add_timing!("plugin", plugin_time);
903 }
904 }
905
906 let service_time = self.timing.created_at.elapsed().as_millis();
908 if !first {
910 timing_str.push_str(", ");
911 }
912 let _ = write!(&mut timing_str, "total;dur={}", service_time);
914
915 timing_str
916 }
917
918 #[inline]
920 pub fn push_cache_key(&mut self, key: String) {
921 let cache_info = self.cache.get_or_insert_default();
922 cache_info
923 .keys
924 .get_or_insert_with(|| Vec::with_capacity(2))
925 .push(key);
926 }
927
928 #[inline]
930 pub fn extend_cache_keys(&mut self, keys: Vec<String>) {
931 let cache_info = self.cache.get_or_insert_default();
932 cache_info
933 .keys
934 .get_or_insert_with(|| Vec::with_capacity(keys.len() + 2))
935 .extend(keys);
936 }
937 #[inline]
939 pub fn update_upstream_timing_from_digest(
940 &mut self,
941 digest: &Digest,
942 reused: bool,
943 ) {
944 let detail = get_digest_detail(digest);
945 self.timing.upstream_connection_duration = Some(detail.connection_time);
946 if reused {
947 return;
948 }
949
950 if let Some(tcp_connect) = detail.tcp_connect {
953 self.timing.upstream_tcp_connect = Some(tcp_connect as i32);
954 self.timing.upstream_tls_handshake =
955 detail.tls_handshake.map(|value| value as i32);
956 self.timing.upstream_connect_offload_wait =
957 detail.connect_offload_wait.map(|value| value as i32);
958 return;
959 }
960
961 let upstream_connect_time =
966 self.timing.upstream_connect.unwrap_or_default();
967 let mut upstream_tcp_connect = upstream_connect_time;
968 if detail.tls_established > detail.tcp_established {
969 let latency =
970 (detail.tls_established - detail.tcp_established) as i32;
971 upstream_tcp_connect -= latency;
972 self.timing.upstream_tls_handshake = Some(latency);
973 }
974 if upstream_tcp_connect > 0 {
975 self.timing.upstream_tcp_connect = Some(upstream_tcp_connect);
976 }
977 }
978}
979
980pub fn get_cache_key(ctx: &Ctx, method: &str, uri: &Uri) -> CacheKey {
991 let Some(cache_info) = &ctx.cache else {
992 return CacheKey::new("", "");
994 };
995 let namespace = cache_info.namespace.as_ref().map_or("", |v| v);
996 let uri_str = uri.to_string();
999 let keys_len = cache_info
1007 .keys
1008 .as_ref()
1009 .map_or(0, |keys| keys.iter().map(|s| s.len() + 1).sum::<usize>());
1010 let mut key_buf = String::with_capacity(
1011 namespace.len() + keys_len + method.len() + 1 + uri_str.len(),
1012 );
1013 key_buf.push_str(namespace);
1014 if let Some(keys) = &cache_info.keys {
1016 for k in keys {
1017 key_buf.push_str(k);
1018 key_buf.push(':');
1019 }
1020 }
1021 key_buf.push_str(method);
1023 key_buf.push(':');
1024 key_buf.push_str(&uri_str);
1025
1026 CacheKey::new(key_buf, namespace)
1027}
1028
1029#[cfg(test)]
1030mod tests {
1031 use super::*;
1032 use bytes::Bytes;
1033 use bytes::BytesMut;
1034 use pingora::cache::key::CacheHashKey;
1035 use pingora::protocols::tls::SslDigest;
1036 use pingora::protocols::tls::SslDigestExtension;
1037 use pretty_assertions::assert_eq;
1038 use std::{sync::Arc, time::Duration};
1039
1040 #[test]
1041 fn test_ctx_new() {
1042 let ctx = Ctx::new();
1043 let elapsed_ms = ctx.timing.created_at.elapsed().as_millis();
1046 assert!(elapsed_ms < 100, "created_at should be a recent timestamp");
1047 assert!(ctx.cache.is_none());
1049 assert!(ctx.features.is_none());
1050 assert_eq!(ctx.conn.id, 0);
1051 }
1052
1053 #[test]
1055 fn test_add_and_get_variable() {
1056 let mut ctx = Ctx::new();
1057 assert!(
1058 ctx.get_variable("key1").is_none(),
1059 "Should be None before adding"
1060 );
1061
1062 ctx.add_variable("key1", "value1");
1063 ctx.add_variable("key2", "value2");
1064
1065 assert_eq!(ctx.get_variable("key1"), Some("value1"));
1066 assert_eq!(ctx.get_variable("key2"), Some("value2"));
1067 assert_eq!(ctx.get_variable("nonexistent"), None);
1068 }
1069
1070 #[test]
1072 fn test_get_time_field() {
1073 let mut ctx = Ctx::new();
1074
1075 ctx.timing.upstream_response = Some(100);
1077 assert_eq!(ctx.get_upstream_response_time(), Some(100));
1078
1079 ctx.timing.upstream_response = Some(-1);
1081 assert_eq!(
1082 ctx.get_upstream_response_time(),
1083 None,
1084 "Time exceeding one hour should be None"
1085 );
1086
1087 ctx.timing.upstream_response = None;
1089 assert_eq!(ctx.get_upstream_response_time(), None);
1090 }
1091
1092 #[test]
1094 fn test_append_log_value_coverage() {
1095 let mut ctx = Ctx::new();
1096 let mut buf = BytesMut::new();
1098 ctx.append_log_value(&mut buf, "unknown_key");
1099 assert!(buf.is_empty(), "Unknown key should not append anything");
1100
1101 buf = BytesMut::new();
1103 ctx.conn.reused = true;
1104 ctx.append_log_value(&mut buf, "connection_reused");
1105 assert_eq!(&buf[..], b"true");
1106
1107 ctx.conn.tls_version = Some("TLSv1.3".into());
1109 buf = BytesMut::new();
1110 ctx.append_log_value(&mut buf, "tls_version");
1111 assert_eq!(&buf[..], b"TLSv1.3");
1112
1113 std::thread::sleep(Duration::from_millis(11));
1115 buf = BytesMut::new();
1116 ctx.append_log_value(&mut buf, "service_time");
1117 let service_time: u64 =
1118 std::str::from_utf8(&buf[..]).unwrap().parse().unwrap();
1119 assert!(service_time >= 10, "Service time should be at least 10ms");
1120 }
1121
1122 #[test]
1124 fn test_get_cache_key() {
1125 let method = "GET";
1126 let uri = Uri::from_static("https://example.com/path");
1127
1128 let ctx_no_cache = Ctx::new();
1130 let key1 = get_cache_key(&ctx_no_cache, method, &uri);
1131 assert_eq!(key1.user_tag, "");
1132 assert_eq!(key1.primary_key_str(), Some(""));
1133
1134 let mut ctx_with_ns = Ctx::new();
1136 ctx_with_ns.cache = Some(CacheInfo {
1137 namespace: Some("my-ns".to_string()),
1138 ..Default::default()
1139 });
1140 let key2 = get_cache_key(&ctx_with_ns, method, &uri);
1141 assert_eq!(key2.user_tag, "my-ns");
1142 assert_eq!(
1143 key2.primary_key_str(),
1144 Some("my-nsGET:https://example.com/path")
1145 );
1146 assert_eq!(key2.primary(), "3f45c68799da5997559d474ba4b5775c");
1150
1151 let mut ctx_with_keys = Ctx::new();
1153 ctx_with_keys.cache = Some(CacheInfo {
1154 namespace: Some("my-ns".to_string()),
1155 keys: Some(vec!["user-123".to_string(), "desktop".to_string()]),
1156 ..Default::default()
1157 });
1158 let key3 = get_cache_key(&ctx_with_keys, method, &uri);
1159 assert_eq!(key3.user_tag, "my-ns");
1160 assert_eq!(
1161 key3.primary_key_str(),
1162 Some("my-nsuser-123:desktop:GET:https://example.com/path")
1163 );
1164 }
1165
1166 #[test]
1169 fn test_generate_server_timing() {
1170 let mut ctx = Ctx::new();
1171 ctx.timing.upstream_connect = Some(1);
1172 ctx.timing.upstream_processing = Some(2);
1173 ctx.timing.cache_lookup = Some(6);
1174 ctx.timing.cache_lock = Some(7);
1175 ctx.add_plugin_processing_time(&Arc::from("plugin1"), 100);
1176
1177 let timing_header = ctx.generate_server_timing();
1178
1179 assert!(timing_header.contains("upstream.connect;dur=1"));
1181 assert!(timing_header.contains("upstream.processing;dur=2"));
1182 assert!(timing_header.contains("upstream;dur=3"));
1183 assert!(timing_header.contains("cache.lookup;dur=6"));
1184 assert!(timing_header.contains("cache.lock;dur=7"));
1185 assert!(timing_header.contains("cache;dur=13"));
1186 assert!(timing_header.contains("plugin.plugin1;dur=100"));
1187 assert!(timing_header.contains("plugin;dur=100"));
1188 assert!(timing_header.contains("total;dur="));
1189 }
1190
1191 #[test]
1192 fn test_format_duration() {
1193 let mut buf = BytesMut::new();
1194 format_duration(&mut buf, (3600 + 3500) * 1000);
1195 assert_eq!(b"1.9h", buf.as_ref());
1196
1197 buf = BytesMut::new();
1198 format_duration(&mut buf, (3600 + 1800) * 1000);
1199 assert_eq!(b"1.5h", buf.as_ref());
1200
1201 buf = BytesMut::new();
1202 format_duration(&mut buf, (3600 + 100) * 1000);
1203 assert_eq!(b"1h", buf.as_ref());
1204
1205 buf = BytesMut::new();
1206 format_duration(&mut buf, (60 + 50) * 1000);
1207 assert_eq!(b"1.8m", buf.as_ref());
1208
1209 buf = BytesMut::new();
1210 format_duration(&mut buf, (60 + 2) * 1000);
1211 assert_eq!(b"1m", buf.as_ref());
1212
1213 buf = BytesMut::new();
1214 format_duration(&mut buf, 1000);
1215 assert_eq!(b"1s", buf.as_ref());
1216
1217 buf = BytesMut::new();
1218 format_duration(&mut buf, 512);
1219 assert_eq!(b"512ms", buf.as_ref());
1220
1221 buf = BytesMut::new();
1222 format_duration(&mut buf, 1112);
1223 assert_eq!(b"1.1s", buf.as_ref());
1224 }
1225
1226 #[test]
1227 fn test_add_variable() {
1228 let mut ctx = Ctx::new();
1229 ctx.add_variable("key1", "value1");
1230 ctx.add_variable("key2", "value2");
1231 ctx.extend_variables(AHashMap::from([
1232 ("key3".to_string(), "value3".to_string()),
1233 ("key4".to_string(), "value4".to_string()),
1234 ]));
1235 let variables =
1236 ctx.features.as_ref().unwrap().variables.as_ref().unwrap();
1237 assert_eq!(variables.get("key1"), Some(&"value1".to_string()));
1240 assert_eq!(variables.get("key2"), Some(&"value2".to_string()));
1241 assert_eq!(variables.get("key3"), Some(&"value3".to_string()));
1242 assert_eq!(variables.get("key4"), Some(&"value4".to_string()));
1243 }
1244
1245 #[test]
1246 fn test_cache_key() {
1247 let mut ctx = Ctx::new();
1248 ctx.push_cache_key("key1".to_string());
1249 ctx.extend_cache_keys(vec!["key2".to_string(), "key3".to_string()]);
1250 assert_eq!(
1251 vec!["key1".to_string(), "key2".to_string(), "key3".to_string()],
1252 ctx.cache.unwrap().keys.unwrap()
1253 );
1254
1255 let mut ctx = Ctx::new();
1256 ctx.cache.get_or_insert_default();
1257 let key = get_cache_key(
1258 &ctx,
1259 "GET",
1260 &Uri::from_static("https://example.com/path"),
1261 );
1262 assert_eq!(key.user_tag, "");
1263 assert_eq!(key.primary_key_str(), Some("GET:https://example.com/path"));
1264 }
1265
1266 #[test]
1267 fn test_state() {
1268 let mut ctx = Ctx::new();
1269
1270 let mut buf = BytesMut::new();
1271 ctx.conn.id = 10;
1272 ctx.append_log_value(&mut buf, "connection_id");
1273 assert_eq!(b"10", buf.as_ref());
1274
1275 buf = BytesMut::new();
1276 ctx.append_log_value(&mut buf, "upstream_reused");
1277 assert_eq!(b"false", buf.as_ref());
1278
1279 buf = BytesMut::new();
1280 ctx.upstream.reused = true;
1281 ctx.append_log_value(&mut buf, "upstream_reused");
1282 assert_eq!(b"true", buf.as_ref());
1283
1284 buf = BytesMut::new();
1285 ctx.upstream.address = "192.168.1.1:80".to_string();
1286 ctx.append_log_value(&mut buf, "upstream_addr");
1287 assert_eq!(b"192.168.1.1:80", buf.as_ref());
1288
1289 buf = BytesMut::new();
1290 ctx.upstream.status = Some(StatusCode::CREATED);
1291 ctx.append_log_value(&mut buf, "upstream_status");
1292 assert_eq!(b"201", buf.as_ref());
1293
1294 buf = BytesMut::new();
1295 ctx.state.processing_count = 10;
1296 ctx.append_log_value(&mut buf, "processing");
1297 assert_eq!(b"10", buf.as_ref());
1298
1299 buf = BytesMut::new();
1300 ctx.timing.upstream_connect = Some(1);
1301 ctx.append_log_value(&mut buf, "upstream_connect_time");
1302 assert_eq!(b"1", buf.as_ref());
1303
1304 buf = BytesMut::new();
1305 ctx.append_log_value(&mut buf, "upstream_connect_time_human");
1306 assert_eq!(b"1ms", buf.as_ref());
1307
1308 buf = BytesMut::new();
1309 ctx.upstream.connected_count = Some(30);
1310 ctx.append_log_value(&mut buf, "upstream_connected");
1311 assert_eq!(b"30", buf.as_ref());
1312
1313 buf = BytesMut::new();
1314 ctx.timing.upstream_processing = Some(2);
1315 ctx.append_log_value(&mut buf, "upstream_processing_time");
1316 assert_eq!(b"2", buf.as_ref());
1317
1318 buf = BytesMut::new();
1319 ctx.append_log_value(&mut buf, "upstream_processing_time_human");
1320 assert_eq!(b"2ms", buf.as_ref());
1321
1322 buf = BytesMut::new();
1323 ctx.timing.upstream_response = Some(3);
1324 ctx.append_log_value(&mut buf, "upstream_response_time");
1325 assert_eq!(b"3", buf.as_ref());
1326
1327 buf = BytesMut::new();
1328 ctx.append_log_value(&mut buf, "upstream_response_time_human");
1329 assert_eq!(b"3ms", buf.as_ref());
1330
1331 buf = BytesMut::new();
1332 ctx.timing.upstream_tcp_connect = Some(100);
1333 ctx.append_log_value(&mut buf, "upstream_tcp_connect_time");
1334 assert_eq!(b"100", buf.as_ref());
1335
1336 buf = BytesMut::new();
1337 ctx.append_log_value(&mut buf, "upstream_tcp_connect_time_human");
1338 assert_eq!(b"100ms", buf.as_ref());
1339
1340 buf = BytesMut::new();
1341 ctx.timing.upstream_tls_handshake = Some(110);
1342 ctx.append_log_value(&mut buf, "upstream_tls_handshake_time");
1343 assert_eq!(b"110", buf.as_ref());
1344
1345 buf = BytesMut::new();
1346 ctx.timing.upstream_connect_offload_wait = Some(3);
1347 ctx.append_log_value(&mut buf, "upstream_connect_offload_wait_time");
1348 assert_eq!(b"3", buf.as_ref());
1349 buf = BytesMut::new();
1350 ctx.append_log_value(
1351 &mut buf,
1352 "upstream_connect_offload_wait_time_human",
1353 );
1354 assert_eq!(b"3ms", buf.as_ref());
1355
1356 buf = BytesMut::new();
1357 ctx.append_log_value(&mut buf, "upstream_tls_handshake_time_human");
1358 assert_eq!(b"110ms", buf.as_ref());
1359
1360 buf = BytesMut::new();
1361 ctx.timing.upstream_connection_duration = Some(120);
1362 ctx.append_log_value(&mut buf, "upstream_connection_time");
1363 assert_eq!(b"120", buf.as_ref());
1364
1365 buf = BytesMut::new();
1366 ctx.append_log_value(&mut buf, "upstream_connection_time_human");
1367 assert_eq!(b"120ms", buf.as_ref());
1368
1369 buf = BytesMut::new();
1370 ctx.upstream.location = "pingap".to_string().into();
1371 ctx.append_log_value(&mut buf, "location");
1372 assert_eq!(b"pingap", buf.as_ref());
1373
1374 buf = BytesMut::new();
1375 ctx.timing.connection_duration = 4;
1376 ctx.append_log_value(&mut buf, "connection_time");
1377 assert_eq!(b"4", buf.as_ref());
1378
1379 buf = BytesMut::new();
1380 ctx.append_log_value(&mut buf, "connection_time_human");
1381 assert_eq!(b"4ms", buf.as_ref());
1382
1383 buf = BytesMut::new();
1384 ctx.conn.reused = false;
1385 ctx.append_log_value(&mut buf, "connection_reused");
1386 assert_eq!(b"false", buf.as_ref());
1387
1388 buf = BytesMut::new();
1389 ctx.conn.reused = true;
1390 ctx.append_log_value(&mut buf, "connection_reused");
1391 assert_eq!(b"true", buf.as_ref());
1392
1393 buf = BytesMut::new();
1394 ctx.conn.tls_version = Some("TLSv1.3".into());
1395 ctx.append_log_value(&mut buf, "tls_version");
1396 assert_eq!(b"TLSv1.3", buf.as_ref());
1397
1398 buf = BytesMut::new();
1399 ctx.conn.tls_cipher =
1400 Some("ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".into());
1401 ctx.append_log_value(&mut buf, "tls_cipher");
1402 assert_eq!(b"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", buf.as_ref());
1403
1404 buf = BytesMut::new();
1405 ctx.timing.tls_handshake = Some(101);
1406 ctx.append_log_value(&mut buf, "tls_handshake_time");
1407 assert_eq!(b"101", buf.as_ref());
1408
1409 buf = BytesMut::new();
1410 ctx.append_log_value(&mut buf, "tls_handshake_time_human");
1411 assert_eq!(b"101ms", buf.as_ref());
1412
1413 {
1414 let features = ctx.features.get_or_insert_default();
1415 features.compression_stat = Some(CompressionStat {
1416 in_bytes: 1024,
1417 out_bytes: 500,
1418 duration: Duration::from_millis(5),
1419 ..Default::default()
1420 })
1421 }
1422
1423 buf = BytesMut::new();
1424 ctx.append_log_value(&mut buf, "compression_time");
1425 assert_eq!(b"5", buf.as_ref());
1426
1427 buf = BytesMut::new();
1428 ctx.append_log_value(&mut buf, "compression_time_human");
1429 assert_eq!(b"5ms", buf.as_ref());
1430
1431 buf = BytesMut::new();
1432 ctx.append_log_value(&mut buf, "compression_ratio");
1433 assert_eq!(b"2.0", buf.as_ref());
1434
1435 buf = BytesMut::new();
1436 ctx.timing.cache_lookup = Some(6);
1437 ctx.append_log_value(&mut buf, "cache_lookup_time");
1438 assert_eq!(b"6", buf.as_ref());
1439
1440 buf = BytesMut::new();
1441 ctx.append_log_value(&mut buf, "cache_lookup_time_human");
1442 assert_eq!(b"6ms", buf.as_ref());
1443
1444 buf = BytesMut::new();
1445 ctx.timing.cache_lock = Some(7);
1446 ctx.append_log_value(&mut buf, "cache_lock_time");
1447 assert_eq!(b"7", buf.as_ref());
1448
1449 buf = BytesMut::new();
1450 ctx.append_log_value(&mut buf, "cache_lock_time_human");
1451 assert_eq!(b"7ms", buf.as_ref());
1452 }
1453
1454 #[test]
1455 fn test_add_plugin_processing_time() {
1456 let mut ctx = Ctx::new();
1457 ctx.add_plugin_processing_time(&Arc::from("plugin1"), 100);
1458 ctx.add_plugin_processing_time(&Arc::from("plugin2"), 200);
1459 assert_eq!(
1460 ctx.features.unwrap().plugin_processing_times,
1461 Some(vec![
1462 (Arc::from("plugin1"), 100),
1463 (Arc::from("plugin2"), 200)
1464 ])
1465 );
1466 }
1467
1468 #[test]
1469 fn test_get_digest_detail() {
1470 let mut digest = Digest::default();
1471 let detail = get_digest_detail(&digest);
1472 assert_eq!(detail.connection_reused, false);
1473 assert_eq!(detail.connection_time, 0);
1474 assert_eq!(detail.tcp_established, 0);
1475 assert_eq!(detail.tls_established, 0);
1476 assert_eq!(detail.tls_version, None);
1477 assert_eq!(detail.tls_cipher, None);
1478
1479 digest.timing_digest.push(Some(TimingDigest {
1480 established_ts: SystemTime::UNIX_EPOCH
1481 .checked_add(Duration::from_secs(5))
1482 .unwrap(),
1483 ..Default::default()
1484 }));
1485 digest.timing_digest.push(Some(TimingDigest {
1486 established_ts: SystemTime::UNIX_EPOCH
1487 .checked_add(Duration::from_secs(3))
1488 .unwrap(),
1489 ..Default::default()
1490 }));
1491 digest.ssl_digest = Some(Arc::new(SslDigest {
1492 version: "1.3".into(),
1493 cipher: "123".into(),
1494 organization: Some("cloudflare".to_string()),
1495 serial_number: Some(
1496 "0x00000000000000000000000000000abc".to_string(),
1497 ),
1498 cert_digest: vec![],
1499 extension: SslDigestExtension::default(),
1500 }));
1501 let detail = get_digest_detail(&digest);
1502 assert_eq!(detail.connection_reused, true);
1503 assert_eq!(detail.tcp_established, 5000);
1504 assert_eq!(detail.tls_established, 3000);
1505 assert_eq!(detail.tls_version.as_deref(), Some("1.3"));
1506 assert_eq!(detail.tls_cipher.as_deref(), Some("123"));
1507 assert_eq!(detail.tcp_connect, None);
1509 assert_eq!(detail.tls_handshake, None);
1510
1511 digest.timing_digest = vec![
1513 Some(TimingDigest {
1514 establishment_duration: Some(Duration::from_millis(12)),
1515 ..Default::default()
1516 }),
1517 Some(TimingDigest {
1518 establishment_duration: Some(Duration::from_millis(34)),
1519 ..Default::default()
1520 }),
1521 ];
1522 let detail = get_digest_detail(&digest);
1523 assert_eq!(detail.tcp_connect, Some(12));
1524 assert_eq!(detail.tls_handshake, Some(34));
1525 assert_eq!(detail.connect_offload_wait, None);
1527
1528 digest.timing_digest[0] = Some(TimingDigest {
1530 establishment_duration: Some(Duration::from_millis(12)),
1531 offload_wait_duration: Some(Duration::from_millis(2)),
1532 ..Default::default()
1533 });
1534 let detail = get_digest_detail(&digest);
1535 assert_eq!(detail.connect_offload_wait, Some(2));
1536
1537 digest.ssl_digest = None;
1539 digest.timing_digest.truncate(1);
1540 let detail = get_digest_detail(&digest);
1541 assert_eq!(detail.tcp_connect, Some(12));
1542 assert_eq!(detail.tls_handshake, None);
1543 }
1544
1545 #[test]
1546 fn test_update_upstream_timing_from_digest() {
1547 let measured = |tcp: u64, tls: u64| Digest {
1548 timing_digest: vec![
1549 Some(TimingDigest {
1550 establishment_duration: Some(Duration::from_millis(tcp)),
1551 ..Default::default()
1552 }),
1553 Some(TimingDigest {
1554 establishment_duration: Some(Duration::from_millis(tls)),
1555 ..Default::default()
1556 }),
1557 ],
1558 ssl_digest: Some(Arc::new(SslDigest {
1559 version: "1.3".into(),
1560 cipher: "123".into(),
1561 organization: None,
1562 serial_number: None,
1563 cert_digest: vec![],
1564 extension: SslDigestExtension::default(),
1565 })),
1566 ..Default::default()
1567 };
1568
1569 let mut ctx = Ctx::new();
1572 ctx.timing.upstream_connect = Some(100);
1573 ctx.update_upstream_timing_from_digest(&measured(12, 34), false);
1574 assert_eq!(Some(12), ctx.timing.upstream_tcp_connect);
1575 assert_eq!(Some(34), ctx.timing.upstream_tls_handshake);
1576 assert_eq!(None, ctx.timing.upstream_connect_offload_wait);
1577
1578 let mut ctx = Ctx::new();
1581 let mut offloaded = measured(12, 34);
1582 offloaded.timing_digest[0] = Some(TimingDigest {
1583 establishment_duration: Some(Duration::from_millis(12)),
1584 offload_wait_duration: Some(Duration::from_millis(2)),
1585 ..Default::default()
1586 });
1587 ctx.update_upstream_timing_from_digest(&offloaded, false);
1588 assert_eq!(Some(2), ctx.timing.upstream_connect_offload_wait);
1589
1590 let mut ctx = Ctx::new();
1592 ctx.timing.upstream_connect = Some(100);
1593 ctx.update_upstream_timing_from_digest(&measured(12, 34), true);
1594 assert_eq!(None, ctx.timing.upstream_tcp_connect);
1595 assert_eq!(None, ctx.timing.upstream_tls_handshake);
1596
1597 let mut ctx = Ctx::new();
1600 ctx.timing.upstream_connect = Some(100);
1601 let mut unmeasured = measured(0, 0);
1602 unmeasured.timing_digest = vec![
1603 Some(TimingDigest {
1604 established_ts: SystemTime::UNIX_EPOCH
1605 .checked_add(Duration::from_millis(1_000))
1606 .unwrap(),
1607 ..Default::default()
1608 }),
1609 Some(TimingDigest {
1610 established_ts: SystemTime::UNIX_EPOCH
1611 .checked_add(Duration::from_millis(1_030))
1612 .unwrap(),
1613 ..Default::default()
1614 }),
1615 ];
1616 ctx.update_upstream_timing_from_digest(&unmeasured, false);
1617 assert_eq!(Some(70), ctx.timing.upstream_tcp_connect);
1618 assert_eq!(Some(30), ctx.timing.upstream_tls_handshake);
1619 }
1620
1621 #[test]
1622 fn test_modify_body_handler() {
1623 let mut ctx = Ctx::default();
1624
1625 struct TestHandler {}
1626 impl ModifyResponseBody for TestHandler {
1627 fn handle(
1628 &mut self,
1629 _session: &Session,
1630 body: &mut Option<bytes::Bytes>,
1631 _end_of_stream: bool,
1632 ) -> pingora::Result<()> {
1633 *body = Some(Bytes::from("test"));
1634 Ok(())
1635 }
1636 }
1637
1638 ctx.add_modify_body_handler("test", Box::new(TestHandler {}));
1639 assert_eq!(true, ctx.get_modify_body_handler("test").is_some());
1640 }
1641}