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) +}