#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DbOperation {
StoredProcedure,
Select,
Insert,
Update,
Delete,
Ddl,
}
impl DbOperation {
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
DbOperation::StoredProcedure => "sp_call",
DbOperation::Select => "select",
DbOperation::Insert => "insert",
DbOperation::Update => "update",
DbOperation::Delete => "delete",
DbOperation::Ddl => "ddl",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FetchType {
Orchestration,
WorkItem,
}
impl FetchType {
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
FetchType::Orchestration => "orchestration",
FetchType::WorkItem => "work_item",
}
}
}
#[cfg(feature = "db-metrics")]
#[inline]
pub fn record_db_call(operation: DbOperation, sp_name: Option<&str>) {
use metrics::counter;
counter!("duroxide.db.calls", "operation" => operation.as_str()).increment(1);
if operation == DbOperation::StoredProcedure {
if let Some(name) = sp_name {
counter!("duroxide.db.sp_calls", "sp_name" => name.to_string()).increment(1);
}
}
}
#[cfg(not(feature = "db-metrics"))]
#[inline(always)]
pub fn record_db_call(_operation: DbOperation, _sp_name: Option<&str>) {
}
#[cfg(feature = "db-metrics")]
#[inline]
pub fn record_fetch_attempt(fetch_type: FetchType) {
use metrics::counter;
counter!("duroxide.fetch.attempts", "fetch_type" => fetch_type.as_str()).increment(1);
}
#[cfg(not(feature = "db-metrics"))]
#[inline(always)]
pub fn record_fetch_attempt(_fetch_type: FetchType) {
}
#[cfg(feature = "db-metrics")]
#[inline]
pub fn record_fetch_success(fetch_type: FetchType, count: u64) {
use metrics::counter;
counter!("duroxide.fetch.items", "fetch_type" => fetch_type.as_str()).increment(count);
}
#[cfg(not(feature = "db-metrics"))]
#[inline(always)]
pub fn record_fetch_success(_fetch_type: FetchType, _count: u64) {
}
#[cfg(feature = "db-metrics")]
#[inline]
pub fn record_fetch_result(fetch_type: FetchType, items_fetched: u64, duration_ms: f64) {
use metrics::{counter, histogram};
counter!("duroxide.fetch.attempts", "fetch_type" => fetch_type.as_str()).increment(1);
if items_fetched > 0 {
counter!("duroxide.fetch.items", "fetch_type" => fetch_type.as_str())
.increment(items_fetched);
counter!("duroxide.fetch.loaded", "fetch_type" => fetch_type.as_str()).increment(1);
histogram!("duroxide.fetch.loaded_duration_ms", "fetch_type" => fetch_type.as_str())
.record(duration_ms);
} else {
counter!("duroxide.fetch.empty", "fetch_type" => fetch_type.as_str()).increment(1);
histogram!("duroxide.fetch.empty_duration_ms", "fetch_type" => fetch_type.as_str())
.record(duration_ms);
}
}
#[cfg(not(feature = "db-metrics"))]
#[inline(always)]
pub fn record_fetch_result(_fetch_type: FetchType, _items_fetched: u64, _duration_ms: f64) {
}
#[cfg(feature = "db-metrics")]
#[inline]
pub fn record_db_call_with_duration(
operation: DbOperation,
sp_name: Option<&str>,
duration_ms: f64,
) {
use metrics::{counter, histogram};
counter!("duroxide.db.calls", "operation" => operation.as_str()).increment(1);
if operation == DbOperation::StoredProcedure {
if let Some(name) = sp_name {
counter!("duroxide.db.sp_calls", "sp_name" => name.to_string()).increment(1);
histogram!(
"duroxide.db.call_duration_ms",
"operation" => operation.as_str(),
"sp_name" => name.to_string()
)
.record(duration_ms);
} else {
histogram!(
"duroxide.db.call_duration_ms",
"operation" => operation.as_str()
)
.record(duration_ms);
}
} else {
histogram!(
"duroxide.db.call_duration_ms",
"operation" => operation.as_str()
)
.record(duration_ms);
}
}
#[cfg(not(feature = "db-metrics"))]
#[inline(always)]
pub fn record_db_call_with_duration(
_operation: DbOperation,
_sp_name: Option<&str>,
_duration_ms: f64,
) {
}
#[cfg(feature = "db-metrics")]
pub struct DbCallTimer {
operation: DbOperation,
sp_name: Option<&'static str>,
start: std::time::Instant,
}
#[cfg(feature = "db-metrics")]
impl DbCallTimer {
#[inline]
pub fn new(operation: DbOperation, sp_name: Option<&'static str>) -> Self {
Self {
operation,
sp_name,
start: std::time::Instant::now(),
}
}
}
#[cfg(feature = "db-metrics")]
impl Drop for DbCallTimer {
fn drop(&mut self) {
let duration_ms = self.start.elapsed().as_secs_f64() * 1000.0;
record_db_call_with_duration(self.operation, self.sp_name, duration_ms);
}
}
#[cfg(not(feature = "db-metrics"))]
pub struct DbCallTimer;
#[cfg(not(feature = "db-metrics"))]
impl DbCallTimer {
#[inline(always)]
pub fn new(_operation: DbOperation, _sp_name: Option<&'static str>) -> Self {
Self
}
}
#[macro_export]
macro_rules! instrument_db_call {
($op:ident, $sp_name:expr, $body:expr) => {{
#[cfg(feature = "db-metrics")]
{
let _timer = $crate::db_metrics::DbCallTimer::new(
$crate::db_metrics::DbOperation::$op,
Some($sp_name),
);
$crate::db_metrics::record_db_call(
$crate::db_metrics::DbOperation::$op,
Some($sp_name),
);
$body
}
#[cfg(not(feature = "db-metrics"))]
{
$body
}
}};
($op:ident, $body:expr) => {{
#[cfg(feature = "db-metrics")]
{
let _timer =
$crate::db_metrics::DbCallTimer::new($crate::db_metrics::DbOperation::$op, None);
$crate::db_metrics::record_db_call($crate::db_metrics::DbOperation::$op, None);
$body
}
#[cfg(not(feature = "db-metrics"))]
{
$body
}
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_db_operation_as_str() {
assert_eq!(DbOperation::StoredProcedure.as_str(), "sp_call");
assert_eq!(DbOperation::Select.as_str(), "select");
assert_eq!(DbOperation::Insert.as_str(), "insert");
assert_eq!(DbOperation::Update.as_str(), "update");
assert_eq!(DbOperation::Delete.as_str(), "delete");
assert_eq!(DbOperation::Ddl.as_str(), "ddl");
}
#[test]
fn test_record_db_call_compiles() {
record_db_call(DbOperation::StoredProcedure, Some("test_sp"));
record_db_call(DbOperation::Select, None);
}
#[test]
fn test_timer_compiles() {
let _timer = DbCallTimer::new(DbOperation::Select, None);
let _timer2 = DbCallTimer::new(DbOperation::StoredProcedure, Some("test_sp"));
}
}