namespace Server.Controllers; using ElmahCore; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Server.Helper; using Server.Models; using Shared; using Shared.Models; [ApiController] [Route("[controller]")] public class ServerController : ControllerBase { private readonly ILogger _logger; private readonly IConfiguration _config; private AIProvider _aIProvider; private readonly SearchdomainManager _searchdomainManager; private readonly IOptions _options; public ServerController(ILogger logger, IConfiguration config, AIProvider aIProvider, SearchdomainManager searchdomainManager, IOptions options) { _logger = logger; _config = config; _aIProvider = aIProvider; _searchdomainManager = searchdomainManager; _options = options; } /// /// Lists the models available to the server /// /// /// Returns ALL models available to the server - not only the embedding models. /// [HttpGet("Models")] public ActionResult GetModels() { try { string[] models = _aIProvider.GetModels(); return new ServerGetModelsResult() { Models = models, Success = true }; } catch (Exception ex) { _logger.LogError("Unable to get models due to exception {ex.Message} - {ex.StackTrace}", [ex.Message, ex.StackTrace]); return new ServerGetModelsResult() { Success = false, Message = ex.Message}; } } /// /// Gets numeric info regarding the searchdomains /// [HttpGet("Stats")] public async Task> Stats() { try { long size = 0; long elementCount = 0; long embeddingsCount = 0; EnumerableLruCache> embeddingCache = _searchdomainManager.embeddingCache; foreach (KeyValuePair> kv in embeddingCache) { string key = kv.Key; Dictionary entry = kv.Value; size += EstimateEntrySize(key, entry); elementCount++; embeddingsCount += entry.Keys.Count; } var sqlHelper = DatabaseHelper.GetSQLHelper(_options.Value); var databaseTotalSize = DatabaseHelper.GetTotalDatabaseSize(sqlHelper); Task entityCountTask = DatabaseHelper.CountEntities(sqlHelper); long queryCacheUtilization = 0; long queryCacheElementCount = 0; long queryCacheMaxElementCountAll = 0; long queryCacheMaxElementCountLoadedSearchdomainsOnly = 0; foreach (string searchdomain in _searchdomainManager.ListSearchdomains()) { if (SearchdomainHelper.IsSearchdomainLoaded(_searchdomainManager, searchdomain)) { (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_searchdomainManager, searchdomain, _logger); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new ServerGetStatsResult(){Success = false, Message = message}); queryCacheUtilization += searchdomain_.GetSearchCacheSize(); queryCacheElementCount += searchdomain_.queryCache.Count; queryCacheMaxElementCountAll += searchdomain_.queryCache.Capacity; queryCacheMaxElementCountLoadedSearchdomainsOnly += searchdomain_.queryCache.Capacity; } else { var searchdomainSettings = DatabaseHelper.GetSearchdomainSettings(sqlHelper, searchdomain); queryCacheMaxElementCountAll += searchdomainSettings.QueryCacheSize; } }; long entityCount = await entityCountTask; GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); long ramTotalSize = GC.GetTotalMemory(false); return new ServerGetStatsResult() { Success = true, EntityCount = entityCount, QueryCacheUtilization = queryCacheUtilization, QueryCacheElementCount = queryCacheElementCount, QueryCacheMaxElementCountAll = queryCacheMaxElementCountAll, QueryCacheMaxElementCountLoadedSearchdomainsOnly = queryCacheMaxElementCountLoadedSearchdomainsOnly, EmbeddingCacheUtilization = size, EmbeddingCacheMaxElementCount = _searchdomainManager.EmbeddingCacheMaxCount, EmbeddingCacheElementCount = elementCount, EmbeddingsCount = embeddingsCount, DatabaseTotalSize = databaseTotalSize, RamTotalSize = ramTotalSize }; } catch (Exception ex) { ElmahExtensions.RaiseError(ex); return StatusCode(500, new ServerGetStatsResult(){Success = false, Message = ex.Message}); } } private static long EstimateEntrySize(string key, Dictionary value) { int stringOverhead = MemorySizes.Align(MemorySizes.ObjectHeader + sizeof(int)); int arrayOverhead = MemorySizes.ArrayHeader; int dictionaryOverhead = MemorySizes.ObjectHeader; long size = 0; size += stringOverhead + key.Length * sizeof(char); size += dictionaryOverhead; foreach (var kv in value) { size += stringOverhead + kv.Key.Length * sizeof(char); size += arrayOverhead + kv.Value.Length * sizeof(float); } return size; } }