Merge pull request #89 from LD-Reborn/65-add-number-of-cached-queries-to-front-end

65 add number of cached queries to front end
This commit is contained in:
LD50
2026-01-07 01:52:38 +01:00
committed by GitHub
11 changed files with 193 additions and 35 deletions

View File

@@ -286,7 +286,7 @@ public class SearchdomainController : ControllerBase
{ {
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
long sizeInBytes = DatabaseHelper.GetSearchdomainDatabaseSize(searchdomain_.helper, searchdomain); long EmbeddingCacheUtilization = DatabaseHelper.GetSearchdomainDatabaseSize(searchdomain_.helper, searchdomain);
return Ok(new SearchdomainGetDatabaseSizeResult() { SearchdomainDatabaseSizeBytes = sizeInBytes, Success = true }); return Ok(new SearchdomainGetDatabaseSizeResult() { SearchdomainDatabaseSizeBytes = EmbeddingCacheUtilization, Success = true });
} }
} }

View File

@@ -80,6 +80,9 @@ public class ServerController : ControllerBase
var sqlHelper = DatabaseHelper.GetSQLHelper(_options.Value); var sqlHelper = DatabaseHelper.GetSQLHelper(_options.Value);
Task<long> entityCountTask = DatabaseHelper.CountEntities(sqlHelper); Task<long> entityCountTask = DatabaseHelper.CountEntities(sqlHelper);
long queryCacheUtilization = 0; long queryCacheUtilization = 0;
long queryCacheElementCount = 0;
long queryCacheMaxElementCountAll = 0;
long queryCacheMaxElementCountLoadedSearchdomainsOnly = 0;
foreach (string searchdomain in _searchdomainManager.ListSearchdomains()) foreach (string searchdomain in _searchdomainManager.ListSearchdomains())
{ {
if (SearchdomainHelper.IsSearchdomainLoaded(_searchdomainManager, searchdomain)) if (SearchdomainHelper.IsSearchdomainLoaded(_searchdomainManager, searchdomain))
@@ -87,10 +90,29 @@ public class ServerController : ControllerBase
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_searchdomainManager, searchdomain, _logger); (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}); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new ServerGetStatsResult(){Success = false, Message = message});
queryCacheUtilization += searchdomain_.GetSearchCacheSize(); 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; long entityCount = await entityCountTask;
return new ServerGetStatsResult() { Success = true, EntityCount = entityCount, QueryCacheUtilization = queryCacheUtilization, SizeInBytes = size, MaxElementCount = _searchdomainManager.EmbeddingCacheMaxCount, ElementCount = elementCount, EmbeddingsCount = embeddingsCount};
return new ServerGetStatsResult() {
Success = true,
EntityCount = entityCount,
QueryCacheUtilization = queryCacheUtilization,
QueryCacheElementCount = queryCacheElementCount,
QueryCacheMaxElementCountAll = queryCacheMaxElementCountAll,
QueryCacheMaxElementCountLoadedSearchdomainsOnly = queryCacheMaxElementCountLoadedSearchdomainsOnly,
EmbeddingCacheUtilization = size,
EmbeddingCacheMaxElementCount = _searchdomainManager.EmbeddingCacheMaxCount,
EmbeddingCacheElementCount = elementCount,
EmbeddingsCount = embeddingsCount
};
} catch (Exception ex) } catch (Exception ex)
{ {
ElmahExtensions.RaiseError(ex); ElmahExtensions.RaiseError(ex);

View File

@@ -1,6 +1,7 @@
using System.Configuration; using System.Configuration;
using System.Data.Common; using System.Data.Common;
using System.Text; using System.Text;
using System.Text.Json;
using MySql.Data.MySqlClient; using MySql.Data.MySqlClient;
using Server.Exceptions; using Server.Exceptions;
using Server.Models; using Server.Models;
@@ -244,4 +245,22 @@ public class DatabaseHelper(ILogger<DatabaseHelper> logger)
searchdomainSumReader.Close(); searchdomainSumReader.Close();
return result; return result;
} }
public static SearchdomainSettings GetSearchdomainSettings(SQLHelper helper, string searchdomain)
{
Dictionary<string, dynamic> parameters = new()
{
["name"] = searchdomain
};
DbDataReader reader = helper.ExecuteSQLCommand("SELECT settings from searchdomain WHERE name = @name", parameters);
try
{
reader.Read();
string settingsString = reader.GetString(0);
return JsonSerializer.Deserialize<SearchdomainSettings>(settingsString);
} finally
{
reader.Close();
}
}
} }

View File

@@ -1,3 +1,4 @@
using System.Data;
using System.Data.Common; using System.Data.Common;
using MySql.Data.MySqlClient; using MySql.Data.MySqlClient;
@@ -6,6 +7,7 @@ namespace Server.Helper;
public class SQLHelper:IDisposable public class SQLHelper:IDisposable
{ {
public MySqlConnection connection; public MySqlConnection connection;
public DbDataReader? dbDataReader;
public string connectionString; public string connectionString;
public SQLHelper(MySqlConnection connection, string connectionString) public SQLHelper(MySqlConnection connection, string connectionString)
{ {
@@ -30,13 +32,15 @@ public class SQLHelper:IDisposable
lock (connection) lock (connection)
{ {
EnsureConnected(); EnsureConnected();
EnsureDbReaderIsClosed();
using MySqlCommand command = connection.CreateCommand(); using MySqlCommand command = connection.CreateCommand();
command.CommandText = query; command.CommandText = query;
foreach (KeyValuePair<string, dynamic> parameter in parameters) foreach (KeyValuePair<string, dynamic> parameter in parameters)
{ {
command.Parameters.AddWithValue($"@{parameter.Key}", parameter.Value); command.Parameters.AddWithValue($"@{parameter.Key}", parameter.Value);
} }
return command.ExecuteReader(); dbDataReader = command.ExecuteReader();
return dbDataReader;
} }
} }
@@ -45,6 +49,7 @@ public class SQLHelper:IDisposable
lock (connection) lock (connection)
{ {
EnsureConnected(); EnsureConnected();
EnsureDbReaderIsClosed();
using MySqlCommand command = connection.CreateCommand(); using MySqlCommand command = connection.CreateCommand();
command.CommandText = query; command.CommandText = query;
@@ -61,6 +66,7 @@ public class SQLHelper:IDisposable
lock (connection) lock (connection)
{ {
EnsureConnected(); EnsureConnected();
EnsureDbReaderIsClosed();
using MySqlCommand command = connection.CreateCommand(); using MySqlCommand command = connection.CreateCommand();
command.CommandText = query; command.CommandText = query;
@@ -83,11 +89,29 @@ public class SQLHelper:IDisposable
connection.Close(); connection.Close();
connection.Open(); connection.Open();
} }
catch (Exception) catch (Exception ex)
{ {
throw; // TODO add logging here ElmahCore.ElmahExtensions.RaiseError(ex);
throw;
} }
} }
return true; return true;
} }
public void EnsureDbReaderIsClosed()
{
int counter = 0;
int sleepTime = 10;
int timeout = 5000;
while (!(dbDataReader?.IsClosed ?? true))
{
if (counter > timeout / sleepTime)
{
TimeoutException ex = new("Unable to ensure dbDataReader is closed");
ElmahCore.ElmahExtensions.RaiseError(ex);
throw ex;
}
Thread.Sleep(sleepTime);
}
}
} }

View File

@@ -88,7 +88,7 @@ public class SearchdomainHelper(ILogger<SearchdomainHelper> logger, DatabaseHelp
public Entity? EntityFromJSON(SearchdomainManager searchdomainManager, ILogger logger, JSONEntity jsonEntity) //string json) public Entity? EntityFromJSON(SearchdomainManager searchdomainManager, ILogger logger, JSONEntity jsonEntity) //string json)
{ {
SQLHelper helper = searchdomainManager.helper.DuplicateConnection(); using SQLHelper helper = searchdomainManager.helper.DuplicateConnection();
Searchdomain searchdomain = searchdomainManager.GetSearchdomain(jsonEntity.Searchdomain); Searchdomain searchdomain = searchdomainManager.GetSearchdomain(jsonEntity.Searchdomain);
List<Entity> entityCache = searchdomain.entityCache; List<Entity> entityCache = searchdomain.entityCache;
AIProvider aIProvider = searchdomain.aIProvider; AIProvider aIProvider = searchdomain.aIProvider;

View File

@@ -300,4 +300,19 @@
<data name="Unable to fetch searchdomain database utilization" xml:space="preserve"> <data name="Unable to fetch searchdomain database utilization" xml:space="preserve">
<value>Searchdomain Datenbank-Auslastung konnte nicht abgerufen werden</value> <value>Searchdomain Datenbank-Auslastung konnte nicht abgerufen werden</value>
</data> </data>
<data name="Query cache entry count" xml:space="preserve">
<value>Query-Cache Einträge</value>
</data>
<data name="Query cache capacity (all)" xml:space="preserve">
<value>Query-Cache Kapazität (alle)</value>
</data>
<data name="queryCacheEntryCountAllInfo" xml:space="preserve">
<value>Anzahl der Einträge, die insgesamt in den Query-Cache passen. Ungeladene Searchdomains werden berücksichtigt.</value>
</data>
<data name="Query cache capacity (loaded)" xml:space="preserve">
<value>Query-Cache Kapazität (geladen)</value>
</data>
<data name="queryCacheEntryCountLoadedInfo" xml:space="preserve">
<value>Anzahl der Einträge, die insgesamt in den Query-Cache der geladenen Searchdomains passen.</value>
</data>
</root> </root>

View File

@@ -300,4 +300,19 @@
<data name="Unable to fetch searchdomain database utilization" xml:space="preserve"> <data name="Unable to fetch searchdomain database utilization" xml:space="preserve">
<value>Unable to fetch searchdomain database utilization</value> <value>Unable to fetch searchdomain database utilization</value>
</data> </data>
<data name="Query cache entry count" xml:space="preserve">
<value>Query cache entry count</value>
</data>
<data name="Query cache capacity (all)" xml:space="preserve">
<value>Query cache capacity (all)</value>
</data>
<data name="queryCacheEntryCountAllInfo" xml:space="preserve">
<value>Number of query cache entries that can be stored in the query cache, including searchdomains that are currently not loaded.</value>
</data>
<data name="Query cache capacity (loaded)" xml:space="preserve">
<value>Query cache capacity (loaded)</value>
</data>
<data name="queryCacheEntryCountLoadedInfo" xml:space="preserve">
<value>Number of query cache entries that can be stored in the query cache of all loaded searchdomains.</value>
</data>
</root> </root>

View File

@@ -278,15 +278,7 @@ public class Searchdomain
public SearchdomainSettings GetSettings() public SearchdomainSettings GetSettings()
{ {
Dictionary<string, dynamic> parameters = new() return DatabaseHelper.GetSearchdomainSettings(helper, searchdomain);
{
["name"] = searchdomain
};
DbDataReader reader = helper.ExecuteSQLCommand("SELECT settings from searchdomain WHERE name = @name", parameters);
reader.Read();
string settingsString = reader.GetString(0);
reader.Close();
return JsonSerializer.Deserialize<SearchdomainSettings>(settingsString);
} }
public void ReconciliateOrInvalidateCacheForNewOrUpdatedEntity(Entity entity) public void ReconciliateOrInvalidateCacheForNewOrUpdatedEntity(Entity entity)
@@ -343,13 +335,13 @@ public class Searchdomain
public long GetSearchCacheSize() public long GetSearchCacheSize()
{ {
long sizeInBytes = 0; long EmbeddingCacheUtilization = 0;
foreach (var entry in queryCache) foreach (var entry in queryCache)
{ {
sizeInBytes += sizeof(int); // string length prefix EmbeddingCacheUtilization += sizeof(int); // string length prefix
sizeInBytes += entry.Key.Length * sizeof(char); // string characters EmbeddingCacheUtilization += entry.Key.Length * sizeof(char); // string characters
sizeInBytes += entry.Value.EstimateSize(); EmbeddingCacheUtilization += entry.Value.EstimateSize();
} }
return sizeInBytes; return EmbeddingCacheUtilization;
} }
} }

View File

@@ -82,12 +82,18 @@ public class SearchdomainManager
{ {
DbDataReader reader = helper.ExecuteSQLCommand("SELECT name FROM searchdomain", []); DbDataReader reader = helper.ExecuteSQLCommand("SELECT name FROM searchdomain", []);
List<string> results = []; List<string> results = [];
while (reader.Read()) try
{ {
results.Add(reader.GetString(0)); while (reader.Read())
{
results.Add(reader.GetString(0));
}
return results;
}
finally
{
reader.Close();
} }
reader.Close();
return results;
} }
} }

View File

@@ -104,6 +104,43 @@
<span>@T["Total query cache utilization"]</span> <span>@T["Total query cache utilization"]</span>
<strong id="totalQuerycacheUtilization"></strong> <strong id="totalQuerycacheUtilization"></strong>
</div> </div>
<!-- Query cache -->
<div class="d-flex justify-content-between mt-2">
<span>@T["Query cache entry count"]</span>
<strong id="querycacheCount"></strong>
</div>
<div class="d-flex justify-content-between mt-2">
<span>
@T["Query cache capacity (loaded)"]
<i class="bi bi-info-circle-fill text-info"
data-bs-toggle="tooltip"
title="@T["queryCacheEntryCountLoadedInfo"]"></i>
</span>
<strong id="querycacheLoadedMaxElementCount"></strong>
</div>
<div class="progress mt-3" style="height: 8px;">
<div id="querycacheLoadedMaxElementCountProgressBar" class="progress-bar"
style="width: 0.00%"></div>
</div>
<div class="d-flex justify-content-between mt-2">
<span>
@T["Query cache capacity (all)"]
<i class="bi bi-info-circle-fill text-info"
data-bs-toggle="tooltip"
title="@T["queryCacheEntryCountAllInfo"]"></i>
</span>
<strong id="querycacheMaxElementCount"></strong>
</div>
<div class="progress mt-3" style="height: 8px;">
<div id="querycacheMaxElementCountProgressBar" class="progress-bar"
style="width: 0.00%"></div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -135,6 +172,17 @@
let embeddingcacheEmbeddingCount = document.getElementById("embeddingcacheEmbeddingCount"); let embeddingcacheEmbeddingCount = document.getElementById("embeddingcacheEmbeddingCount");
showThrobber(embeddingcacheEmbeddingCount); showThrobber(embeddingcacheEmbeddingCount);
let embeddingcacheElementCountProgressBar = document.getElementById("embeddingcacheElementCountProgressBar"); let embeddingcacheElementCountProgressBar = document.getElementById("embeddingcacheElementCountProgressBar");
let querycacheCount = document.getElementById("querycacheCount");
showThrobber(querycacheCount);
let querycacheMaxElementCount = document.getElementById("querycacheMaxElementCount");
showThrobber(querycacheMaxElementCount);
let querycacheMaxElementCountProgressBar = document.getElementById("querycacheMaxElementCountProgressBar");
let querycacheLoadedMaxElementCount = document.getElementById("querycacheLoadedMaxElementCount");
showThrobber(querycacheLoadedMaxElementCount);
let querycacheLoadedElementCountProgressBar = document.getElementById("querycacheLoadedElementCountProgressBar");
let healthchecksServer = document.getElementById("healthchecksServer"); let healthchecksServer = document.getElementById("healthchecksServer");
let healthchecksAiProvider = document.getElementById("healthchecksAiProvider"); let healthchecksAiProvider = document.getElementById("healthchecksAiProvider");
@@ -145,23 +193,34 @@
searchdomainCount.textContent = searchdomains.length; searchdomainCount.textContent = searchdomains.length;
}); });
getServerStats().then(result => { getServerStats().then(result => {
let utilization = result.SizeInBytes; let utilization = result.EmbeddingCacheUtilization;
let maxElementCount = result.MaxElementCount; let embeddingCacheMaxElementCount = result.EmbeddingCacheMaxElementCount;
let elementCount = result.ElementCount; let embeddingCacheElementCount = result.ElementCount;
let embeddingCount = result.EmbeddingsCount; let embeddingCount = result.EmbeddingsCount;
let entityCount = result.EntityCount; let entityCount = result.EntityCount;
let queryCacheUtilization = result.QueryCacheUtilization; let queryCacheUtilization = result.QueryCacheUtilization;
let queryCacheElementCount = result.QueryCacheElementCount;
let queryCacheMaxElementCountAll = result.QueryCacheMaxElementCountAll;
let queryCacheMaxElementCountLoadedSearchdomainsOnly = result.QueryCacheMaxElementCountLoadedSearchdomainsOnly;
hideThrobber(embeddingcacheSize); hideThrobber(embeddingcacheSize);
embeddingcacheSize.textContent = NumberOfBytesAsHumanReadable(utilization); embeddingcacheSize.textContent = NumberOfBytesAsHumanReadable(utilization);
hideThrobber(embeddingcacheElementCount); hideThrobber(embeddingcacheElementCount);
embeddingcacheElementCount.textContent = `${elementCount.toLocaleString()} / ${maxElementCount.toLocaleString()}`; embeddingcacheElementCount.textContent = `${embeddingCacheElementCount.toLocaleString()} / ${embeddingCacheMaxElementCount.toLocaleString()}`;
hideThrobber(embeddingcacheEmbeddingCount); hideThrobber(embeddingcacheEmbeddingCount);
embeddingcacheEmbeddingCount.textContent = embeddingCount; embeddingcacheEmbeddingCount.textContent = embeddingCount;
embeddingcacheElementCountProgressBar.style.width = `${elementCount / maxElementCount * 100}%`; embeddingcacheElementCountProgressBar.style.width = `${embeddingCacheElementCount / embeddingCacheMaxElementCount * 100}%`;
hideThrobber(searchdomainEntityCount); hideThrobber(searchdomainEntityCount);
searchdomainEntityCount.textContent = entityCount; searchdomainEntityCount.textContent = entityCount;
hideThrobber(totalQuerycacheUtilization); hideThrobber(totalQuerycacheUtilization);
totalQuerycacheUtilization.textContent = NumberOfBytesAsHumanReadable(queryCacheUtilization); totalQuerycacheUtilization.textContent = NumberOfBytesAsHumanReadable(queryCacheUtilization);
hideThrobber(querycacheMaxElementCount);
querycacheCount.textContent = queryCacheElementCount;
hideThrobber(querycacheCount);
querycacheMaxElementCount.textContent = queryCacheMaxElementCountAll.toLocaleString();
querycacheMaxElementCountProgressBar.style.width = `${queryCacheElementCount / queryCacheMaxElementCountAll * 100}%`;
hideThrobber(querycacheLoadedMaxElementCount);
querycacheLoadedMaxElementCount.textContent = queryCacheMaxElementCountLoadedSearchdomainsOnly.toLocaleString();
querycacheLoadedMaxElementCountProgressBar.style.width = `${queryCacheElementCount / queryCacheMaxElementCountLoadedSearchdomainsOnly * 100}%`;
}); });
getHealthCheckStatusAndApply(healthchecksServer, "/healthz/Database"); getHealthCheckStatusAndApply(healthchecksServer, "/healthz/Database");
getHealthCheckStatusAndApply(healthchecksAiProvider, "/healthz/AIProvider"); getHealthCheckStatusAndApply(healthchecksAiProvider, "/healthz/AIProvider");

View File

@@ -10,16 +10,22 @@ public class ServerGetModelsResult : SuccesMessageBaseModel
public class ServerGetStatsResult : SuccesMessageBaseModel public class ServerGetStatsResult : SuccesMessageBaseModel
{ {
[JsonPropertyName("SizeInBytes")] [JsonPropertyName("EmbeddingCacheUtilization")]
public long? SizeInBytes { get; set; } public long? EmbeddingCacheUtilization { get; set; }
[JsonPropertyName("MaxElementCount")] [JsonPropertyName("EmbeddingCacheMaxElementCount")]
public long? MaxElementCount { get; set; } public long? EmbeddingCacheMaxElementCount { get; set; }
[JsonPropertyName("ElementCount")] [JsonPropertyName("ElementCount")]
public long? ElementCount { get; set; } public long? EmbeddingCacheElementCount { get; set; }
[JsonPropertyName("EmbeddingsCount")] [JsonPropertyName("EmbeddingsCount")]
public long? EmbeddingsCount { get; set; } public long? EmbeddingsCount { get; set; }
[JsonPropertyName("EntityCount")] [JsonPropertyName("EntityCount")]
public long? EntityCount { get; set; } public long? EntityCount { get; set; }
[JsonPropertyName("QueryCacheElementCount")]
public long? QueryCacheElementCount { get; set; }
[JsonPropertyName("QueryCacheMaxElementCountAll")]
public long? QueryCacheMaxElementCountAll { get; set; }
[JsonPropertyName("QueryCacheMaxElementCountLoadedSearchdomainsOnly")]
public long? QueryCacheMaxElementCountLoadedSearchdomainsOnly { get; set; }
[JsonPropertyName("QueryCacheUtilization")] [JsonPropertyName("QueryCacheUtilization")]
public long? QueryCacheUtilization { get; set; } public long? QueryCacheUtilization { get; set; }
} }