Skip to content

Commit

Permalink
Merge pull request #111 from ddosify/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
fatihbaltaci authored Jan 16, 2023
2 parents fc89142 + fa4c744 commit 9f47cee
Show file tree
Hide file tree
Showing 26 changed files with 968 additions and 52 deletions.
8 changes: 4 additions & 4 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ builds:
goarm:
- 6
ldflags:
- -s -w -X main.GitVersion={{ .Version }} -X main.GitCommit={{ .ShortCommit }} -X main.BuildDate={{ .CommitDate }}
- -s -w -X main.GitVersion={{ .Tag }} -X main.GitCommit={{ .ShortCommit }} -X main.BuildDate={{ .CommitDate }}
ignore:
- goos: darwin
goarch: 386
Expand Down Expand Up @@ -50,7 +50,7 @@ universal_binaries:
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
name_template: "{{ incpatch .Tag }}-next"
changelog:
sort: asc
use: github
Expand Down Expand Up @@ -92,7 +92,7 @@ dockers:
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.version={{.Tag}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/amd64"
extra_files:
Expand All @@ -106,7 +106,7 @@ dockers:
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.name={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.version={{.Tag}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/arm64"
extra_files:
Expand Down
1 change: 1 addition & 0 deletions Jenkinsfile_benchmark
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pipeline {
allOf {
expression { env.CHANGE_ID != null }
expression { env.CHANGE_TARGET != null }
expression { env.CHANGE_BRANCH != 'develop' }
}
}
steps {
Expand Down
102 changes: 88 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ This section aims to show you how to use Ddosify without deep dive into its deta

ddosify -config ddosify_config_correlation.json
Ddosify allows you to specify variables at the global level and use them throughout the scenario, as well as extract variables from previous steps and inject them to the next steps in each iteration individually. You can inject those variables in requests *url*, *headers* and *payload(body)*. The example config can be found in [correlation-config-example](#Correlation).

7. ### Test Data

ddosify -config ddosify_data_csv.json
Ddosify allows you to load test data from a file, tag specific columns for later use. You can inject those variables in requests *url*, *headers* and *payload(body)*. The example config can be found in [test-data-example](#test-data-set).
## Details

You can configure your load test by the CLI options or a config file. Config file supports more features than the CLI. For example, you can't create a scenario-based load test with CLI options.
Expand Down Expand Up @@ -262,18 +267,43 @@ There is an example config file at [config_examples/config.json](/config_example
- `env` *optional*
Scenario-scoped global variables. Note that dynamic variables changes every iteration.
```json
"steps": [
{
"id": 1,
"url": "http://target.com/endpoint1",
"env": {
"COMPANY_NAME" :"Ddosify",
"randomCountry" : "{{_randomCountry}}"
}
},
]
"env": {
"COMPANY_NAME" :"Ddosify",
"randomCountry" : "{{_randomCountry}}"
}
```

- `data` *optional*
Config for loading test data from a csv file.
[Csv data](https://github.com/ddosify/ddosify/tree/master/config/config_testdata/test.csv) used in below config.
```json
"data":{
"info": {
"path" : "config/config_testdata/test.csv",
"delimiter": ";",
"vars": {
"0":{"tag":"name"},
"1":{"tag":"city"},
"2":{"tag":"team"},
"3":{"tag":"payload", "type":"json"},
"4":{"tag":"age", "type":"int"}
},
"allowQuota" : true,
"order": "sequential",
"skipFirstLine" : true,
"skipEmptyLine" : true
}
}
```
| Field | Description | Type | Default | Required? |
| ------ | -------------------------------------------------------- | ------ | ------- | --------- |
| `path` | Local path or remote url for your csv file | `string` | - | Yes |
| `delimiter` | Delimiter for reading csv | `string` | `,` | No |
| `vars` | Tag columns using column index as key, use `type` field if you want to cast a column to a specific type, default is `string`, can be one of the following: `json`, `int`, `float`,`bool`. | `map` | - | Yes |
| `allowQuota` | If set to true, a quote may appear in an unquoted field and a non-doubled quote may appear in a quoted field | `bool` | `false` | No |
| `order` | Order of reading records from csv. Can be `random` or `sequential` | `string` | `random` | No |
| `skipFirstLine` | Skips first line while reading records from csv. | `bool` | `false` | No |
| `skipEmptyLine` | Skips empty lines while reading records from csv. | `bool` | `true` | No |

- `steps` *mandatory*

This parameter lets you create your scenario. Ddosify runs the provided steps, respectively. For the given example file step id: 2 will be executed immediately after the response of step id: 1 is received. The order of the execution is the same as the order of the steps in the config file.
Expand Down Expand Up @@ -587,14 +617,16 @@ ddosify -config ddosify_config_correlation.json -debug
"TARGET_URL" : "http://localhost:8084/hello",
"USER_KEY" : "ABC",
"COMPANY_NAME" : "Ddosify",
"RANDOM_COUNTRY" : "{{_randomCountry}}"
"RANDOM_COUNTRY" : "{{_randomCountry}}",
"NUMBERS" : [22,33,10,52]
},
}
```



### :hammer: Overall Config and Injection
On array-like captured variables or environment vars, the **rand( )** function can be utilized.
```json
// ddosify_config_correlation.json
{
Expand All @@ -607,7 +639,8 @@ ddosify -config ddosify_config_correlation.json -debug
"url": "{{TARGET_URL}}",
"method": "POST",
"headers": {
"User-Key": "{{USER_KEY}}"
"User-Key": "{{USER_KEY}}",
"Rand-Selected-Num" : "{{rand(NUMBERS)}}"
},
"payload" : "{{COMPANY_NAME}}",
"captureEnv": {
Expand Down Expand Up @@ -640,7 +673,8 @@ ddosify -config ddosify_config_correlation.json -debug
"TARGET_URL" : "http://localhost:8084/hello",
"USER_KEY" : "ABC",
"COMPANY_NAME" : "Ddosify",
"RANDOM_COUNTRY" : "{{_randomCountry}}"
"RANDOM_COUNTRY" : "{{_randomCountry}}",
"NUMBERS" : [22,33,10,52]
},
}
```
Expand All @@ -662,10 +696,50 @@ ddosify -config ddosify_config_correlation.json -debug
}
```

## Test Data Set
Ddosify enables you to load test data from **csv** files. Later, in your scenario, you can inject variables that you tagged.

We are using this [csv data](https://github.com/ddosify/ddosify/tree/master/config/config_testdata/test.csv) in below config.


```json
// config_data_csv.json
"data":{
"csv_test": {
"path" : "config/config_testdata/test.csv",
"delimiter": ";",
"vars": {
"0":{"tag":"name"},
"1":{"tag":"city"},
"2":{"tag":"team"},
"3":{"tag":"payload", "type":"json"},
"4":{"tag":"age", "type":"int"}
},
"allowQuota" : true,
"order": "random",
"skipFirstLine" : true
}
}
```

You can refer to tagged variables in your request like below.

```json
// payload.json
{
"name" : "{{data.csv_test.name}}",
"team" : "{{data.csv_test.team}}",
"city" : "{{data.csv_test.city}}",
"payload" : "{{data.csv_test.payload}}",
"age" : "{{data.csv_test.age}}"
}
```

## Tutorials / Blog Posts

* [Testing the Performance of User Authentication Flow](https://ddosify.com/blog/testing-the-performance-of-user-authentication-flow#introduction)


## Common Issues

### macOS Security Issue
Expand Down
47 changes: 47 additions & 0 deletions config/config_testdata/config_data_csv.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"iteration_count": 4,
"load_type": "waved",
"duration": 1,
"steps": [
{
"id": 2,
"url": "{{LOCAL}}/body",
"name": "JSON",
"method": "GET",
"others": {
"h2": false,
"keep-alive": true,
"disable-redirect": true,
"disable-compression": false
},
"payload_file": "../config/config_testdata/data_json_payload.json",
"timeout": 10
}
],
"output": "stdout",
"env":{
"HTTPBIN" : "https://httpbin.ddosify.com",
"LOCAL" : "http://localhost:8084",
"RANDOM_NAMES" : ["kenan","fatih","kursat","semih","sertac"] ,
"RANDOM_INT" : [52,99,60,33],
"RANDOM_BOOL" : [true,true,true,false]
},
"data":{
"info": {
"path" : "../config/config_testdata/test.csv",
"src" : "local",
"delimiter": ";",
"vars": {
"0":{"tag":"name"},
"1":{"tag":"city"},
"2":{"tag":"team"},
"3":{"tag":"payload", "type":"json"},
"4":{"tag":"age", "type":"int"}
},
"allowQuota" : true,
"order": "random",
"skipFirstLine" : true
}
},
"debug" : false
}
7 changes: 7 additions & 0 deletions config/config_testdata/data_json_payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name" : "{{data.info.name}}",
"team" : "{{data.info.team}}",
"city" : "{{data.info.city}}",
"payload" : "{{rand(data.info.payload)}}",
"age" : "{{data.info.age}}"
}
7 changes: 7 additions & 0 deletions config/config_testdata/test.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Username;City;Team;Payload;Age;Percent;BoolField;;;
Kenan;Tokat;Galatasaray;{"data":{"profile":{"name":"Kenan"}}};25;22.3;true;;;
Fatih;Bolu;Galatasaray;[5,6,7];29;44.3;false;;;
Kursat;Samsun;Besiktas;{"a":"b"};28;12.54;True;;;
Semih;Duzce;Besiktas;{"a":"b"};27;663.67;False;;;
;;;;;;;;;
;;;;;;;;;
131 changes: 131 additions & 0 deletions config/csv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package config

import (
"encoding/csv"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
)

func validateConf(conf CsvConf) error {
if !(conf.Order == "random" || conf.Order == "sequential") {
return fmt.Errorf("unsupported order %s, should be random|sequential", conf.Order)
}
return nil
}

func readCsv(conf CsvConf) ([]map[string]interface{}, error) {
err := validateConf(conf)
if err != nil {
return nil, err
}

var reader io.Reader

if _, err = url.ParseRequestURI(conf.Path); err == nil { // url
req, err := http.NewRequest(http.MethodGet, conf.Path, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}

if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
return nil, fmt.Errorf("request to remote url failed: %d", resp.StatusCode)
}
reader = resp.Body
defer resp.Body.Close()
} else if _, err = os.Stat(conf.Path); err == nil { // local file path
f, err := os.Open(conf.Path)
if err != nil {
return nil, err
}
reader = f
defer f.Close()
} else {
return nil, err
}

// read csv values using csv.Reader
csvReader := csv.NewReader(reader)
csvReader.Comma = []rune(conf.Delimiter)[0]
csvReader.TrimLeadingSpace = true
csvReader.LazyQuotes = conf.AllowQuota

data, err := csvReader.ReadAll()
if err != nil {
return nil, err
}

if conf.SkipFirstLine {
data = data[1:]
}

rt := make([]map[string]interface{}, 0) // unclear how many empty line exist

for _, row := range data {
if conf.SkipEmptyLine && emptyLine(row) {
continue
}
x := map[string]interface{}{}
for index, tag := range conf.Vars {
i, err := strconv.Atoi(index)
if err != nil {
return nil, err
}

if i >= len(row) {
return nil, fmt.Errorf("index number out of range, check your vars or delimiter")
}

// convert
var val interface{}
switch tag.Type {
case "json":
err := json.Unmarshal([]byte(row[i]), &val)
if err != nil {
return nil, fmt.Errorf("can not convert %s to json,%v", row[i], err)
}
case "int":
var err error
val, err = strconv.Atoi(row[i])
if err != nil {
return nil, fmt.Errorf("can not convert %s to int,%v", row[i], err)
}
case "float":
var err error
val, err = strconv.ParseFloat(row[i], 64)
if err != nil {
return nil, fmt.Errorf("can not convert %s to float,%v", row[i], err)
}
case "bool":
var err error
val, err = strconv.ParseBool(row[i])
if err != nil {
return nil, fmt.Errorf("can not convert %s to bool,%v", row[i], err)
}
default:
val = row[i]
}
x[tag.Tag] = val
}
rt = append(rt, x)
}

return rt, nil
}

func emptyLine(row []string) bool {
for _, field := range row {
if field != "" {
return false
}
}
return true
}
Loading

0 comments on commit 9f47cee

Please sign in to comment.