- Updated Monitoring module to calculate hourly/daily stats on-demand from monitoring_checks table - Removed TimescaleDB continuous aggregate dependency (disabled due to licensing) - Enabled all previously skipped TimescaleDB tests with proper test data - Fixed UUID encoding for SQL queries using Ecto.UUID.dump! - Fixed Decimal handling in uptime percentage calculation - Added test for get_uptime_percentage with nil checks - Monitoring module now at 100% coverage
125 lines
3.7 KiB
Elixir
125 lines
3.7 KiB
Elixir
defmodule Towerops.Monitoring do
|
|
@moduledoc """
|
|
The Monitoring context.
|
|
"""
|
|
|
|
import Ecto.Query
|
|
|
|
alias Ecto.Adapters.SQL
|
|
alias Towerops.Monitoring.Check
|
|
alias Towerops.Repo
|
|
|
|
@doc """
|
|
Creates a monitoring check.
|
|
"""
|
|
def create_check(attrs) do
|
|
%Check{}
|
|
|> Check.changeset(attrs)
|
|
|> Repo.insert()
|
|
end
|
|
|
|
@doc """
|
|
Returns the list of checks for an equipment.
|
|
"""
|
|
def list_equipment_checks(equipment_id, limit \\ 100) do
|
|
Repo.all(from(c in Check, where: c.equipment_id == ^equipment_id, order_by: [desc: c.checked_at], limit: ^limit))
|
|
end
|
|
|
|
@doc """
|
|
Returns the latest check for an equipment.
|
|
"""
|
|
def get_latest_check(equipment_id) do
|
|
Repo.one(from(c in Check, where: c.equipment_id == ^equipment_id, order_by: [desc: c.checked_at], limit: 1))
|
|
end
|
|
|
|
@doc """
|
|
Deletes old monitoring checks older than the given date.
|
|
"""
|
|
def delete_old_checks(older_than_date) do
|
|
Repo.delete_all(from(c in Check, where: c.checked_at < ^older_than_date))
|
|
end
|
|
|
|
@doc """
|
|
Gets hourly statistics for equipment over a time range.
|
|
Calculates aggregates on-demand from monitoring_checks.
|
|
"""
|
|
def get_hourly_stats(equipment_id, start_time, end_time) do
|
|
query = """
|
|
SELECT
|
|
date_trunc('hour', checked_at) as bucket,
|
|
COUNT(*) as total_checks,
|
|
COUNT(*) FILTER (WHERE status = 'success') as successful_checks,
|
|
COUNT(*) FILTER (WHERE status IN ('failure', 'timeout')) as failed_checks,
|
|
ROUND(AVG(response_time_ms)::numeric, 2) as avg_response_time_ms,
|
|
MIN(response_time_ms) as min_response_time_ms,
|
|
MAX(response_time_ms) as max_response_time_ms,
|
|
ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'success') / NULLIF(COUNT(*), 0), 2) as uptime_percentage
|
|
FROM monitoring_checks
|
|
WHERE equipment_id = $1::uuid
|
|
AND checked_at >= $2
|
|
AND checked_at <= $3
|
|
GROUP BY bucket
|
|
ORDER BY bucket ASC
|
|
"""
|
|
|
|
SQL.query!(
|
|
Repo,
|
|
query,
|
|
[Ecto.UUID.dump!(equipment_id), start_time, end_time]
|
|
)
|
|
end
|
|
|
|
@doc """
|
|
Gets daily statistics for equipment over a time range.
|
|
Calculates aggregates on-demand from monitoring_checks.
|
|
"""
|
|
def get_daily_stats(equipment_id, start_time, end_time) do
|
|
query = """
|
|
SELECT
|
|
date_trunc('day', checked_at) as bucket,
|
|
COUNT(*) as total_checks,
|
|
COUNT(*) FILTER (WHERE status = 'success') as successful_checks,
|
|
COUNT(*) FILTER (WHERE status IN ('failure', 'timeout')) as failed_checks,
|
|
ROUND(AVG(response_time_ms)::numeric, 2) as avg_response_time_ms,
|
|
MIN(response_time_ms) as min_response_time_ms,
|
|
MAX(response_time_ms) as max_response_time_ms,
|
|
ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'success') / NULLIF(COUNT(*), 0), 2) as uptime_percentage
|
|
FROM monitoring_checks
|
|
WHERE equipment_id = $1::uuid
|
|
AND checked_at >= $2
|
|
AND checked_at <= $3
|
|
GROUP BY bucket
|
|
ORDER BY bucket ASC
|
|
"""
|
|
|
|
SQL.query!(
|
|
Repo,
|
|
query,
|
|
[Ecto.UUID.dump!(equipment_id), start_time, end_time]
|
|
)
|
|
end
|
|
|
|
@doc """
|
|
Gets uptime percentage for equipment over the last N days.
|
|
Calculates on-demand from monitoring_checks.
|
|
"""
|
|
def get_uptime_percentage(equipment_id, days_ago \\ 30) do
|
|
start_time = DateTime.add(DateTime.utc_now(), -days_ago * 24 * 60 * 60, :second)
|
|
|
|
query = """
|
|
SELECT
|
|
ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'success') / NULLIF(COUNT(*), 0), 2) as uptime_percentage
|
|
FROM monitoring_checks
|
|
WHERE equipment_id = $1::uuid
|
|
AND checked_at >= $2
|
|
"""
|
|
|
|
case SQL.query!(Repo, query, [Ecto.UUID.dump!(equipment_id), start_time]) do
|
|
%{rows: [[%Decimal{} = uptime]]} ->
|
|
uptime |> Decimal.to_float() |> Float.round(2)
|
|
|
|
_ ->
|
|
nil
|
|
end
|
|
end
|
|
end
|