From fd830a501ebb09857ec232d72a3e8cf39e2b826b Mon Sep 17 00:00:00 2001 From: Etienne Perot Date: Wed, 8 May 2024 18:16:27 -0700 Subject: [PATCH] `metricsviz`: Group relevant metrics on the same chart if applicable. This is part of a series of changes to add metric charts in performance benchmarks. This change creates a list of named sets of metrics ("metric groups"). When a profiling metrics log contains at least two metrics from any given group, a single chart for that group is created, rather than putting each of these metrics on their own chart. Metrics may be part of multiple groups. If a metric is not in any given group, or if it is the only metric that was profiled out of the metrics in the group that metric is in, it is charted individually as per before this change. PiperOrigin-RevId: 631986612 --- test/metricsviz/BUILD | 1 + test/metricsviz/metricsviz.go | 84 +++++++++++++++++++--------- test/metricsviz/metricsviz_groups.go | 56 +++++++++++++++++++ 3 files changed, 115 insertions(+), 26 deletions(-) create mode 100644 test/metricsviz/metricsviz_groups.go diff --git a/test/metricsviz/BUILD b/test/metricsviz/BUILD index abf7b628d..8ab4b7849 100644 --- a/test/metricsviz/BUILD +++ b/test/metricsviz/BUILD @@ -10,6 +10,7 @@ go_library( testonly = 1, srcs = [ "metricsviz.go", + "metricsviz_groups.go", "metricsviz_publish.go", ], visibility = ["//:sandbox"], diff --git a/test/metricsviz/metricsviz.go b/test/metricsviz/metricsviz.go index bc6035065..0844b0b04 100644 --- a/test/metricsviz/metricsviz.go +++ b/test/metricsviz/metricsviz.go @@ -22,6 +22,7 @@ import ( "fmt" "hash/adler32" "regexp" + "slices" "sort" "strconv" "strings" @@ -122,8 +123,8 @@ type HTMLOptions struct { } type chart struct { - PageTitle string - Series []*TimeSeries + Title string + Series []*TimeSeries } // isCumulative returns whether the given series are all cumulative or all @@ -290,16 +291,7 @@ func (c *chart) Charter() (components.Charter, error) { charts.WithLabelOpts(opts.Label{Show: false}), ) } - metricNames := map[MetricName]struct{}{} - for _, ts := range c.Series { - metricNames[ts.Metric.Name] = struct{}{} - } - metricNameList := make([]string, 0, len(metricNames)) - for m := range metricNames { - metricNameList = append(metricNameList, string(m)) - } - sort.Strings(metricNameList) - chartTitle := fmt.Sprintf("%s: %s", c.PageTitle, strings.Join(metricNameList, ", ")) + chartTitle := c.Title if isCumulative { chartTitle += " per second" yAxis.Name = "per second" @@ -343,24 +335,64 @@ func (d *Data) ToHTML(opts HTMLOptions) (string, error) { page.PageTitle = fmt.Sprintf("Metrics for %s at %v", opts.Title, opts.When.Format(time.DateTime)) page.Theme = echartstypes.ThemeVintage page.SetLayout(components.PageFlexLayout) - titleToChart := make(map[string]*chart) - var titles []string - for maf, ts := range d.data { - title := string(maf.MetricName) - c, ok := titleToChart[title] - if !ok { - c = &chart{PageTitle: opts.Title} - titleToChart[title] = c - titles = append(titles, title) + + // Find which groups contain which metrics that we're seeing in the data. + groupsToMetricNames := make(map[GroupName][]MetricName, len(d.data)) + for maf := range d.data { + for groupName, metricsInGroup := range Groups { + if slices.Contains(metricsInGroup, maf.MetricName) && !slices.Contains(groupsToMetricNames[groupName], maf.MetricName) { + groupsToMetricNames[groupName] = append(groupsToMetricNames[groupName], maf.MetricName) + } } - c.Series = append(c.Series, ts) } - sort.Strings(titles) - for _, title := range titles { - c := titleToChart[title] + + // Find which groups for which we have data for at least 2 metrics. + // These metrics will be displayed in the group charts only. + // The rest of the metrics will be displayed in their own chart. + metricToGroups := make(map[MetricName][]GroupName, len(d.data)) + for groupName, activeGroupMetrics := range groupsToMetricNames { + if len(activeGroupMetrics) >= 2 { + for _, m := range activeGroupMetrics { + metricToGroups[m] = append(metricToGroups[m], groupName) + } + } + } + + // Now go through the data and group it by the chart it'll end up in. + chartNameToChart := make(map[string]*chart, len(d.data)) + var chartNames []string + for maf, ts := range d.data { + groupsForMetric := metricToGroups[maf.MetricName] + if len(groupsForMetric) > 0 { + // Group metric chart. + for _, groupName := range groupsForMetric { + chartName := string(groupName) + c, ok := chartNameToChart[chartName] + if !ok { + c = &chart{Title: fmt.Sprintf("%s: %s", opts.Title, groupName)} + chartNameToChart[chartName] = c + chartNames = append(chartNames, chartName) + } + c.Series = append(c.Series, ts) + } + } else { + // Individual metric chart. + chartName := string(maf.MetricName) + c, ok := chartNameToChart[chartName] + if !ok { + c = &chart{Title: fmt.Sprintf("%s: %s", opts.Title, ts.String())} + chartNameToChart[chartName] = c + chartNames = append(chartNames, chartName) + } + c.Series = append(c.Series, ts) + } + } + sort.Strings(chartNames) + for _, chartName := range chartNames { + c := chartNameToChart[chartName] charter, err := c.Charter() if err != nil { - return "", fmt.Errorf("failed to create charter for %q: %w", title, err) + return "", fmt.Errorf("failed to create charter for %q: %w", chartName, err) } page.AddCharts(charter) } diff --git a/test/metricsviz/metricsviz_groups.go b/test/metricsviz/metricsviz_groups.go new file mode 100644 index 000000000..bd9e501f3 --- /dev/null +++ b/test/metricsviz/metricsviz_groups.go @@ -0,0 +1,56 @@ +// Copyright 2024 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metricsviz + +// GroupName is the name of a group of metrics. +type GroupName string + +// Groups maps metrics which are in the same named group. +// If more than one metric in a group is in the profiled data, it will +// be shown on the same graph. +// A metric may be in multiple groups. +var Groups = map[GroupName][]MetricName{ + "Network packets": { + "/netstack/dropped_packets", + "/netstack/nic/malformed_l4_received_packets", + "/netstack/nic/tx/packets", + "/netstack/nic/tx_packets_dropped_no_buffer_space", + "/netstack/nic/rx/packets", + "/netstack/nic/disabled_rx/packets", + }, + "Network throughput": { + "/netstack/nic/tx/bytes", + "/netstack/nic/rx/bytes", + "/netstack/nic/disabled_rx/bytes", + }, + "IP packets": { + "/netstack/ip/packets_received", + "/netstack/ip/disabled_packets_received", + "/netstack/ip/invalid_addresses_received", + "/netstack/ip/invalid_source_addresses_received", + "/netstack/ip/packets_delivered", + "/netstack/ip/packets_sent", + "/netstack/ip/outgoing_packet_errors", + "/netstack/ip/malformed_packets_received", + "/netstack/ip/malformed_fragments_received", + "/netstack/ip/iptables/prerouting_dropped", + "/netstack/ip/iptables/input_dropped", + "/netstack/ip/iptables/output_dropped", + "/netstack/ip/options/timestamp_received", + "/netstack/ip/options/record_route_received", + "/netstack/ip/options/router_alert_received", + "/netstack/ip/options/unknown_received", + }, +}