Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pkg/ebpf/common/http/sqlpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func hasN1QLVersion(resp *http.Response) bool {
}
contentType := resp.Header.Get("Content-Type")
// Split by semicolons to get individual parameters
iter := split.NewIterator(contentType, ";")
iter := split.NewStringIterator(contentType, ";")
for part, eof := iter.Next(); !eof; part, eof = iter.Next() {
part = strings.TrimSuffix(part, ";")
part = strings.TrimSpace(part)
Expand All @@ -134,7 +134,7 @@ func parseSQLPPTablePath(table string, hasQueryContext bool) (bucket, collection
}

// Count parts first
iter := split.NewIterator(table, ".")
iter := split.NewStringIterator(table, ".")
count := 0
for _, eof := iter.Next(); !eof; _, eof = iter.Next() {
count++
Expand Down Expand Up @@ -198,7 +198,7 @@ func parseSQLPPRequest(req *http.Request) (*sqlppRequest, error) {

// extractFormValue extracts a value from form-encoded data
func extractFormValue(data, key string) string {
iter := split.NewIterator(data, "&")
iter := split.NewStringIterator(data, "&")
for pair, eof := iter.Next(); !eof; pair, eof = iter.Next() {
pair = strings.TrimSuffix(pair, "&")
// Split by first "=" only (equivalent to SplitN with 2)
Expand Down
64 changes: 35 additions & 29 deletions pkg/ebpf/common/redis_detect_transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,25 @@ import (
"go.opentelemetry.io/obi/pkg/internal/split"
)

const minRedisFrameLen = 3

var redisErrorCodes = [...]string{
"ERR ",
"WRONGTYPE ",
"MOVED ",
"ASK ",
"BUSY ",
"NOSCRIPT ",
"CLUSTERDOWN ",
"READONLY ",
const (
minRedisFrameLen = 3
redisDelim = "\r\n"
)

var redisDelimBytes = []byte(redisDelim)

var redisErrors = [...]struct {
prefix []byte
code string
}{
{[]byte("ERR "), "ERR"},
{[]byte("WRONGTYPE "), "WRONGTYPE"},
{[]byte("MOVED "), "MOVED"},
{[]byte("ASK "), "ASK"},
{[]byte("BUSY "), "BUSY"},
{[]byte("NOSCRIPT "), "NOSCRIPT"},
{[]byte("CLUSTERDOWN "), "CLUSTERDOWN"},
{[]byte("READONLY "), "READONLY"},
}

func isRedis(buf *largebuf.LargeBuffer) bool {
Expand Down Expand Up @@ -65,12 +73,12 @@ func isRedisOp(buf []uint8) bool {
}

func getRedisError(buf []uint8) (request.DBError, bool) {
description := strings.Trim(string(buf), "\r\n")
description := string(bytes.Trim(buf, "\r\n"))
errorCode := ""

for _, redisErrorCode := range redisErrorCodes {
if bytes.HasPrefix(buf, []byte(redisErrorCode)) {
errorCode = strings.TrimSpace(redisErrorCode)
for _, e := range redisErrors {
if bytes.HasPrefix(buf, e.prefix) {
errorCode = e.code
break
}
}
Expand Down Expand Up @@ -111,10 +119,8 @@ func isValidRedisChar(c byte) bool {
c == '.' || c == ' ' || c == '-' || c == '_'
}

func parseRedisRequest(buf string) (string, string, bool) {
const redisDelim = "\r\n"

lines := split.NewIterator(buf, redisDelim)
func parseRedisRequest(buf []byte) (string, string, bool) {
lines := split.NewBytesIterator(buf, redisDelimBytes)

_, eof := lines.Next()

Expand Down Expand Up @@ -150,31 +156,31 @@ func parseRedisRequest(buf string) (string, string, bool) {
break
}

if line == redisDelim {
if bytes.Equal(line, redisDelimBytes) {
continue
}

if !read {
if isRedisOp([]uint8(line)) {
if isRedisOp(line) {
read = true
} else {
break
}
} else {
if isRedisOp([]uint8(line)) {
if isRedisOp(line) {
text.WriteString("; ")
continue
}
if !isValidRedisChar(line[0]) {
break
}

trimmed := strings.TrimSuffix(line, redisDelim)
trimmed := bytes.TrimSuffix(line, redisDelimBytes)

if op == "" {
op = trimmed
op = string(trimmed)
}
text.WriteString(trimmed)
text.Write(trimmed)
text.WriteString(" ")
read = false
}
Expand Down Expand Up @@ -204,8 +210,8 @@ func getRedisDB(connInfo BpfConnectionInfoT, op, text string, dbCache *simplelru
return -1, false
}
db, found := dbCache.Get(connInfo)
switch strings.ToUpper(op) {
case "SELECT":
switch {
case strings.EqualFold(op, "SELECT"):
// get db number from text after first space
if text != "" {
parts := strings.Split(text, " ")
Expand All @@ -215,7 +221,7 @@ func getRedisDB(connInfo BpfConnectionInfoT, op, text string, dbCache *simplelru
}
}
}
case "QUIT":
case strings.EqualFold(op, "QUIT"):
dbCache.Remove(connInfo)
}
return db, found
Expand Down Expand Up @@ -283,7 +289,7 @@ func ReadGoRedisRequestIntoSpan(record *ringbuf.Record) (request.Span, bool, err
hostPort = int(event.Conn.D_port)
}

op, text, ok := parseRedisRequest(string(event.Buf[:]))
op, text, ok := parseRedisRequest(event.Buf[:])

if !ok {
// We know it's redis request here, it just didn't complete correctly
Expand Down
12 changes: 6 additions & 6 deletions pkg/ebpf/common/redis_detect_transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,38 +36,38 @@ func TestCRLFMatching(t *testing.T) {
}

func TestRedisParsing(t *testing.T) {
proper := "*2\r\n$3\r\nGET\r\n$5\r\nbeyla"
proper := []byte("*2\r\n$3\r\nGET\r\n$5\r\nbeyla")

op, text, ok := parseRedisRequest(proper)
assert.True(t, ok)
assert.Equal(t, "GET", op)
assert.Equal(t, "GET beyla", text)

weird := "*2\r\nGET\r\nbeyla"
weird := []byte("*2\r\nGET\r\nbeyla")
op, text, ok = parseRedisRequest(weird)
assert.True(t, ok)
assert.Empty(t, op)
assert.Empty(t, text)

unknown := "2\r\nGET\r\nbeyla"
unknown := []byte("2\r\nGET\r\nbeyla")
op, text, ok = parseRedisRequest(unknown)
assert.True(t, ok)
assert.Empty(t, op)
assert.Empty(t, text)

op, text, ok = parseRedisRequest("2")
op, text, ok = parseRedisRequest([]byte("2"))
assert.False(t, ok)
assert.Empty(t, op)
assert.Empty(t, text)

multi := fmt.Sprintf("*4\r\n$6\r\nclient\r\n$7\r\nsetinfo\r\n$8\r\nLIB-NAME\r\n$19\r\n%s(,go1.22.2)\r\n*4\r\n$6\r\nclient\r\n$7\r\nsetinfo\r\n$7\r\nLIB-VER\r\n$5\r\n9.5.1\r\n", "go-redis")
multi := []byte(fmt.Sprintf("*4\r\n$6\r\nclient\r\n$7\r\nsetinfo\r\n$8\r\nLIB-NAME\r\n$19\r\n%s(,go1.22.2)\r\n*4\r\n$6\r\nclient\r\n$7\r\nsetinfo\r\n$7\r\nLIB-VER\r\n$5\r\n9.5.1\r\n", "go-redis"))
op, text, ok = parseRedisRequest(multi)
assert.True(t, ok)
assert.Equal(t, "client", op)
assert.Equal(t, "client setinfo LIB-NAME go-redis(,go1.22.2) ; client setinfo LIB-VER 9.5.1", text)

hmset := []byte{42, 52, 13, 10, 36, 53, 13, 10, 72, 77, 83, 69, 84, 13, 10, 36, 51, 54, 13, 10, 48, 99, 57, 102, 97, 56, 97, 97, 45, 50, 56, 49, 102, 45, 49, 49, 101, 102, 45, 57, 55, 98, 57, 45, 98, 101, 57, 54, 48, 48, 99, 97, 48, 102, 50, 55, 13, 10, 36, 52, 13, 10, 99, 97, 114, 116, 13, 10, 36, 53, 52, 13, 10, 10, 36, 48, 99, 57, 102, 97, 56, 97, 97, 45, 50, 56, 49, 102, 45, 49, 49, 101, 102, 45, 57, 55, 98, 57, 45, 98, 101, 57, 54, 48, 48, 99, 97, 48, 102, 50, 55, 18, 14, 10, 10, 79, 76, 74, 67, 69, 83, 80, 67, 55, 90, 16, 5, 13, 10, 0, 10, 72, 81, 84, 71, 87, 71, 80, 78, 72, 52, 16, 1, 13, 10, 0, 10, 49, 89, 77, 87, 87, 78, 49, 78, 52, 79, 16, 5, 13, 10, 0, 10, 10, 57, 83, 73, 81, 84, 56, 84, 79, 74, 79, 16, 5, 13, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
op, text, ok = parseRedisRequest(string(hmset))
op, text, ok = parseRedisRequest(hmset)

assert.True(t, ok)
assert.Equal(t, "HMSET", op)
Expand Down
4 changes: 2 additions & 2 deletions pkg/ebpf/common/tcp_detect_transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,13 @@ func ReadTCPRequestIntoSpan(parseCtx *EBPFParseContext, cfg *config.EBPFTracer,

switch {
case isRedis(requestBuffer) && isRedis(responseBuffer):
op, text, ok := parseRedisRequest(string(requestBuffer.UnsafeView()))
op, text, ok := parseRedisRequest(requestBuffer.UnsafeView())

if ok {
var status int
var redisErr request.DBError
if op == "" {
op, text, ok = parseRedisRequest(string(responseBuffer.UnsafeView()))
op, text, ok = parseRedisRequest(responseBuffer.UnsafeView())
if !ok || op == "" {
return request.Span{}, true, nil // ignore if we couldn't parse it
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/export/attributes/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
type VarHandler func(k string, v string)

func ParseOTELResourceVariable(envVar string, handler VarHandler) {
variables := split.NewIterator(envVar, ",")
variables := split.NewStringIterator(envVar, ",")

for {
variable, eof := variables.Next()
Expand Down
48 changes: 29 additions & 19 deletions pkg/internal/split/splititerator.go
Original file line number Diff line number Diff line change
@@ -1,39 +1,48 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// Package split provides an Iterator that allows for zero-copy string
// splitting.
// Package split provides a generic Iterator for zero-copy splitting of
// strings and byte slices.
package split // import "go.opentelemetry.io/obi/pkg/internal/split"

import (
"bytes"
"strings"
)

// Iterator is alternative to strings.Split(str, delim) - each call to Nex()
// returns a a substring slice, allowing string tokens or lines to be processed
// in place (zero-copy), without the need of allocations
type Iterator struct {
startBuf string
buf string
delim string
// Iterator is an alternative to strings.Split / bytes.Split — each call to
// Next returns a sub-slice of the original, allowing tokens or lines to be
// processed in place without allocations.
type Iterator[T string | []byte] struct {
Comment thread
rafaelroquetto marked this conversation as resolved.
startBuf T
buf T
delim T
indexOf func(T, T) int
}

func NewIterator(buf string, delim string) *Iterator {
return &Iterator{
startBuf: buf,
buf: buf,
delim: delim,
func NewStringIterator(buf, delim string) Iterator[string] {
if len(delim) == 0 {
panic("split: empty delimiter")
}
return Iterator[string]{startBuf: buf, buf: buf, delim: delim, indexOf: strings.Index}
}

func NewBytesIterator(buf, delim []byte) Iterator[[]byte] {
if len(delim) == 0 {
panic("split: empty delimiter")
}
return Iterator[[]byte]{startBuf: buf, buf: buf, delim: delim, indexOf: bytes.Index}
}
Comment thread
rafaelroquetto marked this conversation as resolved.

// Next returns a token and false if there are any tokens available, otherwise
// returns "" and true to convey EOF has been reached
func (sp *Iterator) Next() (string, bool) {
// returns the zero value and true to convey EOF has been reached.
func (sp *Iterator[T]) Next() (T, bool) {
if len(sp.buf) == 0 {
return "", true
var zero T
return zero, true
}

index := strings.Index(sp.buf, sp.delim)
index := sp.indexOf(sp.buf, sp.delim)

if index == -1 {
buf := sp.buf
Expand All @@ -49,6 +58,7 @@ func (sp *Iterator) Next() (string, bool) {
return buf, false
}

func (sp *Iterator) Reset() {
// Reset repositions the iterator to the beginning of the buffer.
func (sp *Iterator[T]) Reset() {
sp.buf = sp.startBuf
}
Loading
Loading