Reduce heap allocs 2 (#46)

* reduce heap allocations in tcp logging; omit use of AppendFloat which allocates a metric sh*tton

* debugheaplog: better heap statistic logging

* heap: use string for pcap.Frame.Protocol

* add potential to eliminate Flags.String heap alloc, remove incorrect HEAP comments

* add StackAsync.DebugErr and httpraw.SetBytes

* many heap alloc reductions and replacement of bytes.Equal with internal.BytesEqual
This commit is contained in:
Pat Whittingslow
2026-02-28 19:20:46 +01:00
committed by GitHub
parent eab43c4653
commit 989bb6a0b9
22 changed files with 511 additions and 163 deletions
+1
View File
@@ -21,6 +21,7 @@ func (s *cbnode) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int)
}
func (s *cbnode) Demux(carrierData []byte, frameOffset int) error {
debugLog("cbnode:pre-demux")
return s._demux(carrierData, frameOffset)
}
+53 -22
View File
@@ -6,6 +6,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"log/slog"
"math"
"github.com/soypat/lneto"
@@ -29,12 +30,6 @@ var (
ErrFieldByClassNotFound = errors.New("pcap: field by class not found")
)
type proto string
const (
ProtoEthernet proto = "Ethernet"
)
type PacketBreakdown struct {
hdr httpraw.Header
dmsg dns.Message
@@ -66,8 +61,10 @@ func (pc *PacketBreakdown) initFrames() []Frame {
}
func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:eth:start")
if dst == nil {
dst = pc.initFrames()
debuglog("pcap:eth:initframes")
}
if bitOffset%8 != 0 {
return dst, errors.New("ethernet must be parsed at byte boundary")
@@ -80,8 +77,10 @@ func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset in
if pc.validator().HasError() {
return dst, pc.validator().ErrPop()
}
debuglog("pcap:eth:validated")
finfo := reclaimFrame(&dst, ProtoEthernet, bitOffset, baseEthernetFields[:])
finfo := reclaimFrame(&dst, "Ethernet", bitOffset, baseEthernetFields[:])
debuglog("pcap:eth:reclaimed")
etype := efrm.EtherTypeOrSize()
end := 14*octet + bitOffset
if etype.IsSize() {
@@ -102,12 +101,14 @@ func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset in
case ethernet.TypeIPv6:
dst, err = pc.CaptureIPv6(dst, pkt, end)
default:
reclaimRemainingFrame(&dst, etype, FieldClassPayload, end, octet*len(pkt))
reclaimRemainingFrame(&dst, "Unknown Ethertype", FieldClassPayload, end, octet*len(pkt))
}
debuglog("pcap:eth:done")
return dst, err
}
func (pc *PacketBreakdown) CaptureARP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:arp:start")
if bitOffset%8 != 0 {
return dst, errors.New("ARP must be parsed at byte boundary")
}
@@ -120,7 +121,7 @@ func (pc *PacketBreakdown) CaptureARP(dst []Frame, pkt []byte, bitOffset int) ([
return dst, pc.validator().ErrPop()
}
finfo := reclaimFrame(&dst, ethernet.TypeARP, bitOffset, baseARPFields[:])
finfo := reclaimFrame(&dst, "ARP", bitOffset, baseARPFields[:])
const varstart = 8 * octet
_, hlen := afrm.Hardware()
_, plen := afrm.Protocol()
@@ -154,6 +155,7 @@ func (pc *PacketBreakdown) CaptureARP(dst []Frame, pkt []byte, bitOffset int) ([
}
func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:ipv6:start")
if bitOffset%8 != 0 {
return dst, errors.New("IPv6 must be parsed at byte boundary")
}
@@ -165,7 +167,8 @@ func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) (
if pc.validator().HasError() {
return dst, pc.validator().ErrPop()
}
reclaimFrame(&dst, ethernet.TypeIPv6, bitOffset, baseIPv6Fields[:])
reclaimFrame(&dst, "IPv6", bitOffset, baseIPv6Fields[:])
debuglog("pcap:ipv6:reclaimed")
proto := ifrm6.NextHeader()
end := bitOffset + 40*octet
var protoErrs []error
@@ -196,6 +199,7 @@ func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) (
}
func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:ipv4:start")
if bitOffset%8 != 0 {
return dst, errors.New("IPv4 must be parsed at byte boundary")
}
@@ -207,9 +211,11 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
if pc.validator().HasError() {
return dst, pc.validator().ErrPop()
}
debuglog("pcap:ipv4:validated")
// limit packet to the actual IPv4 frame size
pkt = pkt[:bitOffset/8+int(ifrm4.TotalLength())]
finfo := reclaimFrame(&dst, ethernet.TypeIPv4, bitOffset, baseIPv4Fields[:])
finfo := reclaimFrame(&dst, "IPv4", bitOffset, baseIPv4Fields[:])
debuglog("pcap:ipv4:reclaimed")
options := ifrm4.Options()
if len(options) > 0 {
finfo.Fields = append(finfo.Fields, FrameField{
@@ -221,6 +227,7 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
if ifrm4.CalculateHeaderCRC() != 0 {
finfo.Errors = append(finfo.Errors, lneto.ErrBadCRC)
}
debuglog("pcap:ipv4:crc-done")
proto := ifrm4.Protocol()
end := bitOffset + octet*ifrm4.HeaderLength()
var protoErrs []error
@@ -262,10 +269,12 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
}
}
}
debuglog("pcap:ipv4:proto-crc-done")
return pc.captureIPProto(proto, dst, pkt, end, protoErrs...)
}
func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt []byte, bitOffset int, ipProtoErrs ...error) (_ []Frame, err error) {
debuglog("pcap:ipproto:start")
nextFrame := len(dst)
switch proto {
case lneto.IPProtoTCP:
@@ -275,20 +284,22 @@ func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt
case lneto.IPProtoUDPLite:
dst, err = pc.CaptureUDP(dst, pkt, bitOffset)
if len(dst) > nextFrame {
dst[nextFrame].Protocol = lneto.IPProtoUDPLite
dst[nextFrame].Protocol = "UDPLite"
}
case lneto.IPProtoICMP:
dst, err = pc.CaptureICMPv4(dst, pkt, bitOffset)
default:
reclaimRemainingFrame(&dst, proto, 0, bitOffset, octet*len(pkt))
reclaimRemainingFrame(&dst, "unknown proto", 0, bitOffset, octet*len(pkt))
}
if len(ipProtoErrs) > 0 && len(dst) > nextFrame {
dst[nextFrame].Errors = append(dst[nextFrame].Errors, ipProtoErrs...)
}
debuglog("pcap:ipproto:done")
return dst, err
}
func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:tcp:start")
if bitOffset%8 != 0 {
return dst, errors.New("TCP must be parsed at byte boundary")
}
@@ -300,8 +311,10 @@ func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([
if pc.validator().HasError() {
return dst, pc.validator().ErrPop()
}
debuglog("pcap:tcp:validated")
end := bitOffset + octet*tfrm.HeaderLength()
finfo := reclaimFrame(&dst, lneto.IPProtoTCP, bitOffset, baseTCPFields[:])
finfo := reclaimFrame(&dst, "TCP", bitOffset, baseTCPFields[:])
debuglog("pcap:tcp:reclaimed")
options := tfrm.Options()
if len(options) > 0 {
finfo.Fields = append(finfo.Fields, FrameField{
@@ -312,15 +325,19 @@ func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([
}
payload := tfrm.Payload()
if len(payload) > 0 {
debuglog("pcap:tcp:http-start")
dst, err = pc.CaptureHTTP(dst, pkt, end)
debuglog("pcap:tcp:http-done")
if err != nil {
reclaimRemainingFrame(&dst, unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))
}
}
debuglog("pcap:tcp:done")
return dst, nil
}
func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:udp:start")
if bitOffset%8 != 0 {
return dst, errors.New("UDP must be parsed at byte boundary")
}
@@ -332,7 +349,8 @@ func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([
if pc.validator().HasError() {
return dst, pc.validator().ErrPop()
}
reclaimFrame(&dst, lneto.IPProtoUDP, bitOffset, baseUDPFields[:])
reclaimFrame(&dst, "UDP", bitOffset, baseUDPFields[:])
debuglog("pcap:udp:reclaimed")
end := bitOffset + 8*octet
payload := ufrm.Payload()
dstport := ufrm.DestinationPort()
@@ -347,10 +365,12 @@ func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([
if err != nil {
reclaimRemainingFrame(&dst, unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))
}
debuglog("pcap:udp:done")
return dst, nil
}
func (pc *PacketBreakdown) CaptureICMPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:icmp:start")
if bitOffset%8 != 0 {
return dst, errors.New("ICMPv4 must be parsed at byte boundary")
}
@@ -360,7 +380,7 @@ func (pc *PacketBreakdown) CaptureICMPv4(dst []Frame, pkt []byte, bitOffset int)
return dst, err
}
finfo := reclaimFrame(&dst, lneto.IPProtoICMP, bitOffset, baseICMPv4Fields[:])
finfo := reclaimFrame(&dst, "ICMP", bitOffset, baseICMPv4Fields[:])
// Add type-specific fields.
switch ifrm.Type() {
@@ -541,9 +561,9 @@ func httpBodyClass(contentType, body []byte) FieldClass {
switch {
case bytes.HasPrefix(mediaType, []byte("text/")):
return FieldClassText
case bytes.Equal(mediaType, []byte("application/json")),
bytes.Equal(mediaType, []byte("application/javascript")),
bytes.Equal(mediaType, []byte("application/xml")):
case internal.BytesEqual(mediaType, []byte("application/json")),
internal.BytesEqual(mediaType, []byte("application/javascript")),
internal.BytesEqual(mediaType, []byte("application/xml")):
return FieldClassText
case bytes.HasSuffix(mediaType, []byte("+json")),
bytes.HasSuffix(mediaType, []byte("+xml")):
@@ -562,6 +582,7 @@ func httpBodyClass(contentType, body []byte) FieldClass {
}
func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:http:start")
const httpProtocol = "HTTP"
if bitOffset%8 != 0 {
return dst, errors.New("HTTP must be parsed at byte boundary")
@@ -578,6 +599,7 @@ func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) (
if err != nil {
return dst, err
}
debuglog("pcap:http:parsed")
hdrLen := pc.hdr.BufferParsed()
body, _ := pc.hdr.Body()
bodyClass := httpBodyClass(pc.hdr.Get("Content-Type"), body)
@@ -596,6 +618,7 @@ func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) (
BitLength: len(body) * octet,
},
)
debuglog("pcap:http:done")
return dst, nil
}
@@ -626,7 +649,7 @@ func (ff Flags) IsRightAligned() bool { return ff&FlagRightAligned != 0 }
type Frame struct {
PacketBitOffset int
Protocol any
Protocol string
Fields []FrameField
Errors []error
}
@@ -1306,7 +1329,7 @@ var baseNTPFields = [...]FrameField{
// reclaimFrame extends dst via [internal.SliceReclaim], resets the reclaimed Frame
// with the given protocol and bit offset while preserving Fields and Errors
// backing arrays, and appends baseFields. Returns the Frame for further modification.
func reclaimFrame(dst *[]Frame, proto any, bitOffset int, baseFields []FrameField) *Frame {
func reclaimFrame(dst *[]Frame, proto string, bitOffset int, baseFields []FrameField) *Frame {
finfo := internal.SliceReclaim(dst)
*finfo = Frame{
PacketBitOffset: bitOffset,
@@ -1319,10 +1342,18 @@ func reclaimFrame(dst *[]Frame, proto any, bitOffset int, baseFields []FrameFiel
// reclaimRemainingFrame extends dst via SliceReclaim and populates the reclaimed
// Frame as a single-field "remaining payload" frame, reusing the old Fields backing array.
func reclaimRemainingFrame(dst *[]Frame, proto any, class FieldClass, pktBitOffset, pktBitLen int) {
func reclaimRemainingFrame(dst *[]Frame, proto string, class FieldClass, pktBitOffset, pktBitLen int) {
finfo := reclaimFrame(dst, proto, pktBitOffset, nil)
finfo.Fields = append(finfo.Fields, FrameField{
Class: class,
BitLength: pktBitLen - pktBitOffset,
})
}
const enableDebug = false
func debuglog(msg string) {
if enableDebug {
internal.LogAttrs(nil, slog.LevelDebug, msg)
}
}
+9 -8
View File
@@ -209,7 +209,7 @@ func TestRightAlignedFields(t *testing.T) {
// Find IPv6 frame.
var ipv6Frame *Frame
for i := range frames {
if frames[i].Protocol == ethernet.TypeIPv6 {
if frames[i].Protocol == "IPv6" {
ipv6Frame = &frames[i]
break
}
@@ -476,15 +476,16 @@ func ExampleFormatter_dhcp() {
// Output:
// Ethernet len=14; destination=ff:ff:ff:ff:ff:ff; source=de:ad:be:ef:ca:fe; protocol=0x0800
// IPv4 len=20; version=0x04; (Header Length)=5; (Type of Service)=0x00; (Total Length)=312; identification=0x6043; flags=0x4000; (Time to live)=0x40; protocol=0x11; checksum=0xd972; source=0.0.0.0; destination=255.255.255.255
// UDP [RFC768] len=8; (Source port)=68; (Destination port)=67; size=292; checksum=0x0000
// UDP len=8; (Source port)=68; (Destination port)=67; size=292; checksum=0x0000
// DHCPv4 len=240; op=1; (Hardware Address Type)=0x01; (Hardware Address Length)=6; Hops=0x00; (Transaction ID)=0xdeadbeef; (Start Time)=0x0001; Flags=0x0000; (Client Address)=0.0.0.0; (Offered Address)=0.0.0.0; (Server Next Address)=255.255.255.255; (Relay Agent Address)=0.0.0.0; (Client Hardware Address)=de:ad:be:ef:ca:fe; options
// (DHCP message type.)=1
// (Parameter request list)=0x0102031a1c060f2a
// (DHCP maximum message size)=558
// (Requested IP address)=192.168.1.100
// (Client identifier)="lneto-test"
// (Hostname string)="myhost"
// (DHCP message type.)=1
// (Parameter request list)=0x0102031a1c060f2a
// (DHCP maximum message size)=558
// (Requested IP address)=192.168.1.100
// (Client identifier)="lneto-test"
// (Hostname string)="myhost"
}
func writeOpt(dst []byte, opt dhcpv4.OptNum, data ...byte) int {
dst[0] = byte(opt)
dst[1] = byte(len(data))
+18 -22
View File
@@ -4,7 +4,6 @@ import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"math"
"net/netip"
"slices"
@@ -12,8 +11,8 @@ import (
"strings"
"sync"
_ "time"
"unsafe"
"github.com/soypat/lneto"
"github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/ntp"
"github.com/soypat/lneto/tcp"
@@ -38,6 +37,7 @@ type Formatter struct {
// FormatFrames appends the formatted frame data to the destination buffer according the Formatter state.
// Is equivalent to [Formatter.FormatFrame] called on each Frame with the FrameSep inserted between frames.
func (f *Formatter) FormatFrames(dst []byte, frms []Frame, pkt []byte) (_ []byte, err error) {
debuglog("pcap:fmt:start")
sep := f.frameSep()
for ifrm := range frms {
if ifrm != 0 {
@@ -48,14 +48,16 @@ func (f *Formatter) FormatFrames(dst []byte, frms []Frame, pkt []byte) (_ []byte
return dst, err
}
}
debuglog("pcap:fmt:done")
return dst, nil
}
// FormatFrame formats a single frame's protocol, fields, and errors into dst.
func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, err error) {
debuglog("pcap:fmtframe:start")
sep := f.fieldSep()
bitlen := frm.LenBits()
dst = appendProtocol(dst, frm.Protocol)
dst = append(dst, frm.Protocol...)
if bitlen%8 == 0 {
dst = append(dst, " len="...)
dst = strconv.AppendInt(dst, int64(bitlen/8), 10)
@@ -70,7 +72,7 @@ func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, er
continue
}
dst = append(dst, sep...)
if field.Class == FieldClassFlags && frm.Protocol == lneto.IPProtoTCP {
if field.Class == FieldClassFlags && frm.Protocol == "TCP" {
// TCP flags pretty print special case.
dst = append(dst, "flags="...)
v, err := f.fieldAsUint(pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.Flags.IsRightAligned())
@@ -95,6 +97,7 @@ func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, er
}
dst = append(dst, ')')
}
debuglog("pcap:fmtframe:done")
return dst, nil
}
@@ -135,6 +138,9 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
if hasSpaces {
dst = append(dst, ')')
}
if field.BitLength == 0 {
return dst, nil
}
dst = append(dst, '=')
f.mubuf.Lock()
defer f.mubuf.Unlock()
@@ -142,6 +148,7 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
if err != nil {
return dst, err
}
debuglog("pcap:fmtfield:append-done")
fieldBitStart := pktStartOff + field.FrameBitOffset
switch field.Class {
default:
@@ -151,6 +158,7 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
dst = append(dst, "0x"...)
dst = hex.AppendEncode(dst, f.buf)
case FieldClassTimestamp:
debuglog("pcap:fmtfield:timestamp")
// inspired by [time.RFC3339]
const littlerfc3339 = "2006-01-02T15:04:05.9999"
if len(f.buf) != 8 {
@@ -158,8 +166,13 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
}
ts := ntp.TimestampFromUint64(binary.BigEndian.Uint64(f.buf))
dst = ts.Time().AppendFormat(dst, littlerfc3339)
debuglog("pcap:fmtfield:timestamp-done")
case FieldClassText:
dst = strconv.AppendQuote(dst, string(f.buf))
debuglog("pcap:fmtfield:text")
if len(f.buf) > 0 {
dst = strconv.AppendQuote(dst, unsafe.String(&f.buf[0], len(f.buf)))
}
debuglog("pcap:fmtfield:text-done")
case FieldClassDst, FieldClassSrc, FieldClassSize, FieldClassAddress, FieldClassOperation:
// IP, MAC addresses and ports.
if field.BitLength <= 16 {
@@ -219,20 +232,3 @@ func (f *Formatter) fieldAsUint(pkt []byte, fieldBitStart, bitlen int, rightAlig
}
return binary.BigEndian.Uint64(f.uintBuf[:]), nil
}
// appendProtocol appends the string representation of a Frame.Protocol value
// to dst without going through fmt.Appendf reflect machinery.
func appendProtocol(dst []byte, p any) []byte {
switch p := p.(type) {
case proto:
return append(dst, string(p)...)
case string:
return append(dst, p...)
case ethernet.Type:
return append(dst, p.String()...)
case lneto.IPProto:
return append(dst, p.String()...)
default:
return fmt.Appendf(dst, "%v", p)
}
}
+8
View File
@@ -232,3 +232,11 @@ func (l logger) debug(msg string, attrs ...slog.Attr) {
func (l logger) trace(msg string, attrs ...slog.Attr) {
internal.LogAttrs(l.log, internal.LevelTrace, msg, attrs...)
}
const enableAllocLog = false
func debugLog(msg string) {
if enableAllocLog {
internal.LogAllocs(msg)
}
}