Skip to content

Commit abc1193

Browse files
committed
move Selector to database crate
1 parent ffb00d4 commit abc1193

File tree

7 files changed

+400
-400
lines changed

7 files changed

+400
-400
lines changed

database/src/selector.rs

Lines changed: 385 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,41 @@
1-
use std::fmt;
1+
//! Selector API for returning subset of series which will be rendered in some
2+
//! format.
3+
//!
4+
//! We have the following expected paths:
5+
//!
6+
//! * :benchmark/:profile/:scenario/:metric => [cid => u64]
7+
//! * :crate/:profile/:scenario/:self_profile_query/:stat (SelfProfileTime, SelfProfileCacheHits, ...)
8+
//! :stat = time => Duration,
9+
//! :stat = cache hits => u32,
10+
//! :stat = invocation count => u32,
11+
//! :stat = blocked time => Duration,
12+
//! :stat = incremental load time => Duration,
13+
//!
14+
//! Note that the returned series always have a "simple" type of a small set --
15+
//! things like arrays, integers. We aggregate into higher level types above the
16+
//! primitive series readers.
17+
//!
18+
//! We specify a single struct per path style above.
19+
//!
20+
//! `Option<T>` in the path either specifies a specific T to filter by, or
21+
//! requests that all are provided. Note that this is a cartesian product if
22+
//! there are multiple `None`s.
223
24+
use std::{
25+
fmt::{self, Debug},
26+
hash::Hash,
27+
ops::RangeInclusive,
28+
sync::Arc,
29+
};
30+
31+
use async_trait::async_trait;
332
use chrono::NaiveDate;
433
use serde::Deserialize;
534

6-
use crate::{ArtifactId, ArtifactIdIter, Commit};
35+
use crate::{
36+
comparison::Metric, interpolate::Interpolate, ArtifactId, ArtifactIdIter, Benchmark,
37+
CodegenBackend, Commit, Connection, Index, Lookup, Profile, Scenario,
38+
};
739

840
/// The bound for finding an artifact
941
///
@@ -166,3 +198,354 @@ impl<T: Clone + PartialEq + fmt::Debug> Point for (T, f64) {
166198
// no-op
167199
}
168200
}
201+
202+
/// Finds the most appropriate `ArtifactId` for a given bound.
203+
///
204+
/// Searches the commits in the index either from the left or the right.
205+
/// If not found in those commits, searches through the artifacts in the index.
206+
pub fn artifact_id_for_bound(data: &Index, bound: Bound, is_left: bool) -> Option<ArtifactId> {
207+
let commits = data.commits();
208+
let commit = if is_left {
209+
commits
210+
.iter()
211+
.find(|commit| bound.left_match(commit))
212+
.cloned()
213+
} else {
214+
commits
215+
.iter()
216+
.rfind(|commit| bound.right_match(commit))
217+
.cloned()
218+
};
219+
commit.map(ArtifactId::Commit).or_else(|| {
220+
data.artifacts()
221+
.find(|aid| match &bound {
222+
Bound::Commit(c) => *c == **aid,
223+
Bound::Date(_) => false,
224+
Bound::None => false,
225+
})
226+
.map(|aid| ArtifactId::Tag(aid.to_string()))
227+
})
228+
}
229+
230+
pub fn range_subset(data: Vec<Commit>, range: RangeInclusive<Bound>) -> Vec<Commit> {
231+
let (a, b) = range.into_inner();
232+
233+
let left_idx = data.iter().position(|commit| a.left_match(commit));
234+
let right_idx = data.iter().rposition(|commit| b.right_match(commit));
235+
236+
if let (Some(left), Some(right)) = (left_idx, right_idx) {
237+
data.get(left..=right)
238+
.map(|s| s.to_vec())
239+
.unwrap_or_else(|| {
240+
log::error!(
241+
"Failed to compute left/right indices from {:?}..={:?}",
242+
a,
243+
b
244+
);
245+
vec![]
246+
})
247+
} else {
248+
vec![]
249+
}
250+
}
251+
252+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
253+
pub enum Selector<T> {
254+
All,
255+
Subset(Vec<T>),
256+
One(T),
257+
}
258+
259+
impl<T> Selector<T> {
260+
fn map<U>(self, mut f: impl FnMut(T) -> U) -> Selector<U> {
261+
match self {
262+
Selector::All => Selector::All,
263+
Selector::Subset(subset) => Selector::Subset(subset.into_iter().map(f).collect()),
264+
Selector::One(o) => Selector::One(f(o)),
265+
}
266+
}
267+
pub fn try_map<U, E>(self, mut f: impl FnMut(T) -> Result<U, E>) -> Result<Selector<U>, E> {
268+
Ok(match self {
269+
Selector::All => Selector::All,
270+
Selector::Subset(subset) => {
271+
Selector::Subset(subset.into_iter().map(f).collect::<Result<_, _>>()?)
272+
}
273+
Selector::One(o) => Selector::One(f(o)?),
274+
})
275+
}
276+
277+
fn matches<U>(&self, other: U) -> bool
278+
where
279+
U: PartialEq<T>,
280+
{
281+
match self {
282+
Selector::One(c) => other == *c,
283+
Selector::Subset(subset) => subset.iter().any(|c| other == *c),
284+
Selector::All => true,
285+
}
286+
}
287+
}
288+
289+
/// Represents the parameters of a single benchmark execution that collects a set of statistics.
290+
pub trait TestCase: Debug + Clone + Hash + PartialEq + Eq + PartialOrd + Ord {}
291+
292+
#[derive(Debug)]
293+
pub struct SeriesResponse<Case, T> {
294+
pub test_case: Case,
295+
pub series: T,
296+
}
297+
298+
impl<TestCase, T> SeriesResponse<TestCase, T> {
299+
pub fn map<U>(self, m: impl FnOnce(T) -> U) -> SeriesResponse<TestCase, U> {
300+
let SeriesResponse {
301+
test_case: key,
302+
series,
303+
} = self;
304+
SeriesResponse {
305+
test_case: key,
306+
series: m(series),
307+
}
308+
}
309+
310+
pub fn interpolate(self) -> SeriesResponse<TestCase, Interpolate<T>>
311+
where
312+
T: Iterator,
313+
T::Item: Point,
314+
{
315+
self.map(|s| Interpolate::new(s))
316+
}
317+
}
318+
319+
#[async_trait]
320+
pub trait BenchmarkQuery: Debug + Clone {
321+
type TestCase: TestCase;
322+
323+
async fn execute(
324+
&self,
325+
connection: &mut dyn Connection,
326+
index: &Index,
327+
artifact_ids: Arc<Vec<ArtifactId>>,
328+
) -> Result<Vec<SeriesResponse<Self::TestCase, StatisticSeries>>, String>;
329+
}
330+
331+
// Compile benchmarks querying
332+
#[derive(Clone, Hash, Eq, PartialEq, Debug)]
333+
pub struct CompileBenchmarkQuery {
334+
benchmark: Selector<String>,
335+
scenario: Selector<Scenario>,
336+
profile: Selector<Profile>,
337+
backend: Selector<CodegenBackend>,
338+
metric: Selector<crate::Metric>,
339+
}
340+
341+
impl CompileBenchmarkQuery {
342+
pub fn benchmark(mut self, selector: Selector<String>) -> Self {
343+
self.benchmark = selector;
344+
self
345+
}
346+
347+
pub fn profile(mut self, selector: Selector<Profile>) -> Self {
348+
self.profile = selector;
349+
self
350+
}
351+
352+
pub fn scenario(mut self, selector: Selector<Scenario>) -> Self {
353+
self.scenario = selector;
354+
self
355+
}
356+
357+
pub fn metric(mut self, selector: Selector<Metric>) -> Self {
358+
self.metric = selector.map(|v| v.as_str().into());
359+
self
360+
}
361+
362+
pub fn all_for_metric(metric: Metric) -> Self {
363+
Self {
364+
benchmark: Selector::All,
365+
profile: Selector::All,
366+
scenario: Selector::All,
367+
backend: Selector::All,
368+
metric: Selector::One(metric.as_str().into()),
369+
}
370+
}
371+
}
372+
373+
impl Default for CompileBenchmarkQuery {
374+
fn default() -> Self {
375+
Self {
376+
benchmark: Selector::All,
377+
scenario: Selector::All,
378+
profile: Selector::All,
379+
backend: Selector::All,
380+
metric: Selector::All,
381+
}
382+
}
383+
}
384+
385+
#[async_trait]
386+
impl BenchmarkQuery for CompileBenchmarkQuery {
387+
type TestCase = CompileTestCase;
388+
389+
async fn execute(
390+
&self,
391+
conn: &mut dyn Connection,
392+
index: &Index,
393+
artifact_ids: Arc<Vec<ArtifactId>>,
394+
) -> Result<Vec<SeriesResponse<Self::TestCase, StatisticSeries>>, String> {
395+
let mut statistic_descriptions: Vec<_> = index
396+
.compile_statistic_descriptions()
397+
.filter(|(&(b, p, s, backend, m), _)| {
398+
self.benchmark.matches(b)
399+
&& self.profile.matches(p)
400+
&& self.scenario.matches(s)
401+
&& self.backend.matches(backend)
402+
&& self.metric.matches(m)
403+
})
404+
.map(|(&(benchmark, profile, scenario, backend, metric), sid)| {
405+
(
406+
CompileTestCase {
407+
benchmark,
408+
profile,
409+
scenario,
410+
backend,
411+
},
412+
metric,
413+
sid,
414+
)
415+
})
416+
.collect();
417+
418+
statistic_descriptions.sort_unstable();
419+
420+
let sids: Vec<_> = statistic_descriptions
421+
.iter()
422+
.map(|(_, _, sid)| *sid)
423+
.collect();
424+
425+
let aids = artifact_ids
426+
.iter()
427+
.map(|aid| aid.lookup(index))
428+
.collect::<Vec<_>>();
429+
430+
Ok(conn
431+
.get_pstats(&sids, &aids)
432+
.await
433+
.into_iter()
434+
.zip(statistic_descriptions)
435+
.filter(|(points, _)| points.iter().any(|value| value.is_some()))
436+
.map(|(points, (test_case, metric, _))| {
437+
SeriesResponse {
438+
series: StatisticSeries {
439+
artifact_ids: ArtifactIdIter::new(artifact_ids.clone()),
440+
points: if *metric == *"cpu-clock" || *metric == *"task-clock" {
441+
// Convert to seconds -- perf reports these measurements in
442+
// milliseconds
443+
points
444+
.into_iter()
445+
.map(|p| p.map(|v| v / 1000.0))
446+
.collect::<Vec<_>>()
447+
.into_iter()
448+
} else {
449+
points.into_iter()
450+
},
451+
},
452+
test_case,
453+
}
454+
})
455+
.collect::<Vec<_>>())
456+
}
457+
}
458+
459+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
460+
pub struct CompileTestCase {
461+
pub benchmark: Benchmark,
462+
pub profile: Profile,
463+
pub scenario: Scenario,
464+
pub backend: CodegenBackend,
465+
}
466+
467+
impl TestCase for CompileTestCase {}
468+
469+
// Runtime benchmarks querying
470+
#[derive(Clone, Hash, Eq, PartialEq, Debug)]
471+
pub struct RuntimeBenchmarkQuery {
472+
benchmark: Selector<String>,
473+
metric: Selector<crate::Metric>,
474+
}
475+
476+
impl RuntimeBenchmarkQuery {
477+
pub fn benchmark(mut self, selector: Selector<String>) -> Self {
478+
self.benchmark = selector;
479+
self
480+
}
481+
482+
pub fn metric(mut self, selector: Selector<Metric>) -> Self {
483+
self.metric = selector.map(|v| v.as_str().into());
484+
self
485+
}
486+
487+
pub fn all_for_metric(metric: Metric) -> Self {
488+
Self {
489+
benchmark: Selector::All,
490+
metric: Selector::One(metric.as_str().into()),
491+
}
492+
}
493+
}
494+
495+
impl Default for RuntimeBenchmarkQuery {
496+
fn default() -> Self {
497+
Self {
498+
benchmark: Selector::All,
499+
metric: Selector::All,
500+
}
501+
}
502+
}
503+
504+
#[async_trait]
505+
impl BenchmarkQuery for RuntimeBenchmarkQuery {
506+
type TestCase = RuntimeTestCase;
507+
508+
async fn execute(
509+
&self,
510+
conn: &mut dyn Connection,
511+
index: &Index,
512+
artifact_ids: Arc<Vec<ArtifactId>>,
513+
) -> Result<Vec<SeriesResponse<Self::TestCase, StatisticSeries>>, String> {
514+
let mut statistic_descriptions: Vec<_> = index
515+
.runtime_statistic_descriptions()
516+
.filter(|(&(b, m), _)| self.benchmark.matches(b) && self.metric.matches(m))
517+
.map(|(&(benchmark, _), sid)| (RuntimeTestCase { benchmark }, sid))
518+
.collect();
519+
520+
statistic_descriptions.sort_unstable();
521+
522+
let sids: Vec<_> = statistic_descriptions.iter().map(|(_, sid)| *sid).collect();
523+
524+
let aids = artifact_ids
525+
.iter()
526+
.map(|aid| aid.lookup(index))
527+
.collect::<Vec<_>>();
528+
529+
Ok(conn
530+
.get_runtime_pstats(&sids, &aids)
531+
.await
532+
.into_iter()
533+
.zip(statistic_descriptions)
534+
.filter(|(points, _)| points.iter().any(|value| value.is_some()))
535+
.map(|(points, (test_case, _))| SeriesResponse {
536+
series: StatisticSeries {
537+
artifact_ids: ArtifactIdIter::new(artifact_ids.clone()),
538+
points: points.into_iter(),
539+
},
540+
test_case,
541+
})
542+
.collect::<Vec<_>>())
543+
}
544+
}
545+
546+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
547+
pub struct RuntimeTestCase {
548+
pub benchmark: Benchmark,
549+
}
550+
551+
impl TestCase for RuntimeTestCase {}

0 commit comments

Comments
 (0)