2024-12-04 16:40:52 -06:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"loadr/lib"
|
2024-12-04 17:26:34 -06:00
|
|
|
"math"
|
2024-12-04 16:40:52 -06:00
|
|
|
"os"
|
|
|
|
)
|
|
|
|
|
2024-12-04 17:26:34 -06:00
|
|
|
// Version number of the loadr tool
|
2024-12-04 16:40:52 -06:00
|
|
|
var version string = "1.0.2"
|
|
|
|
|
2024-12-04 17:26:34 -06:00
|
|
|
// parseCommandLine processes command line arguments and returns configuration parameters
|
|
|
|
// Returns:
|
|
|
|
// - requestsPerSecond: target rate of requests
|
|
|
|
// - maxRequests: total number of requests to send
|
|
|
|
// - url: target endpoint
|
|
|
|
// - patterns: array of request patterns
|
|
|
|
// - jsonFilePath: path to JSON file containing request body
|
|
|
|
// - bearerToken: authorization token
|
2024-12-04 16:40:52 -06:00
|
|
|
func parseCommandLine() (float64, int, string, []lib.RequestPattern, string, string) {
|
|
|
|
requestsPerSecond := flag.Float64("rate", 10, "Number of requests per second")
|
|
|
|
maxRequests := flag.Int("max", 50, "Maximum number of requests to send (0 for unlimited)")
|
|
|
|
url := flag.String("url", "https://example.com", "The URL to make requests to")
|
2024-12-04 17:26:34 -06:00
|
|
|
pattern := flag.String("pattern", "", `Request pattern (e.g., "5p" for 5 POSTs, "1p5g" for sequential, "20%p80%g" for probabilistic)`)
|
2024-12-04 16:40:52 -06:00
|
|
|
jsonFilePath := flag.String("json", "", "Path to the JSON file with request data")
|
|
|
|
bearerToken := flag.String("token", "", "Bearer token for authorization")
|
|
|
|
versionFlag := flag.Bool("version", false, "Print the version and exit")
|
|
|
|
versionFlagShort := flag.Bool("v", false, "Print the version and exit")
|
|
|
|
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
if *versionFlag || *versionFlagShort {
|
|
|
|
fmt.Println("Version:", version)
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
patterns, err := parsePattern(*pattern)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Warning: %v. Using default GET pattern.\n", err)
|
2024-12-04 17:26:34 -06:00
|
|
|
patterns = []lib.RequestPattern{{Verb: "GET", Sequence: 1}}
|
2024-12-04 16:40:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return *requestsPerSecond, *maxRequests, *url, patterns, *jsonFilePath, *bearerToken
|
|
|
|
}
|
|
|
|
|
2024-12-04 17:26:34 -06:00
|
|
|
// parsePattern interprets the pattern string and converts it to RequestPattern structs
|
|
|
|
// Pattern formats:
|
|
|
|
// - Sequential: "5p" (5 POSTs), "1p5g" (1 POST then 5 GETs)
|
|
|
|
// - Probabilistic: "20%p80%g" (20% POSTs, 80% GETs)
|
|
|
|
//
|
|
|
|
// Returns error if pattern is invalid or percentages don't sum to 100
|
2024-12-04 16:40:52 -06:00
|
|
|
func parsePattern(pattern string) ([]lib.RequestPattern, error) {
|
|
|
|
if pattern == "" {
|
2024-12-04 17:26:34 -06:00
|
|
|
return []lib.RequestPattern{{Verb: "GET", Sequence: 1}}, nil
|
2024-12-04 16:40:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
var patterns []lib.RequestPattern
|
|
|
|
var current int
|
2024-12-04 17:26:34 -06:00
|
|
|
isProbabilistic := false
|
2024-12-04 16:40:52 -06:00
|
|
|
|
2024-12-04 17:26:34 -06:00
|
|
|
// Parse pattern string character by character
|
2024-12-04 16:40:52 -06:00
|
|
|
for i := 0; i < len(pattern); i++ {
|
|
|
|
c := pattern[i]
|
|
|
|
switch {
|
|
|
|
case c >= '0' && c <= '9':
|
|
|
|
current = current*10 + int(c-'0')
|
2024-12-04 17:26:34 -06:00
|
|
|
case c == '%':
|
|
|
|
isProbabilistic = true
|
|
|
|
case c == 'p' || c == 'P' || c == 'g' || c == 'G':
|
|
|
|
verb := "GET"
|
|
|
|
if c == 'p' || c == 'P' {
|
|
|
|
verb = "POST"
|
2024-12-04 16:40:52 -06:00
|
|
|
}
|
2024-12-04 17:26:34 -06:00
|
|
|
|
|
|
|
if isProbabilistic {
|
|
|
|
patterns = append(patterns, lib.RequestPattern{
|
|
|
|
Verb: verb,
|
|
|
|
Percentage: float64(current),
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
if current == 0 {
|
|
|
|
current = 1 // Default to 1 if no number specified
|
|
|
|
}
|
|
|
|
patterns = append(patterns, lib.RequestPattern{
|
|
|
|
Verb: verb,
|
|
|
|
Sequence: current,
|
|
|
|
})
|
2024-12-04 16:40:52 -06:00
|
|
|
}
|
|
|
|
current = 0
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("invalid pattern character: %c", c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(patterns) == 0 {
|
|
|
|
return nil, fmt.Errorf("no valid patterns found in: %s", pattern)
|
|
|
|
}
|
|
|
|
|
2024-12-04 17:26:34 -06:00
|
|
|
// For probabilistic patterns, ensure percentages sum to 100
|
|
|
|
if isProbabilistic {
|
|
|
|
total := 0.0
|
|
|
|
for _, p := range patterns {
|
|
|
|
total += p.Percentage
|
|
|
|
}
|
|
|
|
if math.Abs(total-100.0) > 0.001 {
|
|
|
|
return nil, fmt.Errorf("percentages must sum to 100, got: %.1f", total)
|
|
|
|
}
|
2024-12-04 16:40:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return patterns, nil
|
|
|
|
}
|
|
|
|
|
2024-12-04 17:26:34 -06:00
|
|
|
// readJSONFile loads and returns the contents of a JSON file if specified
|
|
|
|
// Returns nil if filePath is empty
|
2024-12-04 16:40:52 -06:00
|
|
|
func readJSONFile(filePath string) ([]byte, error) {
|
|
|
|
if filePath == "" {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return os.ReadFile(filePath)
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
requestsPerSecond, maxRequests, url, patterns, jsonFilePath, bearerToken := parseCommandLine()
|
|
|
|
|
|
|
|
if maxRequests <= 0 {
|
|
|
|
fmt.Println("Error: max must be an integer greater than 0")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonData, err := readJSONFile(jsonFilePath)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("Error reading JSON file:", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
lib.SendRequests(url, patterns, maxRequests, requestsPerSecond, bearerToken, jsonData)
|
|
|
|
}
|