use {
crate::*,
chrono::prelude::*,
rusqlite::{
params,
Connection,
OptionalExtension,
Row,
},
std::time::Duration,
};
pub const VERSION: &str = "1";
fn create_tables(con: &Connection) -> Result<(), GlassBenchError> {
con.execute(
"CREATE TABLE IF NOT EXISTS bench (
id INTEGER PRIMARY KEY,
time INTEGER NOT NULL,
name TEXT NOT NULL,
title TEXT NOT NULL,
tag TEXT,
commit_id TEXT
)",
params![],
)?;
con.execute(
"CREATE TABLE IF NOT EXISTS task (
bench INTEGER NOT NULL,
name TEXT NOT NULL,
iterations INTEGER NOT NULL,
total_duration_ns INTEGER NOT NULL,
mean_duration_ns INTEGER NOT NULL,
FOREIGN KEY(bench) REFERENCES bench(id),
PRIMARY KEY(bench, name)
)",
params![],
)?;
Ok(())
}
pub struct Db {
pub con: Connection,
}
impl Db {
pub fn open() -> Result<Self, GlassBenchError> {
let path = std::env::current_dir()?
.join(format!("glassbench_v{}.db", VERSION));
let con = Connection::open(path)?;
create_tables(&con)?;
Ok(Db {
con,
})
}
pub fn save_bench(&mut self, bench: &Bench) -> Result<i64, GlassBenchError> {
self.con.execute(
"INSERT INTO bench (time, name, title, tag, commit_id) VALUES (?1, ?2, ?3, ?4, ?5)",
params![
bench.time.timestamp(),
&bench.name,
&bench.title,
&bench.tag,
bench.git_info.as_ref().map(|gi| &gi.commit_id),
],
)?;
let bench_id = self.con.last_insert_rowid();
let mut ps = self.con.prepare(
"INSERT INTO task
(bench, name, iterations, total_duration_ns, mean_duration_ns)
VALUES (?1, ?2, ?3, ?4, ?5)"
)?;
let tasks = bench.tasks
.iter()
.filter_map(|t| {
t.measure
.as_ref()
.map(|mes| (&t.name, mes))
});
for (name, mes) in tasks {
ps.execute(params![
bench_id,
name,
mes.iterations,
mes.total_duration.as_nanos() as i64,
mes.mean_duration().as_nanos() as i64,
])?;
}
Ok(bench_id)
}
pub fn last_bench_named(
&mut self,
name: &str,
) -> Result<Option<Bench>, GlassBenchError> {
match self.con.query_row(
"SELECT id, time, name, title, tag, commit_id
fROM bench WHERE name=?1 ORDER BY id DESC LIMIT 1",
params![name],
parse_bench,
).optional()? {
Some((bench_id, mut bench)) => {
let mut ps = self.con.prepare(
"SELECT name, iterations, total_duration_ns FROM task WHERE bench=?1"
)?;
let iter = ps
.query_map(
params![bench_id],
parse_task,
)?;
for task in iter {
bench.tasks.push(task?);
}
Ok(Some(bench))
}
None => {
Ok(None)
}
}
}
pub fn task_history(
&mut self,
bench_name: &str,
task_name: &str,
) -> Result<TaskHistory, GlassBenchError> {
let mut history = TaskHistory {
bench_name: bench_name.into(),
task_name: task_name.into(),
records: Vec::new(),
};
let mut ps = self.con.prepare(
"SELECT
bench.time, bench.tag, bench.commit_id,
task.iterations, task.total_duration_ns
FROM task JOIN bench ON task.bench=bench.id
WHERE bench.name=?1 AND task.name=?2
ORDER BY bench.time"
)?;
let iter = ps
.query_map(
params![bench_name, task_name],
parse_task_record,
)?;
for record in iter {
history.records.push(record?);
}
Ok(history)
}
}
fn parse_bench(row: &Row<'_>) -> Result<(i64, Bench), rusqlite::Error> {
let bench_id: i64 = row.get(0)?;
let commit_id: Option<String> = row.get(5)?;
let bench = Bench {
time: Utc.timestamp(row.get(1)?, 0),
name: row.get(2)?,
title: row.get(3)?,
tag: row.get(4)?,
git_info: commit_id.map(|commit_id| GitInfo { commit_id }),
tasks: Vec::new(),
};
Ok((bench_id, bench))
}
fn parse_task(row: &Row<'_>) -> Result<TaskBench, rusqlite::Error> {
let nanos: i64 = row.get(2)?;
Ok(TaskBench {
name: row.get(0)?,
measure: Some(TaskMeasure {
iterations: row.get(1)?,
total_duration: Duration::from_nanos(nanos as u64),
}),
})
}
fn parse_task_record(row: &Row<'_>) -> Result<TaskRecord, rusqlite::Error> {
let commit_id: Option<String> = row.get(2)?;
let nanos: i64 = row.get(4)?;
Ok(TaskRecord {
time: Utc.timestamp(row.get(0)?, 0),
git_info: commit_id.map(|commit_id| GitInfo { commit_id }),
tag: row.get(1)?,
measure: TaskMeasure {
iterations: row.get(3)?,
total_duration: Duration::from_nanos(nanos as u64),
},
})
}