yo_resp/dispatch/debug.rs
1//! `DEBUG`, the container a test suite talks to rather than a client.
2//!
3//! # What it is for
4//!
5//! Every other command here exists so that somebody can store something and get
6//! it back. This one exists so that somebody can make the server do a thing that
7//! would otherwise be impossible to arrange from the outside: send a reply of a
8//! type no ordinary command sends, stop sweeping expired keys, stop the clock
9//! work, fill a database with a hundred thousand keys without a hundred thousand
10//! round trips, or answer with an error whose text the caller chose.
11//!
12//! Redis's own test suite leans on it heavily, which is why it is here at all:
13//! most of the suite's `assert_encoding` and expiry tests do not run at all
14//! against a server that has no `DEBUG`.
15//!
16//! # Which subcommands are here
17//!
18//! A real server has around sixty and most of them are about parts that do not
19//! exist here: the AOF, cluster links, atomic slot migration, forking, crashing
20//! on purpose. What is here is the part that is about this server, and `DEBUG
21//! HELP` lists exactly that rather than listing what Redis has, for the same
22//! reason `CLIENT HELP` does: somebody reading it to find out what they can send
23//! should not be told about a subcommand that would come back unknown.
24//!
25//! The four knobs are the interesting ones, because a knob that is remembered
26//! and read by nothing is worse than no knob at all. Three of them really move
27//! something: `SET-ACTIVE-EXPIRE` gates the sweep that reclaims keys nobody asks
28//! for again, `PAUSE-CRON` gates the whole maintenance slice the shard loop runs
29//! between batches, and `SET-SKIP-CHECKSUM-VALIDATION` is read by the code that
30//! opens a `RESTORE` payload. `DICT-RESIZING` gates arena compaction, which is
31//! the nearest thing here to the dictionary resize it turns off on a real
32//! server: both are the background reclaim of room a table no longer needs. The
33//! one that is remembered and does nothing is
34//! `QUICKLIST-PACKED-THRESHOLD`, which is D-128.
35//!
36//! `RELOAD` is the odd one out, because it moves the whole dataset rather than a
37//! knob. It is here for the same reason the knobs are: the suite calls it after
38//! almost every case, and a case that passes on both sides of it has proved that
39//! the writer and the reader of the file agree about the value it just made.
40//!
41//! Then there are the four that only look: `OBJECT`, `SDSLEN`, `LISTPACK` and
42//! `QUICKLIST`. None of them change anything and none of them count as a use of
43//! the key they are about, which is the property that makes them worth having at
44//! all. A suite that wants to know how big a value really is, or how many nodes
45//! a list broke into, has nowhere else to ask, because everything on the ordinary
46//! command surface answers about the value rather than about the way it is
47//! written down.
48//!
49//! `DIGEST` and `DIGEST-VALUE` only look as well, and they are the pair the
50//! suite leans on hardest. One number for a whole server, another for one value,
51//! and the whole worth of both is that another server computes them the same
52//! way, so the recipe in [`yo_kv::digest`] is copied to the byte. Everything the
53//! suite checks after a reload, a replica catching up or a rewrite comes down to
54//! holding two of these next to each other.
55//!
56//! # How the errors work
57//!
58//! Every complaint in this file is the same sentence, `unknown subcommand or
59//! wrong number of arguments for '<what was sent>'. Try DEBUG HELP.`, and that
60//! is not a shortcut. A real server's `DEBUG` is a chain of `strcasecmp` tests
61//! each of which also checks `argc`, and anything that falls off the end of the
62//! chain gets that one line, so a subcommand that does not exist and a
63//! subcommand handed the wrong number of arguments are the same case. The name
64//! is echoed in the case it was sent in.
65//!
66//! The two exceptions are the two subcommands that read their argument and can
67//! fail on the value rather than on the count, which are
68//! `QUICKLIST-PACKED-THRESHOLD` and `POPULATE`, and each has its own sentence.
69
70use core::fmt::Write as _;
71use std::sync::atomic::AtomicU64;
72use std::sync::atomic::Ordering::Relaxed;
73
74use yo_common::num::parse_i64;
75use yo_common::{Code, Error, Result};
76use yo_kv::{SetOptions, digest, lookups};
77
78use super::args::{self, Args, is};
79use super::{Server, Session, persist};
80use crate::reply::Out;
81
82/// The knobs `DEBUG` turns, all of them on a word each.
83///
84/// One word rather than a lock because the readers are the shard loop's
85/// maintenance slice and the payload reader, which is to say the hottest places
86/// that could possibly read a debugging flag, and the writer is a human at a
87/// test suite. The three gates are stored as their `true` meaning, so a default
88/// `Knobs` is a server with everything running.
89#[derive(Debug)]
90pub(crate) struct Knobs {
91 /// Whether the expiry sweep runs, which `SET-ACTIVE-EXPIRE 0` turns off.
92 expiring: AtomicU64,
93 /// Whether the maintenance slice runs at all, which `PAUSE-CRON 1` stops.
94 cron: AtomicU64,
95 /// Whether arena compaction runs, which `DICT-RESIZING 0` stops.
96 resizing: AtomicU64,
97 /// The packed node threshold, which nothing here reads. See D-128.
98 packed: AtomicU64,
99}
100
101impl Default for Knobs {
102 fn default() -> Knobs {
103 Knobs {
104 expiring: AtomicU64::new(1),
105 cron: AtomicU64::new(1),
106 resizing: AtomicU64::new(1),
107 packed: AtomicU64::new(DEFAULT_PACKED),
108 }
109 }
110}
111
112/// What the packed threshold goes back to when it is set to nought, which is a
113/// gigabyte and is Redis's default.
114const DEFAULT_PACKED: u64 = 1 << 30;
115
116/// The largest packed threshold that is taken, which is four gigabytes less a
117/// megabyte.
118///
119/// Redis's `quicklistSetPackedThreshold` refuses anything above this, with a
120/// comment saying it will not allow the threshold even slightly below four
121/// gigabytes. The error text says bigger than one and smaller than 4gb, and
122/// neither half of that sentence is quite what the code checks, since one is
123/// taken and `4294967295` is not.
124const MAX_PACKED: u64 = (1 << 32) - (1 << 20);
125
126impl Server {
127 /// Whether the expiry sweep should run.
128 #[must_use]
129 pub(crate) fn expiring(&self) -> bool {
130 self.debug.expiring.load(Relaxed) != 0
131 }
132
133 /// Whether the maintenance slice should run at all.
134 #[must_use]
135 pub fn cron_running(&self) -> bool {
136 self.debug.cron.load(Relaxed) != 0
137 }
138
139 /// Whether arena compaction should run.
140 #[must_use]
141 pub(crate) fn resizing(&self) -> bool {
142 self.debug.resizing.load(Relaxed) != 0
143 }
144}
145
146/// `DEBUG <subcommand> [...]`.
147pub(super) fn execute(
148 server: &Server,
149 session: &mut Session,
150 args: Args<'_>,
151 out: &mut Out,
152) -> Result<()> {
153 let sub = args.get(1);
154 if is(sub, b"HELP") && args.len() == 2 {
155 super::server::help(out, HELP);
156 } else if is(sub, b"PROTOCOL") && args.len() == 3 {
157 return protocol(args.get(2), out);
158 } else if is(sub, b"ERROR") && args.len() == 3 {
159 // Straight out, with no code in front of it and no checking of what is
160 // in it beyond the newlines, because the whole point is to hand a client
161 // library an error line it chose. The empty prefix is there because this
162 // is the one error line the server did not write any of, and the newline
163 // folding that comes with it is what a real server does too and is what
164 // stops this from being a way to write two replies with one command.
165 out.error_line(b"", args.get(2));
166 } else if is(sub, b"LOG") && args.len() == 3 {
167 // The server log is stderr here, which is what the service file or the
168 // shell redirection points wherever the operator wants it.
169 yo_alloc::allow(|| {
170 eprintln!("yodb: DEBUG LOG: {}", String::from_utf8_lossy(args.get(2)));
171 });
172 out.ok();
173 } else if is(sub, b"SLEEP") && args.len() == 3 {
174 sleep(args.get(2));
175 out.ok();
176 } else if is(sub, b"POPULATE") && (3..=5).contains(&args.len()) {
177 return populate(server, session, args, out);
178 } else if is(sub, b"SET-ACTIVE-EXPIRE") && args.len() == 3 {
179 server.debug.expiring.store(flag(args.get(2)), Relaxed);
180 out.ok();
181 } else if is(sub, b"PAUSE-CRON") && args.len() == 3 {
182 // The one gate that is stored the other way up from how it is written,
183 // because the subcommand names the stopping and the field names the
184 // running.
185 server.debug.cron.store(1 - flag(args.get(2)), Relaxed);
186 out.ok();
187 } else if is(sub, b"DICT-RESIZING") && args.len() == 3 {
188 server.debug.resizing.store(flag(args.get(2)), Relaxed);
189 out.ok();
190 } else if is(sub, b"SET-SKIP-CHECKSUM-VALIDATION") && args.len() == 3 {
191 yo_kv::rdb::skip_checksums(flag(args.get(2)) != 0);
192 out.ok();
193 } else if is(sub, b"QUICKLIST-PACKED-THRESHOLD") && args.len() == 3 {
194 return packed(server, args.get(2), out);
195 } else if is(sub, b"RELOAD") {
196 return reload(server, args, out);
197 } else if is(sub, b"OBJECT") && args.len() == 3 {
198 return object(server, session, args.get(2), out);
199 } else if is(sub, b"SDSLEN") && args.len() == 3 {
200 return sdslen(server, session, args.get(2), out);
201 } else if is(sub, b"LISTPACK") && args.len() == 3 {
202 return packing(server, session, args.get(2), Packing::Listpack, out);
203 } else if is(sub, b"QUICKLIST") && (3..=4).contains(&args.len()) {
204 return packing(server, session, args.get(2), Packing::Quicklist, out);
205 } else if is(sub, b"MARK-INTERNAL-CLIENT")
206 && (args.len() == 2 || (args.len() == 3 && is(args.get(2), b"UNMARK")))
207 {
208 // The one way to become a node of the cluster without being one, which
209 // is what a test uses to drive the slot migration protocol by hand
210 // rather than standing up a second server.
211 session.serve_internal(args.len() == 2);
212 out.ok();
213 } else if is(sub, b"INTERNAL_SECRET") && args.len() == 2 {
214 // The secret itself never leaves the server, which is the point of it,
215 // so what comes back is a checksum of it. That is enough for a test to
216 // see that two nodes have converged on the same one and no use at all to
217 // anybody trying to log in with it.
218 let secret = server.cluster_secret();
219 if secret.is_empty() {
220 return Err(Error::new(Code::Invalid, "Internal secret is missing"));
221 }
222 out.int(i64::from(yo_common::crc::crc16(secret.as_bytes())));
223 } else if is(sub, b"CHANGE-REPL-ID") && args.len() == 2 {
224 server.change_id();
225 out.ok();
226 } else if is(sub, b"DIGEST") && args.len() == 2 {
227 whole_digest(server, out);
228 } else if is(sub, b"DIGEST-VALUE") {
229 value_digests(server, session, args, out);
230 } else {
231 return Err(args::subcommand_syntax(sub, "DEBUG"));
232 }
233 Ok(())
234}
235
236/// A `0` or `1` argument, read the way C reads one.
237///
238/// Which is `atoi`, so anything that is not a number at all is nought and the
239/// gate goes off. That is worth reproducing rather than tidying up, because a
240/// test suite that sends `DEBUG SET-ACTIVE-EXPIRE no` gets a server with the
241/// sweep turned off on a real server and would get one with it left on here if
242/// this refused what it could not read.
243fn flag(value: &[u8]) -> u64 {
244 let value = value.strip_prefix(b"-").unwrap_or(value);
245 let digits = value
246 .iter()
247 .take_while(|b| b.is_ascii_digit())
248 .fold(0u64, |n, b| {
249 n.saturating_mul(10).saturating_add(u64::from(b - b'0'))
250 });
251 u64::from(digits != 0)
252}
253
254/// `DEBUG SLEEP <seconds>`, which stops this thread where it stands.
255///
256/// Decimals allowed and read with C's `strtod`, so a word is nought seconds and
257/// a negative number is nought seconds, and both answer `OK` at once. There is
258/// no upper bound, which is the point: a suite that wants a server that does not
259/// answer for ten seconds asks for ten seconds.
260///
261/// On a server with one shard thread, which is the default, this is the whole
262/// server, which is what it is on Redis. Above one thread it is the thread this
263/// connection landed on and the others keep answering, which is D-129.
264fn sleep(value: &[u8]) {
265 let text = core::str::from_utf8(value).unwrap_or("");
266 let seconds = leading_double(text);
267 if seconds > 0.0 {
268 std::thread::sleep(std::time::Duration::from_secs_f64(seconds));
269 }
270}
271
272/// As much of the front of `text` as reads as a double, or nought.
273///
274/// `strtod` takes the longest prefix that is a number and stops, so `1.5s` is a
275/// second and a half and `abc` is nothing. Rust's parser wants the whole string,
276/// so the prefix is found here.
277fn leading_double(text: &str) -> f64 {
278 let mut end = 0;
279 for (at, _) in text.char_indices() {
280 if text[..=at].parse::<f64>().is_ok() {
281 end = at + 1;
282 }
283 }
284 text[..end].parse().unwrap_or(0.0)
285}
286
287/// `DEBUG QUICKLIST-PACKED-THRESHOLD <size>`.
288fn packed(server: &Server, value: &[u8], out: &mut Out) -> Result<()> {
289 let size = super::server::parse_memory(value).filter(|&n| n <= MAX_PACKED);
290 let Some(size) = size else {
291 return Err(Error::new(
292 Code::Invalid,
293 "argument must be a memory value bigger than 1 and smaller than 4gb",
294 ));
295 };
296 // Nought is not a threshold of nothing, it is the word for putting the
297 // default back, which is the one part of this subcommand that is not
298 // guessable from its name.
299 let size = if size == 0 { DEFAULT_PACKED } else { size };
300 server.debug.packed.store(size, Relaxed);
301 out.ok();
302 Ok(())
303}
304
305/// What a reload says when the file did not come back.
306///
307/// One sentence for every way it can go wrong, which is the reference's answer
308/// too. A client can do nothing with the difference between a bad checksum and a
309/// file that stops halfway, and whoever can is reading the log, so that is where
310/// the reason goes.
311const LOAD_FAILED: &str = "Error trying to load the RDB dump, check server logs.";
312
313/// `DEBUG RELOAD [MERGE] [NOFLUSH] [NOSAVE]`, the round trip a suite leans on.
314///
315/// Write the whole dataset out as an RDB, throw away what is in memory and build
316/// it again out of the file. It is here because Redis's own suite calls it after
317/// almost every case: a value that comes back the same way it went in has proved
318/// its writer and its reader agree, and a value that does not has found a bug in
319/// one of them without anybody having to say which.
320///
321/// The three options are the reference's three. `NOSAVE` skips the write and
322/// reads whatever file is already on disk, which is how a suite loads a file it
323/// put there itself. `NOFLUSH` keeps what is in memory and lets the file land on
324/// top of it. `MERGE` is read and changes nothing here, and that is D-131: on a
325/// real server it is what makes a key that is in the file and in memory legal,
326/// and without it the server takes itself down with `Duplicated key found in RDB
327/// file`. A key arriving over one that is already there is an ordinary import
328/// here, so there is nothing for the word to turn on.
329///
330/// The other difference from a real server is the window. Redis forks for the
331/// save and has one thread for the load, so nothing can write in between. Here
332/// the save walks one stripe at a time and another connection can write to a
333/// stripe that has already been walked, which is D-132 and is the same window
334/// [`super::persist::build`] already has for `SAVE`.
335fn reload(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
336 let (mut save, mut flush) = (true, true);
337 for i in 2..args.len() {
338 let word = args.get(i);
339 if is(word, b"NOSAVE") {
340 save = false;
341 } else if is(word, b"NOFLUSH") {
342 flush = false;
343 } else if !is(word, b"MERGE") {
344 return Err(Error::new(
345 Code::Invalid,
346 "DEBUG RELOAD only supports the MERGE, NOFLUSH and NOSAVE options.",
347 ));
348 }
349 }
350 if save {
351 if !persist::write_file(server) {
352 // The bare line `SAVE` answers, for the reason it gives.
353 out.error(b"ERR");
354 return Ok(());
355 }
356 // A key with no RDB shape is not in the file that was just written, so
357 // flushing and reading it back would be a way of deleting it. Nothing on
358 // a real server can be in this position, which is why the sentence is
359 // ours: the reply says which way out there is rather than leaving the
360 // caller to find out from a `DBSIZE` that came back short.
361 let lost = persist::skipped(server);
362 if flush && lost > 0 {
363 return Err(if lost == 1 {
364 Error::new(
365 Code::Invalid,
366 "DEBUG RELOAD would drop 1 key with no RDB form, use NOFLUSH to keep it",
367 )
368 } else {
369 Error::fmt(
370 Code::Invalid,
371 format_args!(
372 "DEBUG RELOAD would drop {lost} keys with no RDB form, use NOFLUSH to keep them"
373 ),
374 )
375 });
376 }
377 }
378 if yo_alloc::allow(|| load_file(server, flush)) {
379 out.ok();
380 Ok(())
381 } else {
382 Err(Error::new(Code::Invalid, LOAD_FAILED))
383 }
384}
385
386/// Read `dump.rdb` back over the keyspace, and say whether all of it landed.
387///
388/// The walk itself is [`Server::load_image`], which is shared with the restore
389/// the tool does at startup. What is here is the two things that are this
390/// command's own: the file it reads is always the one `SAVE` writes, and a
391/// reason it could not is a line in the log rather than a sentence to a client,
392/// for the reason [`LOAD_FAILED`] gives.
393///
394/// The libraries the file carries are counted and dropped rather than loaded.
395/// They are already here, because the flush above takes the databases and not
396/// the function registry, and loading a library that is already registered is an
397/// error rather than a no op.
398fn load_file(server: &Server, flush: bool) -> bool {
399 let path = server.dir().join(persist::FILE);
400 let image = match std::fs::read(&path) {
401 Ok(image) => image,
402 Err(e) => {
403 eprintln!("yodb: DEBUG RELOAD: {}: {e}", path.display());
404 return false;
405 }
406 };
407 match server.load_image(&image, flush) {
408 Ok(_) => true,
409 Err(refused) => {
410 eprintln!("yodb: DEBUG RELOAD: {refused}");
411 false
412 }
413 }
414}
415
416/// What every one of the inspection subcommands says about a key that is not
417/// there.
418///
419/// `OBJECT` is the odd one out among the key commands generally, since
420/// `OBJECT ENCODING` on a missing key is a nil rather than this. `DEBUG OBJECT`
421/// is not `OBJECT` and answers the error, which is checked rather than assumed.
422const NO_SUCH_KEY: &str = "no such key";
423
424/// The LRU clock is twenty four bits of seconds, and wraps every 194 days.
425///
426/// A real server keeps the same three bytes for the same reason it is worth
427/// keeping here: the field lives inside the object header next to the type and
428/// the encoding, and a client that reads it is comparing two of them rather than
429/// reading it as a date.
430const LRU_CLOCK_MAX: u64 = (1 << 24) - 1;
431
432/// What a `DUMP` payload carries that the value itself is not.
433///
434/// One type byte in front, then two bytes of RDB version and eight of checksum
435/// behind. `serializedlength` is the body between them, which is what
436/// `rdbSavedObjectLen` counts on a real server, so taking these off the payload
437/// this server already knows how to build is the whole of that number.
438const DUMP_AROUND: usize = 11;
439
440/// `DEBUG OBJECT <key>`, the low level line about one value.
441///
442/// Seven fields, or twelve for a quicklist. Three of them are about the value as
443/// bytes, which is the encoding, the serialized length and the quicklist shape,
444/// and those are the ones a person actually reads. The rest are about the object
445/// header a real server keeps: the address it is at, how many things point at it
446/// and when it was last touched.
447///
448/// `serializedlength` is the value's RDB body, without the type byte in front of
449/// it and without the version and checksum a `DUMP` puts behind it. That is
450/// `rdbSavedObjectLen` on a real server and it means the same thing here, so a
451/// value this build writes differently is a value with a different number, and
452/// the five list shapes D-111 already covers are the ones that differ.
453///
454/// `refcount` is one, always, for the reason `OBJECT REFCOUNT` gives. `at` is
455/// where the record sits rather than where an object header would, which is
456/// D-134: see [`yo_kv::keyspace::Keyspace::value_address`] for why that is the
457/// same answer to the question anybody asks it.
458///
459/// `lru` is the same clock `OBJECT IDLETIME` counts back from, so the two agree
460/// by construction: the clock now, less the seconds the key has been idle,
461/// wrapped into twenty four bits. Reading it is not using the key, so a second
462/// call answers a larger idle time and the same `lru`.
463fn object(server: &Server, session: &Session, key: &[u8], out: &mut Out) -> Result<()> {
464 let mut held = server.dbs[session.db].hold(key);
465 let Some(encoding) = held.encoding_name(key) else {
466 return Err(Error::new(Code::Invalid, NO_SUCH_KEY));
467 };
468 // The encoding above is the lookup this is counted for, and everything
469 // below asks about the same key again.
470 let _quiet = lookups::quiet();
471 let at = held.value_address(key).unwrap_or(0);
472 let idle = held.idle_secs(key).unwrap_or(0);
473 // A value with no RDB shape has no serialized length either, and nought is
474 // the honest answer rather than a refusal: the rest of the line is about
475 // the same value and is still true.
476 let serialized = held
477 .dump(key)
478 .map_or(0, |payload| payload.len() - DUMP_AROUND);
479 let quicklist = (encoding == "quicklist")
480 .then(|| held.list_shape(key))
481 .flatten()
482 .map(|(nodes, bytes)| {
483 // The average is elements over nodes, which is what the reference
484 // divides too, and both sides print it to two places.
485 let len = held.llen(key).unwrap_or(0);
486 let fill = list_fill(&held.bands().list);
487 (nodes, len as f64 / nodes.max(1) as f64, fill, bytes)
488 });
489 drop(held);
490
491 let now = server.clock.now_ms() / 1_000;
492 let lru = now.saturating_sub(idle) & LRU_CLOCK_MAX;
493 let mut line = String::with_capacity(192);
494 yo_alloc::allow(|| {
495 let _ = write!(
496 line,
497 "Value at:{at:#x} refcount:1 encoding:{encoding} \
498 serializedlength:{serialized} lru:{lru} lru_seconds_idle:{idle}",
499 );
500 if let Some((nodes, avg, fill, bytes)) = quicklist {
501 let _ = write!(
502 line,
503 " ql_nodes:{nodes} ql_avg_node:{avg:.2} ql_listpack_max:{fill} \
504 ql_compressed:0 ql_uncompressed_size:{bytes}",
505 );
506 }
507 });
508 out.simple(line.as_bytes());
509 Ok(())
510}
511
512/// The `list-max-listpack-size` a set of list thresholds came from.
513///
514/// Backwards, because the setting is one number and the bands are two fields,
515/// and the two fields are what everything downstream of the parse wants. A count
516/// is itself and a size is the index into Redis's five, so this reads a band
517/// nobody set as the `-2` that made it.
518fn list_fill(limits: &yo_kv::list::Limits) -> i32 {
519 if let Some(count) = limits.max_packed_entries {
520 return i32::try_from(count).unwrap_or(i32::MAX);
521 }
522 match limits.max_packed_bytes {
523 4096 => -1,
524 16384 => -3,
525 32768 => -4,
526 65536 => -5,
527 _ => -2,
528 }
529}
530
531/// `DEBUG SDSLEN <key>`, the six numbers about a string and its name.
532///
533/// The two lengths are real and the four numbers around them are D-135. On a
534/// real server they are `sds` and `zmalloc` internals: how much spare room the
535/// string header left on the end and how many bytes the allocator handed back
536/// for the request, which are questions about jemalloc rather than about the
537/// value. Nothing here has either. A name is held packed with no spare and a
538/// string value is held at exactly its length, so the spare is nought and the
539/// allocation is the length, and those are true statements rather than
540/// placeholders.
541///
542/// An integer encoded string is refused, which is the reference's answer too and
543/// is for the same reason: there is no string there to measure, only the number
544/// it was read as.
545fn sdslen(server: &Server, session: &Session, key: &[u8], out: &mut Out) -> Result<()> {
546 let mut held = server.dbs[session.db].hold(key);
547 let Some(encoding) = held.encoding_name(key) else {
548 return Err(Error::new(Code::Invalid, NO_SUCH_KEY));
549 };
550 if !matches!(encoding, "raw" | "embstr") {
551 return Err(Error::new(Code::Invalid, "Not an sds encoded string."));
552 }
553 let _quiet = lookups::quiet();
554 let len = held.strlen(key).unwrap_or(0);
555 drop(held);
556
557 let mut line = String::with_capacity(128);
558 yo_alloc::allow(|| {
559 // The space after each `zmalloc:` and after nothing else is the
560 // reference's, and a suite reading the line by column would notice.
561 let _ = write!(
562 line,
563 "key_sds_len:{}, key_sds_avail:0, key_zmalloc: {}, \
564 val_sds_len:{len}, val_sds_avail:0, val_zmalloc: {len}",
565 key.len(),
566 key.len(),
567 );
568 });
569 out.simple(line.as_bytes());
570 Ok(())
571}
572
573/// Which of the two structure dumps was asked for.
574#[derive(Clone, Copy)]
575enum Packing {
576 Listpack,
577 Quicklist,
578}
579
580impl Packing {
581 /// The word for it, which is also the encoding a value has to be in.
582 const fn word(self) -> &'static str {
583 match self {
584 Packing::Listpack => "LISTPACK",
585 Packing::Quicklist => "QUICKLIST",
586 }
587 }
588
589 /// The encoding this dump is about.
590 const fn encoding(self) -> &'static str {
591 match self {
592 Packing::Listpack => "listpack",
593 Packing::Quicklist => "quicklist",
594 }
595 }
596
597 /// The sentence the client gets, which says where the real answer went.
598 const fn said(self) -> &'static [u8] {
599 match self {
600 Packing::Listpack => b"Listpack structure printed on stdout",
601 Packing::Quicklist => b"Quicklist structure printed on stdout",
602 }
603 }
604
605 /// The refusal for a value that is not in that representation.
606 const fn refusal(self) -> &'static str {
607 match self {
608 Packing::Listpack => "Not a listpack encoded object.",
609 Packing::Quicklist => "Not a quicklist encoded object.",
610 }
611 }
612}
613
614/// `DEBUG LISTPACK <key>` and `DEBUG QUICKLIST <key> [<level>]`.
615///
616/// Both of them write to the server's own output and answer the client a
617/// sentence saying so, which is what makes them usable at all: the structure of
618/// a listpack is pages of entry headers and nobody wants it on a socket. So the
619/// reply is fixed and the interesting part goes where the log goes.
620///
621/// The level argument on `QUICKLIST` is read and dropped, and a level that is not
622/// a number is accepted rather than refused, both of which are the reference's
623/// behaviour. It reads the word with `atoi` and prints more or less depending on
624/// what came back, and there is one amount of detail here.
625///
626/// A listpack is any value whose encoding is `listpack`, whatever type it is on,
627/// so a small list, hash, set and sorted set all answer. An `intset` does not,
628/// which is the one that reads like an exception and is not: an intset is a
629/// different packing with a different header.
630fn packing(
631 server: &Server,
632 session: &Session,
633 key: &[u8],
634 which: Packing,
635 out: &mut Out,
636) -> Result<()> {
637 let mut held = server.dbs[session.db].hold(key);
638 let Some(encoding) = held.encoding_name(key) else {
639 return Err(Error::new(Code::Invalid, NO_SUCH_KEY));
640 };
641 if encoding != which.encoding() {
642 return Err(Error::new(Code::Invalid, which.refusal()));
643 }
644 let _quiet = lookups::quiet();
645 let kind = held.type_name(key).unwrap_or("none");
646 let shape = held.list_shape(key);
647 let serialized = held
648 .dump(key)
649 .map_or(0, |payload| payload.len() - DUMP_AROUND);
650 drop(held);
651
652 yo_alloc::allow(|| {
653 let name = String::from_utf8_lossy(key);
654 let mut line = format!(
655 "yodb: DEBUG {}: {name}: {kind}, {serialized} byte(s)",
656 which.word()
657 );
658 if let Some((nodes, bytes)) = shape {
659 let _ = write!(line, ", {nodes} node(s) holding {bytes}");
660 }
661 println!("{line}");
662 });
663 out.simple(which.said());
664 Ok(())
665}
666
667/// `DEBUG DIGEST`, the forty characters that stand for everything in the server.
668///
669/// This is the check the Redis suite runs after nearly every interesting thing
670/// it does. Load a file and digest, promote a replica and digest, rewrite the
671/// log and digest, and the assertion is that the number did not move. It is the
672/// only affordable way to say two servers hold the same million keys, and it
673/// only works because both of them compute it the same way, which is why
674/// [`yo_kv::digest`] copies the recipe rather than choosing a better hash.
675///
676/// Every database in order, the empty ones passed over, the number of each one
677/// folded in before its keys. Inside a database the keys are order free, which
678/// is what lets this walk the stripes one at a time and what lets a server with
679/// four stripes agree with a server with sixty four.
680///
681/// Reading a key here is not using it. A suite that digests between every step
682/// would otherwise be rewriting the working set it is testing, so the whole walk
683/// runs with the lookup counters held quiet. `DEBUG DIGEST-VALUE` does count,
684/// because a real server counts there and not here.
685fn whole_digest(server: &Server, out: &mut Out) {
686 let _quiet = lookups::quiet();
687 let mut whole = digest::EMPTY;
688 for (i, db) in server.dbs.iter().enumerate() {
689 // An empty database is passed over entirely rather than folded in as an
690 // empty one, so a server with one key in database nine answers the same
691 // as a server with sixteen databases and the same one key.
692 if db.is_empty() {
693 continue;
694 }
695 digest::number(&mut whole, i as u32);
696 db.digest(&mut whole);
697 }
698 out.simple(&digest::hex(&whole));
699}
700
701/// `DEBUG DIGEST-VALUE <key> [<key> ...]`, the same thing for one value at a
702/// time.
703///
704/// One simple string per key, in the order they were asked for. The key name is
705/// not folded in, which is what makes this the digest of a value rather than of
706/// an entry: the same list under two names answers the same forty characters,
707/// and that is the point, since the usual use is checking that a key survived a
708/// rename or arrived on a replica under a different name.
709///
710/// A key that is not there is forty zeros rather than an error, so a client can
711/// ask about several keys without having to know first which of them exist.
712/// That also means a key holding nothing and a key holding a value that happens
713/// to digest to zero are told apart by `EXISTS` and not by this, which is a
714/// theoretical complaint about a hash nobody is going to hit.
715fn value_digests(server: &Server, session: &Session, args: Args<'_>, out: &mut Out) {
716 out.array(args.len() - 2);
717 for i in 2..args.len() {
718 let key = args.get(i);
719 let mut one = digest::EMPTY;
720 // Left at nothing when the key is not there, which is the forty zeros.
721 server.dbs[session.db].hold(key).digest_value(key, &mut one);
722 out.simple(&digest::hex(&one));
723 }
724}
725
726/// `DEBUG POPULATE <count> [<prefix> [<size>]]`.
727///
728/// Keys are `<prefix>:<n>` counting from nought, with `key` as the prefix if
729/// none was given, and each value is `value:<n>`. A size pads that with zero
730/// bytes to exactly that many, or cuts it short, and a size of nought means the
731/// value is left as it is rather than made empty.
732///
733/// A key that is already there is left alone, value and deadline both, which is
734/// the surprising half and is what makes this safe to run twice. A real server
735/// checks the dictionary and skips, and it does that because the whole point of
736/// the subcommand is filling a database quickly, and quickly means not paying
737/// for a delete of something it is about to write over. Here that falls out of
738/// asking for the write the way `SET key value NX` asks for it.
739///
740/// Nothing is told about the keys this writes: no keyspace notification, no
741/// index update. The notifications are the reference's choice, since it adds the
742/// keys to the dictionary directly and never goes near the event code. The
743/// indexes are this build's, and they are safe to leave out rather than merely
744/// cheap: an index follows hashes or JSON documents and every key here is a
745/// string, and a key that was already a document is one of the keys this skips.
746fn populate(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
747 let count = positive(args.get(2))?;
748 let prefix = if args.len() >= 4 { args.get(3) } else { b"key" };
749 let size = if args.len() == 5 {
750 positive(args.get(4))? as usize
751 } else {
752 0
753 };
754 // Two buffers reused across the whole run rather than a pair of allocations
755 // per key, since the count a suite passes here is routinely a hundred
756 // thousand and every one of those is the same two shapes with a different
757 // number on the end.
758 let mut key = Vec::with_capacity(prefix.len() + 24);
759 let mut value = Vec::with_capacity(size.max(32));
760 let db = &server.dbs[session.db];
761 for n in 0..count {
762 key.clear();
763 key.extend_from_slice(prefix);
764 key.push(b':');
765 push_int(&mut key, n);
766 value.clear();
767 value.extend_from_slice(b"value:");
768 push_int(&mut value, n);
769 if size != 0 {
770 // Shorter than the name is a cut and longer is zero bytes on the
771 // end, which is what the reference's `sdsgrowzero` does and is why
772 // a size of five gives `value` and not `value:0` cut to five.
773 value.resize(size, 0);
774 }
775 // One stripe held per key rather than one for the run, because the keys
776 // are spread across every stripe by design and holding them all would
777 // be holding the whole database against every other thread for as long
778 // as the fill takes.
779 db.hold(&key)
780 .set(&key, &value, SetOptions::PLAIN.if_missing())?;
781 }
782 out.ok();
783 Ok(())
784}
785
786/// A count argument, which has to be a whole number that is not negative.
787///
788/// The reference reads both of `POPULATE`'s numbers with the same call and says
789/// the same thing about both, so a size that is not a number complains about a
790/// range rather than about not being a number.
791fn positive(value: &[u8]) -> Result<i64> {
792 parse_i64(value)
793 .filter(|&n| n >= 0)
794 .ok_or_else(|| Error::new(Code::Invalid, "value is out of range, must be positive"))
795}
796
797/// A whole number, appended.
798fn push_int(out: &mut Vec<u8>, mut n: i64) {
799 let start = out.len();
800 if n == 0 {
801 out.push(b'0');
802 return;
803 }
804 while n > 0 {
805 out.push(b'0' + (n % 10) as u8);
806 n /= 10;
807 }
808 out[start..].reverse();
809}
810
811/// `DEBUG PROTOCOL <type>`, which is one reply of each type RESP3 has.
812///
813/// This is the command a client library's own test suite points at itself to
814/// find out whether it decodes the protocol, so every one of these was read off
815/// the wire of an 8.10.1 rather than off the documentation, on both protocols.
816/// Two of them are worth spelling out.
817///
818/// `attrib` on RESP3 sends an attribute and then a real reply behind it, and on
819/// RESP2 sends only the reply, because RESP2 has no way to carry the attribute
820/// and dropping it is what the other side does. `push` is the other way round:
821/// on RESP3 the real reply goes out first and the push follows it, and on RESP2
822/// the whole subcommand is an error, because a push on RESP2 would be an
823/// ordinary array and a client would read it as the reply.
824// The double the reference sends is 3.141, which is close enough to pi for the
825// lint to think somebody meant pi and typed it badly. Nobody did: it is a test
826// value chosen to have three decimal places, and rounding it to the real
827// constant would change the bytes on the wire, which are the whole point.
828#[allow(clippy::approx_constant)]
829fn protocol(kind: &[u8], out: &mut Out) -> Result<()> {
830 if is(kind, b"string") {
831 out.bulk(b"Hello World");
832 } else if is(kind, b"integer") {
833 out.int(12345);
834 } else if is(kind, b"double") {
835 out.double(3.141);
836 } else if is(kind, b"bignum") {
837 out.big_number(b"1234567999999999999999999999999999999");
838 } else if is(kind, b"null") {
839 out.nil();
840 } else if is(kind, b"array") {
841 out.array(3);
842 for n in 0..3 {
843 out.int(n);
844 }
845 } else if is(kind, b"set") {
846 out.set(3);
847 for n in 0..3 {
848 out.int(n);
849 }
850 } else if is(kind, b"map") {
851 // The keys are numbers and the values are booleans, so a RESP2 client
852 // sees three pairs flattened with the booleans as `:0` and `:1`, which
853 // is the shape a RESP2 client already gets from every map here.
854 out.map(3);
855 for n in 0..3 {
856 out.int(n);
857 out.bool(n == 1);
858 }
859 } else if is(kind, b"attrib") {
860 if out.proto().is_resp3() {
861 out.attribute(1);
862 out.bulk(b"key-popularity");
863 out.array(2);
864 out.bulk(b"key:123");
865 out.int(90);
866 }
867 out.bulk(b"Some real reply following the attribute");
868 } else if is(kind, b"push") {
869 if !out.proto().is_resp3() {
870 return Err(Error::new(
871 Code::Invalid,
872 "RESP2 is not supported by this command",
873 ));
874 }
875 out.bulk(b"Some real reply following the push reply");
876 out.push(2);
877 out.bulk(b"server-cpu-usage");
878 out.int(42);
879 } else if is(kind, b"verbatim") {
880 out.verbatim(b"txt", b"This is a verbatim\nstring");
881 } else if is(kind, b"true") {
882 out.bool(true);
883 } else if is(kind, b"false") {
884 out.bool(false);
885 } else {
886 return Err(Error::new(
887 Code::Invalid,
888 "Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false",
889 ));
890 }
891 Ok(())
892}
893
894/// What `DEBUG HELP` says, which is what is here and not what Redis has.
895const HELP: &[&str] = &[
896 "DEBUG <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
897 "CHANGE-REPL-ID",
898 " Change the replication IDs of the server. Useful for testing the",
899 " replication sub system.",
900 "DICT-RESIZING <0|1>",
901 " Enable or disable the background reclaim of room the store no longer",
902 " needs.",
903 "DIGEST",
904 " Output a hex signature representing the current DB content.",
905 "DIGEST-VALUE <key> [<key> ...]",
906 " Output a hex signature of the values of all the specified keys.",
907 "ERROR <string>",
908 " Return a Redis protocol error with <string> as message. Useful for",
909 " clients unit tests to simulate Redis errors.",
910 "INTERNAL_SECRET",
911 " Return the cluster internal secret (hashed with crc16) or error if not in cluster mode.",
912 "LISTPACK <key>",
913 " Show low level info about the listpack encoding of <key>.",
914 "LOG <message>",
915 " Write <message> to the server log.",
916 "MARK-INTERNAL-CLIENT [UNMARK]",
917 " Promote the current connection to an internal connection.",
918 "OBJECT <key>",
919 " Show low level info about `key` and associated value.",
920 "PAUSE-CRON <0|1>",
921 " Stop periodic cron job processing.",
922 "POPULATE <count> [<prefix>] [<size>]",
923 " Create <count> string keys named key:<num>. If <prefix> is specified",
924 " then it is used instead of the 'key' prefix. A key that already exists",
925 " is left alone.",
926 "PROTOCOL <type>",
927 " Reply with a test value of the specified type. <type> can be: string,",
928 " integer, double, bignum, null, array, set, map, attrib, push, verbatim,",
929 " true, false.",
930 "QUICKLIST <key> [<0|1>]",
931 " Show low level info about the quicklist encoding of <key>.",
932 " The optional argument (0 by default) sets the level of detail",
933 "QUICKLIST-PACKED-THRESHOLD <size>",
934 " Sets the threshold for elements to be inserted as plain vs packed nodes",
935 " Default value is 1GB, allows values up to 4GB. Setting to 0 restores to default.",
936 "RELOAD [MERGE] [NOFLUSH] [NOSAVE]",
937 " Save the dataset to the RDB file and load it back. NOSAVE reads the file",
938 " that is already there, NOFLUSH keeps what is in memory and lets the file",
939 " land on top of it, and MERGE is accepted and does nothing.",
940 "SDSLEN <key>",
941 " Show low level SDS string info representing `key` and value.",
942 "SET-ACTIVE-EXPIRE <0|1>",
943 " Setting it to 0 disables expiring keys in background when they are not",
944 " accessed (otherwise the Redis behavior). Setting it to 1 reenables back",
945 " the default.",
946 "SET-SKIP-CHECKSUM-VALIDATION <0|1>",
947 " Enables or disables checksum checks for RESTORE's payload.",
948 "SLEEP <seconds>",
949 " Stop the server for <seconds>. Decimals allowed.",
950 "HELP",
951 " Print this help.",
952];
953
954#[cfg(test)]
955mod tests {
956 use super::{flag, leading_double};
957
958 /// The flag reads what C reads out of the same bytes.
959 #[test]
960 fn a_flag_is_atoi_and_anything_unreadable_is_off() {
961 for (text, want) in [
962 (&b"0"[..], 0),
963 (b"1", 1),
964 (b"00", 0),
965 (b"01", 1),
966 (b"2", 1),
967 (b"-1", 1),
968 (b"-0", 0),
969 (b"x", 0),
970 (b"", 0),
971 (b"1x", 1),
972 (b"true", 0),
973 (b"18446744073709551617", 1),
974 ] {
975 assert_eq!(flag(text), want, "{}", String::from_utf8_lossy(text));
976 }
977 }
978
979 /// A sleep argument reads as much of itself as is a number.
980 #[test]
981 fn a_sleep_reads_the_longest_number_at_the_front() {
982 for (text, want) in [
983 ("0", 0.0),
984 ("0.05", 0.05),
985 ("-1", -1.0),
986 ("abc", 0.0),
987 ("", 0.0),
988 ("1.5s", 1.5),
989 ("2x3", 2.0),
990 ] {
991 assert!(
992 (leading_double(text) - want).abs() < 1e-9,
993 "{text} read as {}",
994 leading_double(text)
995 );
996 }
997 }
998}