Skip to content

Commit

Permalink
Merge pull request #4823 from piyushsingariya/refactor/perf-functions
Browse files Browse the repository at this point in the history
[mesheryctl] Support quoted args and refactoring code in `mesheryctl perf`
  • Loading branch information
leecalcote committed Jan 17, 2022
2 parents 5ef4ee0 + 41d0d29 commit cd18f99
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 172 deletions.
95 changes: 25 additions & 70 deletions mesheryctl/internal/cli/root/perf/apply.go
@@ -1,10 +1,8 @@
package perf

import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
Expand Down Expand Up @@ -140,38 +138,23 @@ mesheryctl perf apply local-perf --url https://192.168.1.15/productpage --mesh i
if len(args) == 0 {
return ErrNoProfileName()
}

// handles spaces in args if quoted args passed
for i, arg := range args {
args[i] = strings.ReplaceAll(arg, " ", "%20")
}
// join all args to form profile name
profileName = strings.Join(args, "%20")

// Check if the profile name is valid, if not prompt the user to create a new one
log.Debug("Fetching performance profile")

req, err = utils.NewRequest("GET", mctlCfg.GetBaseMesheryURL()+"/api/user/performance/profiles?search="+profileName, nil)
profiles, _, err := fetchPerformanceProfiles(mctlCfg.GetBaseMesheryURL(), profileName, pageSize, pageNumber-1)
if err != nil {
return err
}

resp, err := client.Do(req)
if err != nil {
return ErrFailRequest(err)
}

var response *models.PerformanceProfilesAPIResponse
// failsafe for the case when a valid uuid v4 is not an id of any pattern (bad api call)
if resp.StatusCode != 200 {
return ErrFailReqStatus(resp.StatusCode)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, utils.PerfError("failed to read response body"))
}
err = json.Unmarshal(body, &response)
if err != nil {
return ErrFailUnmarshal(err)
}

index := 0
if len(response.Profiles) == 0 {
if len(profiles) == 0 {
// if the provided performance profile does not exist, prompt the user to create a new one

// skip asking confirmation if -y flag used
Expand All @@ -191,28 +174,31 @@ mesheryctl perf apply local-perf --url https://192.168.1.15/productpage --mesh i
return ErrNoProfileFound()
}
} else {
if len(response.Profiles) == 1 {
profileID = response.Profiles[0].ID.String()
} else {
// Multiple profiles with same name
index = multipleProfileConfirmation(response.Profiles)
profileID = response.Profiles[index].ID.String()
if len(profiles) == 1 { // if single performance profile found set profileID
profileID = profiles[0].ID.String()
} else { // multiple profiles found with matching name ask for profile index
data := profilesToStringArrays(profiles)
index, err = userPrompt("profile", "Enter index of the profile", data)
if err != nil {
return err
}
profileID = profiles[index].ID.String()
}
// what if user passed profile-name but didn't passed the url
// we use url from performance profile
if testURL == "" {
testURL = response.Profiles[index].Endpoints[0]
testURL = profiles[index].Endpoints[0]
}

// reset profile name without %20
// pull test configuration from the profile only if a test configuration is not provided
if filePath == "" {
profileName = response.Profiles[index].Name
loadGenerator = response.Profiles[index].LoadGenerators[0]
concurrentRequests = strconv.Itoa(response.Profiles[index].ConcurrentRequest)
qps = strconv.Itoa(response.Profiles[index].QPS)
testDuration = response.Profiles[index].Duration
testMesh = response.Profiles[index].ServiceMesh
profileName = profiles[index].Name
loadGenerator = profiles[index].LoadGenerators[0]
concurrentRequests = strconv.Itoa(profiles[index].ConcurrentRequest)
qps = strconv.Itoa(profiles[index].QPS)
testDuration = profiles[index].Duration
testMesh = profiles[index].ServiceMesh
}
}

Expand Down Expand Up @@ -253,7 +239,7 @@ mesheryctl perf apply local-perf --url https://192.168.1.15/productpage --mesh i

log.Info("Initiating Performance test ...")

resp, err = client.Do(req)
resp, err := client.Do(req)
if err != nil {
return ErrFailRequest(err)
}
Expand All @@ -276,37 +262,6 @@ mesheryctl perf apply local-perf --url https://192.168.1.15/productpage --mesh i
},
}

func multipleProfileConfirmation(profiles []models.PerformanceProfile) int {
reader := bufio.NewReader(os.Stdin)

for index, a := range profiles {
fmt.Printf("Index: %v\n", index)
fmt.Printf("Name: %v\n", a.Name)
fmt.Printf("ID: %s\n", a.ID.String())
fmt.Printf("Endpoint: %v\n", a.Endpoints[0])
fmt.Printf("Load Generators: %v\n", a.LoadGenerators[0])
fmt.Println("---------------------")
}

for {
fmt.Printf("Enter the index of profile: ")
response, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}
response = strings.ToLower(strings.TrimSpace(response))
index, err := strconv.Atoi(response)
if err != nil {
log.Info(err)
}
if index < 0 || index >= len(profiles) {
log.Info("Invalid index")
} else {
return index
}
}
}

func init() {
applyCmd.Flags().StringVar(&testURL, "url", "", "(optional) Endpoint URL to test (required with --profile)")
applyCmd.Flags().StringVar(&testName, "name", "", "(optional) Name of the Test")
Expand Down
3 changes: 2 additions & 1 deletion mesheryctl/internal/cli/root/perf/apply_test.go
Expand Up @@ -171,5 +171,6 @@ func resetVariables() {
loadGenerator = "fortio"
filePath = ""
outputFormatFlag = ""
expand = false
viewSingleProfile = false
viewSingleResult = false
}
118 changes: 49 additions & 69 deletions mesheryctl/internal/cli/root/perf/profile.go
Expand Up @@ -8,13 +8,10 @@ import (
"strconv"
"strings"

"github.com/gofrs/uuid"
"github.com/manifoldco/promptui"
termbox "github.com/nsf/termbox-go"
log "github.com/sirupsen/logrus"

"github.com/layer5io/meshery/internal/sql"

"github.com/ghodss/yaml"
"github.com/layer5io/meshery/mesheryctl/internal/cli/root/config"
"github.com/layer5io/meshery/mesheryctl/pkg/utils"
Expand All @@ -25,22 +22,10 @@ import (
)

var (
pageSize = 25
expand bool
pageSize = 25
viewSingleProfile bool
)

type profileStruct struct {
Name string
ID *uuid.UUID
TotalResults int
Endpoints []string
QPS int
Duration string
Loadgenerators []string
ServiceMesh string
LastRun *sql.Time
}

var profileCmd = &cobra.Command{
Use: "profile [profile-name]",
Short: "List performance profiles",
Expand All @@ -67,55 +52,54 @@ mesheryctl perf profile test --view
return ErrMesheryConfig(err)
}

profileURL := mctlCfg.GetBaseMesheryURL() + "/api/user/performance/profiles"

if len(args) > 0 {
// Merge args to get profile-name
searchString = strings.Join(args, "%20")
// handles spaces in args if quoted args passed
for i, arg := range args {
args[i] = strings.ReplaceAll(arg, " ", "%20")
}
// Merge args to get profile-name
searchString = strings.Join(args, "%20")

data, expandedData, body, err := fetchPerformanceProfiles(profileURL, searchString)
profiles, _, err := fetchPerformanceProfiles(mctlCfg.GetBaseMesheryURL(), searchString, pageSize, pageNumber-1)
if err != nil {
return err
}

if len(data) == 0 {
if len(profiles) == 0 {
log.Info("No Performance Profiles to display")
return nil
}

// get profiles as string arrays for printing tabular format profiles
data := profilesToStringArrays(profiles)

// print in json/yaml format
if outputFormatFlag != "" {
var tempStruct *models.PerformanceProfilesAPIResponse
err := json.Unmarshal(body, &tempStruct)
if err != nil {
return ErrFailUnmarshal(err)
}
body, _ = json.Marshal(tempStruct.Profiles)
body, _ := json.Marshal(profiles)
if outputFormatFlag == "yaml" {
body, _ = yaml.JSONToYAML(body)
} else if outputFormatFlag != "json" {
return ErrInvalidOutputChoice()
}
log.Info(string(body))
} else if !expand {
} else if !viewSingleProfile { // print all profiles
utils.PrintToTable([]string{"Name", "ID", "RESULTS", "Load-Generator", "Last-Run"}, data)
} else {
// if data consists only one profile, directly print profile
} else { // print single profile
index := 0
if len(data) > 1 {
// if profiles more than one profile, ask for profile index
if len(profiles) > 1 {
index, err = userPrompt("profile", "Enter index of the profile", data)
if err != nil {
return err
}
}

a := expandedData[index]
a := profiles[index]

fmt.Printf("Name: %v\n", a.Name)
fmt.Printf("ID: %s\n", a.ID.String())
fmt.Printf("Total Results: %d\n", a.TotalResults)
fmt.Printf("Endpoint: %v\n", a.Endpoints[0])
fmt.Printf("Load Generators: %v\n", a.Loadgenerators[0])
fmt.Printf("Load Generators: %v\n", a.LoadGenerators[0])
fmt.Printf("Test run duration: %v\n", a.Duration)
fmt.Printf("QPS: %d\n", a.QPS)
fmt.Printf("Service Mesh: %v\n", a.ServiceMesh)
Expand All @@ -130,77 +114,68 @@ mesheryctl perf profile test --view
},
}

// Fetch all the profiles
func fetchPerformanceProfiles(url, searchString string) ([][]string, []profileStruct, []byte, error) {
// Fetch performance profiles
func fetchPerformanceProfiles(baseURL, searchString string, pageSize, pageNumber int) ([]models.PerformanceProfile, []byte, error) {
client := &http.Client{}
var response *models.PerformanceProfilesAPIResponse

url := baseURL + "/api/user/performance/profiles"

// update the url
tempURL := fmt.Sprintf("%s?page_size=%d&page=%d", url, pageSize, resultPage-1)
url = fmt.Sprintf("%s?page_size=%d&page=%d", url, pageSize, pageNumber)
if searchString != "" {
tempURL = tempURL + "&search=" + searchString
url = url + "&search=" + searchString
}

req, err := utils.NewRequest("GET", tempURL, nil)
log.Debug(url)

req, err := utils.NewRequest("GET", url, nil)
if err != nil {
return nil, nil, nil, err
return nil, nil, err
}

resp, err := client.Do(req)
if err != nil {
return nil, nil, nil, ErrFailRequest(err)
return nil, nil, ErrFailRequest(err)
}

// failsafe for not being authenticated
if utils.ContentTypeIsHTML(resp) {
return nil, nil, nil, ErrUnauthenticated()
return nil, nil, ErrUnauthenticated()
}
// failsafe for the case when a valid uuid v4 is not an id of any pattern (bad api call)
if resp.StatusCode != 200 {
return nil, nil, nil, ErrFailReqStatus(resp.StatusCode)
return nil, nil, ErrFailReqStatus(resp.StatusCode)
}

defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, nil, errors.Wrap(err, utils.PerfError("failed to read response body"))
return nil, nil, errors.Wrap(err, utils.PerfError("failed to read response body"))
}

err = json.Unmarshal(body, &response)
if err != nil {
return nil, nil, nil, ErrFailUnmarshal(err)
return nil, nil, ErrFailUnmarshal(err)
}

return response.Profiles, body, nil
}

// add profiles as string arrays to print in a tabular format
func profilesToStringArrays(profiles []models.PerformanceProfile) [][]string {
var data [][]string
var expendedData []profileStruct

for _, profile := range response.Profiles {
// adding stuff to data for list output
for _, profile := range profiles {
// adding profile to data for list output
if profile.LastRun != nil {
data = append(data, []string{profile.Name, profile.ID.String(), fmt.Sprintf("%d", profile.TotalResults), profile.LoadGenerators[0], profile.LastRun.Time.Format("2006-01-02 15:04:05")})
} else {
data = append(data, []string{profile.Name, profile.ID.String(), fmt.Sprintf("%d", profile.TotalResults), profile.LoadGenerators[0], ""})
}
// adding stuff to expendedData for expended output
a := profileStruct{
Name: profile.Name,
ID: profile.ID,
TotalResults: profile.TotalResults,
Endpoints: profile.Endpoints,
QPS: profile.QPS,
Duration: profile.Duration,
Loadgenerators: profile.LoadGenerators,
LastRun: profile.LastRun,
ServiceMesh: profile.ServiceMesh,
}
expendedData = append(expendedData, a)
}

return data, expendedData, body, nil
}

func init() {
profileCmd.Flags().BoolVarP(&expand, "view", "", false, "(optional) View single performance profile with more info")
profileCmd.Flags().IntVarP(&resultPage, "page", "p", 1, "(optional) List next set of performance results with --page (default = 1)")
return data
}

func userPrompt(key string, label string, data [][]string) (int, error) {
Expand Down Expand Up @@ -250,3 +225,8 @@ func userPrompt(key string, label string, data [][]string) (int, error) {
}
return index, nil
}

func init() {
profileCmd.Flags().BoolVarP(&viewSingleProfile, "view", "", false, "(optional) View single performance profile with more info")
profileCmd.Flags().IntVarP(&pageNumber, "page", "p", 1, "(optional) List next set of performance results with --page (default = 1)")
}

0 comments on commit cd18f99

Please sign in to comment.