From 965b693a19353f3fd65a4ef560805becaf24fe6f Mon Sep 17 00:00:00 2001 From: Jose Henrique Date: Thu, 19 Jun 2025 21:41:16 -0300 Subject: [PATCH] tech stats --- OpenCand.API/Controllers/StatsController.cs | 6 ++ .../Repository/EstatisticaRepository.cs | 2 +- OpenCand.API/Repository/OpenCandRepository.cs | 58 +++++++++++++++++++ OpenCand.API/Services/CachePreloadService.cs | 1 + OpenCand.API/Services/OpenCandService.cs | 10 ++++ OpenCand.Core/Models/OpenCandStats.cs | 36 +++++++++--- 6 files changed, 105 insertions(+), 8 deletions(-) diff --git a/OpenCand.API/Controllers/StatsController.cs b/OpenCand.API/Controllers/StatsController.cs index 876b8d4..5ab679b 100644 --- a/OpenCand.API/Controllers/StatsController.cs +++ b/OpenCand.API/Controllers/StatsController.cs @@ -27,6 +27,12 @@ namespace OpenCand.API.Controllers { return await openCandService.GetDataAvailabilityStatsAsync(); } + + [HttpGet("tech")] + public async Task GetDatabaseTechStats() + { + return await openCandService.GetDatabaseTechStatsAsync(); + } } } diff --git a/OpenCand.API/Repository/EstatisticaRepository.cs b/OpenCand.API/Repository/EstatisticaRepository.cs index aa7ba30..0f337d8 100644 --- a/OpenCand.API/Repository/EstatisticaRepository.cs +++ b/OpenCand.API/Repository/EstatisticaRepository.cs @@ -67,7 +67,7 @@ namespace OpenCand.API.Repository { result.Partidos = (await connection.QueryAsync(@"SELECT DISTINCT sigla FROM partido ORDER BY sigla ASC;")).AsList(); result.SiglasUF = (await connection.QueryAsync(@"SELECT DISTINCT siglauf FROM candidato_mapping ORDER BY siglauf ASC;")).AsList(); - result.Anos = (await connection.QueryAsync(@"SELECT DISTINCT ano FROM candidato_mapping ORDER BY ano ASC;")).AsList(); + result.Anos = (await connection.QueryAsync(@"SELECT DISTINCT ano FROM candidato_mapping ORDER BY ano DESC;")).AsList(); result.Cargos = (await connection.QueryAsync(@"SELECT DISTINCT cargo FROM candidato_mapping ORDER BY cargo ASC;")).AsList(); } diff --git a/OpenCand.API/Repository/OpenCandRepository.cs b/OpenCand.API/Repository/OpenCandRepository.cs index 6096e20..92b4339 100644 --- a/OpenCand.API/Repository/OpenCandRepository.cs +++ b/OpenCand.API/Repository/OpenCandRepository.cs @@ -81,5 +81,63 @@ namespace OpenCand.API.Repository return result ?? new DataAvailabilityStats(); } + + public async Task GetDatabaseTechStatsAsync() + { + string cacheKey = GenerateCacheKey("DatabaseTechStats"); + + var result = await GetOrSetCacheAsync(cacheKey, async () => + { + using (var connection = new NpgsqlConnection(ConnectionString)) + { + var stats = new DatabaseTechStats(); + + + // Get table stats using reltuples for entries + var tableStats = await connection.QueryAsync(@" + SELECT + pt.schemaname||'.'||pt.tablename as Name, + pg_total_relation_size(pt.schemaname||'.'||pt.tablename) as TotalSize, + COALESCE(c.reltuples,0)::bigint as Entries + FROM pg_tables pt + JOIN pg_class c ON c.relname = pt.tablename AND c.relkind = 'r' + WHERE pt.schemaname = 'public' + ORDER BY pg_total_relation_size(pt.schemaname||'.'||pt.tablename) DESC;"); + + + var tableStatsList = tableStats.ToList(); + stats.Tables = tableStatsList; + stats.TotalSize = tableStatsList.Sum(t => t.TotalSize); + stats.TotalEntries = tableStatsList.Sum(t => t.Entries); + + // Get materialized view stats using reltuples for entries + var materializedViewStats = await connection.QueryAsync(@" + SELECT + pmv.schemaname||'.'||pmv.matviewname as Name, + pg_total_relation_size(pmv.schemaname||'.'||pmv.matviewname) as TotalSize, + COALESCE(c.reltuples,0)::bigint as Entries + FROM pg_matviews pmv + JOIN pg_class c ON c.relname = pmv.matviewname AND c.relkind = 'm' + WHERE pmv.schemaname = 'public' + ORDER BY pg_total_relation_size(pmv.schemaname||'.'||pmv.matviewname) DESC;"); + + stats.MaterializedViews = materializedViewStats.ToList(); + + // Get index stats + var indexStats = await connection.QueryFirstOrDefaultAsync(@" + SELECT + COUNT(*) as Amount, + SUM(pg_relation_size(indexrelid)) as Size + FROM pg_stat_user_indexes + WHERE schemaname = 'public';"); + + stats.Indexes = indexStats ?? new IndexStats(); + + return stats; + } + }); + + return result ?? new DatabaseTechStats(); + } } } diff --git a/OpenCand.API/Services/CachePreloadService.cs b/OpenCand.API/Services/CachePreloadService.cs index 4cb3263..b5c3da5 100644 --- a/OpenCand.API/Services/CachePreloadService.cs +++ b/OpenCand.API/Services/CachePreloadService.cs @@ -66,6 +66,7 @@ namespace OpenCand.API.Services await PerformPreLoad("GetOpenCandStatsAsync", estatisticaService.GetMaioresEnriquecimentos); await PerformPreLoad("GetOpenCandStatsAsync", openCandService.GetOpenCandStatsAsync); + await PerformPreLoad("GetDatabaseTechStatsAsync", openCandService.GetDatabaseTechStatsAsync); await PerformPreLoad("GetDataAvailabilityStatsAsync", openCandService.GetDataAvailabilityStatsAsync); logger.LogInformation("Single-call endpoints preload completed"); diff --git a/OpenCand.API/Services/OpenCandService.cs b/OpenCand.API/Services/OpenCandService.cs index 05085c6..f504ee6 100644 --- a/OpenCand.API/Services/OpenCandService.cs +++ b/OpenCand.API/Services/OpenCandService.cs @@ -47,6 +47,16 @@ namespace OpenCand.API.Services return stats; } + public async Task GetDatabaseTechStatsAsync() + { + var stats = await openCandRepository.GetDatabaseTechStatsAsync(); + + stats.Tables = stats.Tables.OrderBy(t => t.Name).ToList(); + stats.MaterializedViews = stats.MaterializedViews.OrderBy(mv => mv.Name).ToList(); + + return stats; + } + public async Task SearchCandidatosAsync(string query) { return new CandidatoSearchResult() diff --git a/OpenCand.Core/Models/OpenCandStats.cs b/OpenCand.Core/Models/OpenCandStats.cs index 6ffe41d..4d45745 100644 --- a/OpenCand.Core/Models/OpenCandStats.cs +++ b/OpenCand.Core/Models/OpenCandStats.cs @@ -11,12 +11,34 @@ public class DataAvailabilityStats { - public List Candidatos { get; set; } - public List BemCandidatos { get; set; } - public List DespesaCandidatos { get; set; } - public List ReceitaCandidatos { get; set; } - public List RedeSocialCandidatos { get; set; } - - public List FotosCandidatos { get; set; } + public List Candidatos { get; set; } = new List(); + public List BemCandidatos { get; set; } = new List(); + public List DespesaCandidatos { get; set; } = new List(); + public List ReceitaCandidatos { get; set; } = new List(); + public List RedeSocialCandidatos { get; set; } = new List(); + + public List FotosCandidatos { get; set; } = new List(); + } + + public class DatabaseTechStats + { + public List Tables { get; set; } = new List(); + public List MaterializedViews { get; set; } = new List(); + public IndexStats Indexes { get; set; } = new IndexStats(); + public long TotalSize { get; set; } + public long TotalEntries { get; set; } + } + + public class TableStats + { + public string Name { get; set; } = string.Empty; + public long TotalSize { get; set; } + public long Entries { get; set; } + } + + public class IndexStats + { + public int Amount { get; set; } + public long Size { get; set; } } }