Skip to content

Commit

Permalink
Improve performance with single fetch to database.
Browse files Browse the repository at this point in the history
We are now fetching the database entries once, and passing around the
unmodifed data. This has improved the speed of timetracker-print in one
example from ~0.33 seconds to ~0.04 seconds (~8x faster).
  • Loading branch information
david-cattermole committed Jan 26, 2024
1 parent bfab9e0 commit 75122c9
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 72 deletions.
126 changes: 115 additions & 11 deletions core/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ fn get_last_database_entry(connection: &rusqlite::Connection) -> Result<Entry> {
Ok(last_entry)
}

fn utc_seconds_to_datetime_local(utc_time_seconds: u64) -> chrono::DateTime<chrono::Local> {
chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(
chrono::NaiveDateTime::from_timestamp_opt(utc_time_seconds.try_into().unwrap(), 0).unwrap(),
chrono::Utc,
)
.with_timezone(&chrono::Local)
}

fn update_existing_entry_rows_into_database(
connection: &rusqlite::Connection,
existing_entries_dedup: &Vec<Entry>,
Expand All @@ -105,15 +113,7 @@ fn update_existing_entry_rows_into_database(
WHERE utc_time_seconds = :utc_time_seconds ;",
)?;
for entry in existing_entries_dedup {
let datetime = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(
chrono::NaiveDateTime::from_timestamp_opt(
entry.utc_time_seconds.try_into().unwrap(),
0,
)
.unwrap(),
chrono::Utc,
)
.with_timezone(&chrono::Local);
let datetime = utc_seconds_to_datetime_local(entry.utc_time_seconds);

let duration = chrono::Duration::seconds(entry.duration_seconds.try_into().unwrap());
let duration_formatted = crate::format::format_duration(
Expand Down Expand Up @@ -308,6 +308,105 @@ fn insert_new_entry_rows_into_database(
Ok(())
}

// Store read-only entries.
//
// Allows filtering the full list of entries by a sub-set of
// times/dates (without having to fetch data from teh database).
#[derive(Debug)]
pub struct Entries {
start_datetime: chrono::DateTime<chrono::Local>,
end_datetime: chrono::DateTime<chrono::Local>,
entries: Vec<Entry>,
}

impl Entries {
pub fn builder() -> EntriesBuilder {
EntriesBuilder::default()
}

pub fn start_datetime(&self) -> chrono::DateTime<chrono::Local> {
self.start_datetime
}

pub fn end_datetime(&self) -> chrono::DateTime<chrono::Local> {
self.end_datetime
}

// Get a slice of the entries for the datetime range given.
pub fn datetime_range_entries(
&self,
start_datetime: chrono::DateTime<chrono::Local>,
end_datetime: chrono::DateTime<chrono::Local>,
) -> &[Entry] {
let start_of_time = start_datetime.timestamp() as u64;
let end_of_time = end_datetime.timestamp() as u64;

let mut start_index = 0;
let mut end_index = 0;
for (i, entry) in self.entries.iter().enumerate() {
if (entry.utc_time_seconds > start_of_time) && (entry.utc_time_seconds < end_of_time) {
start_index = std::cmp::min(start_index, i);
end_index = std::cmp::max(end_index, i);
}
}

&self.entries[start_index..end_index]
}

pub fn is_datetime_range_empty(
&self,
start_datetime: chrono::DateTime<chrono::Local>,
end_datetime: chrono::DateTime<chrono::Local>,
) -> bool {
self.datetime_range_entries(start_datetime, end_datetime)
.is_empty()
}

pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}

#[derive(Default)]
pub struct EntriesBuilder {
start_datetime: chrono::DateTime<chrono::Local>,
end_datetime: chrono::DateTime<chrono::Local>,
entries: Vec<Entry>,
}

impl EntriesBuilder {
pub fn new() -> EntriesBuilder {
EntriesBuilder {
start_datetime: chrono::DateTime::<chrono::Local>::MIN_UTC.into(),
end_datetime: chrono::DateTime::<chrono::Local>::MAX_UTC.into(),
entries: Vec::new(),
}
}

pub fn start_datetime(mut self, value: chrono::DateTime<chrono::Local>) -> EntriesBuilder {
self.start_datetime = value;
self
}

pub fn end_datetime(mut self, value: chrono::DateTime<chrono::Local>) -> EntriesBuilder {
self.end_datetime = value;
self
}

pub fn entries(mut self, entries: Vec<Entry>) -> EntriesBuilder {
self.entries = entries;
self
}

pub fn build(self) -> Entries {
Entries {
start_datetime: self.start_datetime,
end_datetime: self.end_datetime,
entries: self.entries,
}
}
}

pub struct Storage {
connection: rusqlite::Connection,
entries: Vec<Entry>,
Expand Down Expand Up @@ -403,7 +502,7 @@ impl Storage {
&mut self,
start_utc_time_seconds: u64,
end_utc_time_seconds: u64,
) -> Result<Vec<Entry>> {
) -> Result<Entries> {
let mut statement = self.connection.prepare(
"SELECT utc_time_seconds, duration_seconds, status,
executable,
Expand Down Expand Up @@ -442,7 +541,12 @@ impl Storage {
let entry = Entry::new(utc_time_seconds, duration_seconds, status, vars);
entries.push(entry);
}
Ok(entries)

Ok(Entries::builder()
.start_datetime(utc_seconds_to_datetime_local(start_utc_time_seconds))
.end_datetime(utc_seconds_to_datetime_local(end_utc_time_seconds))
.entries(entries)
.build())
}

pub fn write_entries(&mut self) -> Result<()> {
Expand Down
27 changes: 25 additions & 2 deletions print-bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use timetracker_print_lib::print::get_relative_week_start_end;
mod settings;

fn print_presets(args: &CommandArguments, settings: &PrintAppSettings) -> Result<()> {
let now = SystemTime::now();
let database_file_path = get_database_file_path(
&settings.core.database_dir,
&settings.core.database_file_name,
Expand All @@ -32,11 +33,15 @@ fn print_presets(args: &CommandArguments, settings: &PrintAppSettings) -> Result
&settings.core.database_file_name, &settings.core.database_dir
);
}
let duration = now.elapsed()?.as_secs_f32();
debug!("Time taken (find database): {:.4} seconds", duration);

let mut storage = Storage::open_as_read_only(
&database_file_path.expect("Database file path should be valid"),
RECORD_INTERVAL_SECONDS,
)?;
let duration = now.elapsed()?.as_secs_f32();
debug!("Time taken (open database): {:.4} seconds", duration);

let relative_week = if args.last_week {
-1
Expand All @@ -56,6 +61,7 @@ fn print_presets(args: &CommandArguments, settings: &PrintAppSettings) -> Result
);
println!("");

let now = SystemTime::now();
let (presets, missing_preset_names) = create_presets(
settings.print.time_scale,
settings.print.format_datetime,
Expand All @@ -66,11 +72,28 @@ fn print_presets(args: &CommandArguments, settings: &PrintAppSettings) -> Result
&settings.print.display_presets,
&settings.print.presets,
)?;
let duration = now.elapsed()?.as_secs_f32();
debug!("Time taken (create presets): {:.4} seconds", duration);

let lines = generate_presets(&presets, &mut storage, week_datetime_pair)?;
let now = SystemTime::now();
let (week_start_datetime, week_end_datetime) = week_datetime_pair;
let week_start_of_time = week_start_datetime.timestamp() as u64;
let week_end_of_time = week_end_datetime.timestamp() as u64;
let week_entries = storage.read_entries(week_start_of_time, week_end_of_time)?;
let duration = now.elapsed()?.as_secs_f32();
debug!("Time taken (read database): {:.4} seconds", duration);

let now = SystemTime::now();
let lines = generate_presets(&presets, &week_entries)?;
let duration = now.elapsed()?.as_secs_f32();
debug!("Time taken (generate presets): {:.4} seconds", duration);

let now = SystemTime::now();
for line in &lines {
println!("{}", line);
}
let duration = now.elapsed()?.as_secs_f32();
debug!("Time taken (print to terminal): {:.4} seconds", duration);

if !missing_preset_names.is_empty() {
let all_preset_names = get_map_keys_sorted_strings(&settings.print.presets.keys());
Expand Down Expand Up @@ -115,7 +138,7 @@ fn main() -> Result<()> {
};

let duration = now.elapsed()?.as_secs_f32();
debug!("Time taken: {:.2} seconds", duration);
debug!("Time taken: {:.4} seconds", duration);

Ok(())
}
8 changes: 4 additions & 4 deletions print-lib/src/preset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use timetracker_core::format::PrintType;
use timetracker_core::format::TimeBlockUnit;
use timetracker_core::format::TimeScale;
use timetracker_core::settings::PrintPresetSettings;
use timetracker_core::storage::Storage;
use timetracker_core::storage::Entries;

pub fn override_preset_value<T>(new_value: Option<T>, old_value: Option<T>) -> Option<T> {
match new_value {
Expand Down Expand Up @@ -87,10 +87,10 @@ pub fn create_presets(

pub fn generate_presets(
presets: &Vec<PrintPresetSettings>,
storage: &mut Storage,
week_datetime_pair: DateTimeLocalPair,
entries: &Entries,
) -> Result<Vec<String>> {
let color = colored::Color::Green;
let week_datetime_pair: DateTimeLocalPair = (entries.start_datetime(), entries.end_datetime());

let mut lines = Vec::new();
for preset in presets {
Expand All @@ -115,7 +115,7 @@ pub fn generate_presets(
};

generate_preset_lines(
storage,
entries,
&mut lines,
week_datetime_pair,
print_type,
Expand Down
Loading

0 comments on commit 75122c9

Please sign in to comment.