Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/dev/loadgen/cmd/benchmark.go
2498 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package cmd
6
7
import (
8
"context"
9
"crypto/tls"
10
"crypto/x509"
11
"encoding/json"
12
"fmt"
13
"io/ioutil"
14
"os"
15
"os/signal"
16
"path"
17
"path/filepath"
18
"syscall"
19
"time"
20
21
"github.com/google/uuid"
22
log "github.com/sirupsen/logrus"
23
"github.com/spf13/cobra"
24
"google.golang.org/grpc"
25
"google.golang.org/grpc/credentials"
26
"google.golang.org/grpc/credentials/insecure"
27
"google.golang.org/protobuf/types/known/timestamppb"
28
"sigs.k8s.io/yaml"
29
30
"github.com/gitpod-io/gitpod/loadgen/pkg/loadgen"
31
"github.com/gitpod-io/gitpod/loadgen/pkg/observer"
32
"github.com/gitpod-io/gitpod/ws-manager/api"
33
)
34
35
var benchmarkOpts struct {
36
TLSPath string
37
Host string
38
}
39
40
// benchmarkCommand represents the run command
41
var benchmarkCommand = &cobra.Command{
42
Use: "benchmark <scenario.yaml>",
43
Short: "starts a bunch of workspaces for benchmarking startup time",
44
Args: cobra.MinimumNArgs(1),
45
Run: func(cmd *cobra.Command, args []string) {
46
fn := args[0]
47
fc, err := ioutil.ReadFile(fn)
48
if err != nil {
49
log.WithError(err).WithField("fn", fn).Fatal("cannot read scenario file")
50
}
51
var scenario BenchmarkScenario
52
err = yaml.Unmarshal(fc, &scenario)
53
if err != nil {
54
log.WithError(err).WithField("fn", fn).Fatal("cannot unmarshal scenario file")
55
}
56
57
var load loadgen.LoadGenerator
58
load = loadgen.NewFixedLoadGenerator(800*time.Millisecond, 300*time.Millisecond)
59
load = loadgen.NewWorkspaceCountLimitingGenerator(load, scenario.Workspaces)
60
61
template := &api.StartWorkspaceRequest{
62
Id: "will-be-overriden",
63
Metadata: &api.WorkspaceMetadata{
64
MetaId: "will-be-overriden",
65
Owner: "c0f5dbf1-8d50-4d2a-8cd9-fe563fa53c71",
66
StartedAt: timestamppb.Now(),
67
},
68
ServicePrefix: "will-be-overriden",
69
Spec: &api.StartWorkspaceSpec{
70
IdeImage: &api.IDEImage{
71
WebRef: scenario.IDEImage,
72
},
73
Admission: api.AdmissionLevel_ADMIT_OWNER_ONLY,
74
Git: &api.GitSpec{
75
Email: "[email protected]",
76
Username: "foobar",
77
},
78
FeatureFlags: scenario.FeatureFlags,
79
Timeout: scenario.WorkspaceTimeout,
80
WorkspaceImage: "will-be-overriden",
81
Envvars: scenario.Environment,
82
Class: scenario.WorkspaceClass,
83
},
84
Type: api.WorkspaceType_REGULAR,
85
}
86
87
var opts []grpc.DialOption
88
if benchmarkOpts.TLSPath != "" {
89
ca, err := ioutil.ReadFile(filepath.Join(benchmarkOpts.TLSPath, "ca.crt"))
90
if err != nil {
91
log.Fatal(err)
92
}
93
capool := x509.NewCertPool()
94
capool.AppendCertsFromPEM(ca)
95
cert, err := tls.LoadX509KeyPair(filepath.Join(benchmarkOpts.TLSPath, "tls.crt"), filepath.Join(benchmarkOpts.TLSPath, "tls.key"))
96
if err != nil {
97
log.Fatal(err)
98
}
99
creds := credentials.NewTLS(&tls.Config{
100
Certificates: []tls.Certificate{cert},
101
RootCAs: capool,
102
ServerName: "ws-manager",
103
})
104
opts = append(opts, grpc.WithTransportCredentials(creds))
105
} else {
106
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
107
}
108
109
conn, err := grpc.Dial(benchmarkOpts.Host, opts...)
110
if err != nil {
111
log.Fatal(err)
112
}
113
defer conn.Close()
114
115
d, err := time.ParseDuration(scenario.RunningTimeout)
116
if err != nil {
117
log.Fatal(err)
118
}
119
120
success := observer.NewSuccessObserver(scenario.SuccessRate)
121
122
sessionID := uuid.New().String()
123
resultsDir := fmt.Sprintf("results/benchmark-%s", sessionID)
124
if err = os.MkdirAll(resultsDir, 0755); err != nil {
125
log.Fatal(err)
126
}
127
log.Infof("Results will be saved in dir %s", resultsDir)
128
129
session := &loadgen.Session{
130
Executor: &loadgen.WsmanExecutor{
131
C: api.NewWorkspaceManagerClient(conn),
132
SessionId: sessionID,
133
},
134
// Executor: loadgen.NewFakeExecutor(),
135
Load: load,
136
Specs: &loadgen.MultiWorkspaceGenerator{
137
Template: template,
138
Config: loadgen.MultiGeneratorConfig{
139
Repos: scenario.Repos,
140
Auth: scenario.RepositoryAuth,
141
},
142
},
143
Worker: 5,
144
Observer: []chan<- *loadgen.SessionEvent{
145
observer.NewLogObserver(true),
146
observer.NewProgressBarObserver(scenario.Workspaces),
147
observer.NewStatsObserver(func(s *observer.Stats) {
148
fc, err := json.Marshal(s)
149
if err != nil {
150
return
151
}
152
os.WriteFile(path.Join(resultsDir, "stats.json"), fc, 0644)
153
}),
154
success.Observe(),
155
},
156
PostLoadWait: func() {
157
ctx, cancel := context.WithTimeout(context.Background(), d)
158
defer cancel()
159
160
log.Info("Waiting for workspaces to enter running phase")
161
if err := success.Wait(ctx, scenario.Workspaces); err != nil {
162
log.Errorf("%v", err)
163
log.Info("load generation did not complete successfully")
164
} else {
165
log.Info("load generation completed successfully")
166
}
167
},
168
Termination: func(executor loadgen.Executor) error {
169
return handleWorkspaceDeletion(scenario.StoppingTimeout, resultsDir, executor, false)
170
},
171
}
172
173
sctx, scancel := context.WithCancel(context.Background())
174
175
go func() {
176
sigc := make(chan os.Signal, 1)
177
signal.Notify(sigc, syscall.SIGINT)
178
<-sigc
179
// cancel workspace creation so that no new workspaces are created while we are deleting them
180
scancel()
181
182
if err := handleWorkspaceDeletion(scenario.StoppingTimeout, resultsDir, session.Executor, true); err != nil {
183
log.Warnf("could not delete workspaces: %v", err)
184
os.Exit(1)
185
}
186
187
os.Exit(0)
188
}()
189
190
err = session.Run(sctx)
191
if err != nil {
192
log.WithError(err).Fatal()
193
}
194
},
195
}
196
197
func init() {
198
rootCmd.AddCommand(benchmarkCommand)
199
200
benchmarkCommand.Flags().StringVar(&benchmarkOpts.TLSPath, "tls", "", "path to ws-manager's TLS certificates")
201
benchmarkCommand.Flags().StringVar(&benchmarkOpts.Host, "host", "localhost:8080", "ws-manager host to talk to")
202
}
203
204
type BenchmarkScenario struct {
205
Workspaces int `json:"workspaces"`
206
IDEImage string `json:"ideImage"`
207
Repos []loadgen.WorkspaceCfg `json:"repos"`
208
Environment []*api.EnvironmentVariable `json:"environment"`
209
RunningTimeout string `json:"waitForRunning"`
210
StoppingTimeout string `json:"waitForStopping"`
211
SuccessRate float32 `json:"successRate"`
212
WorkspaceClass string `json:"workspaceClass"`
213
FeatureFlags []api.WorkspaceFeatureFlag `json:"featureFlags"`
214
RepositoryAuth *loadgen.RepositoryAuth `json:"repoAuth,omitempty"`
215
WorkspaceTimeout string `json:"workspaceTimeout,omitempty"`
216
}
217
218
func handleWorkspaceDeletion(timeout string, resultsDir string, executor loadgen.Executor, canceled bool) error {
219
if runOpts.Interactive {
220
if !confirmDeletion() {
221
return nil
222
}
223
224
return stopWorkspaces(timeout, resultsDir, executor)
225
} else {
226
if !canceled {
227
fmt.Println("Waiting for 2 minutes before deleting workspaces")
228
time.Sleep(2 * time.Minute)
229
}
230
231
return stopWorkspaces(timeout, resultsDir, executor)
232
}
233
}
234
235
func confirmDeletion() bool {
236
fmt.Println("Do you want to delete the created workspaces? y/n")
237
var response string
238
_, err := fmt.Scanln(&response)
239
if err != nil && err.Error() != "unexpected newline" {
240
log.Fatal(err)
241
}
242
243
if response != "y" && response != "n" {
244
return confirmDeletion()
245
}
246
247
return response == "y"
248
}
249
250
func stopWorkspaces(timeout string, resultsDir string, executor loadgen.Executor) error {
251
stopping, err := time.ParseDuration(timeout)
252
if err != nil {
253
return fmt.Errorf("invalid timeout")
254
}
255
ctx, cancel := context.WithTimeout(context.Background(), stopping)
256
defer cancel()
257
resultFile := path.Join(resultsDir, "benchmark-result.json")
258
if err := executor.Dump(resultFile); err != nil {
259
log.Warn("could not dump workspace state, trying to stop them anyway")
260
}
261
log.Infof("Saved benchmark results to %s", resultFile)
262
return executor.StopAll(ctx)
263
}
264
265