From 6c2adf6d048b85871e302019f1f2b2197bf74fcb Mon Sep 17 00:00:00 2001 From: Shreyas Zare Date: Sat, 11 Jan 2025 17:41:08 +0530 Subject: [PATCH] StatsManager: added support for reading minute stats for given date range. Fixed minor time zone issue in reading hourly stats. Other minor changes done. --- DnsServerCore/Dns/StatsManager.cs | 184 ++++++++++++++++++++++++++---- 1 file changed, 163 insertions(+), 21 deletions(-) diff --git a/DnsServerCore/Dns/StatsManager.cs b/DnsServerCore/Dns/StatsManager.cs index 6dee527c..9dbe9ddd 100644 --- a/DnsServerCore/Dns/StatsManager.cs +++ b/DnsServerCore/Dns/StatsManager.cs @@ -1,6 +1,6 @@ /* Technitium DNS Server -Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2025 Shreyas Zare (shreyas@technitium.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -407,14 +407,14 @@ namespace DnsServerCore.Dns } } - private HourlyStats LoadHourlyStats(DateTime dateTime) + private HourlyStats LoadHourlyStats(DateTime dateTime, bool forceReload = false) { if (_enableInMemoryStats) return _emptyHourlyStats; DateTime hourlyDateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, 0, 0, 0, DateTimeKind.Utc); - if (!_hourlyStatsCache.TryGetValue(hourlyDateTime, out HourlyStats hourlyStats)) + if (forceReload || !_hourlyStatsCache.TryGetValue(hourlyDateTime, out HourlyStats hourlyStats)) { string hourlyStatsFile = Path.Combine(_statsFolder, dateTime.ToString("yyyyMMddHH") + ".stat"); @@ -429,9 +429,7 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _dnsServer.LogManager; - if (log != null) - log.Write(ex); + _dnsServer.LogManager?.Write(ex); hourlyStats = new HourlyStats(); } @@ -441,11 +439,7 @@ namespace DnsServerCore.Dns hourlyStats = new HourlyStats(); } - if (!_hourlyStatsCache.TryAdd(hourlyDateTime, hourlyStats)) - { - if (!_hourlyStatsCache.TryGetValue(hourlyDateTime, out hourlyStats)) - throw new DnsServerException("Unable to load hourly stats."); - } + _hourlyStatsCache[hourlyDateTime] = hourlyStats; } return hourlyStats; @@ -823,17 +817,120 @@ namespace DnsServerCore.Dns return data; } + public Dictionary>> GetMinuteWiseStats(DateTime startDate, DateTime endDate, bool utcFormat) + { + return GetMinuteWiseStats(startDate, Convert.ToInt32((endDate - startDate).TotalMinutes) + 1, utcFormat); + } + + public Dictionary>> GetMinuteWiseStats(DateTime startDate, int minutes, bool utcFormat) + { + startDate = startDate.AddMinutes(-1); + + StatCounter totalStatCounter = new StatCounter(); + totalStatCounter.Lock(); + + List> totalQueriesPerInterval = new List>(); + List> totalNoErrorPerInterval = new List>(); + List> totalServerFailurePerInterval = new List>(); + List> totalNxDomainPerInterval = new List>(); + List> totalRefusedPerInterval = new List>(); + + List> totalAuthHitPerInterval = new List>(); + List> totalRecursionsPerInterval = new List>(); + List> totalCacheHitPerInterval = new List>(); + List> totalBlockedPerInterval = new List>(); + List> totalDroppedPerInterval = new List>(); + + List> totalClientsPerInterval = new List>(); + + for (int minute = 0; minute < minutes; minute++) + { + DateTime lastDateTime = startDate.AddMinutes(minute); + + HourlyStats hourlyStats = LoadHourlyStats(lastDateTime); + if (hourlyStats.MinuteStats is null) + hourlyStats = LoadHourlyStats(lastDateTime, true); + + StatCounter minuteStatCounter = hourlyStats.MinuteStats[lastDateTime.Minute]; + + string label; + + if (utcFormat) + label = lastDateTime.AddMinutes(1).ToString("O"); + else + label = lastDateTime.AddMinutes(1).ToLocalTime().ToString("MM/dd HH:mm"); + + totalStatCounter.Merge(minuteStatCounter); + + totalQueriesPerInterval.Add(new KeyValuePair(label, minuteStatCounter.TotalQueries)); + totalNoErrorPerInterval.Add(new KeyValuePair(label, minuteStatCounter.TotalNoError)); + totalServerFailurePerInterval.Add(new KeyValuePair(label, minuteStatCounter.TotalServerFailure)); + totalNxDomainPerInterval.Add(new KeyValuePair(label, minuteStatCounter.TotalNxDomain)); + totalRefusedPerInterval.Add(new KeyValuePair(label, minuteStatCounter.TotalRefused)); + + totalAuthHitPerInterval.Add(new KeyValuePair(label, minuteStatCounter.TotalAuthoritative)); + totalRecursionsPerInterval.Add(new KeyValuePair(label, minuteStatCounter.TotalRecursive)); + totalCacheHitPerInterval.Add(new KeyValuePair(label, minuteStatCounter.TotalCached)); + totalBlockedPerInterval.Add(new KeyValuePair(label, minuteStatCounter.TotalBlocked)); + totalDroppedPerInterval.Add(new KeyValuePair(label, minuteStatCounter.TotalDropped)); + + totalClientsPerInterval.Add(new KeyValuePair(label, minuteStatCounter.TotalClients)); + } + + Dictionary>> data = new Dictionary>>(); + + { + List> stats = new List>(6); + + stats.Add(new KeyValuePair("totalQueries", totalStatCounter.TotalQueries)); + stats.Add(new KeyValuePair("totalNoError", totalStatCounter.TotalNoError)); + stats.Add(new KeyValuePair("totalServerFailure", totalStatCounter.TotalServerFailure)); + stats.Add(new KeyValuePair("totalNxDomain", totalStatCounter.TotalNxDomain)); + stats.Add(new KeyValuePair("totalRefused", totalStatCounter.TotalRefused)); + + stats.Add(new KeyValuePair("totalAuthoritative", totalStatCounter.TotalAuthoritative)); + stats.Add(new KeyValuePair("totalRecursive", totalStatCounter.TotalRecursive)); + stats.Add(new KeyValuePair("totalCached", totalStatCounter.TotalCached)); + stats.Add(new KeyValuePair("totalBlocked", totalStatCounter.TotalBlocked)); + stats.Add(new KeyValuePair("totalDropped", totalStatCounter.TotalDropped)); + + stats.Add(new KeyValuePair("totalClients", totalStatCounter.TotalClients)); + + data.Add("stats", stats); + } + + data.Add("totalQueriesPerInterval", totalQueriesPerInterval); + data.Add("totalNoErrorPerInterval", totalNoErrorPerInterval); + data.Add("totalServerFailurePerInterval", totalServerFailurePerInterval); + data.Add("totalNxDomainPerInterval", totalNxDomainPerInterval); + data.Add("totalRefusedPerInterval", totalRefusedPerInterval); + + data.Add("totalAuthHitPerInterval", totalAuthHitPerInterval); + data.Add("totalRecursionsPerInterval", totalRecursionsPerInterval); + data.Add("totalCacheHitPerInterval", totalCacheHitPerInterval); + data.Add("totalBlockedPerInterval", totalBlockedPerInterval); + data.Add("totalDroppedPerInterval", totalDroppedPerInterval); + + data.Add("totalClientsPerInterval", totalClientsPerInterval); + + data.Add("topDomains", totalStatCounter.GetTopDomains(10)); + data.Add("topBlockedDomains", totalStatCounter.GetTopBlockedDomains(10)); + data.Add("topClients", totalStatCounter.GetTopClients(10)); + data.Add("queryTypes", totalStatCounter.GetTopQueryTypes(10)); + data.Add("protocolTypes", totalStatCounter.GetTopProtocolTypes()); + + return data; + } + public Dictionary>> GetHourWiseStats(DateTime startDate, DateTime endDate, bool utcFormat) { - int hours = Convert.ToInt32((endDate - startDate).TotalHours) + 1; - if (hours < 24) - hours = 24; - - return GetHourWiseStats(startDate, hours, utcFormat); + return GetHourWiseStats(startDate, Convert.ToInt32((endDate - startDate).TotalHours) + 1, utcFormat); } public Dictionary>> GetHourWiseStats(DateTime startDate, int hours, bool utcFormat) { + startDate = new DateTime(startDate.Year, startDate.Month, startDate.Day, startDate.Hour, 0, 0, 0, DateTimeKind.Utc); + StatCounter totalStatCounter = new StatCounter(); totalStatCounter.Lock(); @@ -1113,17 +1210,56 @@ namespace DnsServerCore.Dns } } + public List> GetMinuteWiseTopStats(DateTime startDate, DateTime endDate, TopStatsType type, int limit) + { + return GetMinuteWiseTopStats(startDate, Convert.ToInt32((endDate - startDate).TotalMinutes) + 1, type, limit); + } + + public List> GetMinuteWiseTopStats(DateTime startDate, int minutes, TopStatsType type, int limit) + { + startDate = startDate.AddMinutes(-1); + + StatCounter totalStatCounter = new StatCounter(); + totalStatCounter.Lock(); + + for (int minute = 0; minute < minutes; minute++) + { + DateTime lastDateTime = startDate.AddMinutes(minute); + + HourlyStats hourlyStats = LoadHourlyStats(lastDateTime); + if (hourlyStats.MinuteStats is null) + hourlyStats = LoadHourlyStats(lastDateTime, true); + + StatCounter minuteStatCounter = hourlyStats.MinuteStats[lastDateTime.Minute]; + + totalStatCounter.Merge(minuteStatCounter); + } + + switch (type) + { + case TopStatsType.TopDomains: + return totalStatCounter.GetTopDomains(limit); + + case TopStatsType.TopBlockedDomains: + return totalStatCounter.GetTopBlockedDomains(limit); + + case TopStatsType.TopClients: + return totalStatCounter.GetTopClients(limit); + + default: + throw new NotSupportedException(); + } + } + public List> GetHourWiseTopStats(DateTime startDate, DateTime endDate, TopStatsType type, int limit) { - int hours = Convert.ToInt32((endDate - startDate).TotalHours) + 1; - if (hours < 24) - hours = 24; - - return GetHourWiseTopStats(startDate, hours, type, limit); + return GetHourWiseTopStats(startDate, Convert.ToInt32((endDate - startDate).TotalHours) + 1, type, limit); } public List> GetHourWiseTopStats(DateTime startDate, int hours, TopStatsType type, int limit) { + startDate = new DateTime(startDate.Year, startDate.Month, startDate.Day, startDate.Hour, 0, 0, 0, DateTimeKind.Utc); + StatCounter totalStatCounter = new StatCounter(); totalStatCounter.Lock(); @@ -1284,6 +1420,12 @@ namespace DnsServerCore.Dns { _hourStat = new StatCounter(); _hourStat.Lock(); + + for (int i = 0; i < _minuteStats.Length; i++) + { + _minuteStats[i] = new StatCounter(); + _minuteStats[i].Lock(); + } } public HourlyStats(BinaryReader bR)