testing.B.Loop() standardization and StackAsync.Debug improvement (#156)

* testing.B.Loop() standardization and StackAsync.Debug heap debugging improvement

* apply @MDr164 fixes

* @MDr164 great suggestions to prevent panic and print correct mallocs
This commit is contained in:
Pat Whittingslow
2026-07-17 11:59:27 -03:00
committed by GitHub
parent 766e03e90e
commit 157a8537a2
10 changed files with 64 additions and 45 deletions
+1 -1
View File
@@ -124,7 +124,7 @@ func BenchmarkParseBytes(b *testing.B) {
var hdr Header var hdr Header
b.StartTimer() b.StartTimer()
for i := 0; i < b.N; i++ { for b.Loop() {
err := hdr.ParseBytes(asRequest, data) err := hdr.ParseBytes(asRequest, data)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
+29 -14
View File
@@ -2,16 +2,16 @@ package internal
import ( import (
"log/slog" "log/slog"
"os"
"runtime" "runtime"
"strconv" "strconv"
"sync" "sync"
"unsafe"
) )
const ( const (
LevelTrace slog.Level = slog.LevelDebug - 2 LevelTrace slog.Level = slog.LevelDebug - 2
usePrintLogAllocs = true usePrintLogAllocs = HeapAllocDebugging
) )
var ( var (
@@ -19,9 +19,13 @@ var (
lastAllocs uint64 lastAllocs uint64
lastMallocs uint64 lastMallocs uint64
allocmu sync.Mutex allocmu sync.Mutex
allocbuf [256]byte allocbuf [128]byte
) )
func LogAttrsAndAllocs(allocmsg string, l *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr) {
logAttrsAndAllocs(allocmsg, l, level, msg, attrs...)
}
func LogAllocs(msg string) { func LogAllocs(msg string) {
allocmu.Lock() allocmu.Lock()
runtime.ReadMemStats(&memstats) runtime.ReadMemStats(&memstats)
@@ -29,23 +33,34 @@ func LogAllocs(msg string) {
allocmu.Unlock() allocmu.Unlock()
return return
} }
inc := int64(memstats.TotalAlloc) - int64(lastAllocs)
numAlloc := int64(memstats.Mallocs) - int64(lastMallocs)
free := memstats.HeapSys - memstats.HeapInuse
if usePrintLogAllocs { if usePrintLogAllocs {
// Branch used when debugheaplog enabled
print("[ALLOC] ", msg) print("[ALLOC] ", msg)
print(" inc=", int64(memstats.TotalAlloc)-int64(lastAllocs)) print(" inc=", inc)
print(" n=", int64(memstats.Mallocs)-int64(lastMallocs)) print(" n=", numAlloc)
print(" heap=", memstats.HeapAlloc) print(" heap=", memstats.HeapAlloc)
print(" free=", memstats.HeapSys-memstats.HeapInuse) print(" free=", free)
print(" tot=", memstats.TotalAlloc) print(" tot=", memstats.TotalAlloc)
println() println()
} else { } else {
n := copy(allocbuf[:], "[ALLOC] ") buf := allocbuf[:len(allocbuf)-1] // keep one character for newline.
n += copy(allocbuf[n:], msg) n := copy(buf[:], "[ALLOC] ")
n += copyValueInt(allocbuf[n:], "inc", int64(memstats.TotalAlloc)-int64(lastAllocs)) n += copy(buf[n:], msg)
n += copyValueInt(allocbuf[n:], "n", int64(memstats.Mallocs)-int64(lastMallocs)) n += copyValueInt(buf[n:], "inc", inc)
n += copyValueUint(allocbuf[n:], "heap", memstats.HeapAlloc) n += copyValueInt(buf[n:], "n", numAlloc)
n += copyValueUint(allocbuf[n:], "free", memstats.HeapSys-memstats.HeapInuse) n += copyValueUint(buf[n:], "heap", memstats.HeapAlloc)
n += copyValueUint(allocbuf[n:], "tot", memstats.TotalAlloc) n += copyValueUint(buf[n:], "free", free)
println(unsafe.String(&allocbuf[0], n)) lastN := copyValueUint(buf[n:], "tot", memstats.TotalAlloc)
n += lastN
allocbuf[n] = '\n' // n will never be larger than len(allocbuf)-1
os.Stdout.Write(allocbuf[:n+1])
if lastN == 0 {
n2 := copy(allocbuf[:], "[WARN] ALLOC BUF OVERRUN\n")
os.Stdout.Write(allocbuf[:n2])
}
} }
lastAllocs = memstats.TotalAlloc lastAllocs = memstats.TotalAlloc
lastMallocs = memstats.Mallocs lastMallocs = memstats.Mallocs
+5 -11
View File
@@ -4,7 +4,6 @@ package internal
import ( import (
"log/slog" "log/slog"
"runtime"
"time" "time"
"unsafe" "unsafe"
) )
@@ -22,10 +21,13 @@ func LogEnabled(l *slog.Logger, lvl slog.Level) bool {
return true return true
} }
func logAttrsAndAllocs(allocmsg string, l *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr) {
LogAttrs(nil, level, msg, attrs...) // already logs attributes
}
func LogAttrs(_ *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr) { func LogAttrs(_ *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr) {
now := time.Now() now := time.Now()
n := len(now.AppendFormat(timebuf[:0], timefmt)) n := len(now.AppendFormat(timebuf[:0], timefmt))
LogAllocs(msg)
print("time=", unsafe.String(&timebuf[0], n), " ") print("time=", unsafe.String(&timebuf[0], n), " ")
if level == LevelTrace { if level == LevelTrace {
print("TRACE ") print("TRACE ")
@@ -35,7 +37,6 @@ func LogAttrs(_ *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr)
print(level.String(), " ") print(level.String(), " ")
} }
print(msg) print(msg)
for _, a := range attrs { for _, a := range attrs {
switch a.Value.Kind() { switch a.Value.Kind() {
case slog.KindString: case slog.KindString:
@@ -49,12 +50,5 @@ func LogAttrs(_ *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr)
} }
} }
println() println()
allocmu.Lock() LogAllocs(msg)
runtime.ReadMemStats(&memstats)
if lastAllocs != memstats.TotalAlloc {
print("alloc increase in heaplog")
}
lastAllocs = memstats.TotalAlloc
lastMallocs = memstats.Mallocs
allocmu.Unlock()
} }
+6
View File
@@ -21,3 +21,9 @@ func LogAttrs(l *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr)
l.LogAttrs(context.Background(), level, msg, attrs...) l.LogAttrs(context.Background(), level, msg, attrs...)
} }
} }
func logAttrsAndAllocs(allocmsg string, l *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr) {
LogAllocs(allocmsg)
LogAttrs(l, level, msg, attrs...)
LogAllocs(msg) // Should not log again unless LogAttrs allocated.
}
+4 -4
View File
@@ -132,7 +132,7 @@ func BenchmarkPcap(b *testing.B) {
var frames []Frame var frames []Frame
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for b.Loop() {
frames, _ = pb.CaptureEthernet(frames[:0], tc.pkt, 0) frames, _ = pb.CaptureEthernet(frames[:0], tc.pkt, 0)
} }
}) })
@@ -148,7 +148,7 @@ func BenchmarkPcap(b *testing.B) {
var buf []byte var buf []byte
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for b.Loop() {
buf, _ = f.FormatFrames(buf[:0], frames, tc.pkt) buf, _ = f.FormatFrames(buf[:0], frames, tc.pkt)
} }
}) })
@@ -161,7 +161,7 @@ func BenchmarkPcap(b *testing.B) {
var buf []byte var buf []byte
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for b.Loop() {
frames, _ = pb.CaptureEthernet(frames[:0], tc.pkt, 0) frames, _ = pb.CaptureEthernet(frames[:0], tc.pkt, 0)
buf, _ = f.FormatFrames(buf[:0], frames, tc.pkt) buf, _ = f.FormatFrames(buf[:0], frames, tc.pkt)
} }
@@ -193,7 +193,7 @@ func BenchmarkPcapPhases(b *testing.B) {
var buf []byte var buf []byte
var decNs, fmtNs int64 var decNs, fmtNs int64
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for b.Loop() {
t0 := time.Now() t0 := time.Now()
frames, _ = pb.CaptureEthernet(frames[:0], tc.pkt, 0) frames, _ = pb.CaptureEthernet(frames[:0], tc.pkt, 0)
t1 := time.Now() t1 := time.Now()
+2 -2
View File
@@ -227,7 +227,7 @@ func BenchmarkSYNCookie_Generate(b *testing.B) {
clientISN := Value(0x12345678) clientISN := Value(0x12345678)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for b.Loop() {
sc.MakeSYNCookie(srcAddr, dstAddr, srcPort, dstPort, clientISN) sc.MakeSYNCookie(srcAddr, dstAddr, srcPort, dstPort, clientISN)
} }
} }
@@ -247,7 +247,7 @@ func BenchmarkSYNCookie_Validate(b *testing.B) {
ackNum := cookie + 1 ackNum := cookie + 1
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for b.Loop() {
sc.ValidateSYNCookie(srcAddr, dstAddr, srcPort, dstPort, clientISN, ackNum) sc.ValidateSYNCookie(srcAddr, dstAddr, srcPort, dstPort, clientISN, ackNum)
} }
} }
+4 -10
View File
@@ -842,24 +842,18 @@ func addr4(addr [4]byte, ok bool) netip.Addr {
return netip.AddrFrom4(addr) return netip.AddrFrom4(addr)
} }
// Debug prints debugging information. Very useful for users when coupled with // Debug prints debugging and heap information.
// the debugheaplog build tag. See [internal.LogAttrs] debugheaplog version.
//
// go build -tags=debugheaplog ./yourprogram
func (s *StackAsync) Debug(msg string) { func (s *StackAsync) Debug(msg string) {
internal.LogAttrs(slog.Default(), slog.LevelDebug, "stackasync", internal.LogAttrsAndAllocs(msg, slog.Default(), slog.LevelDebug, "stackasync",
slog.String("umsg", msg), slog.String("umsg", msg),
slog.Uint64("sent", s.stats.TotalSent), slog.Uint64("sent", s.stats.TotalSent),
slog.Uint64("recv", s.stats.TotalReceived), slog.Uint64("recv", s.stats.TotalReceived),
) )
} }
// DebugErr prints debugging and error info. Very useful for users when coupled with // DebugErr prints debugging and heap information.
// the debugheaplog build tag. See [internal.LogAttrs] debugheaplog version.
//
// go build -tags=debugheaplog ./yourprogram
func (s *StackAsync) DebugErr(msg, err string) { func (s *StackAsync) DebugErr(msg, err string) {
internal.LogAttrs(slog.Default(), slog.LevelError, "stackasync", internal.LogAttrsAndAllocs(msg, slog.Default(), slog.LevelError, "stackasync",
slog.String("umsg", msg), slog.String("umsg", msg),
slog.String("err", err), slog.String("err", err),
slog.Uint64("sent", s.stats.TotalSent), slog.Uint64("sent", s.stats.TotalSent),
+2 -2
View File
@@ -41,7 +41,7 @@ func BenchmarkARPExchange(b *testing.B) {
var buf [frameSize]byte var buf [frameSize]byte
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for b.Loop() {
err = c1.StartResolveHardwareAddress6(queryAddr) err = c1.StartResolveHardwareAddress6(queryAddr)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@@ -130,7 +130,7 @@ func BenchmarkTCPHandshake(b *testing.B) {
var pktbuf [frameSize]byte var pktbuf [frameSize]byte
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for b.Loop() {
// Setup connections. // Setup connections.
err = sv.ListenTCP4(svconn, svPort) err = sv.ListenTCP4(svconn, svPort)
if err != nil { if err != nil {
+8 -1
View File
@@ -2,7 +2,11 @@
package xnet package xnet
import "os" import (
"os"
"github.com/soypat/lneto/internal"
)
var _pcap CapturePrinter var _pcap CapturePrinter
@@ -14,4 +18,7 @@ func init() {
func debugPacket(msg string, b []byte) { func debugPacket(msg string, b []byte) {
_pcap.PrintEthernet(msg, b) _pcap.PrintEthernet(msg, b)
if internal.HeapAllocDebugging {
internal.LogAllocs(msg)
}
} }
+3
View File
@@ -13,6 +13,9 @@ import (
) )
func TestTinyGoTest(t *testing.T) { func TestTinyGoTest(t *testing.T) {
if testing.Short() {
t.Skip("`tinygo test` skipped on short test")
}
if exec.Command("tinygo", "version").Run() != nil { if exec.Command("tinygo", "version").Run() != nil {
t.Skip("tinygo not installed") t.Skip("tinygo not installed")
} }