github.com/redhat-appstudio/e2e-tests@v0.0.0-20230619105049-9a422b2094d7/cmd/loadTests.go (about)

     1  package cmd
     2  
     3  import (
     4  	"encoding/json"
     5  	"flag"
     6  	"fmt"
     7  	"net/http"
     8  	"os"
     9  	"runtime"
    10  	"strconv"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/codeready-toolchain/toolchain-e2e/setup/auth"
    15  	"github.com/codeready-toolchain/toolchain-e2e/setup/metrics"
    16  	"github.com/codeready-toolchain/toolchain-e2e/setup/metrics/queries"
    17  	"github.com/codeready-toolchain/toolchain-e2e/setup/terminal"
    18  	"github.com/gosuri/uiprogress"
    19  	"github.com/gosuri/uitable/util/strutil"
    20  	"github.com/redhat-appstudio/e2e-tests/pkg/constants"
    21  	"github.com/redhat-appstudio/e2e-tests/pkg/framework"
    22  	"github.com/redhat-appstudio/e2e-tests/pkg/utils"
    23  	"github.com/spf13/cobra"
    24  	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
    25  	"k8s.io/apimachinery/pkg/util/rand"
    26  	k8swait "k8s.io/apimachinery/pkg/util/wait"
    27  	"k8s.io/klog/v2"
    28  	"knative.dev/pkg/apis"
    29  )
    30  
    31  var (
    32  	componentRepoUrl          string = "https://github.com/devfile-samples/devfile-sample-code-with-quarkus"
    33  	usernamePrefix            string = "testuser"
    34  	numberOfUsers             int
    35  	waitPipelines             bool
    36  	verbose                   bool
    37  	token                     string
    38  	logConsole                bool
    39  	failFast                  bool
    40  	disableMetrics            bool
    41  	threadCount               int
    42  	pipelineSkipInitialChecks bool
    43  )
    44  
    45  var (
    46  	UserCreationTimeMaxPerThread         []time.Duration
    47  	ResourceCreationTimeMaxPerThread     []time.Duration
    48  	PipelineRunSucceededTimeMaxPerThread []time.Duration
    49  	UserCreationTimeSumPerThread         []time.Duration
    50  	ResourceCreationTimeSumPerThread     []time.Duration
    51  	PipelineRunSucceededTimeSumPerThread []time.Duration
    52  	PipelineRunFailedTimeSumPerThread    []time.Duration
    53  	SuccessfulUserCreationsPerThread     []int64
    54  	SuccessfulResourceCreationsPerThread []int64
    55  	SuccessfulPipelineRunsPerThread      []int64
    56  	FailedUserCreationsPerThread         []int64
    57  	FailedResourceCreationsPerThread     []int64
    58  	FailedPipelineRunsPerThread          []int64
    59  	frameworkMap                         *sync.Map
    60  	userComponentMap                     *sync.Map
    61  	errorCountMap                        map[int]ErrorCount
    62  	errorMutex                           = &sync.Mutex{}
    63  	usersBarMutex                        = &sync.Mutex{}
    64  	resourcesBarMutex                    = &sync.Mutex{}
    65  	pipelinesBarMutex                    = &sync.Mutex{}
    66  	threadsWG                            *sync.WaitGroup
    67  	logData                              LogData
    68  )
    69  
    70  type ErrorOccurrence struct {
    71  	ErrorCode int    `json:"errorCode"`
    72  	Message   string `json:"message"`
    73  }
    74  
    75  type ErrorCount struct {
    76  	ErrorCode int `json:"errorCode"`
    77  	Count     int `json:"count"`
    78  }
    79  
    80  type LogData struct {
    81  	Timestamp                         string            `json:"timestamp"`
    82  	EndTimestamp                      string            `json:"endTimestamp"`
    83  	MachineName                       string            `json:"machineName"`
    84  	BinaryDetails                     string            `json:"binaryDetails"`
    85  	ComponentRepoUrl                  string            `json:"componentRepoUrl"`
    86  	NumberOfThreads                   int               `json:"threads"`
    87  	NumberOfUsersPerThread            int               `json:"usersPerThread"`
    88  	NumberOfUsers                     int               `json:"totalUsers"`
    89  	PipelineSkipInitialChecks         bool              `json:"pipelineSkipInitialChecks"`
    90  	LoadTestCompletionStatus          string            `json:"status"`
    91  	AverageTimeToSpinUpUsers          float64           `json:"createUserTimeAvg"`
    92  	MaxTimeToSpinUpUsers              float64           `json:"createUserTimeMax"`
    93  	AverageTimeToCreateResources      float64           `json:"createResourcesTimeAvg"`
    94  	MaxTimeToCreateResources          float64           `json:"createResourcesTimeMax"`
    95  	AverageTimeToRunPipelineSucceeded float64           `json:"runPipelineSucceededTimeAvg"`
    96  	MaxTimeToRunPipelineSucceeded     float64           `json:"runPipelineSucceededTimeMax"`
    97  	AverageTimeToRunPipelineFailed    float64           `json:"runPipelineFailedTimeAvg"`
    98  	UserCreationFailureCount          int64             `json:"createUserFailures"`
    99  	UserCreationFailureRate           float64           `json:"createUserFailureRate"`
   100  	ResourceCreationFailureCount      int64             `json:"createResourcesFailures"`
   101  	ResourceCreationFailureRate       float64           `json:"createResourcesFailureRate"`
   102  	PipelineRunFailureCount           int64             `json:"runPipelineFailures"`
   103  	PipelineRunFailureRate            float64           `json:"runPipelineFailureRate"`
   104  	ErrorCounts                       []ErrorCount      `json:"errorCounts"`
   105  	Errors                            []ErrorOccurrence `json:"errors"`
   106  	ErrorsTotal                       int               `json:"errorsTotal"`
   107  }
   108  
   109  func createLogDataJSON(outputFile string, logDataInput LogData) error {
   110  	jsonData, err := json.MarshalIndent(logDataInput, "", "  ")
   111  	if err != nil {
   112  		return fmt.Errorf("error marshalling JSON: %v", err)
   113  	}
   114  
   115  	err = os.WriteFile(outputFile, jsonData, 0644)
   116  	if err != nil {
   117  		return fmt.Errorf("error writing JSON file: %v", err)
   118  	}
   119  
   120  	return nil
   121  }
   122  
   123  var rootCmd = &cobra.Command{
   124  	Use:           "load-test",
   125  	Short:         "Used to Generate Users and Run Load Tests on AppStudio.",
   126  	Long:          `Used to Generate Users and Run Load Tests on AppStudio.`,
   127  	SilenceErrors: true,
   128  	SilenceUsage:  false,
   129  	Args:          cobra.NoArgs,
   130  	Run:           setup,
   131  }
   132  
   133  func ExecuteLoadTest() {
   134  	err := rootCmd.Execute()
   135  	if err != nil {
   136  		os.Exit(1)
   137  	}
   138  }
   139  
   140  func init() {
   141  	rootCmd.Flags().StringVar(&componentRepoUrl, "component-repo", componentRepoUrl, "the component repo URL to be used")
   142  	rootCmd.Flags().StringVar(&usernamePrefix, "username", usernamePrefix, "the prefix used for usersignup names")
   143  	// TODO use a custom kubeconfig and introduce debug logging and trace
   144  	rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "if 'debug' traces should be displayed in the console")
   145  	rootCmd.Flags().IntVarP(&numberOfUsers, "users", "u", 5, "the number of user accounts to provision per thread")
   146  	rootCmd.Flags().BoolVarP(&waitPipelines, "waitpipelines", "w", false, "if you want to wait for pipelines to finish")
   147  	rootCmd.Flags().BoolVarP(&logConsole, "log-to-console", "l", false, "if you want to log to console in addition to the log file")
   148  	rootCmd.Flags().BoolVar(&failFast, "fail-fast", false, "if you want the test to fail fast at first failure")
   149  	rootCmd.Flags().BoolVar(&disableMetrics, "disable-metrics", false, "if you want to disable metrics gathering")
   150  	rootCmd.Flags().IntVarP(&threadCount, "threads", "t", 1, "number of concurrent threads to execute")
   151  	rootCmd.Flags().BoolVar(&pipelineSkipInitialChecks, "pipeline-skip-initial-checks", true, "if pipeline runs' initial checks are to be skipped")
   152  }
   153  
   154  func logError(errCode int, message string) {
   155  	msg := fmt.Sprintf("Error #%d: %s", errCode, message)
   156  	if failFast {
   157  		klog.Fatalln(msg)
   158  	} else {
   159  		klog.Errorln(msg)
   160  	}
   161  	errorMutex.Lock()
   162  	defer errorMutex.Unlock()
   163  
   164  	errorCount, ok := errorCountMap[errCode]
   165  	if ok {
   166  		errorCount.Count = errorCount.Count + 1
   167  		errorCountMap[errCode] = errorCount
   168  	} else {
   169  		errorCountMap[errCode] = ErrorCount{
   170  			ErrorCode: errCode,
   171  			Count:     1,
   172  		}
   173  	}
   174  
   175  	errorOccurrence := ErrorOccurrence{
   176  		ErrorCode: errCode,
   177  		Message:   message,
   178  	}
   179  	logData.Errors = append(logData.Errors, errorOccurrence)
   180  }
   181  
   182  func setKlogFlag(fs flag.FlagSet, name string, value string) {
   183  	err := fs.Set(name, value)
   184  	if err != nil {
   185  		klog.Fatalf("Unable to set klog flag %s: %v", name, err)
   186  	}
   187  }
   188  
   189  func setup(cmd *cobra.Command, args []string) {
   190  	cmd.SilenceUsage = true
   191  	term := terminal.New(cmd.InOrStdin, cmd.OutOrStdout, verbose)
   192  
   193  	logFile, err := os.Create("load-tests.log")
   194  	if err != nil {
   195  		klog.Fatalf("Error creating log file: %v", err)
   196  	}
   197  	var fs flag.FlagSet
   198  	klog.InitFlags(&fs)
   199  	setKlogFlag(fs, "log_file", logFile.Name())
   200  	setKlogFlag(fs, "logtostderr", "false")
   201  	setKlogFlag(fs, "alsologtostderr", strconv.FormatBool(logConsole))
   202  
   203  	overallCount := numberOfUsers * threadCount
   204  
   205  	klog.Infof("Number of threads: %d", threadCount)
   206  	klog.Infof("Number of users per thread: %d", numberOfUsers)
   207  	klog.Infof("Number of users overall: %d", overallCount)
   208  	klog.Infof("Pipeline run initial checks skipped: %t", pipelineSkipInitialChecks)
   209  
   210  	klog.Infof("🕖 initializing...\n")
   211  	globalframework, err := framework.NewFramework("load-tests")
   212  	if err != nil {
   213  		klog.Fatalf("error creating client-go %v", err)
   214  	}
   215  
   216  	if len(token) == 0 {
   217  		token, err = auth.GetTokenFromOC()
   218  		if err != nil {
   219  			tokenRequestURI, err := auth.GetTokenRequestURI(globalframework.AsKubeAdmin.CommonController.KubeRest())
   220  			if err != nil {
   221  				klog.Fatalf("a token is required to capture metrics, use oc login to log into the cluster: %v", err)
   222  			}
   223  			klog.Fatalf("a token is required to capture metrics, use oc login to log into the cluster. alternatively request a token and use the token flag: %v", tokenRequestURI)
   224  		}
   225  	}
   226  
   227  	var stopMetrics chan struct{}
   228  	var metricsInstance *metrics.Gatherer
   229  	if !disableMetrics {
   230  		metricsInstance = metrics.NewEmpty(term, globalframework.AsKubeAdmin.CommonController.KubeRest(), 10*time.Minute)
   231  
   232  		prometheusClient := metrics.GetPrometheusClient(term, globalframework.AsKubeAdmin.CommonController.KubeRest(), token)
   233  
   234  		metricsInstance.AddQueries(
   235  			queries.QueryClusterCPUUtilisation(prometheusClient),
   236  			queries.QueryClusterMemoryUtilisation(prometheusClient),
   237  			queries.QueryNodeMemoryUtilisation(prometheusClient),
   238  			queries.QueryEtcdMemoryUsage(prometheusClient),
   239  			queries.QueryWorkloadCPUUsage(prometheusClient, constants.OLMOperatorNamespace, constants.OLMOperatorWorkload),
   240  			queries.QueryWorkloadMemoryUsage(prometheusClient, constants.OLMOperatorNamespace, constants.OLMOperatorWorkload),
   241  			queries.QueryOpenshiftKubeAPIMemoryUtilisation(prometheusClient),
   242  			queries.QueryWorkloadCPUUsage(prometheusClient, constants.OSAPIServerNamespace, constants.OSAPIServerWorkload),
   243  			queries.QueryWorkloadMemoryUsage(prometheusClient, constants.OSAPIServerNamespace, constants.OSAPIServerWorkload),
   244  			queries.QueryWorkloadCPUUsage(prometheusClient, constants.HostOperatorNamespace, constants.HostOperatorWorkload),
   245  			queries.QueryWorkloadMemoryUsage(prometheusClient, constants.HostOperatorNamespace, constants.HostOperatorWorkload),
   246  			queries.QueryWorkloadCPUUsage(prometheusClient, constants.MemberOperatorNamespace, constants.MemberOperatorWorkload),
   247  			queries.QueryWorkloadMemoryUsage(prometheusClient, constants.MemberOperatorNamespace, constants.MemberOperatorWorkload),
   248  			queries.QueryWorkloadCPUUsage(prometheusClient, "application-service", "application-service-application-service-controller-manager"),
   249  			queries.QueryWorkloadMemoryUsage(prometheusClient, "application-service", "application-service-application-service-controller-manager"),
   250  			queries.QueryWorkloadCPUUsage(prometheusClient, "build-service", "build-service-controller-manager"),
   251  			queries.QueryWorkloadMemoryUsage(prometheusClient, "build-service", "build-service-controller-manager"),
   252  		)
   253  		stopMetrics = metricsInstance.StartGathering()
   254  
   255  		klog.Infof("Sleeping till all metrics queries gets init")
   256  		time.Sleep(time.Second * 10)
   257  	}
   258  
   259  	machineName, err := os.Hostname()
   260  	if err != nil {
   261  		klog.Errorf("error getting hostname: %v\n", err)
   262  		return
   263  	}
   264  
   265  	goVersion := runtime.Version()
   266  	goOS := runtime.GOOS
   267  	goArch := runtime.GOARCH
   268  	binaryDetails := fmt.Sprintf("Built with %s for %s/%s", goVersion, goOS, goArch)
   269  
   270  	logData = LogData{
   271  		Timestamp:                 time.Now().Format("2006-01-02T15:04:05Z07:00"),
   272  		MachineName:               machineName,
   273  		BinaryDetails:             binaryDetails,
   274  		ComponentRepoUrl:          componentRepoUrl,
   275  		NumberOfThreads:           threadCount,
   276  		NumberOfUsersPerThread:    numberOfUsers,
   277  		NumberOfUsers:             overallCount,
   278  		PipelineSkipInitialChecks: pipelineSkipInitialChecks,
   279  		Errors:                    []ErrorOccurrence{},
   280  		ErrorCounts:               []ErrorCount{},
   281  	}
   282  
   283  	klog.Infof("🍿 provisioning users...\n")
   284  
   285  	uip := uiprogress.New()
   286  	uip.Start()
   287  
   288  	barLength := 60
   289  
   290  	AppStudioUsersBar := uip.AddBar(overallCount).AppendCompleted().PrependFunc(func(b *uiprogress.Bar) string {
   291  		return strutil.PadLeft(fmt.Sprintf("Creating AppStudio Users (%d/%d) [%d failed]", b.Current(), overallCount, sumFromArray(FailedUserCreationsPerThread)), barLength, ' ')
   292  	})
   293  
   294  	ResourcesBar := uip.AddBar(overallCount).AppendCompleted().PrependFunc(func(b *uiprogress.Bar) string {
   295  		return strutil.PadLeft(fmt.Sprintf("Creating AppStudio User Resources (%d/%d) [%d failed]", b.Current(), overallCount, sumFromArray(FailedResourceCreationsPerThread)), barLength, ' ')
   296  	})
   297  
   298  	PipelinesBar := uip.AddBar(overallCount).AppendCompleted().PrependFunc(func(b *uiprogress.Bar) string {
   299  		return strutil.PadLeft(fmt.Sprintf("Waiting for pipelines to finish (%d/%d) [%d failed]", b.Current(), overallCount, sumFromArray(FailedPipelineRunsPerThread)), barLength, ' ')
   300  	})
   301  
   302  	UserCreationTimeMaxPerThread = make([]time.Duration, threadCount)
   303  	ResourceCreationTimeMaxPerThread = make([]time.Duration, threadCount)
   304  	PipelineRunSucceededTimeMaxPerThread = make([]time.Duration, threadCount)
   305  	UserCreationTimeSumPerThread = make([]time.Duration, threadCount)
   306  	ResourceCreationTimeSumPerThread = make([]time.Duration, threadCount)
   307  	PipelineRunSucceededTimeSumPerThread = make([]time.Duration, threadCount)
   308  	PipelineRunFailedTimeSumPerThread = make([]time.Duration, threadCount)
   309  	SuccessfulUserCreationsPerThread = make([]int64, threadCount)
   310  	SuccessfulResourceCreationsPerThread = make([]int64, threadCount)
   311  	SuccessfulPipelineRunsPerThread = make([]int64, threadCount)
   312  	FailedUserCreationsPerThread = make([]int64, threadCount)
   313  	FailedResourceCreationsPerThread = make([]int64, threadCount)
   314  	FailedPipelineRunsPerThread = make([]int64, threadCount)
   315  	frameworkMap = &sync.Map{}
   316  	userComponentMap = &sync.Map{}
   317  	errorCountMap = make(map[int]ErrorCount)
   318  
   319  	rand.Seed(time.Now().UnixNano())
   320  
   321  	threadsWG = &sync.WaitGroup{}
   322  	threadsWG.Add(threadCount)
   323  	for thread := 0; thread < threadCount; thread++ {
   324  		go userJourneyThread(frameworkMap, threadsWG, thread, AppStudioUsersBar, ResourcesBar, PipelinesBar)
   325  	}
   326  
   327  	// Todo add cleanup functions that will delete user signups
   328  
   329  	threadsWG.Wait()
   330  	uip.Stop()
   331  
   332  	logData.EndTimestamp = time.Now().Format("2006-01-02T15:04:05Z07:00")
   333  
   334  	logData.LoadTestCompletionStatus = "Completed"
   335  
   336  	userCreationFailureCount := sumFromArray(FailedUserCreationsPerThread)
   337  	logData.UserCreationFailureCount = userCreationFailureCount
   338  
   339  	averageTimeToSpinUpUsers := float64(0)
   340  	userCreationSuccessCount := sumFromArray(SuccessfulUserCreationsPerThread)
   341  	if userCreationSuccessCount > 0 {
   342  		averageTimeToSpinUpUsers = sumDurationFromArray(UserCreationTimeSumPerThread).Seconds() / float64(userCreationSuccessCount)
   343  	}
   344  	logData.AverageTimeToSpinUpUsers = averageTimeToSpinUpUsers
   345  
   346  	logData.MaxTimeToSpinUpUsers = maxDurationFromArray(UserCreationTimeMaxPerThread).Seconds()
   347  
   348  	resourceCreationFailureCount := sumFromArray(FailedResourceCreationsPerThread)
   349  	logData.ResourceCreationFailureCount = resourceCreationFailureCount
   350  
   351  	averageTimeToCreateResources := float64(0)
   352  	resourceCreationSuccessCount := sumFromArray(SuccessfulResourceCreationsPerThread)
   353  	if resourceCreationSuccessCount > 0 {
   354  		averageTimeToCreateResources = sumDurationFromArray(ResourceCreationTimeSumPerThread).Seconds() / float64(resourceCreationSuccessCount)
   355  	}
   356  	logData.AverageTimeToCreateResources = averageTimeToCreateResources
   357  
   358  	logData.MaxTimeToCreateResources = maxDurationFromArray(ResourceCreationTimeMaxPerThread).Seconds()
   359  
   360  	pipelineRunFailureCount := sumFromArray(FailedPipelineRunsPerThread)
   361  	logData.PipelineRunFailureCount = pipelineRunFailureCount
   362  
   363  	averageTimeToRunPipelineSucceeded := float64(0)
   364  	pipelineRunSuccessCount := sumFromArray(SuccessfulPipelineRunsPerThread)
   365  	if pipelineRunSuccessCount > 0 {
   366  		averageTimeToRunPipelineSucceeded = sumDurationFromArray(PipelineRunSucceededTimeSumPerThread).Seconds() / float64(pipelineRunSuccessCount)
   367  	}
   368  	logData.AverageTimeToRunPipelineSucceeded = averageTimeToRunPipelineSucceeded
   369  
   370  	logData.MaxTimeToRunPipelineSucceeded = maxDurationFromArray(PipelineRunSucceededTimeMaxPerThread).Seconds()
   371  
   372  	averageTimeToRunPipelineFailed := float64(0)
   373  	if pipelineRunFailureCount > 0 {
   374  		averageTimeToRunPipelineFailed = sumDurationFromArray(PipelineRunFailedTimeSumPerThread).Seconds() / float64(pipelineRunFailureCount)
   375  	}
   376  	logData.AverageTimeToRunPipelineFailed = averageTimeToRunPipelineFailed
   377  
   378  	userCreationFailureRate := float64(userCreationFailureCount) / float64(overallCount)
   379  	logData.UserCreationFailureRate = userCreationFailureRate
   380  
   381  	resourceCreationFailureRate := float64(resourceCreationFailureCount) / float64(overallCount)
   382  	logData.ResourceCreationFailureRate = resourceCreationFailureRate
   383  
   384  	pipelineRunFailureRate := float64(pipelineRunFailureCount) / float64(overallCount)
   385  	logData.PipelineRunFailureRate = pipelineRunFailureRate
   386  
   387  	klog.Infof("🏁 Load Test Completed!")
   388  	klog.Infof("📈 Results 📉")
   389  	klog.Infof("Average Time to spin up users: %.2f s", averageTimeToSpinUpUsers)
   390  	klog.Infof("Maximal Time to spin up users: %.2f s", logData.MaxTimeToSpinUpUsers)
   391  	klog.Infof("Average Time to create Resources: %.2f s", averageTimeToCreateResources)
   392  	klog.Infof("Maximal Time to create Resources: %.2f s", logData.MaxTimeToCreateResources)
   393  	klog.Infof("Average Time to run Pipelines successfully: %.2f s", averageTimeToRunPipelineSucceeded)
   394  	klog.Infof("Maximal Time to run Pipelines successfully: %.2f s", logData.MaxTimeToRunPipelineSucceeded)
   395  	klog.Infof("Average Time to fail Pipelines: %.2f s", averageTimeToRunPipelineFailed)
   396  	klog.Infof("Number of times user creation failed: %d (%.2f %%)", userCreationFailureCount, userCreationFailureRate*100)
   397  	klog.Infof("Number of times resource creation failed: %d (%.2f %%)", resourceCreationFailureCount, resourceCreationFailureRate*100)
   398  	klog.Infof("Number of times pipeline run failed: %d (%.2f %%)", pipelineRunFailureCount, pipelineRunFailureRate*100)
   399  	klog.Infoln("Error summary:")
   400  	for _, errorCount := range errorCountMap {
   401  		klog.Infof("Number of error #%d occured: %d", errorCount.ErrorCode, errorCount.Count)
   402  		logData.ErrorCounts = append(logData.ErrorCounts, errorCount)
   403  	}
   404  	logData.ErrorsTotal = len(logData.Errors)
   405  	klog.Infof("Total number of errors occured: %d", logData.ErrorsTotal)
   406  
   407  	err = createLogDataJSON("load-tests.json", logData)
   408  	if err != nil {
   409  		klog.Errorf("error while marshalling JSON: %v\n", err)
   410  	}
   411  
   412  	klog.StopFlushDaemon()
   413  	klog.Flush()
   414  	if !disableMetrics {
   415  		defer close(stopMetrics)
   416  		metricsInstance.PrintResults()
   417  	}
   418  }
   419  
   420  func maxDurationFromArray(durations []time.Duration) time.Duration {
   421  	max := time.Duration(0)
   422  	for _, i := range durations {
   423  		if i > max {
   424  			max = i
   425  		}
   426  	}
   427  	return max
   428  }
   429  
   430  func sumDurationFromArray(durations []time.Duration) time.Duration {
   431  	sum := time.Duration(0)
   432  	for _, i := range durations {
   433  		sum += i
   434  	}
   435  	return sum
   436  }
   437  
   438  func sumFromArray(array []int64) int64 {
   439  	sum := int64(0)
   440  	for _, i := range array {
   441  		sum += i
   442  	}
   443  	return sum
   444  }
   445  
   446  func increaseBar(bar *uiprogress.Bar, mutex *sync.Mutex) {
   447  	mutex.Lock()
   448  	defer mutex.Unlock()
   449  	bar.Incr()
   450  }
   451  
   452  func componentForUser(username string) string {
   453  	val, ok := userComponentMap.Load(username)
   454  	if ok {
   455  		componentName, ok2 := val.(string)
   456  		if ok2 {
   457  			return componentName
   458  		} else {
   459  			klog.Errorf("Invalid type of map value: %+v", val)
   460  		}
   461  	}
   462  	return ""
   463  }
   464  
   465  func frameworkForUser(username string) *framework.Framework {
   466  	val, ok := frameworkMap.Load(username)
   467  	if ok {
   468  		framework, ok2 := val.(*framework.Framework)
   469  		if ok2 {
   470  			return framework
   471  		} else {
   472  			klog.Errorf("Invalid type of map value: %+v", val)
   473  		}
   474  	}
   475  	return nil
   476  }
   477  
   478  func tryNewFramework(username string, timeout time.Duration) (*framework.Framework, error) {
   479  	ch := make(chan *framework.Framework)
   480  	var fw *framework.Framework
   481  	var err error
   482  	go func() {
   483  		fw, err = framework.NewFrameworkWithTimeout(username, time.Minute*60)
   484  		ch <- fw
   485  	}()
   486  
   487  	var ret *framework.Framework
   488  
   489  	select {
   490  	case result := <-ch:
   491  		ret = result
   492  	case <-time.After(timeout):
   493  		ret = nil
   494  		err = fmt.Errorf("unable to create new framework for user %s within %v", username, timeout)
   495  	}
   496  
   497  	return ret, err
   498  }
   499  
   500  func userJourneyThread(frameworkMap *sync.Map, threadWaitGroup *sync.WaitGroup, threadIndex int, usersBar *uiprogress.Bar, resourcesBar *uiprogress.Bar, pipelinesBar *uiprogress.Bar) {
   501  	chUsers := make(chan string, numberOfUsers)
   502  	chPipelines := make(chan string, numberOfUsers)
   503  
   504  	defer threadWaitGroup.Done()
   505  
   506  	var wg *sync.WaitGroup = &sync.WaitGroup{}
   507  
   508  	if waitPipelines {
   509  		wg.Add(3)
   510  	} else {
   511  		wg.Add(2)
   512  	}
   513  
   514  	go func() {
   515  		defer wg.Done()
   516  		for userIndex := 1; userIndex <= numberOfUsers; userIndex++ {
   517  			startTime := time.Now()
   518  			username := fmt.Sprintf("%s-%04d", usernamePrefix, threadIndex*numberOfUsers+userIndex)
   519  			framework, err := tryNewFramework(username, 60*time.Minute)
   520  			if err != nil {
   521  				logError(1, fmt.Sprintf("Unable to provision user '%s': %v", username, err))
   522  				FailedUserCreationsPerThread[threadIndex] += 1
   523  				increaseBar(usersBar, usersBarMutex)
   524  				continue
   525  			} else {
   526  				frameworkMap.Store(username, framework)
   527  			}
   528  
   529  			chUsers <- username
   530  
   531  			userCreationTime := time.Since(startTime)
   532  			UserCreationTimeSumPerThread[threadIndex] += userCreationTime
   533  			if userCreationTime > UserCreationTimeMaxPerThread[threadIndex] {
   534  				UserCreationTimeMaxPerThread[threadIndex] = userCreationTime
   535  			}
   536  
   537  			SuccessfulUserCreationsPerThread[threadIndex] += 1
   538  			increaseBar(usersBar, usersBarMutex)
   539  		}
   540  		close(chUsers)
   541  	}()
   542  
   543  	go func() {
   544  		defer wg.Done()
   545  		for username := range chUsers {
   546  			startTime := time.Now()
   547  			framework := frameworkForUser(username)
   548  			if framework == nil {
   549  				logError(2, fmt.Sprintf("Framework not found for username %s", username))
   550  				increaseBar(resourcesBar, resourcesBarMutex)
   551  				continue
   552  			}
   553  			usernamespace := framework.UserNamespace
   554  			ApplicationName := fmt.Sprintf("%s-app", username)
   555  			app, err := framework.AsKubeDeveloper.HasController.CreateHasApplicationWithTimeout(ApplicationName, usernamespace, 60*time.Minute)
   556  			if err != nil {
   557  				logError(4, fmt.Sprintf("Unable to create the Application %s: %v", ApplicationName, err))
   558  				FailedResourceCreationsPerThread[threadIndex] += 1
   559  				increaseBar(resourcesBar, resourcesBarMutex)
   560  				continue
   561  			}
   562  			gitopsRepoInterval := 5 * time.Second
   563  			gitopsRepoTimeout := 60 * time.Minute
   564  			repoUrl := utils.ObtainGitOpsRepositoryUrl(app.Status.Devfile)
   565  			if err := utils.WaitUntilWithInterval(func() (done bool, err error) {
   566  				resp, err := http.Get(repoUrl)
   567  				if err != nil {
   568  					return false, fmt.Errorf("unable to request gitops repo %s: %+v", repoUrl, err)
   569  				}
   570  				defer resp.Body.Close()
   571  				if resp.StatusCode == 404 {
   572  					return false, nil
   573  				} else if resp.StatusCode == 200 {
   574  					return true, nil
   575  				} else {
   576  					return false, fmt.Errorf("unexpected response code when requesting gitop repo %s: %v", repoUrl, err)
   577  				}
   578  			}, gitopsRepoInterval, gitopsRepoTimeout); err != nil {
   579  				logError(5, fmt.Sprintf("Unable to create application %s gitops repo within %v: %v", ApplicationName, gitopsRepoTimeout, err))
   580  				FailedResourceCreationsPerThread[threadIndex] += 1
   581  				increaseBar(resourcesBar, resourcesBarMutex)
   582  				continue
   583  			}
   584  
   585  			ComponentDetectionQueryName := fmt.Sprintf("%s-cdq", username)
   586  			cdq, err := framework.AsKubeDeveloper.HasController.CreateComponentDetectionQueryWithTimeout(ComponentDetectionQueryName, usernamespace, componentRepoUrl, "", "", "", false, 60*time.Minute)
   587  			if err != nil {
   588  				logError(6, fmt.Sprintf("Unable to create ComponentDetectionQuery %s: %v", ComponentDetectionQueryName, err))
   589  				FailedResourceCreationsPerThread[threadIndex] += 1
   590  				increaseBar(resourcesBar, resourcesBarMutex)
   591  				continue
   592  			}
   593  			if cdq.Name != ComponentDetectionQueryName {
   594  				logError(7, fmt.Sprintf("Actual cdq name (%s) does not match expected (%s): %v", cdq.Name, ComponentDetectionQueryName, err))
   595  				FailedResourceCreationsPerThread[threadIndex] += 1
   596  				increaseBar(resourcesBar, resourcesBarMutex)
   597  				continue
   598  			}
   599  			if len(cdq.Status.ComponentDetected) > 1 {
   600  				logError(8, fmt.Sprintf("cdq (%s) detected more than 1 component", cdq.Name))
   601  				FailedResourceCreationsPerThread[threadIndex] += 1
   602  				increaseBar(resourcesBar, resourcesBarMutex)
   603  				continue
   604  			}
   605  
   606  			for _, compStub := range cdq.Status.ComponentDetected {
   607  				component, err := framework.AsKubeDeveloper.HasController.CreateComponentFromStubSkipInitialChecks(compStub, usernamespace, "", "", ApplicationName, pipelineSkipInitialChecks)
   608  
   609  				if err != nil {
   610  					logError(9, fmt.Sprintf("Unable to create the Component %s: %v", compStub.ComponentStub.ComponentName, err))
   611  					FailedResourceCreationsPerThread[threadIndex] += 1
   612  					increaseBar(resourcesBar, resourcesBarMutex)
   613  					continue
   614  				}
   615  				if component.Name != compStub.ComponentStub.ComponentName {
   616  					logError(10, fmt.Sprintf("Actual component name (%s) does not match expected (%s): %v", component.Name, compStub.ComponentStub.ComponentName, err))
   617  					FailedResourceCreationsPerThread[threadIndex] += 1
   618  					increaseBar(resourcesBar, resourcesBarMutex)
   619  					continue
   620  				}
   621  				userComponentMap.Store(username, component.Name)
   622  			}
   623  
   624  			resourceCreationTime := time.Since(startTime)
   625  			ResourceCreationTimeSumPerThread[threadIndex] += resourceCreationTime
   626  			if resourceCreationTime > ResourceCreationTimeMaxPerThread[threadIndex] {
   627  				ResourceCreationTimeMaxPerThread[threadIndex] = resourceCreationTime
   628  			}
   629  			SuccessfulResourceCreationsPerThread[threadIndex] += 1
   630  
   631  			chPipelines <- username
   632  			increaseBar(resourcesBar, resourcesBarMutex)
   633  		}
   634  		close(chPipelines)
   635  	}()
   636  
   637  	if waitPipelines {
   638  		go func() {
   639  			defer wg.Done()
   640  			for username := range chPipelines {
   641  				framework := frameworkForUser(username)
   642  				if framework == nil {
   643  					logError(11, fmt.Sprintf("Framework not found for username %s", username))
   644  					increaseBar(pipelinesBar, pipelinesBarMutex)
   645  					continue
   646  				}
   647  				usernamespace := framework.UserNamespace
   648  				componentName := componentForUser(username)
   649  				if componentName == "" {
   650  					logError(12, fmt.Sprintf("Component not found for username %s", username))
   651  					increaseBar(pipelinesBar, pipelinesBarMutex)
   652  					continue
   653  				}
   654  				applicationName := fmt.Sprintf("%s-app", username)
   655  				pipelineCreatedRetryInterval := time.Second * 5
   656  				pipelineCreatedTimeout := time.Minute * 15
   657  				var pipelineRun *v1beta1.PipelineRun
   658  				err := k8swait.Poll(pipelineCreatedRetryInterval, pipelineCreatedTimeout, func() (done bool, err error) {
   659  					pipelineRun, err = framework.AsKubeDeveloper.HasController.GetComponentPipelineRun(componentName, applicationName, usernamespace, "")
   660  					if err != nil {
   661  						time.Sleep(time.Millisecond * time.Duration(rand.IntnRange(10, 200)))
   662  						return false, nil
   663  					}
   664  					return true, nil
   665  				})
   666  				if err != nil {
   667  					logError(13, fmt.Sprintf("PipelineRun for %s/%s has not been created within %v: %v", applicationName, componentName, pipelineCreatedTimeout, err))
   668  					FailedPipelineRunsPerThread[threadIndex] += 1
   669  					increaseBar(pipelinesBar, pipelinesBarMutex)
   670  					continue
   671  				}
   672  				pipelineRunRetryInterval := time.Second * 5
   673  				pipelineRunTimeout := time.Minute * 60
   674  				err = k8swait.Poll(pipelineRunRetryInterval, pipelineRunTimeout, func() (done bool, err error) {
   675  					pipelineRun, err = framework.AsKubeDeveloper.HasController.GetComponentPipelineRun(componentName, applicationName, usernamespace, "")
   676  					if err != nil {
   677  						time.Sleep(time.Millisecond * time.Duration(rand.IntnRange(10, 200)))
   678  						return false, nil
   679  					}
   680  					if pipelineRun.IsDone() {
   681  						succeededCondition := pipelineRun.Status.GetCondition(apis.ConditionSucceeded)
   682  						if succeededCondition.IsFalse() {
   683  							dur := pipelineRun.Status.CompletionTime.Sub(pipelineRun.CreationTimestamp.Time)
   684  							PipelineRunFailedTimeSumPerThread[threadIndex] += dur
   685  							logError(14, fmt.Sprintf("Pipeline run for %s/%s failed due to %v: %v", applicationName, componentName, succeededCondition.Reason, succeededCondition.Message))
   686  							FailedPipelineRunsPerThread[threadIndex] += 1
   687  						} else {
   688  							dur := pipelineRun.Status.CompletionTime.Sub(pipelineRun.CreationTimestamp.Time)
   689  							PipelineRunSucceededTimeSumPerThread[threadIndex] += dur
   690  							if dur > PipelineRunSucceededTimeMaxPerThread[threadIndex] {
   691  								PipelineRunSucceededTimeMaxPerThread[threadIndex] = dur
   692  							}
   693  							SuccessfulPipelineRunsPerThread[threadIndex] += 1
   694  						}
   695  						increaseBar(pipelinesBar, pipelinesBarMutex)
   696  					}
   697  					return pipelineRun.IsDone(), nil
   698  				})
   699  				if err != nil {
   700  					logError(15, fmt.Sprintf("Pipeline run for %s/%s failed to succeed within %v: %v", applicationName, componentName, pipelineRunTimeout, err))
   701  					FailedPipelineRunsPerThread[threadIndex] += 1
   702  					increaseBar(pipelinesBar, pipelinesBarMutex)
   703  					continue
   704  				}
   705  			}
   706  		}()
   707  	}
   708  	wg.Wait()
   709  }