Skip to content

Commit 59e8753

Browse files
committed
Formalize mechanism for recording/managing benchmark results.
This patch defines new types and mechanisms for managing benchmark results using a channel-based appriach, as the previous gmeasure.Stopwatch-based approach did not provide a mechanism for associating operations which are part of a larger lifecycle being benchmarked. (e.g. container CRUD operations) Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com>
1 parent b0894a3 commit 59e8753

File tree

6 files changed

+333
-82
lines changed

6 files changed

+333
-82
lines changed

cmd/critest/cri_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ func TestCRISuite(t *testing.T) {
174174
flag.Set("ginkgo.focus", "benchmark")
175175
flag.Set("ginkgo.succinct", "true")
176176
} else {
177+
// Skip benchmark measurements for validation tests.
177178
flag.Set("ginkgo.skipMeasurements", "true")
178179
}
179180
if *parallel > 1 {

docs/benchmark.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,32 @@ git clone https://github.com/kubernetes-sigs/cri-tools -b release-1.9 $GOPATH/sr
2626

2727
Before running the test, you need to _ensure that the CRI server under test is running and listening on a Unix socket_ or a Windows tcp socket. Because the benchmark tests are designed to request changes (e.g., create/delete) to the containers and verify that correct status is reported, it expects to be the only user of the CRI server. Please make sure that 1) there are no existing CRI-managed containers running on the node, and 2) no other processes (e.g., Kubelet) will interfere with the tests.
2828

29+
### Defining benchmarking parameters
30+
31+
You can optionally specify some parameters detailing how benchmarks should be run.
32+
33+
```yaml
34+
# The number of container lifecycle benchmarks to run:
35+
containersNumber: 100
36+
37+
# The number of container lifecycle benchmarks to run in parallel.
38+
# The total number of samples will be floor(containersNumber / containersNumberParallel)
39+
containersNumberParallel: 2
40+
41+
42+
# The number of pod lifecycle benchmarks to run:
43+
podsNumber: 1000
44+
# The number of pod lifecycle benchmarks to run in parallel.
45+
# The total number of samples will be floor(podsNumber/ podsNumberParallel)
46+
podsNumberParallel: 1
47+
```
48+
2949
### Run
3050
3151
```sh
3252
critest -benchmark
53+
[--benchmarking-params-file /path/to/params.yml]
54+
[--benchmarking-output-dir /path/to/outdir/]
3355
```
3456

3557
This will
@@ -45,5 +67,9 @@ critest connects to Unix: `unix:///var/run/dockershim.sock` or Windows: `tcp://l
4567
- `-ginkgo.focus`: Only run the tests that match the regular expression.
4668
- `-image-endpoint`: Set the endpoint of image service. Same with runtime-endpoint if not specified.
4769
- `-runtime-endpoint`: Set the endpoint of runtime service. Default to Unix: `unix:///var/run/dockershim.sock` or Windows: `tcp://localhost:3735`.
70+
- `-benchmarking-params-file`: optional path to a YAML file containing parameters describing which
71+
benchmarks should be run.
72+
- `-benchmarking-output-dir`: optional path to a pre-existing directory in which to write JSON
73+
files detailing the results of the benchmarks.
4874
- `-ginkgo.skip`: Skip the tests that match the regular expression.
4975
- `-h`: Should help and all supported options.

pkg/benchmark/benchmark.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,9 @@ import (
3333
. "github.com/onsi/gomega"
3434
)
3535

36-
// Transforms a slice of `time.Duration`s into their `int64` nanosecond representations.
37-
func getNanosecondsForDurations(durations []time.Duration) []int64 {
38-
var ns []int64
39-
for _, duration := range durations {
40-
ns = append(ns, duration.Nanoseconds())
41-
}
42-
return ns
43-
}
36+
const (
37+
defaultOperationTimes int = 20
38+
)
4439

4540
// TestPerformance checks configuration parameters (specified through flags) and then runs
4641
// benchmark tests using the Ginkgo runner.

pkg/benchmark/container.go

Lines changed: 66 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2021 The Kubernetes Authors.
2+
Copyright 2022 The Kubernetes Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -17,22 +17,17 @@ limitations under the License.
1717
package benchmark
1818

1919
import (
20-
"encoding/json"
21-
"io/ioutil"
2220
"path"
21+
"time"
2322

2423
"github.com/golang/glog"
2524
"github.com/kubernetes-sigs/cri-tools/pkg/framework"
2625
. "github.com/onsi/ginkgo"
2726
"github.com/onsi/gomega/gmeasure"
2827
internalapi "k8s.io/cri-api/pkg/apis"
29-
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
28+
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
3029
)
3130

32-
type ContainerExperimentData struct {
33-
CreateContainer, StatusContainer, StopContainer, RemoveContainer, StartContainer []int64
34-
}
35-
3631
var _ = framework.KubeDescribe("Container", func() {
3732
f := framework.NewDefaultCRIFramework()
3833

@@ -46,75 +41,109 @@ var _ = framework.KubeDescribe("Container", func() {
4641

4742
Context("benchmark about operations on Container", func() {
4843
It("benchmark about basic operations on Container", func() {
44+
// Setup sampling config from TestContext:
45+
samplingConfig := gmeasure.SamplingConfig{
46+
N: framework.TestContext.BenchmarkingParams.ContainersNumber,
47+
NumParallel: framework.TestContext.BenchmarkingParams.ContainersNumberParallel,
48+
}
49+
if samplingConfig.N < 1 {
50+
samplingConfig.N = 1
51+
}
52+
if samplingConfig.NumParallel < 1 {
53+
samplingConfig.NumParallel = 1
54+
}
55+
56+
// Setup results reporting channel:
57+
resultsSet := LifecycleBenchmarksResultsSet{
58+
OperationsNames: []string{"CreateContainer", "StartContainer", "StatusContainer", "StopContainer", "RemoveContainer"},
59+
NumParallel: samplingConfig.NumParallel,
60+
Datapoints: make([]LifecycleBenchmarkDatapoint, 0),
61+
}
62+
resultsManager := NewLifecycleBenchmarksResultsManager(
63+
resultsSet,
64+
60,
65+
)
66+
resultsChannel := resultsManager.StartResultsConsumer()
67+
4968
experiment := gmeasure.NewExperiment("ContainerOps")
5069
experiment.Sample(func(idx int) {
5170
var podID string
5271
var podConfig *runtimeapi.PodSandboxConfig
5372
var containerID string
73+
var lastStartTime, lastEndTime int64
5474
var err error
75+
durations := make([]int64, len(resultsSet.OperationsNames))
5576

5677
podID, podConfig = framework.CreatePodSandboxForContainer(rc)
5778

5879
By("CreatingContainer")
59-
stopwatch := experiment.NewStopwatch()
60-
stopwatch.Reset()
80+
startTime := time.Now().UnixNano()
81+
lastStartTime = startTime
6182
containerID = framework.CreateDefaultContainer(rc, ic, podID, podConfig, "Benchmark-container-")
62-
stopwatch.Record("CreateContainer")
83+
lastEndTime = time.Now().UnixNano()
84+
durations[0] = lastEndTime - lastStartTime
6385

6486
By("StartingContainer")
65-
stopwatch.Reset()
87+
lastStartTime = time.Now().UnixNano()
6688
err = rc.StartContainer(containerID)
67-
stopwatch.Record("StartContainer")
89+
lastEndTime = time.Now().UnixNano()
90+
durations[1] = lastEndTime - lastStartTime
6891
framework.ExpectNoError(err, "failed to start Container: %v", err)
6992

7093
By("ContainerStatus")
71-
stopwatch.Reset()
94+
lastStartTime = time.Now().UnixNano()
7295
_, err = rc.ContainerStatus(containerID)
73-
stopwatch.Record("StatusContainer")
96+
lastEndTime = time.Now().UnixNano()
97+
durations[2] = lastEndTime - lastStartTime
7498
framework.ExpectNoError(err, "failed to get Container status: %v", err)
7599

76100
By("ContainerStop")
77-
stopwatch.Reset()
101+
lastStartTime = time.Now().UnixNano()
78102
err = rc.StopContainer(containerID, framework.DefaultStopContainerTimeout)
79-
stopwatch.Record("StopContainer")
103+
lastEndTime = time.Now().UnixNano()
104+
durations[3] = lastEndTime - lastStartTime
80105
framework.ExpectNoError(err, "failed to stop Container: %v", err)
81106

82107
By("ContainerRemove")
83-
stopwatch.Reset()
108+
lastStartTime = time.Now().UnixNano()
84109
err = rc.RemoveContainer(containerID)
85-
stopwatch.Record("RemoveContainer")
110+
lastEndTime = time.Now().UnixNano()
111+
durations[4] = lastEndTime - lastStartTime
86112
framework.ExpectNoError(err, "failed to remove Container: %v", err)
87113

114+
res := LifecycleBenchmarkDatapoint{
115+
SampleIndex: idx,
116+
StartTime: startTime,
117+
EndTime: lastEndTime,
118+
OperationsDurationsNs: durations,
119+
MetaInfo: map[string]string{"podId": podID, "containerId": containerID},
120+
}
121+
resultsChannel <- &res
122+
88123
By("stop PodSandbox")
89124
rc.StopPodSandbox(podID)
90125
By("delete PodSandbox")
91126
rc.RemovePodSandbox(podID)
92127

93-
}, gmeasure.SamplingConfig{N: framework.TestContext.BenchmarkingParams.ContainersNumber, NumParallel: framework.TestContext.BenchmarkingParams.ContainersNumberParallel})
128+
}, samplingConfig)
94129

95-
data := ContainerExperimentData{
96-
CreateContainer: getNanosecondsForDurations(experiment.Get("CreateContainer").Durations),
97-
StartContainer: getNanosecondsForDurations(experiment.Get("StartContainer").Durations),
98-
StatusContainer: getNanosecondsForDurations(experiment.Get("StatusContainer").Durations),
99-
StopContainer: getNanosecondsForDurations(experiment.Get("StopContainer").Durations),
100-
RemoveContainer: getNanosecondsForDurations(experiment.Get("RemoveContainer").Durations),
130+
// Send nil and give the manager a minute to process any already-queued results:
131+
resultsChannel <- nil
132+
err := resultsManager.AwaitAllResults(60)
133+
if err != nil {
134+
glog.Errorf("Results manager failed to await all results: %s", err)
101135
}
102136

103137
if framework.TestContext.BenchmarkingOutputDir != "" {
104-
filepath := path.Join(framework.TestContext.BenchmarkingOutputDir, "container_benchmark_data.json")
105-
data, err := json.MarshalIndent(data, "", " ")
106-
if err == nil {
107-
err = ioutil.WriteFile(filepath, data, 0644)
108-
if err != nil {
109-
glog.Errorf("Failed to write container benchmark data: %v", filepath)
110-
}
111-
} else {
112-
glog.Errorf("Failed to serialize benchmark data: %v", err)
138+
filepath := path.Join(framework.TestContext.BenchmarkingOutputDir, "newf_container_benchmark_data.json")
139+
err = resultsManager.WriteResultsFile(filepath)
140+
if err != nil {
141+
glog.Errorf("Error occurred while writing benchmark results to file %s: %s", filepath, err)
113142
}
114143
} else {
115-
glog.Infof("No benchmarking output dir provided, skipping writing benchmarking resulsts.")
144+
glog.Infof("No benchmarking output dir provided, skipping writing benchmarking results file.")
145+
glog.Infof("Benchmark results were: %+v", resultsManager.resultsSet)
116146
}
117147
})
118-
119148
})
120149
})

pkg/benchmark/pod.go

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2021 The Kubernetes Authors.
2+
Copyright 2022 The Kubernetes Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package benchmark
1818

1919
import (
2020
"path"
21+
"time"
2122

2223
"github.com/golang/glog"
2324
"github.com/kubernetes-sigs/cri-tools/pkg/framework"
@@ -26,19 +27,8 @@ import (
2627
"github.com/onsi/gomega/gmeasure"
2728
internalapi "k8s.io/cri-api/pkg/apis"
2829
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
29-
30-
"encoding/json"
31-
"io/ioutil"
32-
)
33-
34-
const (
35-
defaultOperationTimes int = 20
3630
)
3731

38-
type ExperimentData struct {
39-
CreatePod, StatusPod, StopPod, RemovePod []int64
40-
}
41-
4232
var _ = framework.KubeDescribe("PodSandbox", func() {
4333
f := framework.NewDefaultCRIFramework()
4434

@@ -50,11 +40,36 @@ var _ = framework.KubeDescribe("PodSandbox", func() {
5040

5141
Context("benchmark about operations on PodSandbox", func() {
5242
It("benchmark about lifecycle of PodSandbox", func() {
43+
// Setup sampling config from TestContext:
44+
samplingConfig := gmeasure.SamplingConfig{
45+
N: framework.TestContext.BenchmarkingParams.PodsNumber,
46+
NumParallel: framework.TestContext.BenchmarkingParams.PodsNumberParallel,
47+
}
48+
if samplingConfig.N < 1 {
49+
samplingConfig.N = 1
50+
}
51+
if samplingConfig.NumParallel < 1 {
52+
samplingConfig.NumParallel = 1
53+
}
54+
55+
// Setup results reporting channel:
56+
resultsSet := LifecycleBenchmarksResultsSet{
57+
OperationsNames: []string{"CreatePod", "StatusPod", "StopPod", "RemovePod"},
58+
NumParallel: samplingConfig.NumParallel,
59+
Datapoints: make([]LifecycleBenchmarkDatapoint, 0),
60+
}
61+
resultsManager := NewLifecycleBenchmarksResultsManager(
62+
resultsSet,
63+
60,
64+
)
65+
resultsChannel := resultsManager.StartResultsConsumer()
5366

5467
experiment := gmeasure.NewExperiment("PodLifecycle")
5568
experiment.Sample(func(idx int) {
69+
var lastStartTime, lastEndTime int64
5670
var podID string
5771
var err error
72+
durations := make([]int64, len(resultsSet.OperationsNames))
5873

5974
podSandboxName := "PodSandbox-for-creating-performance-test-" + framework.NewUUID()
6075
uid := framework.DefaultUIDPrefix + framework.NewUUID()
@@ -67,54 +82,62 @@ var _ = framework.KubeDescribe("PodSandbox", func() {
6782
}
6883

6984
By("Creating a pod")
70-
stopwatch := experiment.NewStopwatch()
7185

86+
startTime := time.Now().UnixNano()
87+
lastStartTime = startTime
7288
podID, err = c.RunPodSandbox(config, framework.TestContext.RuntimeHandler)
73-
stopwatch.Record("CreatePod")
89+
lastEndTime = time.Now().UnixNano()
90+
durations[0] = lastEndTime - lastStartTime
7491
framework.ExpectNoError(err, "failed to create PodSandbox: %v", err)
7592

7693
By("Get Pod status")
77-
stopwatch.Reset()
94+
lastStartTime = time.Now().UnixNano()
7895
_, err = c.PodSandboxStatus(podID)
79-
stopwatch.Record("StatusPod")
96+
lastEndTime = time.Now().UnixNano()
97+
durations[1] = lastEndTime - lastStartTime
8098
framework.ExpectNoError(err, "failed to get PodStatus: %v", err)
8199

82100
By("Stop PodSandbox")
83-
stopwatch.Reset()
101+
lastStartTime = time.Now().UnixNano()
84102
err = c.StopPodSandbox(podID)
85-
stopwatch.Record("StopPod")
103+
lastEndTime = time.Now().UnixNano()
104+
durations[2] = lastEndTime - lastStartTime
86105
framework.ExpectNoError(err, "failed to stop PodSandbox: %v", err)
87106

88107
By("Remove PodSandbox")
89-
stopwatch.Reset()
108+
lastStartTime = time.Now().UnixNano()
90109
err = c.RemovePodSandbox(podID)
91-
stopwatch.Record("RemovePod")
110+
lastEndTime = time.Now().UnixNano()
111+
durations[3] = lastEndTime - lastStartTime
92112
framework.ExpectNoError(err, "failed to remove PodSandbox: %v", err)
93113

94-
}, gmeasure.SamplingConfig{N: framework.TestContext.BenchmarkingParams.PodsNumber, NumParallel: framework.TestContext.BenchmarkingParams.PodsNumberParallel})
114+
res := LifecycleBenchmarkDatapoint{
115+
StartTime: startTime,
116+
EndTime: lastEndTime,
117+
OperationsDurationsNs: durations,
118+
MetaInfo: map[string]string{"podId": podID, "podSandboxName": podSandboxName},
119+
}
120+
resultsChannel <- &res
121+
122+
}, samplingConfig)
95123

96-
data := ExperimentData{
97-
CreatePod: getNanosecondsForDurations(experiment.Get("CreatePod").Durations),
98-
StatusPod: getNanosecondsForDurations(experiment.Get("StatusPod").Durations),
99-
StopPod: getNanosecondsForDurations(experiment.Get("StopPod").Durations),
100-
RemovePod: getNanosecondsForDurations(experiment.Get("RemovePod").Durations),
124+
// Send nil and give the manager a minute to process any already-queued results:
125+
resultsChannel <- nil
126+
err := resultsManager.AwaitAllResults(60)
127+
if err != nil {
128+
glog.Errorf("Results manager failed to await all results: %s", err)
101129
}
102130

103131
if framework.TestContext.BenchmarkingOutputDir != "" {
104-
filepath := path.Join(framework.TestContext.BenchmarkingOutputDir, "pod_benchmark_data.json")
105-
data, err := json.MarshalIndent(data, "", " ")
106-
if err == nil {
107-
err = ioutil.WriteFile(filepath, data, 0644)
108-
if err != nil {
109-
glog.Errorf("Failed to write container benchmark data: %v", filepath)
110-
}
111-
} else {
112-
glog.Errorf("Failed to serialize benchmark data: %v", err)
132+
filepath := path.Join(framework.TestContext.BenchmarkingOutputDir, "newf_pod_benchmark_data.json")
133+
err = resultsManager.WriteResultsFile(filepath)
134+
if err != nil {
135+
glog.Errorf("Error occurred while writing benchmark results to file %s: %s", filepath, err)
113136
}
114137
} else {
115-
glog.Infof("No benchmarking out dir provided, skipping writing benchmarking resulsts.")
138+
glog.Infof("No benchmarking out dir provided, skipping writing benchmarking results.")
139+
glog.Infof("Benchmark results were: %+v", resultsManager.resultsSet)
116140
}
117141
})
118142
})
119-
120143
})

0 commit comments

Comments
 (0)