diff --git a/aggregator/data.go b/aggregator/data.go index b11fd4b..51b758f 100644 --- a/aggregator/data.go +++ b/aggregator/data.go @@ -1046,6 +1046,10 @@ func (a *Aggregator) processL7(ctx context.Context, d *l7_req.L7Event) { _, path, _, reqHostHeader = parseHttpPayload(string(d.Payload[0:d.PayloadSize])) } + if d.Protocol == l7_req.L7_PROTOCOL_REDIS { + path = string(d.Payload[0:d.PayloadSize]) + } + err := a.setFromTo(skInfo, d, &reqDto, reqHostHeader) if err != nil { return @@ -1054,11 +1058,12 @@ func (a *Aggregator) processL7(ctx context.Context, d *l7_req.L7Event) { reqDto.Path = path reqDto.Completed = !d.Failed - // In AMQP-DELIVER event, we are capturing from read syscall, + // In AMQP-DELIVER or REDIS-PUSHED_EVENT event, we are capturing from read syscall, // exchange sockets // In Alaz context, From is always the one that makes the write // and To is the one that makes the read - if d.Protocol == l7_req.L7_PROTOCOL_AMQP && d.Method == l7_req.DELIVER { + if (d.Protocol == l7_req.L7_PROTOCOL_AMQP && d.Method == l7_req.DELIVER) || + (d.Protocol == l7_req.L7_PROTOCOL_REDIS && d.Method == l7_req.REDIS_PUSHED_EVENT) { reqDto.FromIP, reqDto.ToIP = reqDto.ToIP, reqDto.FromIP reqDto.FromPort, reqDto.ToPort = reqDto.ToPort, reqDto.FromPort reqDto.FromUID, reqDto.ToUID = reqDto.ToUID, reqDto.FromUID diff --git a/ebpf/c/bpf.c b/ebpf/c/bpf.c index ca81439..ef937c0 100644 --- a/ebpf/c/bpf.c +++ b/ebpf/c/bpf.c @@ -33,6 +33,7 @@ #include "http.c" #include "amqp.c" #include "postgres.c" +#include "redis.c" #include "openssl.c" #include "http2.c" #include "tcp_sock.c" diff --git a/ebpf/c/bpf_bpfeb.go b/ebpf/c/bpf_bpfeb.go index 16952e1..5644d36 100644 --- a/ebpf/c/bpf_bpfeb.go +++ b/ebpf/c/bpf_bpfeb.go @@ -157,11 +157,13 @@ type bpfProgramSpecs struct { SysEnterRecvfrom *ebpf.ProgramSpec `ebpf:"sys_enter_recvfrom"` SysEnterSendto *ebpf.ProgramSpec `ebpf:"sys_enter_sendto"` SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"` + SysEnterWritev *ebpf.ProgramSpec `ebpf:"sys_enter_writev"` SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"` SysExitRead *ebpf.ProgramSpec `ebpf:"sys_exit_read"` SysExitRecvfrom *ebpf.ProgramSpec `ebpf:"sys_exit_recvfrom"` SysExitSendto *ebpf.ProgramSpec `ebpf:"sys_exit_sendto"` SysExitWrite *ebpf.ProgramSpec `ebpf:"sys_exit_write"` + SysExitWritev *ebpf.ProgramSpec `ebpf:"sys_exit_writev"` } // bpfMapSpecs contains maps before they are loaded into the kernel. @@ -282,11 +284,13 @@ type bpfPrograms struct { SysEnterRecvfrom *ebpf.Program `ebpf:"sys_enter_recvfrom"` SysEnterSendto *ebpf.Program `ebpf:"sys_enter_sendto"` SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"` + SysEnterWritev *ebpf.Program `ebpf:"sys_enter_writev"` SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"` SysExitRead *ebpf.Program `ebpf:"sys_exit_read"` SysExitRecvfrom *ebpf.Program `ebpf:"sys_exit_recvfrom"` SysExitSendto *ebpf.Program `ebpf:"sys_exit_sendto"` SysExitWrite *ebpf.Program `ebpf:"sys_exit_write"` + SysExitWritev *ebpf.Program `ebpf:"sys_exit_writev"` } func (p *bpfPrograms) Close() error { @@ -310,11 +314,13 @@ func (p *bpfPrograms) Close() error { p.SysEnterRecvfrom, p.SysEnterSendto, p.SysEnterWrite, + p.SysEnterWritev, p.SysExitConnect, p.SysExitRead, p.SysExitRecvfrom, p.SysExitSendto, p.SysExitWrite, + p.SysExitWritev, ) } diff --git a/ebpf/c/bpf_bpfeb.o b/ebpf/c/bpf_bpfeb.o index 88a06fb..bd144ae 100644 Binary files a/ebpf/c/bpf_bpfeb.o and b/ebpf/c/bpf_bpfeb.o differ diff --git a/ebpf/c/bpf_bpfel.go b/ebpf/c/bpf_bpfel.go index 89db140..d7789f8 100644 --- a/ebpf/c/bpf_bpfel.go +++ b/ebpf/c/bpf_bpfel.go @@ -157,11 +157,13 @@ type bpfProgramSpecs struct { SysEnterRecvfrom *ebpf.ProgramSpec `ebpf:"sys_enter_recvfrom"` SysEnterSendto *ebpf.ProgramSpec `ebpf:"sys_enter_sendto"` SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"` + SysEnterWritev *ebpf.ProgramSpec `ebpf:"sys_enter_writev"` SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"` SysExitRead *ebpf.ProgramSpec `ebpf:"sys_exit_read"` SysExitRecvfrom *ebpf.ProgramSpec `ebpf:"sys_exit_recvfrom"` SysExitSendto *ebpf.ProgramSpec `ebpf:"sys_exit_sendto"` SysExitWrite *ebpf.ProgramSpec `ebpf:"sys_exit_write"` + SysExitWritev *ebpf.ProgramSpec `ebpf:"sys_exit_writev"` } // bpfMapSpecs contains maps before they are loaded into the kernel. @@ -282,11 +284,13 @@ type bpfPrograms struct { SysEnterRecvfrom *ebpf.Program `ebpf:"sys_enter_recvfrom"` SysEnterSendto *ebpf.Program `ebpf:"sys_enter_sendto"` SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"` + SysEnterWritev *ebpf.Program `ebpf:"sys_enter_writev"` SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"` SysExitRead *ebpf.Program `ebpf:"sys_exit_read"` SysExitRecvfrom *ebpf.Program `ebpf:"sys_exit_recvfrom"` SysExitSendto *ebpf.Program `ebpf:"sys_exit_sendto"` SysExitWrite *ebpf.Program `ebpf:"sys_exit_write"` + SysExitWritev *ebpf.Program `ebpf:"sys_exit_writev"` } func (p *bpfPrograms) Close() error { @@ -310,11 +314,13 @@ func (p *bpfPrograms) Close() error { p.SysEnterRecvfrom, p.SysEnterSendto, p.SysEnterWrite, + p.SysEnterWritev, p.SysExitConnect, p.SysExitRead, p.SysExitRecvfrom, p.SysExitSendto, p.SysExitWrite, + p.SysExitWritev, ) } diff --git a/ebpf/c/bpf_bpfel.o b/ebpf/c/bpf_bpfel.o index a8b1662..34f78a0 100644 Binary files a/ebpf/c/bpf_bpfel.o and b/ebpf/c/bpf_bpfel.o differ diff --git a/ebpf/c/l7.c b/ebpf/c/l7.c index d97f521..15c2128 100644 --- a/ebpf/c/l7.c +++ b/ebpf/c/l7.c @@ -4,6 +4,7 @@ #define PROTOCOL_AMQP 2 #define PROTOCOL_POSTGRES 3 #define PROTOCOL_HTTP2 4 +#define PROTOCOL_REDIS 5 #define MAX_PAYLOAD_SIZE 1024 #define PAYLOAD_PREFIX_SIZE 16 @@ -228,6 +229,12 @@ int process_enter_of_syscalls_write_sendto(void* ctx, __u64 fd, __u8 is_tls, cha bpf_map_update_elem(&active_writes, &id, &args, BPF_ANY); } req->protocol = PROTOCOL_POSTGRES; + }else if (is_redis_ping(buf, count)){ + req->protocol = PROTOCOL_REDIS; + req->method = METHOD_REDIS_PING; + }else if (!is_redis_pong(buf,count) && is_redis_command(buf,count)){ + req->protocol = PROTOCOL_REDIS; + req->method = METHOD_UNKNOWN; }else if (is_rabbitmq_publish(buf,count)){ req->protocol = PROTOCOL_AMQP; req->method = METHOD_PUBLISH; @@ -561,6 +568,37 @@ int process_exit_of_syscalls_read_recvfrom(void* ctx, __u64 id, __u32 pid, __s64 } bpf_map_delete_elem(&active_reads, &id); return 0; + }else if (is_redis_pushed_event(read_info->buf, ret)){ + // reset payload + for (int i = 0; i < MAX_PAYLOAD_SIZE; i++) { + e->payload[i] = 0; + } + e->protocol = PROTOCOL_REDIS; + e->method = METHOD_REDIS_PUSHED_EVENT; + e->duration = timestamp - read_info->read_start_ns; + e->write_time_ns = read_info->read_start_ns; // TODO: it is not write time, but start of read time + + bpf_probe_read(e->payload, MAX_PAYLOAD_SIZE, read_info->buf); + if (ret > MAX_PAYLOAD_SIZE){ + e->payload_size = MAX_PAYLOAD_SIZE; + e->payload_read_complete = 0; + }else{ + e->payload_size = ret; + e->payload_read_complete = 1; + } + e->failed = 0; // success + e->status = 0; + e->fd = k.fd; + e->pid = k.pid; + + // for distributed tracing + e->seq = 0; // default value + e->tid = bpf_get_current_pid_tgid() & 0xFFFFFFFF; + + bpf_map_delete_elem(&active_reads, &id); + + bpf_perf_event_output(ctx, &l7_events, BPF_F_CURRENT_CPU, e, sizeof(*e)); + return 0; } bpf_map_delete_elem(&active_reads, &id); @@ -591,8 +629,8 @@ int process_exit_of_syscalls_read_recvfrom(void* ctx, __u64 id, __u32 pid, __s64 e->tid = active_req->tid; e->status = 0; - if(read_info->buf && ret > PAYLOAD_PREFIX_SIZE){ - if(e->protocol==PROTOCOL_HTTP){ // if http, try to parse status code + if(read_info->buf){ + if(e->protocol==PROTOCOL_HTTP && ret > PAYLOAD_PREFIX_SIZE){ // if http, try to parse status code // read first 16 bytes of read buffer char buf_prefix[PAYLOAD_PREFIX_SIZE]; long r = bpf_probe_read(&buf_prefix, sizeof(buf_prefix), (void *)(read_info->buf)) ; @@ -626,6 +664,13 @@ int process_exit_of_syscalls_read_recvfrom(void* ctx, __u64 id, __u32 pid, __s64 }else if (active_req->request_type == POSTGRES_MESSAGE_PARSE || active_req->request_type == POSTGRES_MESSAGE_BIND){ e->method = METHOD_EXTENDED_QUERY; } + }else if (e->protocol == PROTOCOL_REDIS){ + if (e->method == METHOD_REDIS_PING){ + e->status = is_redis_pong(read_info->buf, ret); + }else{ + e->status = parse_redis_response(read_info->buf, ret); + e->method = METHOD_REDIS_COMMAND; + } } }else{ bpf_map_delete_elem(&active_reads, &id); @@ -858,6 +903,25 @@ int sys_enter_write(struct trace_event_raw_sys_enter_write* ctx) { return process_enter_of_syscalls_write_sendto(ctx, ctx->fd, 0, ctx->buf, ctx->count); } +// SEC("tracepoint/syscalls/sys_enter_writev") +// int sys_enter_writev(struct trace_event_raw_sys_enter_write* ctx) { +// return process_enter_of_syscalls_write_sendto(ctx, ctx->fd, 0, ctx->buf, ctx->count); +// } + + +struct iov { + char* buf; + __u64 size; +}; +SEC("tracepoint/syscalls/sys_enter_writev") +int sys_enter_writev(struct trace_event_raw_sys_enter_writev* ctx) { + struct iov iov0 = {}; + if (bpf_probe_read(&iov0, sizeof(struct iov), (void *)ctx->vec) < 0) { + return 0; + } + return process_enter_of_syscalls_write_sendto(ctx, ctx->fd, 0, iov0.buf, iov0.size); +} + SEC("tracepoint/syscalls/sys_enter_sendto") int sys_enter_sendto(struct trace_event_raw_sys_enter_sendto* ctx) { return process_enter_of_syscalls_write_sendto(ctx, ctx->fd, 0 ,ctx->buff, ctx->len); @@ -868,6 +932,11 @@ int sys_exit_write(struct trace_event_raw_sys_exit_write* ctx) { return process_exit_of_syscalls_write_sendto(ctx, ctx->ret); } +SEC("tracepoint/syscalls/sys_exit_writev") +int sys_exit_writev(struct trace_event_raw_sys_exit_writev* ctx) { + return process_exit_of_syscalls_write_sendto(ctx, ctx->ret); +} + SEC("tracepoint/syscalls/sys_exit_sendto") int sys_exit_sendto(struct trace_event_raw_sys_exit_sendto* ctx) { return process_exit_of_syscalls_write_sendto(ctx, ctx->ret); diff --git a/ebpf/c/redis.c b/ebpf/c/redis.c new file mode 100644 index 0000000..d63d5a2 --- /dev/null +++ b/ebpf/c/redis.c @@ -0,0 +1,181 @@ +//go:build ignore +// Redis serialization protocol (RESP) specification +// https://redis.io/docs/reference/protocol-spec/ + +// A client sends the Redis server an array consisting of only bulk strings. +// A Redis server replies to clients, sending any valid RESP data type as a reply. + + +#define STATUS_SUCCESS 1 +#define STATUS_ERROR 2 +#define STATUS_UNKNOWN 3 + +#define METHOD_REDIS_COMMAND 1 +#define METHOD_REDIS_PUSHED_EVENT 2 +#define METHOD_REDIS_PING 3 + + +static __always_inline +int is_redis_ping(char *buf, __u64 buf_size) { + // *1\r\n$4\r\nping\r\n + if (buf_size < 14) { + return 0; + } + char b[14]; + if (bpf_probe_read(&b, sizeof(b), (void *)((char *)buf)) < 0) { + return 0; + } + + if (b[0] != '*' || b[1] != '1' || b[2] != '\r' || b[3] != '\n' || b[4] != '$' || b[5] != '4' || b[6] != '\r' || b[7] != '\n') { + return 0; + } + + if (b[8] != 'p' || b[9] != 'i' || b[10] != 'n' || b[11] != 'g' || b[12] != '\r' || b[13] != '\n') { + return 0; + } + + return STATUS_SUCCESS; +} + +static __always_inline +int is_redis_pong(char *buf, __u64 buf_size) { + // *2\r\n$4\r\npong\r\n$0\r\n\r\n + if (buf_size < 14) { + return 0; + } + char b[14]; + if (bpf_probe_read(&b, sizeof(b), (void *)((char *)buf)) < 0) { + return 0; + } + + if (b[0] != '*' || b[1] < '0' || b[1] > '9' || b[2] != '\r' || b[3] != '\n' || b[4] != '$' || b[5] != '4' || b[6] != '\r' || b[7] != '\n') { + return 0; + } + + if (b[8] != 'p' || b[9] != 'o' || b[10] != 'n' || b[11] != 'g' || b[12] != '\r' || b[13] != '\n') { + return 0; + } + + return STATUS_SUCCESS; +} + +static __always_inline +int is_redis_command(char *buf, __u64 buf_size) { + //*3\r\n$7\r\nmessage\r\n$10\r\nmy_channel\r\n$13\r\nHello, World!\r\n + if (buf_size < 11) { + return 0; + } + char b[11]; + if (bpf_probe_read(&b, sizeof(b), (void *)((char *)buf)) < 0) { + return 0; + } + // Clients send commands to the Redis server as RESP arrays + // * is the array prefix + // latter is the number of elements in the array + // check if it is a RESP array + if (b[0] != '*' || b[1] < '0' || b[1] > '9') { + return 0; + } + // Check if command is not "message", message command is used for pub/sub by server to notify sub. + // CLRF(\r\n) is the seperator in RESP protocol + if (b[2] == '\r' && b[3] == '\n') { + if (b[4]=='$' && b[5] == '7' && b[6] == '\r' && b[7] == '\n' && b[8] == 'm' && b[9] == 'e' && b[10] == 's'){ + return 0; + } + return 1; + } + + // Array length can exceed 9, so check if the second byte is a digit + if (b[2] >= '0' && b[2] <= '9' && b[3] == '\r' && b[4] == '\n') { + if (b[5]=='$' && b[6] == '7' && b[7] == '\r' && b[8] == '\n' && b[9] == 'm' && b[10] == 'e'){ + return 0; + } + return 1; + } + + + return 0; +} + +static __always_inline +__u32 is_redis_pushed_event(char *buf, __u64 buf_size){ + char b[17]; + if (buf_size < 17) { + return 0; + } + if (bpf_probe_read(&b, sizeof(b), (void *)((char *)buf)) < 0) { + return 0; + } + + //*3\r\n$7\r\nmessage\r\n$10\r\nmy_channel\r\n$13\r\nHello, World!\r\n + // message received from the Redis server + + // In RESP3 protocol, the first byte of the pushed event is '>' + // whereas in RESP2 protocol, the first byte is '*' + if ((b[0] != '>' && b[0] != '*') || b[1] < '0' || b[1] > '9') { + return 0; + } + + // CLRF(\r\n) is the seperator in RESP protocol + if (b[2] == '\r' && b[3] == '\n') { + if (b[4]=='$' && b[5] == '7' && b[6] == '\r' && b[7] == '\n' && b[8] == 'm' && b[9] == 'e' && b[10] == 's' && b[11] == 's' && b[12] == 'a' && b[13] == 'g' && b[14] == 'e' && b[15] == '\r' && b[16] == '\n'){ + return 1; + }else{ + return 0; + } + } + + // TODO: long messages ? + // // Array length can exceed 9, so check if the second byte is a digit + // if (b[2] >= '0' && b[2] <= '9' && b[3] == '\r' && b[4] == '\n') { + // return 1; + // } + + return 0; +} + +static __always_inline +__u32 parse_redis_response(char *buf, __u64 buf_size) { + char type; + if (bpf_probe_read(&type, sizeof(type), (void *)((char *)buf)) < 0) { + return STATUS_UNKNOWN; + } + char end[2]; // must end with \r\n + + if (bpf_probe_read(&end, sizeof(end), (void *)((char *)buf+buf_size-2)) < 0) { + return 0; + } + + if (end[0] != '\r' || end[1] != '\n') { + return STATUS_UNKNOWN; + } + + // Accepted since RESP2 + // Array | Integer | Bulk String | Simple String + if (type == '*' || type == ':' || type == '$' || type == '+' + ) { + return STATUS_SUCCESS; + } + + // https://redis.io/docs/latest/develop/reference/protocol-spec/#simple-errors + // Accepted since RESP2 + // Error + if (type == '-') { + return STATUS_ERROR; + } + + // Accepted since RESP3 + // Null | Boolean | Double | Big Numbers | Verbatim String | Maps | Set + if (type == '_' || type == '#' || type == ',' || type =='(' || type == '=' || type == '%' || type == '~') { + return STATUS_SUCCESS; + } + + + // Accepted since RESP3 + // Bulk Errors + if (type == '!') { + return STATUS_ERROR; + } + + return STATUS_UNKNOWN; +} diff --git a/ebpf/headers/l7_req.h b/ebpf/headers/l7_req.h index 8289623..fdd21a6 100644 --- a/ebpf/headers/l7_req.h +++ b/ebpf/headers/l7_req.h @@ -56,6 +56,12 @@ struct trace_event_raw_sys_exit_sendto { __s64 ret; }; + +struct trace_event_raw_sys_exit_writev { + __u64 unused; + __s32 id; + __s64 ret; +}; struct trace_event_raw_sys_enter_write { struct trace_entry ent; __s32 __syscall_nr; @@ -64,6 +70,14 @@ struct trace_event_raw_sys_enter_write { __u64 count; }; +struct trace_event_raw_sys_enter_writev { + struct trace_entry ent; + __s32 __syscall_nr; + __u64 fd; + struct iovec * vec; // struct iovec * + __u64 vlen; +}; + // TODO: remove unused fields ? struct trace_event_raw_sys_enter_sendto { struct trace_entry ent; diff --git a/ebpf/l7_req/l7.go b/ebpf/l7_req/l7.go index 873b7d8..7e4a71c 100644 --- a/ebpf/l7_req/l7.go +++ b/ebpf/l7_req/l7.go @@ -21,6 +21,7 @@ const ( BPF_L7_PROTOCOL_AMQP BPF_L7_PROTOCOL_POSTGRES BPF_L7_PROTOCOL_HTTP2 + BPF_L7_PROTOCOL_REDIS ) // for user space @@ -29,6 +30,7 @@ const ( L7_PROTOCOL_HTTP2 = "HTTP2" L7_PROTOCOL_AMQP = "AMQP" L7_PROTOCOL_POSTGRES = "POSTGRES" + L7_PROTOCOL_REDIS = "REDIS" L7_PROTOCOL_UNKNOWN = "UNKNOWN" ) @@ -46,6 +48,8 @@ func (e L7ProtocolConversion) String() string { return L7_PROTOCOL_POSTGRES case BPF_L7_PROTOCOL_HTTP2: return L7_PROTOCOL_HTTP2 + case BPF_L7_PROTOCOL_REDIS: + return L7_PROTOCOL_REDIS case BPF_L7_PROTOCOL_UNKNOWN: return L7_PROTOCOL_UNKNOWN default: @@ -103,6 +107,14 @@ const ( //... ) +// match with values in l7.c, order is important +const ( + BPF_REDIS_METHOD_UNKNOWN = iota + METHOD_REDIS_COMMAND + METHOD_REDIS_PUSHED_EVENT + METHOD_REDIS_PING +) + // for http, user space const ( GET = "GET" @@ -135,6 +147,13 @@ const ( SERVER_FRAME = "SERVER_FRAME" ) +// for http2, user space +const ( + REDIS_COMMAND = "COMMAND" + REDIS_PUSHED_EVENT = "PUSHED_EVENT" + REDIS_PING = "PING" +) + // Custom type for the enumeration type HTTPMethodConversion uint32 @@ -211,11 +230,26 @@ func (e Http2MethodConversion) String() string { } } +// Custom type for the enumeration +type RedisMethodConversion uint32 + +// String representation of the enumeration values +func (e RedisMethodConversion) String() string { + switch e { + case METHOD_REDIS_COMMAND: + return REDIS_COMMAND + case METHOD_REDIS_PUSHED_EVENT: + return REDIS_PUSHED_EVENT + case METHOD_REDIS_PING: + return REDIS_PING + default: + return "Unknown" + } +} + // $BPF_CLANG and $BPF_CFLAGS are set by the Makefile. // // go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf l7.c -- -I../headers -const mapKey uint32 = 0 - // bpf structs, copy from generated code type bpfLogMessage struct { Level uint32 @@ -388,6 +422,18 @@ func (l7p *L7Prog) Attach() { log.Logger.Fatal().Err(err).Msg("link sys_exit_write tracepoint") } l7p.links["syscalls/sys_exit_write"] = l7 + + l8, err := link.Tracepoint("syscalls", "sys_enter_writev", c.BpfObjs.SysEnterWritev, nil) + if err != nil { + log.Logger.Fatal().Err(err).Msg("link sys_enter_writev tracepoint") + } + l7p.links["syscalls/sys_enter_writev"] = l8 + + l9, err := link.Tracepoint("syscalls", "sys_exit_writev", c.BpfObjs.SysExitWritev, nil) + if err != nil { + log.Logger.Fatal().Err(err).Msg("link sys_exit_writev tracepoint") + } + l7p.links["syscalls/sys_exit_writev"] = l9 } func (l7p *L7Prog) InitMaps() { @@ -553,6 +599,8 @@ func (l7p *L7Prog) Consume(ctx context.Context, ch chan interface{}) { method = PostgresMethodConversion(l7Event.Method).String() case L7_PROTOCOL_HTTP2: method = Http2MethodConversion(l7Event.Method).String() + case L7_PROTOCOL_REDIS: + method = RedisMethodConversion(l7Event.Method).String() default: method = "Unknown" }