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
+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)
}
}