diff --git a/containers/container.go b/containers/container.go index 41738ebb..9de5a03d 100644 --- a/containers/container.go +++ b/containers/container.go @@ -902,6 +902,18 @@ func (c *Container) onL7Request(pid uint32, fd uint64, timestamp uint64, r *l7.R return ip2fqdn } +// frameDirection labels an HTTP/2 event by which side's frames it carries. +// Other protocols report "-" rather than inventing a direction for them. +func frameDirection(m l7.Method) string { + switch m { + case l7.MethodHttp2ClientFrames: + return "client" + case l7.MethodHttp2ServerFrames: + return "server" + } + return "-" +} + // protocolLabel renders a protocol for use as a metric label. Protocol.String() // falls back to "UNKNOWN:" for unrecognised values, which would be unbounded // cardinality on a label, so those collapse to a single bucket. @@ -1065,7 +1077,7 @@ func (c *Container) onL7RequestWithResult(pid uint32, fd uint64, timestamp uint6 destClass = "external" } proto := protocolLabel(r.Protocol) - L7EventsTotal.WithLabelValues(proto, destClass).Inc() + L7EventsTotal.WithLabelValues(proto, destClass, frameDirection(r.Method)).Inc() if r.PayloadSize > uint64(len(r.Payload)) { L7PayloadTruncatedTotal.WithLabelValues(proto, destClass).Inc() } diff --git a/containers/llm_metrics.go b/containers/llm_metrics.go index 52e2aab7..e1a7c5ef 100644 --- a/containers/llm_metrics.go +++ b/containers/llm_metrics.go @@ -136,12 +136,20 @@ var ( // rate(node_agent_l7_payload_truncated_total{protocol="http2",destination="external"}[5m]) // against the same labels on node_agent_l7_events_total to see what share of // external HTTP/2 traffic is arriving incomplete. + // direction is "client"/"server" for HTTP/2 (which frames the event carries) + // and "-" for protocols where the distinction does not apply. + // + // External HTTP/2 delivers ~22,000 events per stream created, against ~105 + // internally. Splitting by direction separates the two explanations for + // that: if server-frame events are scarce, responses never reach the parser; + // if they are plentiful, the bytes being fed to it are not HTTP/2 at all and + // the port-based detection heuristic is over-matching. L7EventsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "node_agent_l7_events_total", - Help: "L7 events processed, by protocol and destination class", + Help: "L7 events processed, by protocol, destination class and frame direction", }, - []string{"protocol", "destination"}, + []string{"protocol", "destination", "direction"}, ) L7PayloadTruncatedTotal = prometheus.NewCounterVec(