Added search cache estimation, added search cache clearing
This commit is contained in:
@@ -176,4 +176,52 @@ public class SearchdomainController : ControllerBase
|
|||||||
SearchdomainSettings settings = searchdomain_.settings;
|
SearchdomainSettings settings = searchdomain_.settings;
|
||||||
return Ok(new SearchdomainSettingsResults() { Settings = settings, Success = true });
|
return Ok(new SearchdomainSettingsResults() { Settings = settings, Success = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("GetSearchCacheSize")]
|
||||||
|
public ActionResult<SearchdomainSearchCacheSizeResults> GetSearchCacheSize(string searchdomain)
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
}
|
||||||
|
catch (SearchdomainNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to retrieve the searchdomain {searchdomain} - it likely does not exist yet", [searchdomain]);
|
||||||
|
return Ok(new SearchdomainSearchCacheSizeResults() { SearchCacheSizeBytes = null, Success = false, Message = "Searchdomain not found" });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to retrieve the searchdomain {searchdomain} - {ex.Message} - {ex.StackTrace}", [searchdomain, ex.Message, ex.StackTrace]);
|
||||||
|
return Ok(new SearchdomainSearchCacheSizeResults() { SearchCacheSizeBytes = null, Success = false, Message = ex.Message });
|
||||||
|
}
|
||||||
|
Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.searchCache;
|
||||||
|
long sizeInBytes = 0;
|
||||||
|
foreach (var entry in searchCache)
|
||||||
|
{
|
||||||
|
sizeInBytes += sizeof(int); // string length prefix
|
||||||
|
sizeInBytes += entry.Key.Length * sizeof(char); // string characters
|
||||||
|
sizeInBytes += entry.Value.EstimateSize();
|
||||||
|
}
|
||||||
|
return Ok(new SearchdomainSearchCacheSizeResults() { SearchCacheSizeBytes = sizeInBytes, Success = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("ClearSearchCache")]
|
||||||
|
public ActionResult<SearchdomainInvalidateCacheResults> InvalidateSearchCache(string searchdomain)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
searchdomain_.InvalidateSearchCache();
|
||||||
|
} catch (SearchdomainNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to invalidate search cache for searchdomain {searchdomain} - not found", [searchdomain]);
|
||||||
|
return Ok(new SearchdomainInvalidateCacheResults() { Success = false, Message = $"Unable to invalidate search cache for searchdomain {searchdomain} - not found" });
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to invalidate search cache for searchdomain {searchdomain} - Exception: {ex.Message} - {ex.StackTrace}", [searchdomain, ex.Message, ex.StackTrace]);
|
||||||
|
return Ok(new SearchdomainInvalidateCacheResults() { Success = false, Message = $"Unable to invalidate search cache for searchdomain {searchdomain}" });
|
||||||
|
}
|
||||||
|
return Ok(new SearchdomainInvalidateCacheResults(){Success = true});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,9 +68,9 @@
|
|||||||
<!-- Cache -->
|
<!-- Cache -->
|
||||||
<div class="d-flex align-items-center mb-4">
|
<div class="d-flex align-items-center mb-4">
|
||||||
<div class="me-3">
|
<div class="me-3">
|
||||||
<strong>Cache utilization:</strong> 2.47MiB
|
<strong>Cache utilization:</strong> <span id="cacheUtilization">0.00MiB</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary btn-sm">Reset</button>
|
<button id="cacheClear" class="btn btn-warning btn-sm">@T["Clear"]</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Recent Queries -->
|
<!-- Recent Queries -->
|
||||||
@@ -405,6 +405,27 @@
|
|||||||
console.error('Error creating searchdomain:', error);
|
console.error('Error creating searchdomain:', error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('cacheClear')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
const domainKey = getSelectedDomainKey();
|
||||||
|
fetch(`/Searchdomain/ClearSearchCache?searchdomain=${encodeURIComponent(domains[domainKey])}`, {
|
||||||
|
method: 'GET'
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
// TODO add toast
|
||||||
|
console.log('Searchdomain cache cleared successfully');
|
||||||
|
// Update cache utilization display
|
||||||
|
document.querySelector('#cacheUtilization').innerText = '0.00MiB';
|
||||||
|
} else {
|
||||||
|
// TODO add toast
|
||||||
|
console.error('Failed to clear searchdomain cache');
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error clearing searchdomain cache:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function deleteSearchdomain(domainKey) {
|
function deleteSearchdomain(domainKey) {
|
||||||
@@ -480,6 +501,11 @@
|
|||||||
.then(r => r.json());
|
.then(r => r.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSearchdomainCacheUtilization(domainKey) {
|
||||||
|
return fetch(`/Searchdomain/GetSearchCacheSize?searchdomain=${encodeURIComponent(domains[domainKey])}`)
|
||||||
|
.then(r => r.json());
|
||||||
|
}
|
||||||
|
|
||||||
function selectDomain(domainKey) {
|
function selectDomain(domainKey) {
|
||||||
document.querySelectorAll('.domain-item').forEach(item => {
|
document.querySelectorAll('.domain-item').forEach(item => {
|
||||||
item.classList.remove('active');
|
item.classList.remove('active');
|
||||||
@@ -494,6 +520,8 @@
|
|||||||
let searchdomainConfigPromise = getSearchdomainConfig(getSelectedDomainKey());
|
let searchdomainConfigPromise = getSearchdomainConfig(getSelectedDomainKey());
|
||||||
let configElementCacheReconsiliation = document.getElementById('searchdomainConfigCacheReconciliation');
|
let configElementCacheReconsiliation = document.getElementById('searchdomainConfigCacheReconciliation');
|
||||||
|
|
||||||
|
let cacheUtilizationPromise = getSearchdomainCacheUtilization(getSelectedDomainKey());
|
||||||
|
|
||||||
/* ---------- ENTITIES ---------- */
|
/* ---------- ENTITIES ---------- */
|
||||||
let entitiesUrl = `/Entity/List?searchdomain=${encodeURIComponent(domainName)}&returnEmbeddings=false`;
|
let entitiesUrl = `/Entity/List?searchdomain=${encodeURIComponent(domainName)}&returnEmbeddings=false`;
|
||||||
let entitiesCard = document.querySelector("#entitiesTable").parentElement;
|
let entitiesCard = document.querySelector("#entitiesTable").parentElement;
|
||||||
@@ -544,6 +572,17 @@
|
|||||||
console.error('Failed to fetch searchdomain config');
|
console.error('Failed to fetch searchdomain config');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
cacheUtilizationPromise.then(cacheUtilization => {
|
||||||
|
if (cacheUtilization != null && cacheUtilization.SearchCacheSizeBytes != null)
|
||||||
|
{
|
||||||
|
console.log(cacheUtilization);
|
||||||
|
document.querySelector('#cacheUtilization').innerText =
|
||||||
|
`${(cacheUtilization.SearchCacheSizeBytes / (1024 * 1024)).toFixed(2)}MiB`;
|
||||||
|
} else {
|
||||||
|
// TODO add toast
|
||||||
|
console.error('Failed to fetch searchdomain cache utilization');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearEntitiesTable() {
|
function clearEntitiesTable() {
|
||||||
|
|||||||
@@ -8,6 +8,25 @@ public readonly struct ResultItem(float score, string name)
|
|||||||
public readonly float Score { get; } = score;
|
public readonly float Score { get; } = score;
|
||||||
[JsonPropertyName("Name")]
|
[JsonPropertyName("Name")]
|
||||||
public readonly string Name { get; } = name;
|
public readonly string Name { get; } = name;
|
||||||
|
|
||||||
|
public static long EstimateSize(ResultItem item)
|
||||||
|
{
|
||||||
|
long size = 0;
|
||||||
|
|
||||||
|
// string object
|
||||||
|
if (item.Name != null)
|
||||||
|
{
|
||||||
|
size += MemorySizes.ObjectHeader;
|
||||||
|
size += sizeof(int); // string length
|
||||||
|
size += item.Name.Length * sizeof(char);
|
||||||
|
size = Align(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long Align(long size)
|
||||||
|
=> (size + 7) & ~7; // 8-byte alignment
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct DateTimedSearchResult(DateTime dateTime, List<ResultItem> results)
|
public struct DateTimedSearchResult(DateTime dateTime, List<ResultItem> results)
|
||||||
@@ -16,6 +35,57 @@ public struct DateTimedSearchResult(DateTime dateTime, List<ResultItem> results)
|
|||||||
public List<DateTime> AccessDateTimes { get; set; } = [dateTime];
|
public List<DateTime> AccessDateTimes { get; set; } = [dateTime];
|
||||||
[JsonPropertyName("Results")]
|
[JsonPropertyName("Results")]
|
||||||
public List<ResultItem> Results { get; set; } = results;
|
public List<ResultItem> Results { get; set; } = results;
|
||||||
|
|
||||||
|
public long EstimateSize()
|
||||||
|
{
|
||||||
|
long size = 0;
|
||||||
|
|
||||||
|
size += EstimateDateTimeList(AccessDateTimes);
|
||||||
|
size += EstimateResultItemList(Results);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long EstimateDateTimeList(List<DateTime>? list)
|
||||||
|
{
|
||||||
|
if (list == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
long size = 0;
|
||||||
|
|
||||||
|
// List object
|
||||||
|
size += MemorySizes.ObjectHeader;
|
||||||
|
size += MemorySizes.Reference; // reference to array
|
||||||
|
|
||||||
|
// Internal array
|
||||||
|
size += MemorySizes.ArrayHeader;
|
||||||
|
size += list.Capacity * sizeof(long); // DateTime = 8 bytes
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long EstimateResultItemList(List<ResultItem>? list)
|
||||||
|
{
|
||||||
|
if (list == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
long size = 0;
|
||||||
|
|
||||||
|
// List object
|
||||||
|
size += MemorySizes.ObjectHeader;
|
||||||
|
size += MemorySizes.Reference;
|
||||||
|
|
||||||
|
// Internal array of structs
|
||||||
|
size += MemorySizes.ArrayHeader;
|
||||||
|
int resultItemInlineSize = sizeof(float) + IntPtr.Size; // float + string reference
|
||||||
|
size += list.Capacity * resultItemInlineSize;
|
||||||
|
|
||||||
|
// Heap allocations referenced by ResultItem
|
||||||
|
foreach (var item in list)
|
||||||
|
size += ResultItem.EstimateSize(item);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SearchdomainSettings(bool cacheReconciliation = false)
|
public struct SearchdomainSettings(bool cacheReconciliation = false)
|
||||||
@@ -23,3 +93,14 @@ public struct SearchdomainSettings(bool cacheReconciliation = false)
|
|||||||
[JsonPropertyName("CacheReconciliation")]
|
[JsonPropertyName("CacheReconciliation")]
|
||||||
public bool CacheReconciliation { get; set; } = cacheReconciliation;
|
public bool CacheReconciliation { get; set; } = cacheReconciliation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static class MemorySizes
|
||||||
|
{
|
||||||
|
public static readonly int PointerSize = IntPtr.Size;
|
||||||
|
public static readonly int ObjectHeader = PointerSize * 2;
|
||||||
|
public static readonly int Reference = PointerSize;
|
||||||
|
public static readonly int ArrayHeader = Align(ObjectHeader + sizeof(int));
|
||||||
|
|
||||||
|
public static int Align(int size)
|
||||||
|
=> (size + PointerSize - 1) & ~(PointerSize - 1);
|
||||||
|
}
|
||||||
@@ -65,4 +65,25 @@ public class SearchdomainSettingsResults
|
|||||||
|
|
||||||
[JsonPropertyName("Settings")]
|
[JsonPropertyName("Settings")]
|
||||||
public required SearchdomainSettings? Settings { get; set; }
|
public required SearchdomainSettings? Settings { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchdomainSearchCacheSizeResults
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Success")]
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("Message")]
|
||||||
|
public string? Message { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("SearchCacheSizeBytes")]
|
||||||
|
public required long? SearchCacheSizeBytes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchdomainInvalidateCacheResults
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Success")]
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("Message")]
|
||||||
|
public string? Message { get; set; }
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user