mirror of
https://github.com/soypat/lneto.git
synced 2026-09-10 16:49:37 +00:00
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:
+53
-22
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user