Adding lite and full tracking modes; Using atomic variables; Resetting the map only by explicit command using safe operation

This commit is contained in:
Pavel Shevaev 2022-10-19 20:00:22 +03:00
parent 421eeb3750
commit cbd23c5903
1 changed files with 70 additions and 40 deletions

View File

@ -7,15 +7,20 @@ import (
"reflect" "reflect"
"regexp" "regexp"
"runtime/debug" "runtime/debug"
"sort"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
) )
const maxStackLen = 1000 const maxStackLen = 1000
//NOTE: for simplicity not making it an atomic variable const OFF = 0
var enabled bool = false const LITE = 1
const FULL = 2
var mode int32 = OFF
var res2track = &sync.Map{} var res2track = &sync.Map{}
type TrackedInfo struct { type TrackedInfo struct {
@ -38,12 +43,19 @@ func InitHandlers() {
} }
func onHandler(w http.ResponseWriter, r *http.Request) { func onHandler(w http.ResponseWriter, r *http.Request) {
Enable(true) query := r.URL.Query()
fmt.Fprintf(w, "ON\n") m := query.Get("m")
if len(m) == 0 || m == "lite" {
Enable(LITE)
fmt.Fprintf(w, "ON(lite)\n")
} else if m == "full" {
Enable(FULL)
fmt.Fprintf(w, "ON(full)\n")
}
} }
func offHandler(w http.ResponseWriter, r *http.Request) { func offHandler(w http.ResponseWriter, r *http.Request) {
Enable(false) Enable(OFF)
fmt.Fprintf(w, "OFF\n") fmt.Fprintf(w, "OFF\n")
} }
@ -59,31 +71,27 @@ func resetHandler(w http.ResponseWriter, r *http.Request) {
} }
func statusHandler(w http.ResponseWriter, r *http.Request) { func statusHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "enabled: %v, entries: %d\n", enabled, Count()) fmt.Fprintf(w, "mode: %v, entries: %d\n", atomic.LoadInt32(&mode), Count())
} }
func Enable(flag bool) { func Enable(m int32) {
enabled = flag atomic.StoreInt32(&mode, m)
if enabled {
Reset()
}
} }
func Reset() { func Reset() {
res2track = &sync.Map{} res2track.Range(func(k, v interface{}) bool {
res2track.Delete(k)
return true
})
} }
func IsOn() bool { func IsOn() bool {
return enabled return atomic.LoadInt32(&mode) != OFF
} }
func Count() int { func Count() int {
return count(res2track)
}
func count(m *sync.Map) int {
var i int var i int
m.Range(func(k, v interface{}) bool { res2track.Range(func(k, v interface{}) bool {
i++ i++
return true return true
}) })
@ -91,55 +99,75 @@ func count(m *sync.Map) int {
} }
func Track(o interface{}) { func Track(o interface{}) {
if !enabled { m := atomic.LoadInt32(&mode)
if m == OFF {
return return
} }
stack := debug.Stack() var stack []byte = nil
if m == FULL {
stack = debug.Stack()
}
res2track.Store(o, TrackedInfo{time.Now(), stack}) res2track.Store(o, TrackedInfo{time.Now(), stack})
} }
func Untrack(o interface{}) { func Untrack(o interface{}) {
if !enabled { if atomic.LoadInt32(&mode) == OFF {
return return
} }
res2track.Delete(o) res2track.Delete(o)
} }
func FormatReportItems() []TrackReportItem { func FormatReportItems() []TrackReportItem {
var items []TrackReportItem
parensReg := regexp.MustCompile(`\([^\)]+\)`) parensReg := regexp.MustCompile(`\([^\)]+\)`)
fileReg := regexp.MustCompile(`at\s+.*?/([^/]+\.go:\d+)`) fileReg := regexp.MustCompile(`at\s+.*?/([^/]+\.go:\d+)`)
//1. let's get a quick snapshot of the map
var snapshot []interface{}
res2track.Range(func(k, v interface{}) bool { res2track.Range(func(k, v interface{}) bool {
snapshot = append(snapshot, k)
snapshot = append(snapshot, v)
return true
})
//2. let's add formatted items
var items []TrackReportItem
for i := 0; i < len(snapshot); i += 2 {
k := snapshot[i]
v := snapshot[i+1]
t := reflect.TypeOf(k) t := reflect.TypeOf(k)
info, _ := v.(TrackedInfo) info, _ := v.(TrackedInfo)
stackStr := string(info.Stack) stackStr := string(info.Stack)
//let's remove first stack entries if len(stackStr) > 0 {
for i := 0; i < 5; i++ { //let's remove first stack entries
newLineIdx := strings.Index(stackStr, "\n") for i := 0; i < 5; i++ {
if newLineIdx != -1 { newLineIdx := strings.Index(stackStr, "\n")
stackStr = stackStr[newLineIdx+1:] if newLineIdx != -1 {
} else { stackStr = stackStr[newLineIdx+1:]
break } else {
break
}
}
stackStr = strings.ReplaceAll(stackStr, " ", " ")
stackStr = strings.ReplaceAll(stackStr, "\n", " < ")
stackStr = strings.ReplaceAll(stackStr, ") <", ") at")
stackStr = parensReg.ReplaceAllString(stackStr, "(..)")
stackStr = fileReg.ReplaceAllString(stackStr, "../$1")
if len(stackStr) > maxStackLen {
stackStr = stackStr[:maxStackLen] + "..."
} }
} }
stackStr = strings.ReplaceAll(stackStr, " ", " ")
stackStr = strings.ReplaceAll(stackStr, "\n", " < ")
stackStr = strings.ReplaceAll(stackStr, ") <", ") at")
stackStr = parensReg.ReplaceAllString(stackStr, "(..)")
stackStr = fileReg.ReplaceAllString(stackStr, "../$1")
if len(stackStr) > maxStackLen {
stackStr = stackStr[:maxStackLen] + "..."
}
items = append(items, TrackReportItem{T: t, Duration: time.Now().Sub(info.Time), Stack: stackStr}) items = append(items, TrackReportItem{T: t, Duration: time.Now().Sub(info.Time), Stack: stackStr})
}
return true //3. let's sort items by duration descending
sort.Slice(items, func(i, j int) bool {
return items[i].Duration > items[j].Duration
}) })
return items return items
} }
@ -149,8 +177,10 @@ func ReportLines() []string {
now := time.Now() now := time.Now()
items := FormatReportItems() items := FormatReportItems()
lines = append(lines, fmt.Sprintf("=== Tracked connections report %s (total: %d) ===", now.Format("01-02-2006 15:04:05"), len(items))) lines = append(lines, fmt.Sprintf("=== Tracked connections report %s (total: %d) ===", now.Format("01-02-2006 15:04:05"), len(items)))
for idx, item := range items { for idx, item := range items {
//TODO: obtain somehow an actual pointer of the object or provide a hook for a custom formatter
lines = append(lines, fmt.Sprintf("#%d Tracked (%v, duration %v) : %s", idx+1, item.T, item.Duration, item.Stack)) lines = append(lines, fmt.Sprintf("#%d Tracked (%v, duration %v) : %s", idx+1, item.T, item.Duration, item.Stack))
} }
return lines return lines