1use std::collections::VecDeque;
13use std::time::{SystemTime, UNIX_EPOCH};
14
15use crate::Commands;
16use crate::message::{Agg, Op, Part};
17use crate::shard::Shard;
18use kevy_resp::{ArgvView, encode_array_len, encode_bulk, encode_integer};
19
20#[derive(Debug, Clone)]
22pub struct SlowlogEntry {
23 pub id: u64,
27 pub timestamp_secs: i64,
29 pub micros: u64,
31 pub argv: Vec<Vec<u8>>,
35 pub client_addr: Vec<u8>,
39 pub client_name: Vec<u8>,
41}
42
43pub(crate) struct SlowlogState {
46 pub(crate) buf: VecDeque<SlowlogEntry>,
47 pub(crate) slower_than_micros: i64,
52 pub(crate) max_len: u32,
54 pub(crate) next_local_seq: u64,
57}
58
59impl SlowlogState {
60 pub(crate) fn new(slower_than_micros: i64, max_len: u32) -> Self {
61 Self {
62 buf: VecDeque::with_capacity(max_len.min(1024) as usize),
63 slower_than_micros,
64 max_len,
65 next_local_seq: 0,
66 }
67 }
68}
69
70const MAX_ARGV_RECORDED: usize = 32;
74
75const MAX_ARG_BYTES_RECORDED: usize = 128;
78
79impl<C: Commands> Shard<C> {
80 #[inline]
86 pub(crate) fn slowlog_record<A: ArgvView + ?Sized>(&mut self, args: &A, elapsed_micros: u64) {
87 let threshold = self.slowlog.slower_than_micros;
88 if threshold < 0 {
89 return;
90 }
91 if (elapsed_micros as i64) < threshold {
97 return;
98 }
99 let local_seq = self.slowlog.next_local_seq;
100 self.slowlog.next_local_seq = self.slowlog.next_local_seq.wrapping_add(1);
101 let id = ((self.id as u64) << 48) | (local_seq & 0x0000_FFFF_FFFF_FFFF);
104 let timestamp_secs =
105 SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |d| d.as_secs() as i64);
106 let mut argv: Vec<Vec<u8>> = Vec::with_capacity(args.len().min(MAX_ARGV_RECORDED));
107 for i in 0..args.len().min(MAX_ARGV_RECORDED) {
108 let a = &args[i];
109 if a.len() > MAX_ARG_BYTES_RECORDED {
110 argv.push(a[..MAX_ARG_BYTES_RECORDED].to_vec());
111 } else {
112 argv.push(a.to_vec());
113 }
114 }
115 self.slowlog.buf.push_back(SlowlogEntry {
116 id,
117 timestamp_secs,
118 micros: elapsed_micros,
119 argv,
120 client_addr: Vec::new(),
121 client_name: Vec::new(),
122 });
123 let cap = self.slowlog.max_len as usize;
124 while self.slowlog.buf.len() > cap {
125 self.slowlog.buf.pop_front();
126 }
127 }
128
129 pub(crate) fn start_slowlog(&mut self, conn_id: u64, seq: u64, sub: SlowlogSub) {
133 match sub {
134 SlowlogSub::Help => self.slowlog_immediate(conn_id, seq, slowlog_help_bytes()),
135 SlowlogSub::Err(b) => self.slowlog_immediate(conn_id, seq, b),
136 SlowlogSub::Reset => {
137 self.slowlog_fanout(conn_id, seq, Agg::AllOk, || Op::SlowlogReset);
138 }
139 SlowlogSub::Len => {
140 self.slowlog_fanout(conn_id, seq, Agg::SumInt(0), || Op::SlowlogLen);
141 }
142 SlowlogSub::Get(count) => self.slowlog_fanout(
143 conn_id,
144 seq,
145 Agg::SlowlogGet { count, entries: Vec::new() },
146 || Op::SlowlogGet,
147 ),
148 }
149 }
150
151 fn slowlog_immediate(&mut self, conn_id: u64, seq: u64, bytes: Vec<u8>) {
152 self.push_pending_slot(conn_id, 1, Agg::First(None), false);
153 self.fold(conn_id, seq, Part::Reply(crate::message::SmallReply::from_vec(bytes)));
154 }
155
156 fn slowlog_fanout(&mut self, conn_id: u64, seq: u64, agg: Agg, mk_op: impl Fn() -> Op) {
157 let targets: Vec<(usize, Op)> = (0..self.nshards).map(|s| (s, mk_op())).collect();
158 self.push_pending_slot(conn_id, targets.len() as u32, agg, false);
159 self.dispatch_targets(conn_id, seq, targets);
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
166pub enum SlowlogSub {
167 Get(Option<i64>),
170 Len,
172 Reset,
174 Help,
176 Err(Vec<u8>),
180}
181
182pub(crate) fn encode_slowlog_get(count: Option<i64>, mut entries: Vec<SlowlogEntry>) -> Vec<u8> {
188 entries.sort_by(|a, b| b.timestamp_secs.cmp(&a.timestamp_secs).then_with(|| b.id.cmp(&a.id)));
189 let limit = match count {
190 None => 10,
191 Some(n) if n < 0 => entries.len(),
192 Some(n) => n as usize,
193 };
194 let n = entries.len().min(limit);
195 let mut out = Vec::with_capacity(64 + n * 64);
196 encode_array_len(&mut out, n as i64);
197 for e in entries.iter().take(n) {
198 encode_array_len(&mut out, 6);
199 encode_integer(&mut out, e.id as i64);
200 encode_integer(&mut out, e.timestamp_secs);
201 encode_integer(&mut out, e.micros as i64);
202 encode_array_len(&mut out, e.argv.len() as i64);
203 for a in &e.argv {
204 encode_bulk(&mut out, a);
205 }
206 encode_bulk(&mut out, &e.client_addr);
207 encode_bulk(&mut out, &e.client_name);
208 }
209 out
210}
211
212pub(crate) fn slowlog_help_bytes() -> Vec<u8> {
214 const LINES: &[&str] = &[
215 "SLOWLOG <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
216 "GET [<count>]",
217 " Return top <count> entries from the slowlog (default: 10, -1 mean all).",
218 " Entries are made of:",
219 " id, timestamp, time in microseconds, arguments array, client IP and port,",
220 " client name",
221 "LEN",
222 " Return the length of the slowlog.",
223 "RESET",
224 " Reset the slowlog.",
225 "HELP",
226 " Print this help.",
227 ];
228 let mut out = Vec::with_capacity(512);
229 encode_array_len(&mut out, LINES.len() as i64);
230 for l in LINES {
231 encode_bulk(&mut out, l.as_bytes());
232 }
233 out
234}
235
236pub fn parse_slowlog_sub<A: ArgvView + ?Sized>(args: &A) -> SlowlogSub {
241 let Some(sub) = args.get(1) else {
242 return SlowlogSub::Err(slowlog_err_bytes("wrong number of arguments for 'slowlog'"));
243 };
244 let mut buf = [0u8; 16];
245 let upper = ascii_upper_into(sub, &mut buf);
246 match upper {
247 b"GET" => parse_slowlog_get(args),
248 b"LEN" if args.len() == 2 => SlowlogSub::Len,
249 b"RESET" if args.len() == 2 => SlowlogSub::Reset,
250 b"HELP" => SlowlogSub::Help,
251 b"LEN" | b"RESET" => SlowlogSub::Err(slowlog_arg_count_err(upper)),
252 _ => SlowlogSub::Err(slowlog_unknown_sub_err(sub)),
253 }
254}
255
256fn parse_slowlog_get<A: ArgvView + ?Sized>(args: &A) -> SlowlogSub {
257 if args.len() == 2 {
258 return SlowlogSub::Get(None);
259 }
260 if args.len() != 3 {
261 return SlowlogSub::Err(slowlog_err_bytes("wrong number of arguments for 'slowlog|get'"));
262 }
263 match std::str::from_utf8(&args[2]).ok().and_then(|s| s.parse::<i64>().ok()) {
264 Some(n) => SlowlogSub::Get(Some(n)),
265 None => SlowlogSub::Err(slowlog_err_bytes("value is not an integer or out of range")),
266 }
267}
268
269fn slowlog_arg_count_err(sub_upper: &[u8]) -> Vec<u8> {
270 let lower: String = sub_upper.iter().map(|b| b.to_ascii_lowercase() as char).collect();
271 slowlog_err_bytes(&format!("wrong number of arguments for 'slowlog|{lower}'"))
272}
273
274fn slowlog_unknown_sub_err(sub: &[u8]) -> Vec<u8> {
275 let msg = format!(
276 "ERR Unknown SLOWLOG subcommand or wrong number of arguments for '{}'",
277 String::from_utf8_lossy(sub),
278 );
279 let mut out = Vec::with_capacity(msg.len() + 3);
280 out.push(b'-');
281 out.extend_from_slice(msg.as_bytes());
282 out.extend_from_slice(b"\r\n");
283 out
284}
285
286fn slowlog_err_bytes(msg: &str) -> Vec<u8> {
287 let mut out = Vec::with_capacity(msg.len() + 7);
288 out.extend_from_slice(b"-ERR ");
289 out.extend_from_slice(msg.as_bytes());
290 out.extend_from_slice(b"\r\n");
291 out
292}
293
294fn ascii_upper_into<'a>(src: &[u8], buf: &'a mut [u8; 16]) -> &'a [u8] {
295 let n = src.len().min(buf.len());
296 for i in 0..n {
297 buf[i] = src[i].to_ascii_uppercase();
298 }
299 &buf[..n]
300}