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"
"regexp"
"runtime/debug"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
)
const maxStackLen = 1000
//NOTE: for simplicity not making it an atomic variable
var enabled bool = false
const OFF = 0
const LITE = 1
const FULL = 2
var mode int32 = OFF
var res2track = &sync.Map{}
type TrackedInfo struct {
@ -38,12 +43,19 @@ func InitHandlers() {
}
func onHandler(w http.ResponseWriter, r *http.Request) {
Enable(true)
fmt.Fprintf(w, "ON\n")
query := r.URL.Query()
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) {
Enable(false)
Enable(OFF)
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) {
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) {
enabled = flag
if enabled {
Reset()
}
func Enable(m int32) {
atomic.StoreInt32(&mode, m)
}
func Reset() {
res2track = &sync.Map{}
res2track.Range(func(k, v interface{}) bool {
res2track.Delete(k)
return true
})
}
func IsOn() bool {
return enabled
return atomic.LoadInt32(&mode) != OFF
}
func Count() int {
return count(res2track)
}
func count(m *sync.Map) int {
var i int
m.Range(func(k, v interface{}) bool {
res2track.Range(func(k, v interface{}) bool {
i++
return true
})
@ -91,32 +99,47 @@ func count(m *sync.Map) int {
}
func Track(o interface{}) {
if !enabled {
m := atomic.LoadInt32(&mode)
if m == OFF {
return
}
stack := debug.Stack()
var stack []byte = nil
if m == FULL {
stack = debug.Stack()
}
res2track.Store(o, TrackedInfo{time.Now(), stack})
}
func Untrack(o interface{}) {
if !enabled {
if atomic.LoadInt32(&mode) == OFF {
return
}
res2track.Delete(o)
}
func FormatReportItems() []TrackReportItem {
var items []TrackReportItem
parensReg := regexp.MustCompile(`\([^\)]+\)`)
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 {
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)
info, _ := v.(TrackedInfo)
stackStr := string(info.Stack)
if len(stackStr) > 0 {
//let's remove first stack entries
for i := 0; i < 5; i++ {
newLineIdx := strings.Index(stackStr, "\n")
@ -135,11 +158,16 @@ func FormatReportItems() []TrackReportItem {
if len(stackStr) > maxStackLen {
stackStr = stackStr[:maxStackLen] + "..."
}
}
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
}
@ -149,8 +177,10 @@ func ReportLines() []string {
now := time.Now()
items := FormatReportItems()
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 {
//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))
}
return lines