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
This commit is contained in:
Etienne Perot
2024-05-08 18:19:37 -07:00
committed by gVisor bot
parent f8b4dab67a
commit fd830a501e
3 changed files with 115 additions and 26 deletions
+1
View File
@@ -10,6 +10,7 @@ go_library(
testonly = 1,
srcs = [
"metricsviz.go",
"metricsviz_groups.go",
"metricsviz_publish.go",
],
visibility = ["//:sandbox"],
+58 -26
View File
@@ -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)
}
+56
View File
@@ -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",
},
}