Skip to main content

diskann_benchmark_runner/
registry.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6use std::collections::HashMap;
7
8use thiserror::Error;
9
10use crate::{
11    dispatcher::{DispatchRule, Map},
12    output::Sink,
13    Any, Checkpoint, Input, Output,
14};
15
16/// A collection of [`crate::Input`].
17pub struct Inputs {
18    // Inputs keyed by their tag type.
19    inputs: HashMap<&'static str, Box<dyn Input>>,
20}
21
22impl Inputs {
23    /// Construct a new empty [`Inputs`] registry.
24    pub fn new() -> Self {
25        Self {
26            inputs: HashMap::new(),
27        }
28    }
29
30    /// Return the input with the registerd `tag` if present. Otherwise, return `None`.
31    pub fn get(&self, tag: &str) -> Option<&dyn Input> {
32        match self.inputs.get(tag) {
33            Some(v) => Some(&**v),
34            None => None,
35        }
36    }
37
38    /// Register `input` in the registry.
39    ///
40    /// Returns an error if any other input with the same [`Input::tag()`] has been registered
41    /// while leaving the underlying registry unchanged.
42    pub fn register<T>(&mut self, input: T) -> anyhow::Result<()>
43    where
44        T: Input + 'static,
45    {
46        use std::collections::hash_map::Entry;
47
48        let tag = input.tag();
49        match self.inputs.entry(tag) {
50            Entry::Vacant(entry) => {
51                entry.insert(Box::new(input));
52                Ok(())
53            }
54            Entry::Occupied(_) => {
55                #[derive(Debug, Error)]
56                #[error("An input with the tag \"{}\" already exists", self.0)]
57                struct AlreadyExists(&'static str);
58
59                Err(anyhow::anyhow!(AlreadyExists(tag)))
60            }
61        }
62    }
63
64    /// Return an iterator over all registered input tags in an unspecified order.
65    pub fn tags(&self) -> impl ExactSizeIterator<Item = &'static str> + use<'_> {
66        self.inputs.keys().copied()
67    }
68}
69
70impl Default for Inputs {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76/// A collection of registerd benchmarks.
77pub struct Benchmarks {
78    dispatcher: Dispatcher,
79}
80
81impl Benchmarks {
82    /// Return a new empty registry.
83    pub fn new() -> Self {
84        Self {
85            dispatcher: Dispatcher::new(),
86        }
87    }
88
89    /// Register a new benchmark with the given name.
90    ///
91    /// The type parameter `T` is used to match this benchmark with a registered
92    /// [`crate::Any`], which is determined using by `<T as DispatchRule<&Any>>`.
93    pub fn register<T>(
94        &mut self,
95        name: impl Into<String>,
96        benchmark: impl Fn(T::Type<'_>, Checkpoint<'_>, &mut dyn Output) -> anyhow::Result<serde_json::Value>
97            + 'static,
98    ) where
99        T: for<'a> Map<Type<'a>: DispatchRule<&'a Any>>,
100    {
101        self.dispatcher
102            .register::<_, T, CheckpointRef, DynOutput>(name.into(), benchmark)
103    }
104
105    pub(crate) fn methods(&self) -> impl ExactSizeIterator<Item = &(String, Method)> {
106        self.dispatcher.methods()
107    }
108
109    /// Return `true` if `job` matches with any registerd benchmark. Otherwise, return `false`.
110    pub fn has_match(&self, job: &Any) -> bool {
111        let sink: &mut dyn Output = &mut Sink::new();
112        self.dispatcher.has_match(&job, &Checkpoint::empty(), &sink)
113    }
114
115    /// Attempt to the best matching benchmark for `job` - forwarding the `checkpoint` and
116    /// `output` to the benchmark.
117    ///
118    /// Returns the results of the benchmark if successful.
119    ///
120    /// Errors if a suitable method could not be found or if the invoked benchmark failed.
121    pub fn call(
122        &self,
123        job: &Any,
124        checkpoint: Checkpoint<'_>,
125        output: &mut dyn Output,
126    ) -> anyhow::Result<serde_json::Value> {
127        self.dispatcher.call(job, checkpoint, output).unwrap()
128    }
129
130    /// Attempt to debug reasons for a missed dispatch, returning at most `methods` reasons.
131    ///
132    /// This implementation works by invoking [`DispatchRule::try_match`] with
133    /// `job` on all registered benchmarks. If no successful matches are found, the lowest
134    /// ranking [`crate::dispatcher::FailureScore`]s are collected and used to report details
135    /// of the nearest misses using [`DispatchRule::description`].
136    ///
137    /// Returns `Ok(())` is a match was found.
138    pub fn debug(&self, job: &Any, methods: usize) -> Result<(), Vec<Mismatch>> {
139        let checkpoint = Checkpoint::empty();
140        let sink: &mut dyn Output = &mut Sink::new();
141        let mismatches = match self.dispatcher.debug(methods, &job, &checkpoint, &sink) {
142            Ok(()) => return Ok(()),
143            Err(mismatches) => mismatches,
144        };
145
146        // Just retrieve the mismatch information for the first argument since that is the
147        // one that does all the heavy lifting.
148        Err(mismatches
149            .into_iter()
150            .map(|m| {
151                let reason = m.mismatches()[0]
152                    .as_ref()
153                    .map(|opt| opt.to_string())
154                    .unwrap_or("<missing>".into());
155                Mismatch {
156                    method: m.method().to_string(),
157                    reason,
158                }
159            })
160            .collect())
161    }
162}
163
164impl Default for Benchmarks {
165    fn default() -> Self {
166        Self::new()
167    }
168}
169
170/// Document the reason for a method mathing failure.
171pub struct Mismatch {
172    method: String,
173    reason: String,
174}
175
176impl Mismatch {
177    /// Return the name of the benchmark that we failed to match.
178    pub fn method(&self) -> &str {
179        &self.method
180    }
181
182    /// Return the reason why this method was not a match.
183    pub fn reason(&self) -> &str {
184        &self.reason
185    }
186}
187
188//------------------//
189// Dispatch Helpers //
190//------------------//
191
192/// A [`Map`] for `&mut dyn Output`.
193pub(crate) struct DynOutput;
194
195impl Map for DynOutput {
196    type Type<'a> = &'a mut dyn Output;
197}
198
199/// A dispatcher compatible mapper for [`Checkpoint`].
200#[derive(Debug, Clone, Copy)]
201pub(crate) struct CheckpointRef;
202
203impl Map for CheckpointRef {
204    type Type<'a> = Checkpoint<'a>;
205}
206
207/// The internal `Dispatcher` used for method resolution.
208type Dispatcher = crate::dispatcher::Dispatcher3<
209    anyhow::Result<serde_json::Value>,
210    crate::dispatcher::Ref<Any>,
211    CheckpointRef,
212    DynOutput,
213>;
214
215/// The concrete type of a method.
216type Method = Box<
217    dyn crate::dispatcher::Dispatch3<
218        anyhow::Result<serde_json::Value>,
219        crate::dispatcher::Ref<Any>,
220        CheckpointRef,
221        DynOutput,
222    >,
223>;