Initial vibecoded proof of concept
This commit is contained in:
parent
74812459af
commit
461318a656
61 changed files with 13306 additions and 0 deletions
540
lua/notex/utils/cache.lua
Normal file
540
lua/notex/utils/cache.lua
Normal file
|
@ -0,0 +1,540 @@
|
|||
-- Caching system for performance optimization
|
||||
local M = {}
|
||||
|
||||
-- Cache storage
|
||||
local cache_storage = {
|
||||
memory = {},
|
||||
lru = {},
|
||||
timed = {}
|
||||
}
|
||||
|
||||
-- Cache configuration
|
||||
local cache_config = {
|
||||
memory = {
|
||||
max_size = 1000,
|
||||
enabled = true
|
||||
},
|
||||
lru = {
|
||||
max_size = 500,
|
||||
enabled = true
|
||||
},
|
||||
timed = {
|
||||
default_ttl = 300, -- 5 minutes
|
||||
cleanup_interval = 60, -- 1 minute
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
-- Performance metrics
|
||||
local cache_metrics = {
|
||||
hits = 0,
|
||||
misses = 0,
|
||||
sets = 0,
|
||||
evictions = 0,
|
||||
cleanups = 0
|
||||
}
|
||||
|
||||
-- Simple memory cache
|
||||
local MemoryCache = {}
|
||||
MemoryCache.__index = MemoryCache
|
||||
|
||||
function MemoryCache.new(max_size)
|
||||
local self = setmetatable({}, MemoryCache)
|
||||
self.data = {}
|
||||
self.max_size = max_size or 1000
|
||||
self.current_size = 0
|
||||
return self
|
||||
end
|
||||
|
||||
function MemoryCache:set(key, value)
|
||||
if self.data[key] == nil then
|
||||
self.current_size = self.current_size + 1
|
||||
end
|
||||
|
||||
self.data[key] = value
|
||||
|
||||
-- Evict if over size limit
|
||||
if self.current_size > self.max_size then
|
||||
self:evict()
|
||||
end
|
||||
|
||||
cache_metrics.sets = cache_metrics.sets + 1
|
||||
end
|
||||
|
||||
function MemoryCache:get(key)
|
||||
local value = self.data[key]
|
||||
if value ~= nil then
|
||||
cache_metrics.hits = cache_metrics.hits + 1
|
||||
return value
|
||||
else
|
||||
cache_metrics.misses = cache_metrics.misses + 1
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function MemoryCache:evict()
|
||||
-- Simple eviction: remove first item
|
||||
local first_key = next(self.data)
|
||||
if first_key then
|
||||
self.data[first_key] = nil
|
||||
self.current_size = self.current_size - 1
|
||||
cache_metrics.evictions = cache_metrics.evictions + 1
|
||||
end
|
||||
end
|
||||
|
||||
function MemoryCache:clear()
|
||||
self.data = {}
|
||||
self.current_size = 0
|
||||
end
|
||||
|
||||
function MemoryCache:size()
|
||||
return self.current_size
|
||||
end
|
||||
|
||||
-- LRU (Least Recently Used) cache
|
||||
local LRUCache = {}
|
||||
LRUCache.__index = LRUCache
|
||||
|
||||
function LRUCache.new(max_size)
|
||||
local self = setmetatable({}, LRUCache)
|
||||
self.data = {}
|
||||
self.access_order = {}
|
||||
self.max_size = max_size or 500
|
||||
return self
|
||||
end
|
||||
|
||||
function LRUCache:set(key, value)
|
||||
if self.data[key] then
|
||||
-- Update existing item
|
||||
self.data[key] = value
|
||||
self:update_access(key)
|
||||
else
|
||||
-- Add new item
|
||||
self.data[key] = value
|
||||
table.insert(self.access_order, key)
|
||||
|
||||
-- Evict if over size limit
|
||||
if #self.access_order > self.max_size then
|
||||
self:evict()
|
||||
end
|
||||
end
|
||||
|
||||
cache_metrics.sets = cache_metrics.sets + 1
|
||||
end
|
||||
|
||||
function LRUCache:get(key)
|
||||
local value = self.data[key]
|
||||
if value ~= nil then
|
||||
self:update_access(key)
|
||||
cache_metrics.hits = cache_metrics.hits + 1
|
||||
return value
|
||||
else
|
||||
cache_metrics.misses = cache_metrics.misses + 1
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function LRUCache:update_access(key)
|
||||
-- Remove key from current position
|
||||
for i, k in ipairs(self.access_order) do
|
||||
if k == key then
|
||||
table.remove(self.access_order, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Add to end (most recently used)
|
||||
table.insert(self.access_order, key)
|
||||
end
|
||||
|
||||
function LRUCache:evict()
|
||||
if #self.access_order > 0 then
|
||||
local lru_key = table.remove(self.access_order, 1)
|
||||
self.data[lru_key] = nil
|
||||
cache_metrics.evictions = cache_metrics.evictions + 1
|
||||
end
|
||||
end
|
||||
|
||||
function LRUCache:clear()
|
||||
self.data = {}
|
||||
self.access_order = {}
|
||||
end
|
||||
|
||||
function LRUCache:size()
|
||||
return #self.access_order
|
||||
end
|
||||
|
||||
-- Timed cache with TTL
|
||||
local TimedCache = {}
|
||||
TimedCache.__index = TimedCache
|
||||
|
||||
function TimedCache.new(default_ttl)
|
||||
local self = setmetatable({}, TimedCache)
|
||||
self.data = {}
|
||||
self.default_ttl = default_ttl or 300
|
||||
self.cleanup_timer = nil
|
||||
self:start_cleanup_timer()
|
||||
return self
|
||||
end
|
||||
|
||||
function TimedCache:set(key, value, ttl)
|
||||
ttl = ttl or self.default_ttl
|
||||
local expire_time = os.time() + ttl
|
||||
|
||||
self.data[key] = {
|
||||
value = value,
|
||||
expire_time = expire_time
|
||||
}
|
||||
|
||||
cache_metrics.sets = cache_metrics.sets + 1
|
||||
end
|
||||
|
||||
function TimedCache:get(key)
|
||||
local item = self.data[key]
|
||||
if item then
|
||||
if os.time() < item.expire_time then
|
||||
cache_metrics.hits = cache_metrics.hits + 1
|
||||
return item.value
|
||||
else
|
||||
-- Expired, remove it
|
||||
self.data[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
cache_metrics.misses = cache_metrics.misses + 1
|
||||
return nil
|
||||
end
|
||||
|
||||
function TimedCache:cleanup()
|
||||
local current_time = os.time()
|
||||
local cleaned = 0
|
||||
|
||||
for key, item in pairs(self.data) do
|
||||
if current_time >= item.expire_time then
|
||||
self.data[key] = nil
|
||||
cleaned = cleaned + 1
|
||||
end
|
||||
end
|
||||
|
||||
cache_metrics.cleanups = cache_metrics.cleanups + 1
|
||||
return cleaned
|
||||
end
|
||||
|
||||
function TimedCache:start_cleanup_timer()
|
||||
if self.cleanup_timer then
|
||||
return
|
||||
end
|
||||
|
||||
self.cleanup_timer = vim.loop.new_timer()
|
||||
if self.cleanup_timer then
|
||||
self.cleanup_timer:start(
|
||||
cache_config.timed.cleanup_interval * 1000,
|
||||
cache_config.timed.cleanup_interval * 1000,
|
||||
vim.schedule_wrap(function()
|
||||
self:cleanup()
|
||||
end)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function TimedCache:stop_cleanup_timer()
|
||||
if self.cleanup_timer then
|
||||
self.cleanup_timer:close()
|
||||
self.cleanup_timer = nil
|
||||
end
|
||||
end
|
||||
|
||||
function TimedCache:clear()
|
||||
self.data = {}
|
||||
end
|
||||
|
||||
function TimedCache:size()
|
||||
local count = 0
|
||||
for _ in pairs(self.data) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
-- Initialize caches
|
||||
function M.init(config)
|
||||
config = config or {}
|
||||
cache_config = vim.tbl_deep_extend("force", cache_config, config)
|
||||
|
||||
-- Initialize cache instances
|
||||
if cache_config.memory.enabled then
|
||||
cache_storage.memory = MemoryCache.new(cache_config.memory.max_size)
|
||||
end
|
||||
|
||||
if cache_config.lru.enabled then
|
||||
cache_storage.lru = LRUCache.new(cache_config.lru.max_size)
|
||||
end
|
||||
|
||||
if cache_config.timed.enabled then
|
||||
cache_storage.timed = TimedCache.new(cache_config.timed.default_ttl)
|
||||
end
|
||||
|
||||
M.info("Cache system initialized", cache_config)
|
||||
end
|
||||
|
||||
-- Memory cache operations
|
||||
function M.memory_set(key, value)
|
||||
if not cache_storage.memory then
|
||||
return false, "Memory cache disabled"
|
||||
end
|
||||
cache_storage.memory:set(key, value)
|
||||
return true
|
||||
end
|
||||
|
||||
function M.memory_get(key)
|
||||
if not cache_storage.memory then
|
||||
return nil
|
||||
end
|
||||
return cache_storage.memory:get(key)
|
||||
end
|
||||
|
||||
-- LRU cache operations
|
||||
function M.lru_set(key, value)
|
||||
if not cache_storage.lru then
|
||||
return false, "LRU cache disabled"
|
||||
end
|
||||
cache_storage.lru:set(key, value)
|
||||
return true
|
||||
end
|
||||
|
||||
function M.lru_get(key)
|
||||
if not cache_storage.lru then
|
||||
return nil
|
||||
end
|
||||
return cache_storage.lru:get(key)
|
||||
end
|
||||
|
||||
-- Timed cache operations
|
||||
function M.timed_set(key, value, ttl)
|
||||
if not cache_storage.timed then
|
||||
return false, "Timed cache disabled"
|
||||
end
|
||||
cache_storage.timed:set(key, value, ttl)
|
||||
return true
|
||||
end
|
||||
|
||||
function M.timed_get(key)
|
||||
if not cache_storage.timed then
|
||||
return nil
|
||||
end
|
||||
return cache_storage.timed:get(key)
|
||||
end
|
||||
|
||||
-- Generic cache operations with automatic cache selection
|
||||
function M.set(key, value, cache_type, ttl)
|
||||
cache_type = cache_type or "memory"
|
||||
|
||||
if cache_type == "memory" then
|
||||
return M.memory_set(key, value)
|
||||
elseif cache_type == "lru" then
|
||||
return M.lru_set(key, value)
|
||||
elseif cache_type == "timed" then
|
||||
return M.timed_set(key, value, ttl)
|
||||
else
|
||||
return false, "Unknown cache type: " .. cache_type
|
||||
end
|
||||
end
|
||||
|
||||
function M.get(key, cache_type)
|
||||
cache_type = cache_type or "memory"
|
||||
|
||||
if cache_type == "memory" then
|
||||
return M.memory_get(key)
|
||||
elseif cache_type == "lru" then
|
||||
return M.lru_get(key)
|
||||
elseif cache_type == "timed" then
|
||||
return M.timed_get(key)
|
||||
else
|
||||
return nil, "Unknown cache type: " .. cache_type
|
||||
end
|
||||
end
|
||||
|
||||
-- Get or set pattern (compute if not cached)
|
||||
function M.get_or_set(key, compute_func, cache_type, ttl)
|
||||
local value = M.get(key, cache_type)
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
|
||||
-- Compute value
|
||||
local success, result = pcall(compute_func)
|
||||
if success then
|
||||
M.set(key, result, cache_type, ttl)
|
||||
return result
|
||||
else
|
||||
error("Failed to compute cached value: " .. result)
|
||||
end
|
||||
end
|
||||
|
||||
-- Cache with multiple backends (try each in order)
|
||||
function M.multi_get(key, cache_types)
|
||||
cache_types = cache_types or {"memory", "lru", "timed"}
|
||||
|
||||
for _, cache_type in ipairs(cache_types) do
|
||||
local value = M.get(key, cache_type)
|
||||
if value ~= nil then
|
||||
return value, cache_type
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Invalidate cache entries
|
||||
function M.invalidate(key, cache_type)
|
||||
if cache_type then
|
||||
-- Invalidate specific cache type
|
||||
if cache_type == "memory" and cache_storage.memory then
|
||||
cache_storage.memory.data[key] = nil
|
||||
elseif cache_type == "lru" and cache_storage.lru then
|
||||
cache_storage.lru.data[key] = nil
|
||||
for i, k in ipairs(cache_storage.lru.access_order) do
|
||||
if k == key then
|
||||
table.remove(cache_storage.lru.access_order, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif cache_type == "timed" and cache_storage.timed then
|
||||
cache_storage.timed.data[key] = nil
|
||||
end
|
||||
else
|
||||
-- Invalidate from all caches
|
||||
M.invalidate(key, "memory")
|
||||
M.invalidate(key, "lru")
|
||||
M.invalidate(key, "timed")
|
||||
end
|
||||
end
|
||||
|
||||
-- Clear all caches
|
||||
function M.clear_all()
|
||||
if cache_storage.memory then
|
||||
cache_storage.memory:clear()
|
||||
end
|
||||
|
||||
if cache_storage.lru then
|
||||
cache_storage.lru:clear()
|
||||
end
|
||||
|
||||
if cache_storage.timed then
|
||||
cache_storage.timed:clear()
|
||||
end
|
||||
|
||||
-- Reset metrics
|
||||
cache_metrics.hits = 0
|
||||
cache_metrics.misses = 0
|
||||
cache_metrics.sets = 0
|
||||
cache_metrics.evictions = 0
|
||||
cache_metrics.cleanups = 0
|
||||
|
||||
M.info("All caches cleared")
|
||||
end
|
||||
|
||||
-- Get cache statistics
|
||||
function M.get_stats()
|
||||
local stats = {
|
||||
metrics = vim.deepcopy(cache_metrics),
|
||||
sizes = {},
|
||||
config = vim.deepcopy(cache_config)
|
||||
}
|
||||
|
||||
-- Calculate hit ratio
|
||||
local total_requests = cache_metrics.hits + cache_metrics.misses
|
||||
stats.metrics.hit_ratio = total_requests > 0 and (cache_metrics.hits / total_requests) or 0
|
||||
|
||||
-- Get cache sizes
|
||||
if cache_storage.memory then
|
||||
stats.sizes.memory = cache_storage.memory:size()
|
||||
end
|
||||
|
||||
if cache_storage.lru then
|
||||
stats.sizes.lru = cache_storage.lru:size()
|
||||
end
|
||||
|
||||
if cache_storage.timed then
|
||||
stats.sizes.timed = cache_storage.timed:size()
|
||||
end
|
||||
|
||||
return stats
|
||||
end
|
||||
|
||||
-- Cache warming functions
|
||||
function M.warm_query_cache(queries)
|
||||
if not cache_storage.lru then
|
||||
return false, "LRU cache not available"
|
||||
end
|
||||
|
||||
local query_engine = require('notex.query')
|
||||
local warmed = 0
|
||||
|
||||
for _, query in ipairs(queries) do
|
||||
local key = "query:" .. query
|
||||
local cached = M.lru_get(key)
|
||||
if not cached then
|
||||
-- Execute query and cache result
|
||||
local result = query_engine.execute_query(query)
|
||||
if result.success then
|
||||
M.lru_set(key, result)
|
||||
warmed = warmed + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.info("Warmed query cache", {queries_warmed = warmed})
|
||||
return true
|
||||
end
|
||||
|
||||
function M.warm_document_cache(document_paths)
|
||||
if not cache_storage.memory then
|
||||
return false, "Memory cache not available"
|
||||
end
|
||||
|
||||
local indexer = require('notex.index')
|
||||
local warmed = 0
|
||||
|
||||
for _, path in ipairs(document_paths) do
|
||||
local key = "document:" .. path
|
||||
local cached = M.memory_get(key)
|
||||
if not cached then
|
||||
-- Get document details and cache
|
||||
local details, err = indexer.get_document_details_by_path(path)
|
||||
if details then
|
||||
M.memory_set(key, details)
|
||||
warmed = warmed + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.info("Warmed document cache", {documents_warmed = warmed})
|
||||
return true
|
||||
end
|
||||
|
||||
-- Cleanup function
|
||||
function M.cleanup()
|
||||
if cache_storage.timed then
|
||||
cache_storage.timed:stop_cleanup_timer()
|
||||
end
|
||||
|
||||
M.clear_all()
|
||||
M.info("Cache system cleaned up")
|
||||
end
|
||||
|
||||
-- Export cache metrics for monitoring
|
||||
M.metrics = cache_metrics
|
||||
M.config = cache_config
|
||||
|
||||
-- Forward logging functions (circular dependency resolution)
|
||||
function M.info(message, context)
|
||||
local ok, logging = pcall(require, 'notex.utils.logging')
|
||||
if ok then
|
||||
logging.info(message, context)
|
||||
else
|
||||
vim.notify("Cache: " .. message, vim.log.levels.INFO)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
Loading…
Add table
Add a link
Reference in a new issue