use chrono::offset::TimeZone;
use chrono::{DateTime, Utc};
use hashbrown::{HashMap, HashSet};
use intern::intern;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::hash;
use std::ops::{Add, Sub};
use std::sync::Arc;
use std::time::Duration;
pub mod interpolate;
pub mod metric;
pub mod pool;
pub mod selector;
pub mod tests;
pub use pool::{Connection, Pool};
intern!(pub struct Metric);
intern!(pub struct Benchmark);
intern!(pub struct TargetName);
intern!(pub struct TagName);
intern!(pub struct RepoName);
pub fn intern_target_name(target: &str) -> TargetName {
intern(target)
}
#[derive(Debug, PartialEq, Eq)]
pub struct QueuedCommit {
pub pr: u32,
pub sha: String,
pub parent_sha: String,
pub include: Option<String>,
pub exclude: Option<String>,
pub runs: Option<i32>,
pub commit_date: Option<Date>,
pub backends: Option<String>,
pub repo: String,
}
#[derive(Debug, Hash, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Date(pub DateTime<Utc>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DateParseError {
pub input: String,
pub format: String,
pub error: chrono::ParseError,
}
impl std::str::FromStr for Date {
type Err = DateParseError;
fn from_str(s: &str) -> Result<Date, DateParseError> {
match DateTime::parse_from_rfc3339(s) {
Ok(value) => Ok(Date(value.with_timezone(&Utc))),
Err(error) => Err(DateParseError {
input: s.to_string(),
format: "RFC 3339".to_string(),
error,
}),
}
}
}
impl Date {
pub fn ymd_hms(year: i32, month: u32, day: u32, h: u32, m: u32, s: u32) -> Date {
Date(Utc.with_ymd_and_hms(year, month, day, h, m, s).unwrap())
}
pub fn empty() -> Date {
Date::ymd_hms(2000, 1, 1, 1, 1, 1)
}
}
impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0.to_rfc3339())
}
}
impl From<DateTime<Utc>> for Date {
fn from(datetime: DateTime<Utc>) -> Date {
Date(datetime)
}
}
impl PartialEq<DateTime<Utc>> for Date {
fn eq(&self, other: &DateTime<Utc>) -> bool {
self.0 == *other
}
}
impl Sub<chrono::Duration> for Date {
type Output = Date;
fn sub(self, rhs: chrono::Duration) -> Date {
Date(self.0 - rhs)
}
}
impl Add<chrono::Duration> for Date {
type Output = Date;
fn add(self, rhs: chrono::Duration) -> Date {
Date(self.0 + rhs)
}
}
impl Serialize for Date {
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(&self.0.to_rfc3339())
}
}
impl<'de> Deserialize<'de> for Date {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Date, D::Error>
where
D: serde::de::Deserializer<'de>,
{
struct DateVisitor;
impl serde::de::Visitor<'_> for DateVisitor {
type Value = Date;
fn visit_str<E>(self, value: &str) -> ::std::result::Result<Date, E>
where
E: serde::de::Error,
{
value.parse::<Date>().map_err(|_| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(value), &self)
})
}
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("an RFC 3339 date")
}
}
deserializer.deserialize_str(DateVisitor)
}
}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct MasterCommitInfo {
pub sha: String,
pub parent_sha: String,
#[serde(default)]
pub pr: Option<u32>,
pub date: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub enum CommitType {
Try,
Master,
}
impl FromStr for CommitType {
type Err = String;
fn from_str(ty: &str) -> Result<Self, Self::Err> {
match ty {
"try" => Ok(CommitType::Try),
"master" => Ok(CommitType::Master),
_ => Err(format!("Wrong commit type {ty}")),
}
}
}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct Commit {
pub sha: String,
pub date: Date,
pub r#type: CommitType,
}
impl Commit {
pub fn is_try(&self) -> bool {
matches!(self.r#type, CommitType::Try)
}
pub fn is_master(&self) -> bool {
matches!(self.r#type, CommitType::Master)
}
}
impl hash::Hash for Commit {
fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
self.sha.hash(hasher);
}
}
impl PartialEq for Commit {
fn eq(&self, other: &Self) -> bool {
self.sha == other.sha
}
}
impl Eq for Commit {}
impl PartialOrd for Commit {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Commit {
fn cmp(&self, other: &Self) -> Ordering {
self.date
.cmp(&other.date)
.then_with(|| self.sha.cmp(&other.sha))
}
}
#[derive(
Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Deserialize, serde::Serialize,
)]
pub enum Profile {
Check,
Debug,
Doc,
DocJson,
Opt,
Clippy,
}
impl Profile {
pub fn as_str(self) -> &'static str {
match self {
Profile::Check => "check",
Profile::Opt => "opt",
Profile::Debug => "debug",
Profile::Doc => "doc",
Profile::DocJson => "doc-json",
Profile::Clippy => "clippy",
}
}
pub fn default_profiles() -> Vec<Self> {
vec![Profile::Check, Profile::Debug, Profile::Opt, Profile::Doc]
}
pub fn all_values() -> &'static [Self] {
&[
Self::Check,
Self::Debug,
Self::Opt,
Self::Doc,
Self::DocJson,
Self::Clippy,
]
}
}
impl std::str::FromStr for Profile {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.to_ascii_lowercase().as_str() {
"check" => Profile::Check,
"debug" => Profile::Debug,
"doc" => Profile::Doc,
"doc-json" => Profile::DocJson,
"opt" => Profile::Opt,
"clippy" => Profile::Clippy,
_ => return Err(format!("{s} is not a profile")),
})
}
}
impl fmt::Display for Profile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Scenario {
Empty,
IncrementalEmpty,
IncrementalFresh,
IncrementalPatch(PatchName),
}
intern!(pub struct PatchName);
impl std::str::FromStr for Scenario {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.to_ascii_lowercase().as_str() {
"full" => Scenario::Empty,
"incr-full" => Scenario::IncrementalEmpty,
"incr-unchanged" => Scenario::IncrementalFresh,
_ => {
if let Some(stripped) = s.strip_prefix("incr-patched: ") {
Scenario::IncrementalPatch(PatchName::from(stripped))
} else {
return Err(format!("{s} is not a scenario"));
}
}
})
}
}
impl fmt::Display for Scenario {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Scenario::Empty => write!(f, "full"),
Scenario::IncrementalEmpty => write!(f, "incr-full"),
Scenario::IncrementalFresh => write!(f, "incr-unchanged"),
Scenario::IncrementalPatch(name) => write!(f, "incr-patched: {name}"),
}
}
}
impl Scenario {
pub fn to_id(&self) -> String {
match self {
Scenario::Empty => "full".to_string(),
Scenario::IncrementalEmpty => "incr-full".to_string(),
Scenario::IncrementalFresh => "incr-unchanged".to_string(),
Scenario::IncrementalPatch(name) => format!("incr-patched-{name}"),
}
}
}
use anyhow::anyhow;
use std::cmp::Ordering;
use std::str::FromStr;
impl Ord for Scenario {
fn cmp(&self, other: &Scenario) -> Ordering {
match (self, other) {
(a, b) if a == b => Ordering::Equal,
(Scenario::Empty, _) => Ordering::Less,
(Scenario::IncrementalEmpty, Scenario::Empty) => Ordering::Greater,
(Scenario::IncrementalEmpty, _) => Ordering::Less,
(Scenario::IncrementalFresh, Scenario::Empty) => Ordering::Greater,
(Scenario::IncrementalFresh, Scenario::IncrementalEmpty) => Ordering::Greater,
(Scenario::IncrementalFresh, _) => Ordering::Less,
(Scenario::IncrementalPatch(_), Scenario::Empty) => Ordering::Greater,
(Scenario::IncrementalPatch(_), Scenario::IncrementalEmpty) => Ordering::Greater,
(Scenario::IncrementalPatch(_), Scenario::IncrementalFresh) => Ordering::Greater,
(Scenario::IncrementalPatch(a), Scenario::IncrementalPatch(b)) => {
if a == "println" {
Ordering::Less
} else if b == "println" {
Ordering::Greater
} else {
a.cmp(b)
}
}
}
}
}
impl PartialOrd for Scenario {
fn partial_cmp(&self, other: &Scenario) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Target {
X86_64UnknownLinuxGnu,
AArch64UnknownLinuxGnu,
Custom(TargetName),
}
impl Target {
pub fn as_str(self) -> &'static str {
match self {
Target::X86_64UnknownLinuxGnu => "x86_64-unknown-linux-gnu",
Target::AArch64UnknownLinuxGnu => "aarch64-unknown-linux-gnu",
Target::Custom(name) => name.as_str(),
}
}
pub fn primary_targets() -> Vec<Self> {
vec![Self::X86_64UnknownLinuxGnu, Self::AArch64UnknownLinuxGnu]
}
pub fn default_targets() -> Vec<Self> {
vec![Self::X86_64UnknownLinuxGnu]
}
pub fn is_optional(&self) -> bool {
*self != Target::X86_64UnknownLinuxGnu
}
}
impl FromStr for Target {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"x86_64-unknown-linux-gnu" => Target::X86_64UnknownLinuxGnu,
"aarch64-unknown-linux-gnu" => Target::AArch64UnknownLinuxGnu,
other => Target::Custom(intern(other)),
})
}
}
impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
pub const DEFAULT_TAG: &str = "default";
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Tag(pub TagName);
impl Tag {
pub fn as_str(self) -> &'static str {
self.0.as_str()
}
pub fn default_tag() -> Self {
Tag(intern(DEFAULT_TAG))
}
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl std::str::FromStr for Tag {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Tag(intern(s)))
}
}
impl serde::Serialize for Tag {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> serde::Deserialize<'de> for Tag {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(Tag(intern(&s)))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Repo(pub RepoName);
impl Repo {
pub fn as_str(self) -> &'static str {
self.0.as_str()
}
}
impl fmt::Display for Repo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl std::str::FromStr for Repo {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Repo(intern(s)))
}
}
impl From<&str> for Repo {
fn from(s: &str) -> Self {
Repo(intern(s))
}
}
impl serde::Serialize for Repo {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> serde::Deserialize<'de> for Repo {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(Repo(intern(&s)))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CodegenBackend {
Llvm,
Cranelift,
}
impl CodegenBackend {
pub fn as_str(self) -> &'static str {
match self {
CodegenBackend::Llvm => "llvm",
CodegenBackend::Cranelift => "cranelift",
}
}
pub fn all_values() -> &'static [Self] {
&[Self::Llvm, Self::Cranelift]
}
}
impl FromStr for CodegenBackend {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.to_ascii_lowercase().as_str() {
"llvm" => CodegenBackend::Llvm,
"cranelift" => CodegenBackend::Cranelift,
_ => return Err(format!("{s} is not a codegen backend")),
})
}
}
impl fmt::Display for CodegenBackend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ArtifactId {
Commit { repo: String, commit: Commit },
Tag { repo: String, tag: String },
}
impl fmt::Display for ArtifactId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ArtifactId::Commit { repo, commit } => {
write!(f, "{repo}: {} ({})", commit.sha, commit.date)
}
ArtifactId::Tag { repo, tag } => write!(f, "{repo}: {tag}"),
}
}
}
struct ArtifactInfo<'a> {
name: &'a str,
date: Option<DateTime<Utc>>,
kind: &'static str,
}
impl ArtifactId {
pub fn repo(&self) -> &str {
match self {
Self::Commit { repo, .. } | Self::Tag { repo, .. } => repo,
}
}
pub fn as_name(&self) -> &str {
match self {
Self::Commit { commit, .. } => commit.sha.as_str(),
Self::Tag { tag, .. } => tag.as_str(),
}
}
pub fn commit(&self) -> Option<&Commit> {
match self {
Self::Commit { commit, .. } => Some(commit),
Self::Tag { .. } => None,
}
}
fn info(&self) -> ArtifactInfo<'_> {
let (name, date, ty) = match self {
Self::Commit { commit, .. } => (
commit.sha.as_str(),
Some(commit.date.0),
if commit.is_try() { "try" } else { "master" },
),
Self::Tag { tag, .. } => (tag.as_str(), None, "release"),
};
ArtifactInfo {
name,
date,
kind: ty,
}
}
}
intern!(pub struct QueryLabel);
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct ArtifactIdNumber(pub u32);
#[derive(Debug)]
pub struct ArtifactIdIter {
ids: Arc<Vec<(ArtifactId, Tag)>>,
idx: usize,
}
impl ArtifactIdIter {
pub fn new(artifact_ids: Arc<Vec<(ArtifactId, Tag)>>) -> ArtifactIdIter {
ArtifactIdIter {
ids: artifact_ids,
idx: 0,
}
}
}
impl Iterator for ArtifactIdIter {
type Item = ArtifactId;
fn next(&mut self) -> Option<Self::Item> {
let (aid, _tag) = self.ids.get(self.idx)?;
self.idx += 1;
Some(aid.clone())
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.ids.len(), Some(self.ids.len()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Index {
commits: Indexed<(Repo, Commit, Tag)>,
artifacts: Indexed<(Repo, Box<str>, Tag)>,
pstat_series: Indexed<(Benchmark, Profile, Scenario, CodegenBackend, Target, Metric)>,
runtime_pstat_series: Indexed<(Benchmark, Target, Metric)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Indexed<T> {
#[serde(with = "index_serde")]
#[serde(bound = "T: Serialize + serde::de::DeserializeOwned + std::hash::Hash + Eq")]
map: HashMap<T, u32>,
}
impl<T> std::iter::FromIterator<(u32, T)> for Indexed<T>
where
T: std::hash::Hash + Eq,
{
fn from_iter<I: IntoIterator<Item = (u32, T)>>(iter: I) -> Self {
Self {
map: iter.into_iter().map(|(idx, v)| (v, idx)).collect(),
}
}
}
impl<T> PartialEq for Indexed<T>
where
T: PartialEq + Eq + std::hash::Hash,
{
fn eq(&self, other: &Self) -> bool {
self.map == other.map
}
}
impl<T> Eq for Indexed<T> where T: PartialEq + Eq + std::hash::Hash {}
impl<T> Default for Indexed<T> {
fn default() -> Self {
Indexed {
map: Default::default(),
}
}
}
impl<T> Indexed<T>
where
T: Clone,
T: Eq + std::hash::Hash,
{
pub fn get<U>(&self, value: &U) -> Option<u32>
where
T: std::borrow::Borrow<U>,
U: std::hash::Hash + Eq + ?Sized,
{
self.map.get(value).copied()
}
}
mod index_serde {
use hashbrown::HashMap;
use serde::de::{DeserializeOwned, MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserializer, Serialize, Serializer};
use std::marker::PhantomData;
pub fn serialize<T, S>(map: &HashMap<T, u32>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize,
{
let mut writer = serializer.serialize_map(Some(map.len()))?;
for (value, idx) in map.iter() {
writer.serialize_entry(idx, value)?;
}
writer.end()
}
struct MapVisitor<T>(PhantomData<T>);
impl<'de, T> Visitor<'de> for MapVisitor<T>
where
T: std::hash::Hash + Eq + DeserializeOwned,
{
type Value = HashMap<T, u32>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "map u32 -> T")
}
fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut map = HashMap::with_capacity(access.size_hint().unwrap_or(64));
while let Some((idx, value)) = access.next_entry()? {
if map.insert(value, idx).is_some() {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Other("duplicate"),
&"no duplicates",
));
}
}
Ok(map)
}
}
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<HashMap<T, u32>, D::Error>
where
D: Deserializer<'de>,
T: DeserializeOwned + Eq + std::hash::Hash,
{
deserializer.deserialize_map(MapVisitor(PhantomData))
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum DbLabel {
StatisticDescription {
benchmark: Benchmark,
profile: Profile,
scenario: Scenario,
backend: CodegenBackend,
target: Target,
metric: Metric,
},
}
pub trait Lookup {
type Id;
fn lookup(&self, index: &Index) -> Option<Self::Id>;
}
impl Lookup for DbLabel {
type Id = u32;
fn lookup(&self, index: &Index) -> Option<Self::Id> {
match self {
DbLabel::StatisticDescription {
benchmark,
profile,
scenario,
backend,
metric,
target,
} => index
.pstat_series
.get(&(*benchmark, *profile, *scenario, *backend, *target, *metric)),
}
}
}
impl Lookup for ArtifactId {
type Id = ArtifactIdNumber;
fn lookup(&self, index: &Index) -> Option<Self::Id> {
index.lookup_artifact_with_tag(self, Tag::default_tag())
}
}
pub type StatisticalDescriptionId = u32;
impl Index {
pub async fn load(conn: &mut dyn pool::Connection) -> Index {
conn.load_index().await
}
pub fn lookup_artifact_with_tag(
&self,
artifact_id: &ArtifactId,
tag: Tag,
) -> Option<ArtifactIdNumber> {
Some(match artifact_id {
ArtifactId::Commit { repo, commit, .. } => {
let repo_key = Repo::from(repo.as_str());
ArtifactIdNumber(*self.commits.map.get(&(repo_key, commit.clone(), tag))?)
}
ArtifactId::Tag {
repo,
tag: artifact,
..
} => {
let repo_key = Repo::from(repo.as_str());
ArtifactIdNumber(*self.artifacts.map.get(&(
repo_key,
artifact.as_str().into(),
tag,
))?)
}
})
}
pub fn lookup(
&self,
label: &DbLabel,
artifact_id: &ArtifactId,
) -> Option<(u32, ArtifactIdNumber)> {
let artifact_row_id = artifact_id.lookup(self)?;
let stat_description_row_id = label.lookup(self)?;
Some((stat_description_row_id, artifact_row_id))
}
pub fn artifacts(&self) -> impl Iterator<Item = &'_ str> + '_ {
self.artifacts.map.keys().map(|(_, s, _)| &**s)
}
pub fn artifacts_for_repo(&self, repo: &str) -> impl Iterator<Item = &'_ str> + '_ {
let repo_key = Repo::from(repo);
self.artifacts
.map
.keys()
.filter(move |(r, _, _)| *r == repo_key)
.map(|(_, s, _)| &**s)
}
pub fn commits(&self) -> Vec<Commit> {
let mut commits: Vec<Commit> = self
.commits
.map
.keys()
.map(|(_, c, _)| c)
.collect::<std::collections::HashSet<_>>()
.into_iter()
.cloned()
.collect();
commits.sort();
commits
}
pub fn commits_for_repo(&self, repo: &str) -> Vec<Commit> {
let repo_key = Repo::from(repo);
let mut commits: Vec<Commit> = self
.commits
.map
.keys()
.filter(|(r, _, _)| *r == repo_key)
.map(|(_, c, _)| c)
.collect::<std::collections::HashSet<_>>()
.into_iter()
.cloned()
.collect();
commits.sort();
commits
}
pub fn tags_for_commit(&self, commit: &Commit) -> Vec<Tag> {
self.commits
.map
.keys()
.filter_map(|(_, c, tag)| if c == commit { Some(*tag) } else { None })
.collect()
}
pub fn tags_for_commit_in_repo(&self, repo: &str, commit: &Commit) -> Vec<Tag> {
let repo_key = Repo::from(repo);
self.commits
.map
.keys()
.filter_map(|(r, c, tag)| {
if *r == repo_key && c == commit {
Some(*tag)
} else {
None
}
})
.collect()
}
pub fn tags_for_artifact(&self, artifact_id: &ArtifactId) -> Vec<Tag> {
match artifact_id {
ArtifactId::Commit { repo, commit, .. } => self.tags_for_commit_in_repo(repo, commit),
ArtifactId::Tag { repo, tag, .. } => {
let repo_key = Repo::from(repo.as_str());
self.artifacts
.map
.keys()
.filter_map(|(r, name, t)| {
if *r == repo_key && name.as_ref() == tag.as_str() {
Some(*t)
} else {
None
}
})
.collect()
}
}
}
pub fn compile_metrics(&self) -> Vec<String> {
self.pstat_series
.map
.keys()
.map(|(_, _, _, _, _, metric)| metric)
.collect::<std::collections::HashSet<_>>()
.into_iter()
.map(|s| s.to_string())
.collect()
}
pub fn runtime_metrics(&self) -> Vec<String> {
self.runtime_pstat_series
.map
.keys()
.map(|(_, _, metric)| metric)
.collect::<std::collections::HashSet<_>>()
.into_iter()
.map(|s| s.to_string())
.collect()
}
pub fn compile_targets(&self) -> Vec<Target> {
self.pstat_series
.map
.keys()
.map(|(_, _, _, _, target, _)| target)
.collect::<std::collections::HashSet<_>>()
.into_iter()
.cloned()
.collect()
}
pub fn runtime_targets(&self) -> Vec<Target> {
self.runtime_pstat_series
.map
.keys()
.map(|(_, target, _)| target)
.collect::<std::collections::HashSet<_>>()
.into_iter()
.cloned()
.collect()
}
pub fn tags(&self) -> Vec<Tag> {
let mut tags: std::collections::HashSet<Tag> =
self.commits.map.keys().map(|(_, _, tag)| *tag).collect();
tags.extend(self.artifacts.map.keys().map(|(_, _, tag)| *tag));
tags.into_iter().collect()
}
pub fn tags_for_repo(&self, repo: &str) -> Vec<Tag> {
let repo_key = Repo::from(repo);
let mut tags: std::collections::HashSet<Tag> = self
.commits
.map
.keys()
.filter(|(r, _, _)| *r == repo_key)
.map(|(_, _, tag)| *tag)
.collect();
tags.extend(
self.artifacts
.map
.keys()
.filter(|(r, _, _)| *r == repo_key)
.map(|(_, _, tag)| *tag),
);
tags.into_iter().collect()
}
pub fn compile_statistic_descriptions(
&self,
) -> impl Iterator<
Item = (
&(Benchmark, Profile, Scenario, CodegenBackend, Target, Metric),
StatisticalDescriptionId,
),
> + '_ {
self.pstat_series
.map
.iter()
.map(|(test_case, &row_id)| (test_case, row_id))
}
pub fn runtime_statistic_descriptions(
&self,
) -> impl Iterator<Item = (&(Benchmark, Target, Metric), StatisticalDescriptionId)> + '_ {
self.runtime_pstat_series
.map
.iter()
.map(|(test_case, &row_id)| (test_case, row_id))
}
pub fn artifact_id_for_commit(&self, commit: &str, repo: &str) -> Option<ArtifactId> {
self.commits_for_repo(repo)
.into_iter()
.find(|c| c.sha == *commit)
.map(|c| ArtifactId::Commit {
repo: repo.to_string(),
commit: c,
})
.or_else(|| {
self.artifacts_for_repo(repo)
.find(|a| *a == commit)
.map(|a| ArtifactId::Tag {
repo: repo.to_string(),
tag: a.to_owned(),
})
})
}
}
#[derive(Debug)]
pub struct Step {
pub name: String,
pub is_done: bool,
pub duration: Duration,
pub expected: Duration,
}
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
pub struct CollectionId(i32);
impl fmt::Display for CollectionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl CollectionId {
pub fn from_inner(cid: i32) -> Self {
Self(cid)
}
pub fn as_inner(&self) -> i32 {
self.0
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CompileBenchmark {
pub name: String,
pub category: String,
}
#[derive(Debug)]
pub struct ArtifactCollection {
pub artifact: ArtifactId,
pub duration: Duration,
pub end_time: DateTime<Utc>,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum BenchmarkRequestStatus {
WaitingForArtifacts,
ArtifactsReady,
InProgress,
Completed {
completed_at: DateTime<Utc>,
duration: Duration,
},
}
const BENCHMARK_REQUEST_STATUS_WAITING_FOR_ARTIFACTS_STR: &str = "waiting_for_artifacts";
const BENCHMARK_REQUEST_STATUS_ARTIFACTS_READY_STR: &str = "artifacts_ready";
const BENCHMARK_REQUEST_STATUS_IN_PROGRESS_STR: &str = "in_progress";
const BENCHMARK_REQUEST_STATUS_COMPLETED_STR: &str = "completed";
impl BenchmarkRequestStatus {
pub fn as_str(&self) -> &str {
match self {
Self::WaitingForArtifacts => BENCHMARK_REQUEST_STATUS_WAITING_FOR_ARTIFACTS_STR,
Self::ArtifactsReady => BENCHMARK_REQUEST_STATUS_ARTIFACTS_READY_STR,
Self::InProgress => BENCHMARK_REQUEST_STATUS_IN_PROGRESS_STR,
Self::Completed { .. } => BENCHMARK_REQUEST_STATUS_COMPLETED_STR,
}
}
pub(crate) fn from_str_and_completion_date(
text: &str,
completion_date: Option<DateTime<Utc>>,
duration_ms: Option<i32>,
) -> anyhow::Result<Self> {
match text {
BENCHMARK_REQUEST_STATUS_WAITING_FOR_ARTIFACTS_STR => Ok(Self::WaitingForArtifacts),
BENCHMARK_REQUEST_STATUS_ARTIFACTS_READY_STR => Ok(Self::ArtifactsReady),
BENCHMARK_REQUEST_STATUS_IN_PROGRESS_STR => Ok(Self::InProgress),
BENCHMARK_REQUEST_STATUS_COMPLETED_STR => Ok(Self::Completed {
completed_at: completion_date.ok_or_else(|| {
anyhow!("No completion date for a completed BenchmarkRequestStatus")
})?,
duration: Duration::from_millis(duration_ms.ok_or_else(|| {
anyhow!("No completion duration for a completed BenchmarkRequestStatus")
})? as u64),
}),
_ => Err(anyhow!("Unknown BenchmarkRequestStatus `{text}`")),
}
}
}
impl fmt::Display for BenchmarkRequestStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
const BENCHMARK_REQUEST_TRY_STR: &str = "try";
const BENCHMARK_REQUEST_MASTER_STR: &str = "master";
const BENCHMARK_REQUEST_RELEASE_STR: &str = "release";
#[derive(Debug, Clone, PartialEq)]
pub enum BenchmarkRequestType {
Try {
sha: Option<String>,
parent_sha: Option<String>,
pr: u32,
repo: String,
},
Master {
sha: String,
parent_sha: String,
pr: u32,
repo: String,
},
Release {
tag: String,
repo: String,
},
}
impl fmt::Display for BenchmarkRequestType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BenchmarkRequestType::Try { .. } => write!(f, "{BENCHMARK_REQUEST_TRY_STR}"),
BenchmarkRequestType::Master { .. } => write!(f, "{BENCHMARK_REQUEST_MASTER_STR}"),
BenchmarkRequestType::Release { .. } => write!(f, "{BENCHMARK_REQUEST_RELEASE_STR}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BenchmarkRequest {
commit_type: BenchmarkRequestType,
commit_date: Option<DateTime<Utc>>,
created_at: DateTime<Utc>,
status: BenchmarkRequestStatus,
benchmark_groups: String,
}
impl BenchmarkRequest {
pub fn create_release(tag: &str, commit_date: DateTime<Utc>, repo: &str) -> Self {
Self {
commit_type: BenchmarkRequestType::Release {
tag: tag.to_string(),
repo: repo.to_string(),
},
commit_date: Some(commit_date),
created_at: Utc::now(),
status: BenchmarkRequestStatus::ArtifactsReady,
benchmark_groups: String::new(),
}
}
pub fn create_try_without_artifacts(pr: u32, repo: &str) -> Self {
Self {
commit_type: BenchmarkRequestType::Try {
pr,
sha: None,
parent_sha: None,
repo: repo.to_string(),
},
commit_date: None,
created_at: Utc::now(),
status: BenchmarkRequestStatus::WaitingForArtifacts,
benchmark_groups: String::new(),
}
}
pub fn create_try_with_artifacts(
pr: u32,
repo: &str,
sha: &str,
parent_sha: &str,
commit_date: DateTime<Utc>,
) -> Self {
Self {
commit_type: BenchmarkRequestType::Try {
pr,
sha: Some(sha.to_string()),
parent_sha: Some(parent_sha.to_string()),
repo: repo.to_string(),
},
commit_date: Some(commit_date),
created_at: Utc::now(),
status: BenchmarkRequestStatus::ArtifactsReady,
benchmark_groups: String::new(),
}
}
pub fn create_master(
sha: &str,
parent_sha: &str,
pr: u32,
commit_date: DateTime<Utc>,
repo: &str,
) -> Self {
Self {
commit_type: BenchmarkRequestType::Master {
pr,
sha: sha.to_string(),
parent_sha: parent_sha.to_string(),
repo: repo.to_string(),
},
commit_date: Some(commit_date),
created_at: Utc::now(),
status: BenchmarkRequestStatus::ArtifactsReady,
benchmark_groups: String::new(),
}
}
pub fn with_benchmark_groups(mut self, groups: &str) -> Self {
self.benchmark_groups = groups.to_string();
self
}
pub fn tag(&self) -> Option<&str> {
match &self.commit_type {
BenchmarkRequestType::Try { sha, .. } => sha.as_deref(),
BenchmarkRequestType::Master { sha, .. } => Some(sha),
BenchmarkRequestType::Release { tag, .. } => Some(tag),
}
}
pub fn pr(&self) -> Option<u32> {
match &self.commit_type {
BenchmarkRequestType::Try { pr, .. } | BenchmarkRequestType::Master { pr, .. } => {
Some(*pr)
}
BenchmarkRequestType::Release { .. } => None,
}
}
pub fn parent_sha(&self) -> Option<&str> {
match &self.commit_type {
BenchmarkRequestType::Try { parent_sha, .. } => parent_sha.as_deref(),
BenchmarkRequestType::Master { parent_sha, .. } => Some(parent_sha),
BenchmarkRequestType::Release { .. } => None,
}
}
pub fn repo(&self) -> Option<&str> {
match &self.commit_type {
BenchmarkRequestType::Try { repo, .. }
| BenchmarkRequestType::Master { repo, .. }
| BenchmarkRequestType::Release { repo, .. } => {
(!repo.is_empty()).then_some(repo.as_str())
}
}
}
pub fn status(&self) -> BenchmarkRequestStatus {
self.status
}
pub fn created_at(&self) -> DateTime<Utc> {
self.created_at
}
pub fn commit_date(&self) -> Option<DateTime<Utc>> {
self.commit_date
}
pub fn commit_type(&self) -> &BenchmarkRequestType {
&self.commit_type
}
pub fn is_master(&self) -> bool {
matches!(self.commit_type, BenchmarkRequestType::Master { .. })
}
pub fn is_try(&self) -> bool {
matches!(self.commit_type, BenchmarkRequestType::Try { .. })
}
pub fn is_release(&self) -> bool {
matches!(self.commit_type, BenchmarkRequestType::Release { .. })
}
pub fn benchmark_groups_raw(&self) -> &str {
&self.benchmark_groups
}
pub fn is_completed(&self) -> bool {
matches!(self.status, BenchmarkRequestStatus::Completed { .. })
}
pub fn is_in_progress(&self) -> bool {
matches!(self.status, BenchmarkRequestStatus::InProgress)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BenchmarkRequestInsertResult {
Inserted,
NothingInserted,
Conflict {
existing_tag: String,
existing_status: String,
},
}
fn parse_comma_separated<T>(raw_string: &str, name: &str) -> Result<Vec<T>, String>
where
T: FromStr,
{
raw_string
.split(',')
.map(|s| T::from_str(s).map_err(|_| format!("Invalid {name}: {s}")))
.collect()
}
pub fn parse_backends(backends: &str) -> Result<Vec<CodegenBackend>, String> {
parse_comma_separated(backends, "backend")
}
pub fn parse_profiles(profiles: &str) -> Result<Vec<Profile>, String> {
parse_comma_separated(profiles, "profile")
}
pub fn parse_targets(targets: &str) -> Result<Vec<Target>, String> {
let primary = Target::primary_targets();
let targets = parse_comma_separated(targets, "target")?;
if !targets.iter().all(|t| primary.contains(t)) {
return Err("Only primary targets can be specified".to_string());
}
Ok(targets)
}
pub struct BenchmarkRequestIndex {
all: HashSet<String>,
}
impl BenchmarkRequestIndex {
pub fn contains_tag(&self, tag: &str) -> bool {
self.all.contains(tag)
}
}
#[derive(Debug)]
pub struct PendingBenchmarkRequests {
pub requests: Vec<BenchmarkRequest>,
pub completed_parent_tags: HashSet<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum BenchmarkJobStatus {
Queued,
InProgress {
started_at: DateTime<Utc>,
collector_name: String,
},
Completed {
started_at: DateTime<Utc>,
completed_at: DateTime<Utc>,
collector_name: String,
success: bool,
},
}
const BENCHMARK_JOB_STATUS_QUEUED_STR: &str = "queued";
const BENCHMARK_JOB_STATUS_IN_PROGRESS_STR: &str = "in_progress";
const BENCHMARK_JOB_STATUS_SUCCESS_STR: &str = "success";
const BENCHMARK_JOB_STATUS_FAILURE_STR: &str = "failure";
impl BenchmarkJobStatus {
pub fn as_str(&self) -> &str {
match self {
BenchmarkJobStatus::Queued => BENCHMARK_JOB_STATUS_QUEUED_STR,
BenchmarkJobStatus::InProgress { .. } => BENCHMARK_JOB_STATUS_IN_PROGRESS_STR,
BenchmarkJobStatus::Completed { success, .. } => {
if *success {
BENCHMARK_JOB_STATUS_SUCCESS_STR
} else {
BENCHMARK_JOB_STATUS_FAILURE_STR
}
}
}
}
}
impl fmt::Display for BenchmarkJobStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BenchmarkJob {
id: u32,
benchmark_group: String,
runtime_config: QueueRuntimeConfig,
request_tag: String,
created_at: DateTime<Utc>,
status: BenchmarkJobStatus,
deque_counter: u32,
is_optional: bool,
tag: String,
}
impl BenchmarkJob {
pub fn id(&self) -> u32 {
self.id
}
pub fn benchmark_group(&self) -> &str {
&self.benchmark_group
}
pub fn runtime_config(&self) -> &QueueRuntimeConfig {
&self.runtime_config
}
pub fn request_tag(&self) -> &str {
&self.request_tag
}
pub fn collector_name(&self) -> Option<&str> {
match &self.status {
BenchmarkJobStatus::Queued => None,
BenchmarkJobStatus::InProgress { collector_name, .. }
| BenchmarkJobStatus::Completed { collector_name, .. } => Some(collector_name),
}
}
pub fn deque_count(&self) -> u32 {
self.deque_counter
}
pub fn status(&self) -> &BenchmarkJobStatus {
&self.status
}
pub fn created_at(&self) -> DateTime<Utc> {
self.created_at
}
pub fn is_optional(&self) -> bool {
self.is_optional
}
pub fn tag(&self) -> &str {
&self.tag
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BenchmarkJobConclusion {
Failure,
Success,
}
impl BenchmarkJobConclusion {
pub fn as_str(&self) -> &str {
match self {
BenchmarkJobConclusion::Failure => BENCHMARK_JOB_STATUS_FAILURE_STR,
BenchmarkJobConclusion::Success => BENCHMARK_JOB_STATUS_SUCCESS_STR,
}
}
}
#[derive(Debug, PartialEq)]
pub struct CollectorConfig {
name: String,
is_active: bool,
last_heartbeat_at: DateTime<Utc>,
date_added: DateTime<Utc>,
commit_sha: Option<String>,
}
impl CollectorConfig {
pub fn name(&self) -> &str {
&self.name
}
pub fn is_active(&self) -> bool {
self.is_active
}
pub fn last_heartbeat_at(&self) -> DateTime<Utc> {
self.last_heartbeat_at
}
pub fn date_added(&self) -> DateTime<Utc> {
self.date_added
}
pub fn commit_sha(&self) -> Option<&str> {
self.commit_sha.as_deref()
}
}
#[derive(Debug, PartialEq)]
pub struct InProgressRequestWithJobs {
pub request: (BenchmarkRequest, Vec<BenchmarkJob>),
pub parent: Option<(BenchmarkRequest, Vec<BenchmarkJob>)>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct QueueRuntimeConfig {
pub group: String,
#[serde(default)]
pub repo_key: String,
#[serde(default)]
pub repo: String,
#[serde(default)]
pub build_cmd: String,
#[serde(default)]
pub build_dir: String,
#[serde(default)]
pub is_rust: bool,
#[serde(default)]
pub post_build: String,
#[serde(default)]
pub iterations: Option<u32>,
#[serde(default)]
pub no_isolate: Option<bool>,
#[serde(default)]
pub exclude: Vec<String>,
#[serde(default)]
pub exclude_suffix: Vec<String>,
#[serde(default)]
pub include: Vec<String>,
#[serde(default)]
pub exact_match: Vec<String>,
#[serde(default, alias = "bench_args")]
pub extra_args: Vec<String>,
}
#[derive(Debug, PartialEq)]
pub struct BenchmarkRequestWithErrors {
pub request: BenchmarkRequest,
pub errors: HashMap<String, String>,
}