chore(stcrashreceiver): better source cache & metrics

Signed-off-by: Jakob Borg <jakob@kastelo.net>
This commit is contained in:
Jakob Borg
2026-05-23 08:51:34 +02:00
parent 33075974cb
commit 79423edbdf
2 changed files with 35 additions and 10 deletions
+10
View File
@@ -47,4 +47,14 @@ var (
Subsystem: "crashreceiver", Subsystem: "crashreceiver",
Name: "ignore_matches_total", Name: "ignore_matches_total",
}, []string{"pattern"}) }, []string{"pattern"})
metricSourceCodeLoadsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: "syncthing",
Subsystem: "crashreceiver",
Name: "source_code_loads_total",
}, []string{"result"})
metricSourceCodeCacheSize = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: "syncthing",
Subsystem: "crashreceiver",
Name: "source_code_cache_size",
})
) )
+25 -10
View File
@@ -15,23 +15,33 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
lru "github.com/hashicorp/golang-lru/v2"
) )
const ( const (
urlPrefix = "https://raw.githubusercontent.com/syncthing/syncthing/" urlPrefix = "https://raw.githubusercontent.com/syncthing/syncthing/"
httpTimeout = 10 * time.Second httpTimeout = 10 * time.Second
maxCacheEntries = 1000
) )
type cacheKey struct {
version string
file string
}
type githubSourceCodeLoader struct { type githubSourceCodeLoader struct {
mut sync.Mutex mut sync.Mutex
version string version string
cache map[string]map[string][][]byte // version -> file -> lines
client *http.Client cache *lru.TwoQueueCache[cacheKey, [][]byte] // version & file -> lines
client *http.Client
} }
func newGithubSourceCodeLoader() *githubSourceCodeLoader { func newGithubSourceCodeLoader() *githubSourceCodeLoader {
cache, _ := lru.New2Q[cacheKey, [][]byte](maxCacheEntries)
return &githubSourceCodeLoader{ return &githubSourceCodeLoader{
cache: make(map[string]map[string][][]byte), cache: cache,
client: &http.Client{Timeout: httpTimeout}, client: &http.Client{Timeout: httpTimeout},
} }
} }
@@ -39,9 +49,6 @@ func newGithubSourceCodeLoader() *githubSourceCodeLoader {
func (l *githubSourceCodeLoader) LockWithVersion(version string) { func (l *githubSourceCodeLoader) LockWithVersion(version string) {
l.mut.Lock() l.mut.Lock()
l.version = version l.version = version
if _, ok := l.cache[version]; !ok {
l.cache[version] = make(map[string][][]byte)
}
} }
func (l *githubSourceCodeLoader) Unlock() { func (l *githubSourceCodeLoader) Unlock() {
@@ -50,11 +57,13 @@ func (l *githubSourceCodeLoader) Unlock() {
func (l *githubSourceCodeLoader) Load(filename string, line, context int) ([][]byte, int) { func (l *githubSourceCodeLoader) Load(filename string, line, context int) ([][]byte, int) {
filename = filepath.ToSlash(filename) filename = filepath.ToSlash(filename)
lines, ok := l.cache[l.version][filename] key := cacheKey{version: l.version, file: filename}
lines, ok := l.cache.Get(key)
if !ok { if !ok {
// Cache whatever we managed to find (or nil if nothing, so we don't try again) // Cache whatever we managed to find (or nil if nothing, so we don't try again)
defer func() { defer func() {
l.cache[l.version][filename] = lines l.cache.Add(key, lines)
metricSourceCodeCacheSize.Set(float64(l.cache.Len()))
}() }()
knownPrefixes := []string{"/lib/", "/cmd/"} knownPrefixes := []string{"/lib/", "/cmd/"}
@@ -73,19 +82,25 @@ func (l *githubSourceCodeLoader) Load(filename string, line, context int) ([][]b
resp, err := l.client.Get(url) resp, err := l.client.Get(url)
if err != nil { if err != nil {
fmt.Println("Loading source:", err) fmt.Println("Loading source:", err)
metricSourceCodeLoadsTotal.WithLabelValues("failed").Inc()
return nil, 0 return nil, 0
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
fmt.Println("Loading source:", resp.Status) fmt.Println("Loading source:", resp.Status)
metricSourceCodeLoadsTotal.WithLabelValues("failed").Inc()
return nil, 0 return nil, 0
} }
data, err := io.ReadAll(resp.Body) data, err := io.ReadAll(resp.Body)
_ = resp.Body.Close() _ = resp.Body.Close()
if err != nil { if err != nil {
fmt.Println("Loading source:", err.Error()) fmt.Println("Loading source:", err.Error())
metricSourceCodeLoadsTotal.WithLabelValues("failed").Inc()
return nil, 0 return nil, 0
} }
lines = bytes.Split(data, []byte{'\n'}) lines = bytes.Split(data, []byte{'\n'})
metricSourceCodeLoadsTotal.WithLabelValues("loaded").Inc()
} else {
metricSourceCodeLoadsTotal.WithLabelValues("cached").Inc()
} }
return getLineFromLines(lines, line, context) return getLineFromLines(lines, line, context)