This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack-csbench.git

commit e8f4412f1ceb523aa0ee91fcbdab142888b34981
Author: Harikrishna Patnala <harikrishna.patn...@gmail.com>
AuthorDate: Fri Jul 21 13:32:22 2023 +0530

    CloudStack Benchmarking tool
---
 apirunner/apirunner.go     | 364 +++++++++++++++++++++++++++++++++++++++++++++
 apirunner/listCommands.txt |  13 ++
 config/config              |  26 ++++
 config/configreader.go     | 130 ++++++++++++++++
 csbench.go                 | 100 +++++++++++++
 go.mod                     |  15 ++
 go.sum                     | 200 +++++++++++++++++++++++++
 listCommands.txt           |  13 ++
 logger/logger.go           |  21 +++
 9 files changed, 882 insertions(+)

diff --git a/apirunner/apirunner.go b/apirunner/apirunner.go
new file mode 100644
index 0000000..a645dad
--- /dev/null
+++ b/apirunner/apirunner.go
@@ -0,0 +1,364 @@
+package apirunner
+
+import (
+    "encoding/json"
+    "io/ioutil"
+    "net/http"
+    "net/url"
+    "time"
+    "strconv"
+    "fmt"
+    "bufio"
+    "os"
+    "crypto/hmac"
+    "crypto/sha1"
+    "encoding/base64"
+    "strings"
+    "math"
+    "encoding/csv"
+
+    logger "csmetrictool/logger"
+)
+
+var processedAPImap = make(map[string]bool)
+var APIscount = 0
+var SuccessAPIs = 0
+var FailedAPIs = 0
+var TotalTime = 0.0
+
+func generateParams(apiKey string, secretKey string, signatureVersion int, 
expires int, command string, page int, pagesize int, keyword string) url.Values 
{
+    logger.Log("Starting to generate parameters")
+    params := url.Values{}
+    params.Set("apiKey", apiKey)
+    params.Set("response", "json")
+    params.Set("signatureVersion", strconv.Itoa(signatureVersion))
+    params.Set("listall", "true")
+    params.Set("expires", time.Now().UTC().Add(time.Duration(expires) * 
time.Second).Format("2006-01-02T15:04:05Z"))
+
+    params.Set("command", command)
+    if command == "listTemplates" {
+        params.Set("templatefilter", "all")
+    }
+
+    if page != 0 {
+        params.Set("page", strconv.Itoa(page))
+        params.Set("pagesize", strconv.Itoa(pagesize))
+    }
+
+    if keyword != "" {
+        params.Set("keyword", keyword)
+    }
+
+    // Generate and add the signature
+    signature := generateSignature(params.Encode(), secretKey)
+    params.Set("signature", signature)
+
+    return params
+}
+
+func RunAPIs(profileName string, apiURL string, apiKey string, secretKey 
string, expires int, signatureVersion int, iterations int, page int, pagesize 
int, dbProfile int) {
+
+    logger.Log(fmt.Sprintf("Starting to run APIs from listCommands.txt file. 
Each command in the file will be run for multiple iterations and with page 
parameters mentioned in the configuration file."))
+
+       commandsFile := "listCommands.txt"
+
+       // Read commands from file
+       commands, commandsKeywordMap, err := readCommandsFromFile(commandsFile)
+       if err != nil {
+               message := fmt.Sprintf("Error reading commands from file: 
%s\n", err.Error())
+               fmt.Printf(message)
+               logger.Log(message)
+               return
+       }
+    reportAppend := false
+    for _, command := range commands {
+        keyword := commandsKeywordMap[command]
+               if processedAPImap[command] {
+                       reportAppend = true
+               }
+        if page != 0 {
+            if iterations != 1 {
+                message := fmt.Sprintf("Calling API [%s] with page %d and 
pagesize %d -> ", command, page, pagesize)
+                logger.Log(message)
+                fmt.Printf(message)
+            } else {
+                message := fmt.Sprintf("Calling API [%s] -> ", command)
+                logger.Log(message)
+                fmt.Printf(message)
+            }
+
+            params := generateParams(apiKey, secretKey, signatureVersion, 
expires, command, page, pagesize, "")
+            executeAPIandCalculate(profileName, apiURL, command, params, 
iterations, page, pagesize, "", dbProfile, reportAppend)
+            reportAppend = true
+        }
+
+        if len(keyword) != 0 || keyword != "" {
+            fmt.Printf("Calling API [%s] with keyword -> ", command)
+            params := generateParams(apiKey, secretKey, signatureVersion, 
expires, command, 0, 0, keyword)
+            executeAPIandCalculate(profileName, apiURL, command, params, 
iterations, 0, 0, keyword, dbProfile, reportAppend)
+        }
+
+        fmt.Printf("Calling API [%s] -> ", command)
+        params := generateParams(apiKey, secretKey, signatureVersion, expires, 
command, 0, 0, "")
+        executeAPIandCalculate(profileName, apiURL, command, params, 
iterations, 0, 0, "", dbProfile, reportAppend)
+
+        
fmt.Printf("------------------------------------------------------------\n")
+        processedAPImap[command] = true
+    }
+}
+
+func executeAPIandCalculate(profileName string, apiURL string, command string, 
params url.Values, iterations int, page int, pagesize int, keyword string, 
dbProfile int, reportAppend bool) {
+    var minTime = math.MaxFloat64
+    var maxTime = 0.0
+    var avgTime float64
+    var totalTime float64
+    var count float64
+    if iterations != 1 {
+        logger.Log(fmt.Sprintf("Calling API %s for %d number of iterations 
with parameters %s", command, iterations, params))
+        for i := 1; i <= iterations; i++ {
+            // fmt.Printf("%d,", i)
+            logger.Log(fmt.Sprintf("Started with iteration %d for the command 
%s", i, command))
+            elapsedTime, apicount, result := executeAPI(apiURL, params)
+            count = apicount
+            if elapsedTime < minTime {
+                minTime = elapsedTime
+            }
+            if elapsedTime > maxTime {
+                maxTime = elapsedTime
+            }
+            totalTime += elapsedTime
+            if !result {
+                break
+            }
+        }
+        avgTime = totalTime / float64(iterations)
+        message := fmt.Sprintf("count [%.f] : Time in seconds [Min - %.2f] 
[Max - %.2f] [Avg - %.2f]\n", count, minTime, maxTime, avgTime)
+        fmt.Printf("%s", message)
+        logger.Log(fmt.Sprintf("Time taken for the API %s\n %s", command, 
message))
+        saveData(apiURL, count, minTime, maxTime, avgTime, page, pagesize, 
keyword, profileName, command, dbProfile, reportAppend)
+    } else {
+        elapsedTime, apicount, _ := executeAPI(apiURL, params)
+        fmt.Printf("\n  Elapsed time [%.2f seconds] for the count [%.0f]\n", 
elapsedTime, apicount)
+        logger.Log(fmt.Sprintf("\n  Elapsed time [%.2f seconds] for the count 
[%.0f]\n", elapsedTime, apicount))
+        saveData(apiURL, count, elapsedTime, elapsedTime, elapsedTime, page, 
pagesize, keyword, profileName, command, dbProfile, reportAppend)
+    }
+}
+
+func saveData(apiURL string, count float64, minTime float64, maxTime float64, 
avgTime float64, page int, pageSize int, keyword string, user string, filename 
string, dbProfile int, reportAppend bool) {
+
+       parsedURL, err := url.Parse(apiURL)
+       if err != nil {
+        logger.Log(fmt.Sprintf("Error parsing URL : %s with error : %s\n", 
apiURL, err))
+               return
+       }
+       host := parsedURL.Hostname()
+
+       err = os.MkdirAll(fmt.Sprintf("report/accumulated/%s", host), 0755)
+       if err != nil {
+        logger.Log(fmt.Sprintf("Error creating host directory : 
report/accumulated/%s\n", host, err))
+               return
+       }
+
+       err = os.MkdirAll(fmt.Sprintf("report/individual/%s", host), 0755)
+       if err != nil {
+        logger.Log(fmt.Sprintf("Error creating host directory : 
report/individual/%s\n", host, err))
+               return
+       }
+
+       fileMode := os.O_WRONLY | os.O_CREATE
+       if reportAppend {
+               fileMode |= os.O_APPEND
+       } else {
+               fileMode |= os.O_TRUNC
+       }
+
+       individualFile, err := 
os.OpenFile(fmt.Sprintf("report/individual/%s/%s.csv", host, filename), 
fileMode, 0644)
+       if err != nil {
+        logger.Log(fmt.Sprintf("Error opening the file CSV : 
report/individual/%s/%s.csv with error %s\n", host, filename, apiURL, err))
+               return
+       }
+       defer individualFile.Close()
+
+       accumulatedFile, err := 
os.OpenFile(fmt.Sprintf("report/accumulated/%s/%s.csv", host, filename), 
os.O_WRONLY | os.O_CREATE | os.O_APPEND, 0644)
+       if err != nil {
+        logger.Log(fmt.Sprintf("Error opening the file CSV : 
report/accumulated/%s/%s.csv with error %s\n", host, filename, apiURL, err))
+               return
+       }
+       defer accumulatedFile.Close()
+
+    filePointers := []*os.File{individualFile, accumulatedFile}
+    for _, file := range filePointers {
+       writer := csv.NewWriter(file)
+       defer writer.Flush()
+
+        reader := bufio.NewReader(file)
+        firstLine, err := reader.ReadString('\n')
+        fmt.Printf("HARI %s", firstLine)
+        if !strings.Contains(firstLine, "Count") {
+            header := []string{"Count", "MinTime", "MaxTime", "AvgTime", 
"Page", "PageSize", "keyword", "User", "DBprofile"}
+            err = writer.Write(header)
+            if err != nil {
+                logger.Log(fmt.Sprintf("Error writing CSV header for the API: 
%s with error %s\n", apiURL, err))
+                return
+            }
+        }
+
+
+       record := []string{}
+       if page != 0 {
+            record = []string{
+                fmt.Sprintf("%.f", count),
+                fmt.Sprintf("%.2f", minTime),
+                fmt.Sprintf("%.2f", maxTime),
+                fmt.Sprintf("%.2f", avgTime),
+                strconv.Itoa(page),
+                strconv.Itoa(pageSize),
+                keyword,
+                user,
+                strconv.Itoa(dbProfile),
+            }
+       } else {
+               record = []string{
+                fmt.Sprintf("%.f", count),
+                       fmt.Sprintf("%.2f", minTime),
+                       fmt.Sprintf("%.2f", maxTime),
+                       fmt.Sprintf("%.2f", avgTime),
+                fmt.Sprintf("-"),
+                fmt.Sprintf("-"),
+                keyword,
+                       user,
+                       strconv.Itoa(dbProfile),
+               }
+       }
+       err = writer.Write(record)
+       if err != nil {
+            logger.Log(fmt.Sprintf("Error writing to CSV for the API: %s with 
error %s\n", apiURL, err))
+               return
+       }
+    }
+
+       message := fmt.Sprintf("Data saved to report/%s/%s.csv 
successfully.\n", host, filename)
+       logger.Log(message)
+}
+
+func executeAPI() (apiURL string, params url.Values) (float64, float64, bool) {
+       concurrentOptions := 5 // Change this value to the desired number of 
concurrent options.
+
+       var wg sync.WaitGroup
+       requestsChan := make(chan int, concurrentOptions)
+
+       // Start the goroutines to make concurrent API requests.
+       for i := 0; i < concurrentOptions; i++ {
+               wg.Add(1)
+               go func() {
+                       defer wg.Done()
+                       for range requestsChan {
+                elapsedTime, apicount, _ := executeAPI(apiURL, params)
+            }
+               }()
+       }
+
+       // Send the API requests to the channel.
+       for i := 0; i < 10; i++ { // Replace '10' with the total number of API 
requests you want to make.
+               requestsChan <- i
+       }
+
+       close(requestsChan)
+       wg.Wait()
+
+}
+
+func executeAPIconcurrent(apiURL string, params url.Values) (float64, float64, 
bool) {
+    // Send the API request and calculate the time
+    apiURL = fmt.Sprintf("%s?%s", apiURL, params.Encode())
+    logger.Log(fmt.Sprintf("Running the API %s", apiURL))
+    start := time.Now()
+    resp, err := http.Get(apiURL)
+    APIscount++
+    if err != nil {
+        logger.Log(fmt.Sprintf("Error sending API request: %s with error 
%s\n", apiURL, err))
+        FailedAPIs++
+        return 0, 0, false
+    }
+    defer resp.Body.Close()
+    elapsed := time.Since(start)
+    TotalTime += elapsed.Seconds()
+
+    body, err := ioutil.ReadAll(resp.Body)
+    if err != nil {
+        logger.Log(fmt.Sprintf("Error reading API response: %s with error 
%s\n", apiURL, err))
+        FailedAPIs++
+        return 0, 0, false
+    }
+
+    var data map[string]interface{}
+    err = json.Unmarshal([]byte(body), &data)
+    if err != nil {
+        logger.Log(fmt.Sprintf("Error parsing JSON for the API response: %s 
with error %s\n", apiURL, err))
+        FailedAPIs++
+        return 0, 0, false
+    }
+    var key string
+    for k := range data {
+        key = k
+        break
+    }
+
+    count, ok := data[key].(map[string]interface{})["count"].(float64)
+    if !ok {
+        errorCode, ok := 
data[key].(map[string]interface{})["errorcode"].(float64)
+        if ok {
+            errorText := 
data[key].(map[string]interface{})["errortext"].(string)
+            message := fmt.Sprintf(" [Error] while calling the API 
ErrorCode[%.0f] ErrorText[%s]", errorCode, errorText)
+            fmt.Printf(message)
+            logger.Log(message)
+            FailedAPIs++
+            return elapsed.Seconds(), count, false
+        }
+    }
+
+    SuccessAPIs++
+    return elapsed.Seconds(), count, true
+}
+
+func generateSignature(unsignedRequest string, secretKey string) string {
+       unsignedRequest = strings.ToLower(unsignedRequest)
+       hasher := hmac.New(sha1.New, []byte(secretKey))
+       hasher.Write([]byte(unsignedRequest))
+       encryptedBytes := hasher.Sum(nil)
+
+       computedSignature := base64.StdEncoding.EncodeToString(encryptedBytes)
+
+       return computedSignature
+}
+
+func readCommandsFromFile(filename string) ([]string, map[string]string, 
error) {
+       file, err := os.Open(filename)
+       if err != nil {
+               return nil, nil, err
+       }
+       defer file.Close()
+
+       var commands []string
+       var commandsKeywordMap = make(map[string]string)
+       scanner := bufio.NewScanner(file)
+       for scanner.Scan() {
+               line := strings.TrimSpace(scanner.Text())
+               keyword := ""
+               if strings.Contains(line, "keyword=") {
+                       keywordStartIndex := strings.Index(line, "keyword=")
+                       keyword = strings.TrimSpace(line[keywordStartIndex+8:])
+                       line = strings.TrimSpace(line[:keywordStartIndex])
+               }
+               if line != "" {
+                       commands = append(commands, line)
+                       commandsKeywordMap[line] = keyword
+               }
+       }
+
+       if err := scanner.Err(); err != nil {
+               return nil, nil, err
+       }
+
+       return commands, commandsKeywordMap, nil
+}
\ No newline at end of file
diff --git a/apirunner/listCommands.txt b/apirunner/listCommands.txt
new file mode 100644
index 0000000..5164fb3
--- /dev/null
+++ b/apirunner/listCommands.txt
@@ -0,0 +1,13 @@
+listDomains keyword=test
+listAccounts
+listVirtualMachinesMetrics
+listVolumes
+listTemplates
+listNetworks
+listPublicIpAddresses
+listVPCs
+listSnapshots
+listVMSnapshot
+listServiceOfferings
+listDiskOfferings
+listHosts
diff --git a/config/config b/config/config
new file mode 100644
index 0000000..6b4627e
--- /dev/null
+++ b/config/config
@@ -0,0 +1,26 @@
+url = http://localhost:8080/client/api/
+iterations = 4
+page = 1
+pagesize = 20
+
+[admin]
+apikey = 
wLvaRCjiMCEKzo8OnF9uH6ieCGZKG_6gfkVK1Qkx5yi5pde5jlUPXku66A2d2ehIowlIanTfGVwQSxMRryN3nA
+secretkey = 
-9e4AGjrCtDThzSFVJKC3E2hPAnD9YV_bintfOvxnzPzMAuDkRB7CslOEYYkrWVqFeCTslLmxp--Bcg39br8ow
+expires = 600
+signatureversion = 3
+timeout = 3600
+
+[user]
+apikey = 
wLvaRCjiMCEKzo8OnF9uH6ieCGZKG_6gfkVK1Qkx5yi5pde5jlUPXku66A2d2ehIowlIanTfGVwQSxMRryN3nA
+secretkey = 
-9e4AGjrCtDThzSFVJKC3E2hPAnD9YV_bintfOvxnzPzMAuDkRB7CslOEYYkrWVqFeCTslLmxp--Bcg39br8ow
+expires = 600
+signatureversion = 3
+timeout = 3600
+
+[domainadmin]
+apikey = 
wLvaRCjiMCEKzo8OnF9uH6ieCGZKG_6gfkVK1Qkx5yi5pde5jlUPXku66A2d2ehIowlIanTfGVwQSxMRryN3nA
+secretkey = 
-9e4AGjrCtDThzSFVJKC3E2hPAnD9YV_bintfOvxnzPzMAuDkRB7CslOEYYkrWVqFeCTslLmxp--Bcg39br8ow
+expires = 600
+signatureversion = 3
+timeout = 3600
+
diff --git a/config/configreader.go b/config/configreader.go
new file mode 100644
index 0000000..3509967
--- /dev/null
+++ b/config/configreader.go
@@ -0,0 +1,130 @@
+package config
+
+import (
+    "bufio"
+    "fmt"
+    "os"
+    "strings"
+    "net/url"
+)
+
+type Profile struct {
+    Name             string
+    ApiKey           string
+    SecretKey        string
+    Expires          int
+    SignatureVersion int
+    Timeout          int
+}
+
+var URL = ""
+var Iterations = 1
+var Page = 0
+var PageSize = 0
+var Host = ""
+
+func ReadProfiles(filePath string) (map[int]*Profile, error) {
+    file, err := os.Open(filePath)
+    if err != nil {
+        return nil, fmt.Errorf("error opening file: %w", err)
+    }
+    defer file.Close()
+
+    scanner := bufio.NewScanner(file)
+    profiles := make(map[int]*Profile)
+    var currentProfile string
+
+    i := 0
+    for scanner.Scan() {
+        line := scanner.Text()
+        line = strings.TrimSpace(line)
+
+        if line == "" || strings.HasPrefix(line, ";") {
+            continue
+        }
+
+        if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
+            currentProfile = line[1 : len(line)-1]
+            i++
+            profiles[i] = &Profile{}
+            profiles[i].Name = currentProfile
+        } else {
+            // Parse key-value pairs within the profile
+            parts := strings.SplitN(line, "=", 2)
+            if len(parts) == 2 {
+                key := strings.TrimSpace(parts[0])
+                value := strings.TrimSpace(parts[1])
+
+                switch strings.ToLower(key) {
+                case "apikey":
+                    profiles[i].ApiKey = value
+                case "secretkey":
+                    profiles[i].SecretKey = value
+                case "url":
+                    URL = value
+                case "iterations":
+                    var iterations int
+                    _, err := fmt.Sscanf(value, "%d", &iterations)
+                    if err == nil {
+                        Iterations = iterations
+                    }
+                case "page":
+                    var page int
+                    _, err := fmt.Sscanf(value, "%d", &page)
+                    if err == nil {
+                        Page = page
+                    }
+                case "pagesize":
+                    var pagesize int
+                    _, err := fmt.Sscanf(value, "%d", &pagesize)
+                    if err == nil {
+                        PageSize = pagesize
+                    }
+                case "expires":
+                    var expires int
+                    _, err := fmt.Sscanf(value, "%d", &expires)
+                    if err == nil {
+                        profiles[i].Expires = expires
+                    }
+                case "signatureversion":
+                    var signatureVersion int
+                    _, err := fmt.Sscanf(value, "%d", &signatureVersion)
+                    if err == nil {
+                        profiles[i].SignatureVersion = signatureVersion
+                    }
+                case "timeout":
+                    var timeout int
+                    _, err := fmt.Sscanf(value, "%d", &timeout)
+                    if err == nil {
+                        profiles[i].Timeout = timeout
+                    }
+                }
+            }
+        }
+    }
+
+    if err := scanner.Err(); err != nil {
+        return nil, fmt.Errorf("error scanning file: %w", err)
+    }
+
+    if len(profiles) == 0 {
+        fmt.Println("No roles are defined in the configuration file")
+        return nil, fmt.Errorf("No roles are defined in the configuration 
file: %w", err)
+    }
+
+    if URL == "" {
+        fmt.Println("URL not found in the configuration, please verify")
+        os.Exit(1)
+    }
+
+       parsedURL, err := url.Parse(URL)
+       if err != nil {
+        fmt.Println("Error parsing URL : %s with error : %s\n", URL, err)
+               return nil, fmt.Errorf("Error parsing URL : %s with error : 
%s\n", URL, err)
+       }
+       Host = parsedURL.Hostname()
+
+
+    return profiles, nil
+}
+
diff --git a/csbench.go b/csbench.go
new file mode 100644
index 0000000..74d5e10
--- /dev/null
+++ b/csbench.go
@@ -0,0 +1,100 @@
+package main
+
+import (
+    "os"
+    "fmt"
+    "flag"
+    "strings"
+
+    config "csmetrictool/config"
+    apirunner "csmetrictool/apirunner"
+    logger "csmetrictool/logger"
+)
+
+var (
+    profiles = make(map[int]*config.Profile)
+)
+
+func readConfigurations() map[int]*config.Profile {
+    profiles, err := config.ReadProfiles("config/config")
+    if err != nil {
+        fmt.Println("Error reading profiles:", err)
+        os.Exit(1)
+    }
+
+    return profiles
+}
+
+func logConfigurationDetails(profiles map[int]*config.Profile) {
+    apiURL := config.URL
+    iterations := config.Iterations
+    page := config.Page
+    pagesize := config.PageSize
+       host := config.Host
+
+       userProfileNames := make([]string, 0, len(profiles))
+       for _, profile := range profiles {
+               userProfileNames = append(userProfileNames, profile.Name)
+       }
+
+    fmt.Printf("\n\n\033[1;34mBenchmarking the CloudStack environment [%s] 
with the following configuration\033[0m\n\n", apiURL)
+    fmt.Printf("Management server : %s\n", host)
+    fmt.Printf("Roles : %s\n", strings.Join(userProfileNames, ","))
+    fmt.Printf("Iterations : %d\n", iterations)
+    fmt.Printf("Page : %d\n", page)
+    fmt.Printf("PageSize : %d\n\n", pagesize)
+
+    logger.Log(fmt.Sprintf("Found %d profiles in the configuration: ", 
len(profiles)))
+
+}
+
+func logReport() {
+    fmt.Printf("\n\n\nLog file : csmetrics.log\n")
+    fmt.Printf("Reports directory per API : report/%s/\n", config.Host)
+    fmt.Printf("Number of APIs : %d\n", apirunner.APIscount)
+    fmt.Printf("Successful APIs : %d\n", apirunner.SuccessAPIs)
+    fmt.Printf("Failed APIs : %d\n", apirunner.FailedAPIs)
+    fmt.Printf("Time in seconds per API: %.2f (avg)\n", 
apirunner.TotalTime/float64(apirunner.APIscount))
+    
fmt.Printf("\n\n\033[1;34m--------------------------------------------------------------------------------\033[0m\n"
 +
+        "                            Done with benchmarking\n" +
+        
"\033[1;34m--------------------------------------------------------------------------------\033[0m\n\n")
+}
+
+func main() {
+       dbprofile := flag.Int("dbprofile", 0, "DB profile number")
+       flag.Usage = func() {
+               fmt.Fprintf(os.Stderr, "Usage: go run csmetrictool.go 
--dbprofile <DB profile number>\n")
+               fmt.Fprintf(os.Stderr, "Options:\n")
+               flag.PrintDefaults()
+       }
+       flag.Parse()
+       if *dbprofile < 0 {
+               fmt.Println("Invalid DB profile number. Please provide a 
positive integer.")
+               return
+       }
+
+    profiles = readConfigurations()
+    apiURL := config.URL
+    iterations := config.Iterations
+    page := config.Page
+    pagesize := config.PageSize
+
+       logger.Log(fmt.Sprintf("\nStarted benchmarking the CloudStack 
environment [%s]", apiURL))
+
+    logConfigurationDetails(profiles)
+
+    for i := 1; i <= len(profiles); i++ {
+        profile := profiles[i]
+        userProfileName := profile.Name
+        logger.Log(fmt.Sprintf("Using profile %d.%s for benchmarking", i, 
userProfileName))
+        
fmt.Printf("\n\033[1;34m============================================================\033[0m\n")
+        fmt.Printf("                    Profile: [%s]\n", userProfileName)
+        
fmt.Printf("\033[1;34m============================================================\033[0m\n")
+        apirunner.RunAPIs(userProfileName, apiURL, profile.ApiKey, 
profile.SecretKey, profile.Expires, profile.SignatureVersion, iterations, page, 
pagesize, *dbprofile)
+    }
+
+    logReport()
+
+       logger.Log(fmt.Sprintf("Done with benchmarking the CloudStack 
environment [%s]", apiURL))
+}
+
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..75d6496
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,15 @@
+module csmetrictool
+
+go 1.17
+
+require (
+       git.sr.ht/~sbinet/gg v0.4.1 // indirect
+       github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect
+       github.com/go-fonts/liberation v0.3.1 // indirect
+       github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 // indirect
+       github.com/go-pdf/fpdf v0.8.0 // indirect
+       github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // 
indirect
+       golang.org/x/image v0.7.0 // indirect
+       golang.org/x/text v0.9.0 // indirect
+       gonum.org/v1/plot v0.13.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..1640139
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,200 @@
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod 
h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod 
h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20221208032759-85de2813cf6b/go.mod 
h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod 
h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
+gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod 
h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
+gioui.org v0.0.0-20230418224039-a7c9ca99f3be/go.mod 
h1:8CFQM/4LurRd9G3NUYdacFb9j2pK0LrAyVO2mAZo4mw=
+gioui.org v0.0.0-20230506155350-febadd314531/go.mod 
h1:8CFQM/4LurRd9G3NUYdacFb9j2pK0LrAyVO2mAZo4mw=
+gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod 
h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
+gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod 
h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
+gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7/go.mod 
h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
+gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
+gioui.org/x v0.0.0-20230426160849-752f112c7a59/go.mod 
h1:nMctdnZS2HKxfSXb+bCPnhw1n2LLsXoxtTarZjtIBuI=
+git.sr.ht/~jackmordaunt/go-toast v1.0.0/go.mod 
h1:aIuRX/HdBOz7yRS8rOVYQCwJQlFS7DbYBTpUV0SHeeg=
+git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo=
+git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod 
h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE=
+git.sr.ht/~sbinet/gg v0.3.1/go.mod 
h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
+git.sr.ht/~sbinet/gg v0.4.1 h1:YccqPPS57/TpqX2fFnSRlisrqQ43gEdqVm3JtabPrp0=
+git.sr.ht/~sbinet/gg v0.4.1/go.mod 
h1:xKrQ22W53kn8Hlq+gzYeyyohGMwR8yGgSMlVpY/mHGc=
+git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod 
h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
+github.com/BurntSushi/toml v0.3.1/go.mod 
h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod 
h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod 
h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
+github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod 
h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
+github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod 
h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
+github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b 
h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
+github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod 
h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
+github.com/andybalholm/stroke v0.0.0-20221221101821-bd29b49d73f0/go.mod 
h1:ccdDYaY5+gO+cbnQdFxEXqfy0RkoV25H3jLXUDNM3wg=
+github.com/boombuler/barcode v1.0.0/go.mod 
h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/boombuler/barcode v1.0.1/go.mod 
h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/esiqveland/notify v0.11.0/go.mod 
h1:63UbVSaeJwF0LVJARHFuPgUAoM7o1BEvCZyknsuonBc=
+github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod 
h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/fogleman/gg v1.3.0/go.mod 
h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/go-fonts/dejavu v0.1.0 
h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ=
+github.com/go-fonts/dejavu v0.1.0/go.mod 
h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
+github.com/go-fonts/latin-modern v0.2.0/go.mod 
h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
+github.com/go-fonts/latin-modern v0.3.0/go.mod 
h1:ysEQXnuT/sCDOAONxC7ImeEDVINbltClhasMAqEtRK0=
+github.com/go-fonts/latin-modern v0.3.1 
h1:/cT8A7uavYKvglYXvrdDw4oS5ZLkcOU22fa2HJ1/JVM=
+github.com/go-fonts/latin-modern v0.3.1/go.mod 
h1:ysEQXnuT/sCDOAONxC7ImeEDVINbltClhasMAqEtRK0=
+github.com/go-fonts/liberation v0.1.1/go.mod 
h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
+github.com/go-fonts/liberation v0.2.0/go.mod 
h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
+github.com/go-fonts/liberation v0.3.0/go.mod 
h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yloUL5Fi8GTGY=
+github.com/go-fonts/liberation v0.3.1 
h1:9RPT2NhUpxQ7ukUvz3jeUckmN42T9D9TpjtQcqK/ceM=
+github.com/go-fonts/liberation v0.3.1/go.mod 
h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yloUL5Fi8GTGY=
+github.com/go-fonts/stix v0.1.0/go.mod 
h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod 
h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod 
h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod 
h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
+github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod 
h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
+github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 
h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs=
+github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod 
h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM=
+github.com/go-ole/go-ole v1.2.6/go.mod 
h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-pdf/fpdf v0.5.0/go.mod 
h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
+github.com/go-pdf/fpdf v0.6.0/go.mod 
h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
+github.com/go-pdf/fpdf v0.8.0 h1:IJKpdaagnWUeSkUFUjTcSzTppFxmv8ucGQyNPQWxYOQ=
+github.com/go-pdf/fpdf v0.8.0/go.mod 
h1:gfqhcNwXrsd3XYKte9a7vM3smvU/jB4ZRDrmWSxpfdc=
+github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae/go.mod 
h1:KmrpWuSMFcO2yjmyhGpnBGQHSKAoEgMTSSzvLDzCuEA=
+github.com/go-text/typesetting v0.0.0-20230502123426-87572f5551cf/go.mod 
h1:KmrpWuSMFcO2yjmyhGpnBGQHSKAoEgMTSSzvLDzCuEA=
+github.com/go-text/typesetting-utils v0.0.0-20230412163830-89e4bcfa3ecc/go.mod 
h1:RaqFwjcYyM5BjbYGwON0H5K0UqwO3sJlo9ukKha80ZE=
+github.com/godbus/dbus/v5 v5.0.3/go.mod 
h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.0.6/go.mod 
h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 
h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod 
h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/google/go-cmp v0.5.8/go.mod 
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/jezek/xgb v1.0.0/go.mod 
h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
+github.com/jung-kurt/gofpdf v1.0.0/go.mod 
h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod 
h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/kisielk/gotool v1.0.0/go.mod 
h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/phpdave11/gofpdf v1.4.2/go.mod 
h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
+github.com/phpdave11/gofpdi v1.0.12/go.mod 
h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
+github.com/phpdave11/gofpdi v1.0.13/go.mod 
h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
+github.com/pkg/errors v0.8.1/go.mod 
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1/go.mod 
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod 
h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
+github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod 
h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
+github.com/stretchr/testify v1.2.2/go.mod 
h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/yuin/goldmark v1.2.1/go.mod 
h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod 
h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod 
h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod 
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod 
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod 
h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod 
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.1.0/go.mod 
h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
+golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod 
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod 
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod 
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod 
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod 
h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
+golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod 
h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
+golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod 
h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod 
h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
+golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea 
h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
+golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod 
h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
+golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod 
h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
+golang.org/x/exp/shiny v0.0.0-20230425010034-47ecfdc1ba53/go.mod 
h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod 
h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod 
h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod 
h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod 
h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod 
h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/image v0.3.0/go.mod 
h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
+golang.org/x/image v0.5.0/go.mod 
h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
+golang.org/x/image v0.6.0/go.mod 
h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
+golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
+golang.org/x/image v0.7.0/go.mod 
h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod 
h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod 
h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod 
h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod 
h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod 
h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod 
h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod 
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod 
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod 
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod 
h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod 
h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod 
h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod 
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod 
h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod 
h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod 
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod 
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod 
h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.1.0/go.mod 
h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.12/go.mod 
h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.2.0/go.mod 
h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
+golang.org/x/tools v0.6.0/go.mod 
h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.7.0/go.mod 
h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod 
h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/gonum v0.8.2/go.mod 
h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
+gonum.org/v1/gonum v0.9.3/go.mod 
h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=
+gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM=
+gonum.org/v1/gonum v0.13.0/go.mod 
h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU=
+gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod 
h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
+gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod 
h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
+gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=
+gonum.org/v1/plot v0.10.1/go.mod 
h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo=
+gonum.org/v1/plot v0.13.0 h1:yb2Z/b8bY5h/xC4uix+ujJ+ixvPUvBmUOtM73CJzpsw=
+gonum.org/v1/plot v0.13.0/go.mod 
h1:mV4Bpu4PWTgN2CETURNF8hCMg7EtlZqJYCcmYo/t4Co=
+honnef.co/go/tools v0.1.3/go.mod 
h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
+rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/listCommands.txt b/listCommands.txt
new file mode 100644
index 0000000..ffe9c53
--- /dev/null
+++ b/listCommands.txt
@@ -0,0 +1,13 @@
+listDomains keyword=test
+listAccounts
+listVirtualMachines
+listVolumes
+listTemplates
+listNetworks
+listPublicIpAddresses
+listVPCs
+listSnapshots
+listVMSnapshot
+listServiceOfferings
+listDiskOfferings
+listHosts
diff --git a/logger/logger.go b/logger/logger.go
new file mode 100644
index 0000000..6ad1b4b
--- /dev/null
+++ b/logger/logger.go
@@ -0,0 +1,21 @@
+package logger
+
+import (
+       "log"
+       "os"
+)
+
+var logger *log.Logger
+
+func init() {
+       logFile, err := os.OpenFile("csmetrics.log", 
os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
+       if err != nil {
+               log.Fatalf("Failed to create log file: %v", err)
+       }
+
+       logger = log.New(logFile, "", log.LstdFlags|log.Lshortfile)
+}
+
+func Log(message string) {
+       logger.Printf("%s", message)
+}


Reply via email to