From 5f8a1ae535031edb5740fafdadd8310248eff55f Mon Sep 17 00:00:00 2001 From: landaiqing Date: Fri, 18 Apr 2025 16:47:08 +0800 Subject: [PATCH] :sparkles: Add helper methods --- cache/cache.go | 54 ++++++++++++++ cache/monitor.go | 45 +++++++++++- pixelnebula.go | 179 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+), 2 deletions(-) diff --git a/cache/cache.go b/cache/cache.go index f289df8..0409c9c 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -311,3 +311,57 @@ func (c *PNCache) UpdateOptions(options CacheOptions) { } } } + +// GetAllItems 获取所有缓存项 +func (c *PNCache) GetAllItems() map[CacheKey]*CacheItem { + c.Mutex.RLock() + defer c.Mutex.RUnlock() + + // 创建一个副本,避免直接暴露内部map + items := make(map[CacheKey]*CacheItem, len(c.Items)) + + // 从EvictionList获取所有缓存项 + for e := c.EvictionList.Front(); e != nil; e = e.Next() { + item := e.Value.(*CacheItem) + + // 查找对应的key + for k, v := range c.Items { + if v == e { + // 创建项的副本 + itemCopy := *item + items[k] = &itemCopy + break + } + } + } + + return items +} + +// GetMonitor 获取缓存监控器 +func (c *PNCache) GetMonitor() *Monitor { + c.Mutex.RLock() + defer c.Mutex.RUnlock() + + return c.Monitor +} + +// DeleteItem 删除指定的缓存项 +func (c *PNCache) DeleteItem(key CacheKey) bool { + c.Mutex.Lock() + defer c.Mutex.Unlock() + + // 查找对应的缓存项 + element, found := c.Items[key] + if !found { + return false + } + + // 从链表中移除 + c.EvictionList.Remove(element) + + // 从映射中删除 + delete(c.Items, key) + + return true +} diff --git a/cache/monitor.go b/cache/monitor.go index 3d4b288..3fabb04 100644 --- a/cache/monitor.go +++ b/cache/monitor.go @@ -99,6 +99,18 @@ func (m *Monitor) GetStats() CacheStats { return m.stats } +// GetSampleHistory 获取采样历史 +func (m *Monitor) GetSampleHistory() []CacheStats { + m.mutex.RLock() + defer m.mutex.RUnlock() + + // 创建一个副本 + result := make([]CacheStats, len(m.sampleHistory)) + copy(result, m.sampleHistory) + + return result +} + // monitorRoutine 监控例程 func (m *Monitor) monitorRoutine() { sampleTicker := time.NewTicker(m.options.SampleInterval) @@ -128,8 +140,37 @@ func (m *Monitor) collectSample() { hits, misses, hitRate := m.cache.Stats() size := m.cache.Size() - // 估算内存使用量(简化计算,实际应用中可能需要更精确的方法) - memoryUsage := int64(size * 1024) // 假设每个缓存项平均占用1KB + // 估算内存使用量 + var memoryUsage int64 + + // 获取所有缓存项 + items := m.cache.GetAllItems() + + for _, item := range items { + itemSize := int64(0) + + // 计算SVG字符串占用的内存 + if !item.IsCompressed { + itemSize += int64(len(item.SVG) * 2) // Go中字符串以UTF-16编码存储,每个字符占2字节 + } + + // 计算压缩数据占用的内存 + if item.IsCompressed { + itemSize += int64(len(item.Compressed)) + } + + // 添加对象结构体本身的大小估计 + // CacheItem结构体的基本大小约为40字节(2个时间戳、1个布尔值、2个引用) + itemSize += 40 + + memoryUsage += itemSize + } + + // 添加缓存管理结构的开销估计 (映射表、双向链表等) + // 每个map项约24字节开销,每个链表节点约16字节 + mapOverhead := int64(size * 24) + listOverhead := int64(size * 16) + memoryUsage += mapOverhead + listOverhead // 创建新的统计样本 newStat := CacheStats{ diff --git a/pixelnebula.go b/pixelnebula.go index 7797c5e..0f171cf 100644 --- a/pixelnebula.go +++ b/pixelnebula.go @@ -10,6 +10,7 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/landaiqing/go-pixelnebula/animation" "github.com/landaiqing/go-pixelnebula/cache" @@ -184,12 +185,22 @@ func (pn *PixelNebula) calcKey(hash []string, opts *PNOptions) [2]int { // WithCache 设置缓存选项 func (pn *PixelNebula) WithCache(options cache.CacheOptions) *PixelNebula { pn.cache = cache.NewCache(options) + // 确保启动监控器 + if pn.cache != nil && options.Monitoring.Enabled && pn.cache.GetMonitor() == nil { + pn.cache.Monitor = cache.NewMonitor(pn.cache, options.Monitoring) + pn.cache.Monitor.Start() + } return pn } // WithDefaultCache 设置默认缓存选项 func (pn *PixelNebula) WithDefaultCache() *PixelNebula { pn.cache = cache.NewDefaultCache() + // 确保启动监控器 + if pn.cache != nil && pn.cache.GetOptions().Monitoring.Enabled && pn.cache.GetMonitor() == nil { + pn.cache.Monitor = cache.NewMonitor(pn.cache, pn.cache.GetOptions().Monitoring) + pn.cache.Monitor.Start() + } return pn } @@ -739,3 +750,171 @@ func (pn *PixelNebula) generateSVG(id string, sansEnv bool, opts *PNOptions) (sv return svg, nil } + +// GetCacheStats 获取缓存统计信息 +func (pn *PixelNebula) GetCacheStats() (size, hits, misses int, hitRate float64, enabled bool, maxSize int, expiration time.Duration, evictionType string) { + if pn.cache == nil { + log.Println("pixelnebula: 缓存未初始化,请先调用WithCache或WithDefaultCache") + return 0, 0, 0, 0, false, 0, 0, "" + } + + hits, misses, hitRate = pn.cache.Stats() + options := pn.cache.GetOptions() + + return pn.cache.Size(), hits, misses, hitRate, options.Enabled, options.Size, options.Expiration, options.EvictionType +} + +// CacheItemInfo 缓存项信息结构体 +type CacheItemInfo struct { + Key cache.CacheKey + SVG string + Compressed []byte + IsCompressed bool + CreatedAt time.Time + LastUsed time.Time +} + +// GetCacheItems 获取所有缓存项 +func (pn *PixelNebula) GetCacheItems() []CacheItemInfo { + result := []CacheItemInfo{} + + if pn.cache == nil { + log.Println("pixelnebula: 缓存未初始化,请先调用WithCache或WithDefaultCache") + return result + } + + // 获取内部缓存项 + items := pn.cache.GetAllItems() + if len(items) == 0 { + log.Println("pixelnebula: 缓存中没有数据,请先生成一些SVG") + } + + for key, item := range items { + cacheItem := CacheItemInfo{ + Key: key, + SVG: item.SVG, + Compressed: item.Compressed, + IsCompressed: item.IsCompressed, + CreatedAt: item.CreatedAt, + LastUsed: item.LastUsed, + } + + result = append(result, cacheItem) + } + + return result +} + +// MonitorSampleInfo 监控样本信息 +type MonitorSampleInfo struct { + Timestamp time.Time + Size int + Hits int + Misses int + HitRate float64 + MemoryUsage int64 +} + +// GetMonitorStats 获取监控统计信息 +func (pn *PixelNebula) GetMonitorStats() (enabled bool, sampleInterval, adjustInterval time.Duration, + targetHitRate float64, lastAdjusted time.Time, samples []MonitorSampleInfo) { + + if pn.cache == nil { + log.Println("pixelnebula: 缓存未初始化,请先调用WithCache或WithDefaultCache") + return false, 0, 0, 0, time.Time{}, nil + } + + options := pn.cache.GetOptions().Monitoring + + // 确保监控器已启用并初始化 + if !options.Enabled { + log.Println("pixelnebula: 监控未启用,请设置Monitoring.Enabled=true") + return options.Enabled, options.SampleInterval, options.AdjustInterval, options.TargetHitRate, time.Time{}, nil + } + + if pn.cache.GetMonitor() == nil { + log.Println("pixelnebula: 监控器未初始化,正在初始化...") + pn.cache.Monitor = cache.NewMonitor(pn.cache, options) + pn.cache.Monitor.Start() + } + + monitor := pn.cache.GetMonitor() + stats := monitor.GetStats() + sampleHistory := monitor.GetSampleHistory() + + if len(sampleHistory) == 0 { + log.Println("pixelnebula: 监控样本为空,请等待采样完成") + } + + // 转换样本历史 + samplesInfo := make([]MonitorSampleInfo, 0, len(sampleHistory)) + for _, sample := range sampleHistory { + sampleInfo := MonitorSampleInfo{ + Timestamp: sample.LastAdjusted, + Size: sample.Size, + Hits: sample.Hits, + Misses: sample.Misses, + HitRate: sample.HitRate, + MemoryUsage: sample.MemoryUsage, + } + samplesInfo = append(samplesInfo, sampleInfo) + } + + return options.Enabled, options.SampleInterval, options.AdjustInterval, + options.TargetHitRate, stats.LastAdjusted, samplesInfo +} + +// DeleteCacheItem 删除指定的缓存项 +func (pn *PixelNebula) DeleteCacheItem(key string) bool { + if pn.cache == nil { + log.Println("pixelnebula: 缓存未初始化,请先调用WithCache或WithDefaultCache") + return false + } + + // 解析key字符串,格式为"id_sansEnv_theme_part" + parts := strings.Split(key, "_") + if len(parts) < 4 { + log.Printf("pixelnebula: 无效的key格式: %s,应为'id_sansEnv_theme_part'", key) + return false + } + + id := parts[0] + sansEnv := parts[1] == "true" + + theme, err := strconv.Atoi(parts[2]) + if err != nil { + log.Printf("pixelnebula: 解析theme失败: %v", err) + return false + } + + part, err := strconv.Atoi(parts[3]) + if err != nil { + log.Printf("pixelnebula: 解析part失败: %v", err) + return false + } + + // 构造CacheKey + cacheKey := cache.CacheKey{ + Id: id, + SansEnv: sansEnv, + Theme: theme, + Part: part, + } + + result := pn.cache.DeleteItem(cacheKey) + if !result { + log.Printf("pixelnebula: 未找到缓存项: %s", key) + } + return result +} + +// ClearCache 清空缓存 +func (pn *PixelNebula) ClearCache() { + if pn.cache == nil { + log.Println("pixelnebula: 缓存未初始化,请先调用WithCache或WithDefaultCache") + return + } + + pn.cache.Clear() + log.Println("pixelnebula: 缓存已清空") +}