Skip to main content

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