Knowledge base of ~80+ markdown files across 14 domains (00-13), Logseq graph, hardware design files (KiCAD), infrastructure configs, and talas-wiki static site. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
132 lines
3.3 KiB
Go
132 lines
3.3 KiB
Go
package wiki
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
type Metrics struct {
|
|
mu sync.RWMutex
|
|
pageViews map[string]int64
|
|
searchCount int64
|
|
editCount int64
|
|
startTime time.Time
|
|
renderTimeSum int64 // nanoseconds
|
|
renderCount int64
|
|
}
|
|
|
|
func NewMetrics() *Metrics {
|
|
return &Metrics{
|
|
pageViews: make(map[string]int64),
|
|
startTime: time.Now(),
|
|
}
|
|
}
|
|
|
|
func (m *Metrics) RecordPageView(path string) {
|
|
m.mu.Lock()
|
|
m.pageViews[path]++
|
|
m.mu.Unlock()
|
|
}
|
|
|
|
func (m *Metrics) RecordSearch() {
|
|
atomic.AddInt64(&m.searchCount, 1)
|
|
}
|
|
|
|
func (m *Metrics) RecordEdit() {
|
|
atomic.AddInt64(&m.editCount, 1)
|
|
}
|
|
|
|
func (m *Metrics) StartTime() time.Time { return m.startTime }
|
|
|
|
func (m *Metrics) RecordRenderTime(d time.Duration) {
|
|
atomic.AddInt64(&m.renderTimeSum, int64(d))
|
|
atomic.AddInt64(&m.renderCount, 1)
|
|
}
|
|
|
|
// PrometheusMetrics returns metrics in Prometheus text exposition format
|
|
func (m *Metrics) PrometheusMetrics(idx *Index) string {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var b strings.Builder
|
|
|
|
// Uptime
|
|
uptime := time.Since(m.startTime).Seconds()
|
|
fmt.Fprintf(&b, "# HELP wiki_uptime_seconds Time since server start\n")
|
|
fmt.Fprintf(&b, "# TYPE wiki_uptime_seconds gauge\nwiki_uptime_seconds %.0f\n\n", uptime)
|
|
|
|
// Total pages
|
|
fmt.Fprintf(&b, "# HELP wiki_pages_total Total indexed pages\n")
|
|
fmt.Fprintf(&b, "# TYPE wiki_pages_total gauge\nwiki_pages_total %d\n\n", len(idx.GetAllPages()))
|
|
|
|
// Page views
|
|
totalViews := int64(0)
|
|
for _, v := range m.pageViews {
|
|
totalViews += v
|
|
}
|
|
fmt.Fprintf(&b, "# HELP wiki_page_views_total Total page views\n")
|
|
fmt.Fprintf(&b, "# TYPE wiki_page_views_total counter\nwiki_page_views_total %d\n\n", totalViews)
|
|
|
|
// Searches
|
|
fmt.Fprintf(&b, "# HELP wiki_searches_total Total searches performed\n")
|
|
fmt.Fprintf(&b, "# TYPE wiki_searches_total counter\nwiki_searches_total %d\n\n", atomic.LoadInt64(&m.searchCount))
|
|
|
|
// Edits
|
|
fmt.Fprintf(&b, "# HELP wiki_edits_total Total edits performed\n")
|
|
fmt.Fprintf(&b, "# TYPE wiki_edits_total counter\nwiki_edits_total %d\n\n", atomic.LoadInt64(&m.editCount))
|
|
|
|
// Render time
|
|
rc := atomic.LoadInt64(&m.renderCount)
|
|
if rc > 0 {
|
|
avg := float64(atomic.LoadInt64(&m.renderTimeSum)) / float64(rc) / 1e6 // ms
|
|
fmt.Fprintf(&b, "# HELP wiki_render_avg_ms Average render time in ms\n")
|
|
fmt.Fprintf(&b, "# TYPE wiki_render_avg_ms gauge\nwiki_render_avg_ms %.2f\n\n", avg)
|
|
}
|
|
|
|
// Domains
|
|
for _, dom := range idx.GetDomains() {
|
|
fmt.Fprintf(&b, "wiki_domain_pages{domain=\"%s\"} %d\n", dom.FullDir, dom.Count)
|
|
}
|
|
|
|
// Top viewed pages (top 10)
|
|
type pv struct {
|
|
path string
|
|
count int64
|
|
}
|
|
var pvs []pv
|
|
for path, count := range m.pageViews {
|
|
pvs = append(pvs, pv{path, count})
|
|
}
|
|
// Simple sort
|
|
for i := 0; i < len(pvs); i++ {
|
|
for j := i + 1; j < len(pvs); j++ {
|
|
if pvs[j].count > pvs[i].count {
|
|
pvs[i], pvs[j] = pvs[j], pvs[i]
|
|
}
|
|
}
|
|
}
|
|
if len(pvs) > 10 {
|
|
pvs = pvs[:10]
|
|
}
|
|
fmt.Fprintf(&b, "\n# HELP wiki_page_views Page view counts\n# TYPE wiki_page_views counter\n")
|
|
for _, p := range pvs {
|
|
fmt.Fprintf(&b, "wiki_page_views{page=\"%s\"} %d\n", p.path, p.count)
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// GetTopViewed returns the most viewed pages
|
|
func (m *Metrics) GetTopViewed(n int) map[string]int64 {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
result := make(map[string]int64)
|
|
for k, v := range m.pageViews {
|
|
result[k] = v
|
|
}
|
|
return result
|
|
}
|