use std::time::{Duration, Instant};
use yo_common::Result;
use yo_search::walk::Ran;
use super::{Args, Watch, aggregated, searched};
use crate::dispatch::Server;
use crate::dispatch::args;
use crate::reply::Out;
const NO_MODE: &[u8] = b"No `SEARCH`, `AGGREGATE`, or `HYBRID` provided";
const NO_QUERY: &[u8] = b"The QUERY keyword is expected";
const NO_CURSOR: &[u8] = b"FT.PROFILE does not support cursor";
const NAME: &str = "FT.PROFILE";
pub(super) fn run(server: &Server, db: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
let mut at = 2;
let searching = if args::is(args.get(at), b"SEARCH") {
true
} else if args::is(args.get(at), b"AGGREGATE") {
false
} else {
out.error(NO_MODE);
return Ok(());
};
at += 1;
let limited = args::is(args.get(at), b"LIMITED");
if limited {
at += 1;
}
if !args::is(args.get(at), b"QUERY") {
out.error(NO_QUERY);
return Ok(());
}
at += 1;
if at >= args.len() {
return Err(args::wrong_arity(NAME));
}
if (at + 1..args.len()).any(|spot| args::is(args.get(spot), b"WITHCURSOR")) {
out.error(NO_CURSOR);
return Ok(());
}
let mut watch = Watch::default();
let clock = Instant::now();
let mut inner = Out::with_capacity(out.proto(), 256);
match searching {
true => searched(server, db, args, at, Some(&mut watch), &mut inner)?,
false => aggregated(server, db, args, at, Some(&mut watch), &mut inner)?,
}
let whole = clock.elapsed();
if inner.as_slice().first() == Some(&b'-') {
out.raw(inner.as_slice());
return Ok(());
}
let three = out.proto().is_resp3();
if three {
out.map(2);
out.simple(b"Results");
} else {
out.array(2);
}
out.raw(inner.as_slice());
if three {
out.simple(b"Profile");
}
out.map(2);
out.simple(b"Shards");
out.array(1);
shard(&watch, whole, limited, out);
out.simple(b"Coordinator");
out.map(0);
Ok(())
}
fn shard(watch: &Watch, whole: Duration, limited: bool, out: &mut Out) {
out.map(7);
out.simple(b"Total profile time");
out.double(millis(whole));
out.simple(b"Parsing time");
out.double(millis(watch.parsing));
out.simple(b"Workers queue time");
out.double(0.0);
out.simple(b"Pipeline creation time");
out.double(millis(watch.creating));
out.simple(b"Warning");
out.array(1);
out.simple(b"None");
out.simple(b"Iterators profile");
match &watch.ran {
Some(ran) => tree(ran, millis(watch.walking), limited, out),
None => out.map(0),
}
out.simple(b"Result processors profile");
out.array(watch.steps.len());
for (name, rows) in &watch.steps {
out.map(3);
out.simple(b"Type");
out.simple(name);
out.simple(b"Time");
out.double(0.0);
out.simple(b"Results processed");
out.uint(*rows as u64);
}
}
fn tree(node: &Ran, spent: f64, limited: bool, out: &mut Out) {
let named = node.term.is_some();
let branch = !matches!(
node.kind,
"TEXT"
| "TAG"
| "NUMERIC"
| "GEO"
| "WILDCARD"
| "EMPTY"
| "VECTOR"
| "ID-LIST-SORTED"
| "METRIC SORTED BY ID - VECTOR DISTANCE"
);
out.map(
3 + usize::from(named) * 2
+ usize::from(node.about.is_some())
+ usize::from(node.mode.is_some())
+ usize::from(branch || node.alone),
);
out.simple(b"Type");
out.simple(node.kind.as_bytes());
if let Some(term) = &node.term {
out.simple(b"Term");
out.bulk(term);
}
if let Some(about) = &node.about {
out.simple(b"Query type");
match about.windows(3).any(|three| three == b" - ") {
true => out.bulk(about),
false => out.simple(about),
}
}
out.simple(b"Time");
out.double(spent);
out.simple(b"Number of reading operations");
out.uint(node.reads);
if let Some(size) = node.size {
out.simple(b"Estimated number of matches");
out.uint(u64::from(size));
}
if let Some(mode) = node.mode {
out.simple(b"Vector search mode");
out.simple(mode.as_bytes());
}
if node.alone {
out.simple(b"Child iterator");
tree(&node.under[0], 0.0, limited, out);
return;
}
if !branch {
return;
}
out.simple(b"Child iterators");
if limited && node.folds {
let mut line = b"The number of iterators in the union is ".to_vec();
line.extend_from_slice(node.under.len().to_string().as_bytes());
out.simple(&line);
return;
}
out.array(node.under.len());
for child in &node.under {
tree(child, 0.0, limited, out);
}
}
fn millis(span: Duration) -> f64 {
span.as_secs_f64() * 1000.0
}