11 KiB
System Resources ORM Setup - Diesel Implementation
This document provides all the code needed to convert the system_resource entity from raw SQL queries to Diesel ORM, following the same pattern used for the user entity.
Current State Analysis
The system_resources entity currently has:
- ✅ Diesel model defined in
infrastructure/src/system_resources/model.rs - ❌ No Diesel repository implementation (only SQL-based)
- ❌ No Diesel adapter implementation (only SQL-based)
- ❌ Missing table definition in
schema.rs
Step 1: Add system_resources Table Definition to schema.rs
File: infrastructure/src/schema.rs
Add the following table definition after the user table:
// Define the system_resources table structure matching the current SQL schema
table! {
system_resources (id) {
id -> Text,
datetime -> Integer,
cpu_usage -> Float,
total_memory -> Integer,
used_memory -> Integer,
}
}
Step 2: Create Diesel Repository Implementation
File: infrastructure/src/system_resources/diesel_repository.rs
use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;
use std::sync::{Arc, Mutex};
use chrono::{DateTime, Utc};
use super::model::SystemResourcesEntity;
#[derive(Clone)]
pub struct SystemResourcesDieselRepository {
pub database_connection: Arc<Mutex<SqliteConnection>>,
}
impl SystemResourcesDieselRepository {
pub fn new(database_connection: Arc<Mutex<SqliteConnection>>) -> Self {
Self {
database_connection,
}
}
fn get_connection(&self) -> std::sync::MutexGuard<'_, SqliteConnection> {
self.database_connection.lock().unwrap()
}
pub fn create_table(&self) -> Result<(), diesel::result::Error> {
let mut connection = self.get_connection();
diesel::sql_query(
"CREATE TABLE IF NOT EXISTS system_resources (
id TEXT PRIMARY KEY,
datetime INTEGER NOT NULL,
cpu_usage REAL NOT NULL,
total_memory INTEGER NOT NULL,
used_memory INTEGER NOT NULL
);"
).execute(&mut *connection)?;
Ok(())
}
pub fn insert(&self, resource: &SystemResourcesEntity) -> Result<(), diesel::result::Error> {
let mut connection = self.get_connection();
diesel::insert_into(crate::schema::system_resources::table)
.values(resource)
.execute(&mut *connection)?;
Ok(())
}
pub fn get_by_id(&self, id: &str) -> Result<Option<SystemResourcesEntity>, diesel::result::Error> {
let mut connection = self.get_connection();
let result: Result<SystemResourcesEntity, _> = crate::schema::system_resources::table
.find(id)
.first(&mut *connection);
match result {
Ok(resource_diesel) => Ok(Some(resource_diesel)),
Err(diesel::result::Error::NotFound) => Ok(None),
Err(e) => Err(e),
}
}
pub fn get_between(&self, start: i64, end: i64) -> Result<Vec<SystemResourcesEntity>, diesel::result::Error> {
let mut connection = self.get_connection();
let resources: Vec<SystemResourcesEntity> = crate::schema::system_resources::table
.filter(crate::schema::system_resources::datetime.between(start, end))
.order(crate::schema::system_resources::datetime.asc())
.load(&mut *connection)?;
Ok(resources)
}
}
Step 3: Create Diesel Adapter Implementation
File: infrastructure/src/system_resources/diesel_adapter.rs
use std::io::{Error, ErrorKind};
use chrono::{DateTime, Utc};
use surveillant_systeme_domain::system_resources::model::SystemResources;
use surveillant_systeme_domain::system_resources::port::SystemResourcesPort;
use crate::system_resources::diesel_repository::SystemResourcesDieselRepository;
use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System};
#[derive(Clone)]
pub struct SystemResourcesDieselAdapter {
pub repository: SystemResourcesDieselRepository
}
impl SystemResourcesDieselAdapter {
pub fn new(repository: SystemResourcesDieselRepository) -> Self {
Self {
repository
}
}
fn get_cpu_usage_from_shell(&self, _: &System) -> f32 {
use std::process::Command;
use log::error;
if cfg!(target_os = "linux") {
self.get_cpu_usage_from_command("echo $[100-$(vmstat 1 2|tail -1|awk '{print $15}')]")
} else if cfg!(target_os = "macos") {
self.get_cpu_usage_from_command("top -l 1 | grep -E \"^CPU\" | cut -d ' ' -f3 | cut -d '%' -f1")
} else {
error!("Unhandled OS…");
0f32
}
}
fn get_cpu_usage_from_command(&self, command: &str) -> f32 {
use std::process::Command;
let command_output = Command::new("sh")
.arg("-c")
.arg(command)
.output()
.expect("Error while retrieving CPU usage from command…");
if command_output.status.success() {
let string = String::from_utf8(command_output.stdout).unwrap();
string.trim().parse().unwrap()
} else {
let stderr = String::from_utf8(command_output.stderr).unwrap();
error!("Command failed with error:\n{}", stderr);
0f32
}
}
}
impl SystemResourcesPort for SystemResourcesDieselAdapter {
fn insert_system_resources(&self, system_resources: &SystemResources) -> Result<(), Error> {
let resource_entity = crate::system_resources::model::SystemResourcesEntity::from(system_resources);
self.repository.insert(&resource_entity)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to insert the system resources… Cause: {}", error)
))
}
fn get_system_resources_lines_between(&self, start: &DateTime<Utc>, end: &DateTime<Utc>) -> Result<Vec<SystemResources>, Error> {
self.repository.get_between(start.timestamp(), end.timestamp())
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to retrieve the system resources in the given period… Cause: {}", error)
))
.map(|resource_entities| resource_entities.into_iter().map(|resource_entity| resource_entity.to_domain()).collect())
}
fn get_system_resources(&self) -> Result<SystemResources, Error> {
let system = System::new_with_specifics(
RefreshKind::nothing().with_cpu(CpuRefreshKind::everything()).with_memory(MemoryRefreshKind::everything()),
);
let cpu_usage = self.get_cpu_usage_from_shell(&system);
let total_memory = system.total_memory();
let used_memory = system.used_memory();
let result = SystemResources::new(
cpu_usage,
total_memory,
used_memory,
);
Ok(result)
}
fn clone_box(&self) -> Box<dyn SystemResourcesPort> {
Box::new(self.clone())
}
}
Step 4: Update Module Files
File: infrastructure/src/system_resources/mod.rs
Add the new Diesel implementations to the module exports:
// Add these lines to expose the new Diesel implementations
pub mod diesel_repository;
pub mod diesel_adapter;
// Re-export the types for convenience
pub use diesel_repository::SystemResourcesDieselRepository;
pub use diesel_adapter::SystemResourcesDieselAdapter;
Step 5: Update Cargo.toml Dependencies
File: infrastructure/Cargo.toml
Ensure Diesel dependencies are properly configured. The file should include:
diesel = { version = "2.1", features = ["sqlite"] }
diesel_migrations = "2.1"
Step 6: Update Database Initialization
File: infrastructure/src/common/database_initialisation.rs
Update the initialization code to use Diesel for creating tables:
// Replace the existing system_resources table creation with Diesel version
impl DatabaseInitialisation {
pub fn create_tables(&self) -> Result<(), Error> {
// User table (already using Diesel)
let user_repository = crate::user::diesel_repository::UserDieselRepository::new(self.database_connection.clone());
user_repository.create_table()?;
// System resources table (now using Diesel)
let system_resources_repository = crate::system_resources::diesel_repository::SystemResourcesDieselRepository::new(self.database_connection.clone());
system_resources_repository.create_table()?;
Ok(())
}
}
Step 7: Update Application Context
File: launcher/src/application_context.rs
Update the application context to use Diesel adapters:
// Replace system resources adapter initialization
let system_resources_repository = SystemResourcesDieselRepository::new(database_connection.clone());
let system_resources_adapter = SystemResourcesDieselAdapter::new(system_resources_repository);
Migration Strategy
Phase 1: Parallel Implementation (Current Step)
- ✅ Create Diesel model (already done)
- ✅ Add table definition to schema.rs
- ✅ Implement SystemResourcesDieselRepository
- ✅ Implement SystemResourcesDieselAdapter
- ✅ Update module exports
Phase 2: Integration
- Update database initialization to use Diesel
- Update application context to use Diesel adapters
- Test Diesel implementation alongside existing SQL version
Phase 3: Validation
- Verify identical behavior through manual testing
- Check performance characteristics
- Ensure all system resources API endpoints work correctly
Phase 4: Cleanup (Future)
- Remove old SQL-based adapter implementation
- Remove old SQL-based repository implementation
- Update all imports to use Diesel versions
Key Differences from User Implementation
-
Model Differences: SystemResourcesEntity uses different field types:
datetimeas i64 (Unix timestamp) instead of DateTimecpu_usageas f32 instead of String- Memory fields as i64 instead of other types
-
Repository Method Differences:
- No equivalent to
get_by_username(system resources use datetime ranges) get_betweenmethod for time-based queries instead of ID/username lookups- Different query patterns for temporal data
- No equivalent to
-
Adapter Method Differences:
- Additional
get_system_resources()method that collects live system data - Time-based range queries instead of single entity lookups
- Additional
Success Criteria
- ✅ SystemResourcesDieselRepository implements all necessary methods
- ✅ SystemResourcesDieselAdapter provides same functionality as SQL version
- ✅ All system resources API endpoints work identically with Diesel backend
- ✅ No data migration required - existing system resources continue to work
- ✅ Performance characteristics meet or exceed current implementation
- ✅ Error handling produces same error types and messages