Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[loadgen] Add benchmark for prod startup time #4820

Merged
merged 2 commits into from
Jul 19, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions dev/loadgen/cmd/benchmark.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package cmd

import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"io/ioutil"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/protobuf/types/known/timestamppb"
"sigs.k8s.io/yaml"

"github.com/gitpod-io/gitpod/loadgen/pkg/loadgen"
"github.com/gitpod-io/gitpod/loadgen/pkg/observer"
"github.com/gitpod-io/gitpod/ws-manager/api"
)

var benchmarkOpts struct {
TLSPath string
Host string
}

// benchmarkCommand represents the run command
var benchmarkCommand = &cobra.Command{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Too big. Can you split this to call smaller functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's your level/criterion for an adequate function size?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find if functions can be refactored then it should be refactored to small functions. I don't think there could be/should be strict checks.

The primary reason I pointed this here is:

  1. There are multiple variables being created in this function. The variables are complex and use other preceeding variables.
  2. Variable/(s) have nested function in their field definition -> Hard to read and understand what is going on
  3. The big constant string in the variable definition
  4. When I try to review it becomes hard for me because now in step X the abstraction is less. I have to review not just the variable creation but also how it is being created.

Does it make any sense? From a reviewer perspective I find it hard to review. The code is good other wise.

Use: "benchmark <scenario.yaml>",
Short: "starts a bunch of workspaces for benchmarking startup time",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fn := args[0]
fc, err := ioutil.ReadFile(fn)
if err != nil {
log.WithError(err).WithField("fn", fn).Fatal("cannot read scenario file")
}
var scenario BenchmarkScenario
err = yaml.Unmarshal(fc, &scenario)
if err != nil {
log.WithError(err).WithField("fn", fn).Fatal("cannot unmarshal scenario file")
}

var load loadgen.LoadGenerator
load = loadgen.NewFixedLoadGenerator(500*time.Millisecond, 300*time.Millisecond)
load = loadgen.NewWorkspaceCountLimitingGenerator(load, scenario.Workspaces)

template := &api.StartWorkspaceRequest{
Id: "will-be-overriden",
Metadata: &api.WorkspaceMetadata{
MetaId: "will-be-overriden",
Owner: "00000000-0000-0000-0000-000000000000",
StartedAt: timestamppb.Now(),
},
ServicePrefix: "will-be-overriden",
Spec: &api.StartWorkspaceSpec{
IdeImage: scenario.IDEImage,
Admission: api.AdmissionLevel_ADMIT_OWNER_ONLY,
CheckoutLocation: "gitpod",
Git: &api.GitSpec{
Email: "test@gitpod.io",
Username: "foobar",
},
FeatureFlags: []api.WorkspaceFeatureFlag{},
Timeout: "5m",
WorkspaceImage: "will-be-overriden",
WorkspaceLocation: "gitpod",
Envvars: []*api.EnvironmentVariable{
{
Name: "THEIA_SUPERVISOR_TOKENS",
Value: `[{"token":"foobar","host":"gitpod-staging.com","scope":["function:getWorkspace","function:getLoggedInUser","function:getPortAuthenticationToken","function:getWorkspaceOwner","function:getWorkspaceUsers","function:isWorkspaceOwner","function:controlAdmission","function:setWorkspaceTimeout","function:getWorkspaceTimeout","function:sendHeartBeat","function:getOpenPorts","function:openPort","function:closePort","function:getLayout","function:generateNewGitpodToken","function:takeSnapshot","function:storeLayout","function:stopWorkspace","resource:workspace::fa498dcc-0a84-448f-9666-79f297ad821a::get/update","resource:workspaceInstance::e0a17083-6a78-441a-9b97-ef90d6aff463::get/update/delete","resource:snapshot::*::create/get","resource:gitpodToken::*::create","resource:userStorage::*::create/get/update"],"expiryDate":"2020-12-01T07:55:12.501Z","reuse":2}]`,
},
},
},
Type: api.WorkspaceType_REGULAR,
}

var opts []grpc.DialOption
if benchmarkOpts.TLSPath != "" {
ca, err := ioutil.ReadFile(filepath.Join(benchmarkOpts.TLSPath, "ca.crt"))
if err != nil {
log.Fatal(err)
}
capool := x509.NewCertPool()
capool.AppendCertsFromPEM(ca)
cert, err := tls.LoadX509KeyPair(filepath.Join(benchmarkOpts.TLSPath, "tls.crt"), filepath.Join(benchmarkOpts.TLSPath, "tls.key"))
if err != nil {
log.Fatal(err)
}
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: capool,
ServerName: "ws-manager",
})
opts = append(opts, grpc.WithTransportCredentials(creds))
} else {
opts = append(opts, grpc.WithInsecure())
}

conn, err := grpc.Dial(benchmarkOpts.Host, opts...)
if err != nil {
log.Fatal(err)
}
defer conn.Close()

session := &loadgen.Session{
Executor: &loadgen.WsmanExecutor{C: api.NewWorkspaceManagerClient(conn)},
// Executor: loadgen.NewFakeExecutor(),
Load: load,
Specs: &loadgen.MultiWorkspaceGenerator{
Template: template,
Repos: scenario.Repos,
},
Worker: 5,
Observer: []chan<- *loadgen.SessionEvent{
observer.NewLogObserver(true),
observer.NewProgressBarObserver(scenario.Workspaces),
observer.NewStatsObserver(func(s *observer.Stats) {
fc, err := json.Marshal(s)
if err != nil {
return
}
os.WriteFile("stats.json", fc, 0644)
}),
},
PostLoadWait: func() {
<-make(chan struct{})
log.Info("load generation complete - press Ctrl+C to finish of")

},
}

go func() {
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, syscall.SIGINT)
<-sigc
os.Exit(0)
}()

err = session.Run()
if err != nil {
log.WithError(err).Fatal()
}

},
}

func init() {
rootCmd.AddCommand(benchmarkCommand)

benchmarkCommand.Flags().StringVar(&benchmarkOpts.TLSPath, "tls", "", "path to ws-manager's TLS certificates")
benchmarkCommand.Flags().StringVar(&benchmarkOpts.Host, "host", "localhost:8080", "ws-manager host to talk to")
}

type BenchmarkScenario struct {
Workspaces int `json:"workspaces"`
IDEImage string `json:"ideImage"`
Repos []loadgen.WorkspaceCfg `json:"repos"`
}
4 changes: 2 additions & 2 deletions dev/loadgen/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var runCmd = &cobra.Command{
Use: "run",
Short: "runs the load generator",
Run: func(cmd *cobra.Command, args []string) {
const workspaceCount = 500
const workspaceCount = 5

var load loadgen.LoadGenerator
load = loadgen.NewFixedLoadGenerator(500*time.Millisecond, 300*time.Millisecond)
Expand All @@ -51,7 +51,7 @@ var runCmd = &cobra.Command{
},
ServicePrefix: "will-be-overriden",
Spec: &api.StartWorkspaceSpec{
IdeImage: "eu.gcr.io/gitpod-dev/ide/theia:master.3206",
IdeImage: "eu.gcr.io/gitpod-core-dev/build/ide/code:commit-8c1466008dedabe79d82cbb91931a16f7ce7994c",
Admission: api.AdmissionLevel_ADMIT_OWNER_ONLY,
CheckoutLocation: "gitpod",
Git: &api.GitSpec{
Expand Down
1 change: 1 addition & 0 deletions dev/loadgen/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/spf13/cobra v1.1.3
google.golang.org/grpc v1.38.0
google.golang.org/protobuf v1.26.0
sigs.k8s.io/yaml v1.2.0
)

replace github.com/gitpod-io/gitpod/common-go => ../../components/common-go // leeway
Expand Down
2 changes: 2 additions & 0 deletions dev/loadgen/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand All @@ -522,4 +523,5 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
14 changes: 12 additions & 2 deletions dev/loadgen/pkg/loadgen/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type FakeExecutor struct {

// StartWorkspace starts a new workspace
func (fe *FakeExecutor) StartWorkspace(spec *StartWorkspaceSpec) (callDuration time.Duration, err error) {
log.WithField("spec", spec).Info("StartWorkspace")
go fe.produceUpdates(spec)
callDuration = time.Duration(rand.Uint32()%5000) * time.Millisecond
return
Expand Down Expand Up @@ -100,7 +101,8 @@ func (fe *FakeExecutor) StopAll() error {

// WsmanExecutor talks to a ws manager
type WsmanExecutor struct {
C api.WorkspaceManagerClient
C api.WorkspaceManagerClient
Sub []context.CancelFunc
}

// StartWorkspace starts a new workspace
Expand All @@ -122,11 +124,16 @@ func (w *WsmanExecutor) StartWorkspace(spec *StartWorkspaceSpec) (callDuration t
// Observe observes all workspaces started by the excecutor
func (w *WsmanExecutor) Observe() (<-chan WorkspaceUpdate, error) {
res := make(chan WorkspaceUpdate)
sub, err := w.C.Subscribe(context.Background(), &api.SubscribeRequest{})

ctx, cancel := context.WithCancel(context.Background())
w.Sub = append(w.Sub, cancel)

sub, err := w.C.Subscribe(ctx, &api.SubscribeRequest{})
if err != nil {
return nil, err
}
go func() {
defer close(res)
for {
resp, err := sub.Recv()
if err != nil {
Expand Down Expand Up @@ -155,6 +162,9 @@ func (w *WsmanExecutor) Observe() (<-chan WorkspaceUpdate, error) {

// StopAll stops all workspaces started by the executor
func (w *WsmanExecutor) StopAll() error {
for _, s := range w.Sub {
s()
}
fmt.Println("kubectl delete pod -l component=workspace")
princerachit marked this conversation as resolved.
Show resolved Hide resolved
return nil
}
45 changes: 45 additions & 0 deletions dev/loadgen/pkg/loadgen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"google.golang.org/protobuf/proto"

"github.com/gitpod-io/gitpod/common-go/namegen"
csapi "github.com/gitpod-io/gitpod/content-service/api"
"github.com/gitpod-io/gitpod/ws-manager/api"
)

Expand Down Expand Up @@ -151,3 +152,47 @@ func (f *FixedLoadGenerator) Close() error {
close(f.close)
return nil
}

type WorkspaceCfg struct {
CloneURL string `json:"cloneURL"`
WorkspaceImage string `json:"workspaceImage"`
}

type MultiWorkspaceGenerator struct {
Template *api.StartWorkspaceRequest
Repos []WorkspaceCfg
}

func (f *MultiWorkspaceGenerator) Generate() (*StartWorkspaceSpec, error) {
instanceID, err := uuid.NewRandom()
if err != nil {
return nil, err
}
workspaceID, err := namegen.GenerateWorkspaceID()
if err != nil {
return nil, err
}

repo := f.Repos[rand.Intn(len(f.Repos))]

out := proto.Clone(f.Template).(*api.StartWorkspaceRequest)
out.Id = instanceID.String()
out.Metadata.MetaId = workspaceID
out.ServicePrefix = workspaceID
out.Spec.Initializer = &csapi.WorkspaceInitializer{
Spec: &csapi.WorkspaceInitializer_Git{
Git: &csapi.GitInitializer{
CheckoutLocation: "",
CloneTaget: "main",
RemoteUri: repo.CloneURL,
TargetMode: csapi.CloneTargetMode_REMOTE_BRANCH,
Config: &csapi.GitConfig{
Authentication: csapi.GitAuthMethod_NO_AUTH,
},
},
},
}
out.Spec.WorkspaceImage = repo.WorkspaceImage
r := StartWorkspaceSpec(*out)
return &r, nil
}
10 changes: 6 additions & 4 deletions dev/loadgen/pkg/loadgen/loadgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ func (s *Session) Run() error {

obs, err := s.Executor.Observe()
if err != nil {
close(updates)
return err
}
infraWG.Add(1)
go func() {
defer close(updates)
defer infraWG.Done()

<-start
Expand Down Expand Up @@ -133,11 +133,13 @@ func (s *Session) Run() error {
s.PostLoadWait()
}
updates <- &SessionEvent{Kind: SessionDone}
err = s.Executor.StopAll()
if err != nil {
return err
}

infraWG.Wait()
close(updates)

return s.Executor.StopAll()
return nil
}

func (s *Session) distributeUpdates(wg *sync.WaitGroup, updates <-chan *SessionEvent) {
Expand Down
42 changes: 42 additions & 0 deletions dev/loadgen/prod-benchmark.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## start with
## loadgen benchmark prod-benchmark.yaml

workspaces: 500
ideImage: eu.gcr.io/gitpod-core-dev/build/ide/code:commit-8c1466008dedabe79d82cbb91931a16f7ce7994c
repos:
- cloneURL: https://github.com/gitpod-io/template-typescript-node/
princerachit marked this conversation as resolved.
Show resolved Hide resolved
# image: gitpod/workspace-mongodb
workspaceImage: eu.gcr.io/gitpod-dev/workspace-images:53489ee25aa4d1797edd10485d8ecc2fc7a7456ae37718399a54efb498f7236f
- cloneURL: https://github.com/gitpod-io/template-typescript-react
# image: gitpod/workspace-full:latest
workspaceImage: eu.gcr.io/gitpod-dev/workspace-images:63bf2cbae693a7ecf60a40fb1eadc90b83a99919a2a010019a336a81b0c54b84
- cloneURL: https://github.com/gitpod-io/template-python-django/
# image: gitpod/workspace-full:latest
workspaceImage: eu.gcr.io/gitpod-dev/workspace-images:63bf2cbae693a7ecf60a40fb1eadc90b83a99919a2a010019a336a81b0c54b84
- cloneURL: https://github.com/gitpod-io/template-python-flask
# image: gitpod/workspace-full:latest
workspaceImage: eu.gcr.io/gitpod-dev/workspace-images:63bf2cbae693a7ecf60a40fb1eadc90b83a99919a2a010019a336a81b0c54b84
- cloneURL: https://github.com/gitpod-io/spring-petclinic/
# image: gitpod/workspace-full:latest
workspaceImage: eu.gcr.io/gitpod-dev/workspace-images:63bf2cbae693a7ecf60a40fb1eadc90b83a99919a2a010019a336a81b0c54b84
# - cloneURL: https://github.com/gitpod-io/template-php-drupal-ddev
# image: (dockerfile)
# workspaceImage:
# - cloneURL: https://github.com/gitpod-io/template-php-laravel-mysql
# image: (dockerfile)
#workspaceImage:
# - cloneURL: https://github.com/gitpod-io/template-ruby-on-rails-postgres/
# image: (dockerfile)
#workspaceImage:
- cloneURL: https://github.com/gitpod-io/template-golang-cli/
# image: gitpod/workspace-full:latest
workspaceImage: eu.gcr.io/gitpod-dev/workspace-images:63bf2cbae693a7ecf60a40fb1eadc90b83a99919a2a010019a336a81b0c54b84
- cloneURL: https://github.com/gitpod-io/template-rust-cli/
# image: gitpod/workspace-full:latest
workspaceImage: eu.gcr.io/gitpod-dev/workspace-images:63bf2cbae693a7ecf60a40fb1eadc90b83a99919a2a010019a336a81b0c54b84
- cloneURL: https://github.com/gitpod-io/template-dotnet-core-cli-csharp/
# image: gitpod/workspace-dotnet
workspaceImage: eu.gcr.io/gitpod-dev/workspace-images:0b62a3c575c8f00b9130f8a6572d9b4fd935e06de4498cef4ec2db8507cd6159
- cloneURL: https://github.com/gitpod-io/template-sveltejs/
# image: gitpod/workspace-full:latest
workspaceImage: eu.gcr.io/gitpod-dev/workspace-images:63bf2cbae693a7ecf60a40fb1eadc90b83a99919a2a010019a336a81b0c54b84