perfdash/config.go (669 lines of code) (raw):

/* Copyright 2016 The Kubernetes 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 main import ( "fmt" "io/ioutil" "net/http" "strconv" "strings" "github.com/ghodss/yaml" "k8s.io/klog" ) // To add new e2e test support, you need to: // 1) Transform e2e performance test result into *PerfData* in k8s/kubernetes/test/e2e/perftype, // and print the PerfData in e2e test log. // 2) Add corresponding bucket, job and test into *TestConfig*. // TestDescription contains test name, output file prefix and parser function. type TestDescription struct { Name string OutputFilePrefix string Parser func(data []byte, buildNumber int, testResult *BuildData) FetchMetricNameFromArtifact bool } // TestDescriptions is a map job->component->description. type TestDescriptions map[string]map[string][]TestDescription // Tests is a map from test label to test description. type Tests struct { Prefix string Descriptions TestDescriptions BuildsCount int ArtifactsDir string } // Jobs is a map from job name to all supported tests in the job. type Jobs map[string]Tests const ( GenericPrometheusQueryMeasurementName = "GenericPrometheusQuery" ) var ( // performanceDescriptions contains metrics exported by a --ginko.focus=[Feature:Performance] // e2e test performanceDescriptions = TestDescriptions{ "E2E": { "DensityResources": []TestDescription{{ Name: "density", OutputFilePrefix: "ResourceUsageSummary", Parser: parseResourceUsageData, }}, "DensityPodStartup": []TestDescription{{ Name: "density", OutputFilePrefix: "PodStartupLatency_PodStartupLatency", Parser: parsePerfData, }}, "DensitySaturationPodStartup": []TestDescription{{ Name: "density", OutputFilePrefix: "PodStartupLatency_SaturationPodStartupLatency", Parser: parsePerfData, }}, "LoadResources": []TestDescription{{ Name: "load", OutputFilePrefix: "ResourceUsageSummary", Parser: parseResourceUsageData, }}, "LoadCreatePhasePodStartup": []TestDescription{{ Name: "load", OutputFilePrefix: "PodStartupLatency_CreatePhasePodStartupLatency", Parser: parsePerfData, }}, "LoadHighThroughputPodStartup": []TestDescription{{ Name: "load", OutputFilePrefix: "PodStartupLatency_HighThroughputPodStartupLatency", Parser: parsePerfData, }}, "LoadPodStartup": []TestDescription{{ Name: "load", OutputFilePrefix: "PodStartupLatency_PodStartupLatency", Parser: parsePerfData, }}, "LoadCreatePhaseStatelessPodStartup": []TestDescription{{ Name: "load", OutputFilePrefix: "StatelessPodStartupLatency_CreatePhasePodStartupLatency", Parser: parsePerfData, }}, "LoadHighThroughputStatelessPodStartup": []TestDescription{{ Name: "load", OutputFilePrefix: "StatelessPodStartupLatency_HighThroughputPodStartupLatency", Parser: parsePerfData, }}, "LoadStatelessPodStartup": []TestDescription{{ Name: "load", OutputFilePrefix: "StatelessPodStartupLatency_PodStartupLatency", Parser: parsePerfData, }}, "LoadCreatePhaseStatefulPodStartup": []TestDescription{{ Name: "load", OutputFilePrefix: "StatefulPodStartupLatency_CreatePhasePodStartupLatency", Parser: parsePerfData, }}, "LoadHighThroughputStatefulPodStartup": []TestDescription{{ Name: "load", OutputFilePrefix: "StatefulPodStartupLatency_HighThroughputPodStartupLatency", Parser: parsePerfData, }}, "LoadStatefulPodStartup": []TestDescription{{ Name: "load", OutputFilePrefix: "StatefulPodStartupLatency_PodStartupLatency", Parser: parsePerfData, }}, "Resources": []TestDescription{{ OutputFilePrefix: "ResourceUsageSummary", Parser: parseResourceUsageData, }}, }, "APIServer": { "DensityResponsiveness": []TestDescription{{ Name: "density", OutputFilePrefix: "APIResponsiveness", Parser: parsePerfData, }}, "DensityRequestCount": []TestDescription{{ Name: "density", OutputFilePrefix: "APIResponsiveness", Parser: parseRequestCountData, }}, "DensityResponsiveness_Prometheus": []TestDescription{{ Name: "density", OutputFilePrefix: "APIResponsivenessPrometheus", Parser: parsePerfData, }}, "DensityRequestCount_Prometheus": []TestDescription{{ Name: "density", OutputFilePrefix: "APIResponsivenessPrometheus", Parser: parseRequestCountData, }}, "DensityResponsiveness_PrometheusSimple": []TestDescription{{ Name: "density", OutputFilePrefix: "APIResponsivenessPrometheus_simple", Parser: parsePerfData, }}, "DensityRequestCount_PrometheusSimple": []TestDescription{{ Name: "density", OutputFilePrefix: "APIResponsivenessPrometheus_simple", Parser: parseRequestCountData, }}, "DensityRequestCountByClient": []TestDescription{{ Name: "density", OutputFilePrefix: "MetricsForE2E", Parser: parseApiserverRequestCount, }}, "DensityInitEventsCount": []TestDescription{{ Name: "density", OutputFilePrefix: "MetricsForE2E", Parser: parseApiserverInitEventsCount, }}, "LoadResponsiveness": []TestDescription{{ Name: "load", OutputFilePrefix: "APIResponsiveness", Parser: parsePerfData, }}, "LoadRequestCount": []TestDescription{{ Name: "load", OutputFilePrefix: "APIResponsiveness", Parser: parseRequestCountData, }}, "LoadResponsiveness_Prometheus": []TestDescription{{ Name: "load", OutputFilePrefix: "APIResponsivenessPrometheus", Parser: parsePerfData, }}, "LoadRequestCount_Prometheus": []TestDescription{{ Name: "load", OutputFilePrefix: "APIResponsivenessPrometheus", Parser: parseRequestCountData, }}, "LoadResponsiveness_PrometheusSimple": []TestDescription{{ Name: "load", OutputFilePrefix: "APIResponsivenessPrometheus_simple", Parser: parsePerfData, }}, "LoadRequestCount_PrometheusSimple": []TestDescription{{ Name: "load", OutputFilePrefix: "APIResponsivenessPrometheus_simple", Parser: parseRequestCountData, }}, "LoadRequestCountByClient": []TestDescription{{ Name: "load", OutputFilePrefix: "MetricsForE2E", Parser: parseApiserverRequestCount, }}, "LoadInitEventsCount": []TestDescription{{ Name: "load", OutputFilePrefix: "MetricsForE2E", Parser: parseApiserverInitEventsCount, }}, "Responsiveness": []TestDescription{{ OutputFilePrefix: "APIResponsiveness", Parser: parsePerfData, }}, "RequestCount": []TestDescription{{ OutputFilePrefix: "APIResponsiveness", Parser: parseRequestCountData, }}, "Responsiveness_Prometheus": []TestDescription{{ OutputFilePrefix: "APIResponsivenessPrometheus", Parser: parsePerfData, }}, "RequestCount_Prometheus": []TestDescription{{ OutputFilePrefix: "APIResponsivenessPrometheus", Parser: parseRequestCountData, }}, "Responsiveness_PrometheusSimple": []TestDescription{{ OutputFilePrefix: "APIResponsivenessPrometheus_simple", Parser: parsePerfData, }}, "RequestCount_PrometheusSimple": []TestDescription{{ OutputFilePrefix: "APIResponsivenessPrometheus_simple", Parser: parseRequestCountData, }}, "RequestCountByClient": []TestDescription{{ OutputFilePrefix: "MetricsForE2E", Parser: parseApiserverRequestCount, }}, "InitEventsCount": []TestDescription{{ OutputFilePrefix: "MetricsForE2E", Parser: parseApiserverInitEventsCount, }}, }, "Scheduler": { "SchedulingLatency": []TestDescription{ // `density_*` items need to be before the `density` item because of // how data file prefixes work. Same applies to SchedulingThroughput. { Name: "density_pod-affinity", OutputFilePrefix: "SchedulingMetrics", Parser: parseSchedulingLatency("pod-affinity"), }, { Name: "density_pod-anti-affinity", OutputFilePrefix: "SchedulingMetrics", Parser: parseSchedulingLatency("pod-anti-affinity"), }, { Name: "density_pod-topology-spread", OutputFilePrefix: "SchedulingMetrics", Parser: parseSchedulingLatency("pod-topology-spread"), }, { Name: "density_vanilla", OutputFilePrefix: "SchedulingMetrics", Parser: parseSchedulingLatency("vanilla"), }, { Name: "density", OutputFilePrefix: "SchedulingMetrics", Parser: parseSchedulingLatency("density"), }, { Name: "load", OutputFilePrefix: "SchedulingMetrics", Parser: parseSchedulingLatency("load"), }, }, "SchedulingThroughput": []TestDescription{ { Name: "density_pod-affinity", OutputFilePrefix: "SchedulingThroughput", Parser: parseSchedulingThroughputCL("pod-affinity"), }, { Name: "density_pod-anti-affinity", OutputFilePrefix: "SchedulingThroughput", Parser: parseSchedulingThroughputCL("pod-anti-affinity"), }, { Name: "density_pod-topology-spread", OutputFilePrefix: "SchedulingThroughput", Parser: parseSchedulingThroughputCL("pod-topology-spread"), }, { Name: "density_vanilla", OutputFilePrefix: "SchedulingMetrics", Parser: parseSchedulingLatency("vanilla"), }, { Name: "density", OutputFilePrefix: "SchedulingThroughput", Parser: parseSchedulingThroughputCL("density"), }, { Name: "load", OutputFilePrefix: "SchedulingThroughput", Parser: parseSchedulingThroughputCL("load"), }, { Name: "load", OutputFilePrefix: "SchedulingThroughputPrometheus", Parser: parseSchedulingThroughputCL("load-prometheus"), }, }, "LoadSchedulingLatency": []TestDescription{ { Name: "load", OutputFilePrefix: "SchedulingMetrics", Parser: parseSchedulingLatency("load"), }, }, "LoadSchedulingThroughput": []TestDescription{ { Name: "load", OutputFilePrefix: "SchedulingThroughput", Parser: parseSchedulingThroughputCL("load"), }, }, }, "Etcd": { "DensityBackendCommitDuration": []TestDescription{{ Name: "density", OutputFilePrefix: "EtcdMetrics", Parser: parseHistogramMetric("backendCommitDuration"), }}, "DensitySnapshotSaveTotalDuration": []TestDescription{{ Name: "density", OutputFilePrefix: "EtcdMetrics", Parser: parseHistogramMetric("snapshotSaveTotalDuration"), }}, "DensityPeerRoundTripTime": []TestDescription{{ Name: "density", OutputFilePrefix: "EtcdMetrics", Parser: parseHistogramMetric("peerRoundTripTime"), }}, "DensityWalFsyncDuration": []TestDescription{{ Name: "density", OutputFilePrefix: "EtcdMetrics", Parser: parseHistogramMetric("walFsyncDuration"), }}, "LoadBackendCommitDuration": []TestDescription{{ Name: "load", OutputFilePrefix: "EtcdMetrics", Parser: parseHistogramMetric("backendCommitDuration"), }}, "LoadSnapshotSaveTotalDuration": []TestDescription{{ Name: "load", OutputFilePrefix: "EtcdMetrics", Parser: parseHistogramMetric("snapshotSaveTotalDuration"), }}, "LoadPeerRoundTripTime": []TestDescription{{ Name: "load", OutputFilePrefix: "EtcdMetrics", Parser: parseHistogramMetric("peerRoundTripTime"), }}, "LoadWalFsyncDuration": []TestDescription{{ Name: "load", OutputFilePrefix: "EtcdMetrics", Parser: parseHistogramMetric("walFsyncDuration"), }}, }, "Network": { "Load_NetworkProgrammingLatency": []TestDescription{{ Name: "load", OutputFilePrefix: "NetworkProgrammingLatency", Parser: parsePerfData, }}, "Load_NetworkLatency": []TestDescription{ { // TODO(oxddr): remove this around Sep '19 when we stop showing old data Name: "load", OutputFilePrefix: "in_cluster_network_latency", Parser: parsePerfData, }, { Name: "load", OutputFilePrefix: "InClusterNetworkLatency", Parser: parsePerfData, }}, "Density_NetworkLatency": []TestDescription{ { // TODO(oxddr): remove this around Sep '19 when we stop showing old data Name: "density", OutputFilePrefix: "in_cluster_network_latency", Parser: parsePerfData, }, { Name: "density", OutputFilePrefix: "InClusterNetworkLatency", Parser: parsePerfData, }}, }, "Network_Baseline": { "TCP_P2P": []TestDescription{{ Name: "network_performance_tcp-1:1", OutputFilePrefix: "NetworkPerformanceMetrics_1:1_TCP_P2P", Parser: parsePerfData, }}, "UDP_P2P": []TestDescription{{ Name: "network_performance_udp-1:1", OutputFilePrefix: "NetworkPerformanceMetrics_1:1_UDP_P2P", Parser: parsePerfData, }}, "HTTP_P2P": []TestDescription{{ Name: "network_performance_http-1:1", OutputFilePrefix: "NetworkPerformanceMetrics_1:1_HTTP_P2P", Parser: parsePerfData, }}, }, "Network_Scale": { "TCP_P2P_N:M": []TestDescription{{ Name: "network_performance_tcp-50:50", OutputFilePrefix: "NetworkPerformanceMetrics_N:M_TCP_P2P", Parser: parsePerfData, }}, "UDP_P2P_N:M": []TestDescription{{ Name: "network_performance_udp-50:50", OutputFilePrefix: "NetworkPerformanceMetrics_N:M_UDP_P2P", Parser: parsePerfData, }}, "HTTP_P2P_N:M": []TestDescription{{ Name: "network_performance_http-50:50", OutputFilePrefix: "NetworkPerformanceMetrics_N:M_HTTP_P2P", Parser: parsePerfData, }}, }, "DNS": { "Load_DNSLookupLatency": []TestDescription{{ Name: "load", OutputFilePrefix: "DnsLookupLatency", Parser: parsePerfData, }}, "Density_DNSLookupLatency": []TestDescription{{ Name: "density", OutputFilePrefix: "DnsLookupLatency", Parser: parsePerfData, }}, "DNSLookupLatency": []TestDescription{{ OutputFilePrefix: "DnsLookupLatency", Parser: parsePerfData, }}, }, "SystemPodMetrics": { "Load_SystemPodMetrics": []TestDescription{{ Name: "load", OutputFilePrefix: "SystemPodMetrics", Parser: parseSystemPodMetrics, }}, "Density_SystemPodMetrics": []TestDescription{{ Name: "density", OutputFilePrefix: "SystemPodMetrics", Parser: parseSystemPodMetrics, }}, "SystemPodMetrics": []TestDescription{{ OutputFilePrefix: "SystemPodMetrics", Parser: parseSystemPodMetrics, }}, "ContainerRestarts": []TestDescription{{ OutputFilePrefix: "ContainerRestarts", Parser: parseContainerRestarts, }}, }, "GenericMeasurements": { "GenericMeasurements": []TestDescription{{ OutputFilePrefix: GenericPrometheusQueryMeasurementName, Parser: parsePerfData, FetchMetricNameFromArtifact: true, }}, }, } // benchmarkDescriptions contains metrics exported by test/integration/scheduler_perf benchmarkDescriptions = TestDescriptions{ "Scheduler": { "BenchmarkResults": []TestDescription{{ Name: "benchmark", OutputFilePrefix: "BenchmarkResults", Parser: parsePerfData, }}, "BenchmarkPerfResults": []TestDescription{{ // Expected file prefix string is constructed as // OutputFilePrefix+"_"+Name. Given we currently generate // file prefixed with "BenchmarkPerfScheduling_" followed by a date, // set the Name to an empty string. Name: "", OutputFilePrefix: "BenchmarkPerfScheduling", Parser: parsePerfData, }}, }, } dnsBenchmarkDescriptions = TestDescriptions{ "dns": { "Latency": []TestDescription{{ Name: "dns", OutputFilePrefix: "Latency", Parser: parsePerfData, }}, "LatencyPerc": []TestDescription{{ Name: "dns", OutputFilePrefix: "LatencyPerc", Parser: parsePerfData, }}, "Queries": []TestDescription{{ Name: "dns", OutputFilePrefix: "Queries", Parser: parsePerfData, }}, "Qps": []TestDescription{{ Name: "dns", OutputFilePrefix: "Qps", Parser: parsePerfData, }}, }, } storageDescriptions = TestDescriptions{ "E2E": { "PodStartup": []TestDescription{ { Name: "storage", OutputFilePrefix: "PodStartupLatency_PodWithVolumesStartupLatency", Parser: parsePerfData, }, }, }, } throughputDescriptions = TestDescriptions{ "E2E": { "PodStartup": []TestDescription{ { Name: "node-throughput", OutputFilePrefix: "PodStartupLatency_PodStartupLatency", Parser: parsePerfData, }, }, }, } windowsDescriptions = TestDescriptions{ "E2E": { "CPUUsage": []TestDescription{ { Name: "node-throughput", OutputFilePrefix: "WindowsCPUUsagePrometheus", Parser: parsePerfData, }, }, "MemoryUsage": []TestDescription{ { Name: "node-throughput", OutputFilePrefix: "WindowsMemoryUsagePrometheus", Parser: parsePerfData, }, }, "ContainersCount": []TestDescription{ { Name: "node-throughput", OutputFilePrefix: "WindowsContainers", Parser: parsePerfData, }, }, "Network": []TestDescription{ { Name: "node-throughput", OutputFilePrefix: "WindowsNetwork", Parser: parsePerfData, }, }, "NodeStorage": []TestDescription{ { Name: "node-throughput", OutputFilePrefix: "WindowsNodeStorage", Parser: parsePerfData, }, }, "OpenFiles": []TestDescription{ { Name: "node-throughput", OutputFilePrefix: "WindowsOpenFiles", Parser: parsePerfData, }, }, "Processes": []TestDescription{ { Name: "node-throughput", OutputFilePrefix: "WindowsProcesses", Parser: parsePerfData, }, }, }, } jobTypeToDescriptions = map[string]TestDescriptions{ "performance": performanceDescriptions, "benchmark": benchmarkDescriptions, "dnsBenchmark": dnsBenchmarkDescriptions, "storage": storageDescriptions, "throughput": throughputDescriptions, "windows": windowsDescriptions, } ) // Minimal subset of the prow config definition at k8s.io/test-infra/prow/config type config struct { Periodics []periodic `json:"periodics"` } type periodic struct { Name string `json:"name"` Tags []string `json:"tags"` } func urlConfigRead(url string) ([]byte, error) { resp, err := http.Get(url) if err != nil { return nil, fmt.Errorf("error fetching prow config from %s: %v", url, err) } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("error reading prow config from %s: %v", url, err) } return b, nil } func fileConfigRead(path string) ([]byte, error) { return ioutil.ReadFile(path) } func getProwConfig(configPaths []string) (Jobs, error) { jobs := Jobs{} for _, configPath := range configPaths { klog.Infof("Fetching config %s", configPath) var content []byte var err error switch { case strings.HasPrefix(configPath, "http://"), strings.HasPrefix(configPath, "https://"): content, err = urlConfigRead(configPath) default: content, err = fileConfigRead(configPath) } if err != nil { return nil, err } conf := &config{} if err := yaml.Unmarshal(content, conf); err != nil { return nil, fmt.Errorf("error unmarshaling prow config from %s: %v", configPath, err) } for _, periodic := range conf.Periodics { config, err := parsePeriodicConfig(periodic) if err != nil { klog.Errorf("warning: failed to parse config of %q due to: %v", periodic.Name, err) continue } shouldUse, err := checkIfConfigShouldBeUsed(config) if err != nil { klog.Errorf("warning: failed to validate config of %q due to: %v", periodic.Name, err) continue } if shouldUse { jobs[periodic.Name] = config } } } klog.Infof("Read configs with %d jobs", len(jobs)) return jobs, nil } func parsePeriodicConfig(periodic periodic) (Tests, error) { var thisPeriodicConfig Tests thisPeriodicConfig.ArtifactsDir = "artifacts" for _, tag := range periodic.Tags { if strings.HasPrefix(tag, "perfDashPrefix:") { split := strings.SplitN(tag, ":", 2) thisPeriodicConfig.Prefix = strings.TrimSpace(split[1]) continue } if strings.HasPrefix(tag, "perfDashJobType:") { split := strings.SplitN(tag, ":", 2) jobType := strings.TrimSpace(split[1]) var exists bool if thisPeriodicConfig.Descriptions, exists = jobTypeToDescriptions[jobType]; !exists { return Tests{}, fmt.Errorf("unknown job type - %s", jobType) } continue } if strings.HasPrefix(tag, "perfDashBuildsCount:") { split := strings.SplitN(tag, ":", 2) i, err := strconv.Atoi(strings.TrimSpace(split[1])) if err != nil { return Tests{}, fmt.Errorf("unparsable builds count - %v", split[1]) } if i < 1 { return Tests{}, fmt.Errorf("non-positive builds count - %v", i) } thisPeriodicConfig.BuildsCount = i continue } if strings.HasPrefix(tag, "perfDashArtifactsDir:") { split := strings.SplitN(tag, ":", 2) thisPeriodicConfig.ArtifactsDir = strings.TrimSpace(split[1]) continue } if strings.HasPrefix(tag, "perfDash") { return Tests{}, fmt.Errorf("unknown perfdash tag name: %q", tag) } } return thisPeriodicConfig, nil } func checkIfConfigShouldBeUsed(config Tests) (bool, error) { if config.Prefix == "" && config.Descriptions == nil { // This is expected case for jobs which are not expected to be visible in perfdash. return false, nil } if config.Prefix == "" || config.Descriptions == nil { return false, fmt.Errorf("none or both of prefix and job type must be specified") } return true, nil }