Skip to content

Commit

Permalink
Merge pull request #29 from ddosify/time_req_count_imp
Browse files Browse the repository at this point in the history
manual_load impl
  • Loading branch information
kursataktas authored Nov 19, 2021
2 parents 3465bfc + 975fd06 commit 0234490
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 25 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ Config file lets you use all capabilities of Ddosify.

The features you can use by config file;
- Scenario creation
- Custom load type creation
- Payload from a file
- Extra connection configuration, like *keep-alive* enable/disable logic
- HTTP2 support
Expand All @@ -199,6 +200,18 @@ There is an example config file at [config_examples/config.json](/config_example

This is the equivalent of the `-d` flag.

- `manual_load` *optional*

If you are looking for creating your own custom load type, you can use this feature. The example below says that Ddosify will run the scenario 5 times, 10 times, and 20 times, respectively along with the provided durations. `request_count` and `duration` will be auto-filled by Ddosify according to `manual_load` configuration. In this example, `request_count` will be 35 and the `duration` will be 18 seconds.
Also `manual_load` overrides `load_type` if you provide both of them. As a result, you don't need to provide these 3 parameters when using `manual_load`.
```json
"manual_load": [
{"duration": 5, "count": 5},
{"duration": 6, "count": 10},
{"duration": 7, "count": 20}
]
```

- `proxy` *optional*

This is the equivalent of the `-P` flag.
Expand Down
13 changes: 13 additions & 0 deletions config/config_testdata/config_manual_load.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"manual_load": [
{"duration": 5, "count": 5},
{"duration": 6, "count": 10},
{"duration": 7, "count": 20}
],
"steps": [
{
"id": 1,
"url": "test.com"
}
]
}
15 changes: 15 additions & 0 deletions config/config_testdata/config_manual_load_override.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"requests_count": 100,
"duration": 22,
"manual_load": [
{"duration": 5, "count": 5},
{"duration": 6, "count": 10},
{"duration": 7, "count": 20}
],
"steps": [
{
"id": 1,
"url": "test.com"
}
]
}
28 changes: 22 additions & 6 deletions config/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func init() {
AvailableConfigReader[ConfigTypeJson] = &JsonReader{}
}

type timeRunCount []struct {
Duration int `json:"duration"`
Count int `json:"count"`
}

type auth struct {
Type string `json:"type"`
Username string `json:"username"`
Expand Down Expand Up @@ -75,12 +80,13 @@ func (s *step) UnmarshalJSON(data []byte) error {
}

type JsonReader struct {
ReqCount int `json:"request_count"`
LoadType string `json:"load_type"`
Duration int `json:"duration"`
Steps []step `json:"steps"`
Output string `json:"output"`
Proxy string `json:"proxy"`
ReqCount int `json:"request_count"`
LoadType string `json:"load_type"`
Duration int `json:"duration"`
TimeRunCount timeRunCount `json:"manual_load"`
Steps []step `json:"steps"`
Output string `json:"output"`
Proxy string `json:"proxy"`
}

func (j *JsonReader) UnmarshalJSON(data []byte) error {
Expand Down Expand Up @@ -140,11 +146,21 @@ func (j *JsonReader) CreateHammer() (h types.Hammer, err error) {
Addr: proxyURL,
}

// TimeRunCount
if len(j.TimeRunCount) > 0 {
j.ReqCount, j.Duration = 0, 0
for _, t := range j.TimeRunCount {
j.ReqCount += t.Count
j.Duration += t.Duration
}
}

// Hammer
h = types.Hammer{
TotalReqCount: j.ReqCount,
LoadType: strings.ToLower(j.LoadType),
TestDuration: j.Duration,
TimeRunCountMap: types.TimeRunCount(j.TimeRunCount),
Scenario: s,
Proxy: p,
ReportDestination: j.Output,
Expand Down
70 changes: 70 additions & 0 deletions config/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,76 @@ func TestCreateHammer(t *testing.T) {
}
}

func TestCreateHammerManualLoad(t *testing.T) {
t.Parallel()

jsonReader, _ := NewConfigReader(readConfigFile("config_testdata/config_manual_load.json"), ConfigTypeJson)
expectedHammer := types.Hammer{
TotalReqCount: 35,
LoadType: types.DefaultLoadType,
TestDuration: 18,
TimeRunCountMap: types.TimeRunCount{{Duration: 5, Count: 5}, {Duration: 6, Count: 10}, {Duration: 7, Count: 20}},
ReportDestination: types.DefaultOutputType,
Scenario: types.Scenario{
Scenario: []types.ScenarioItem{{
ID: 1,
URL: strings.ToLower(types.DefaultProtocol) + "://test.com",
Protocol: types.DefaultProtocol,
Method: types.DefaultMethod,
Timeout: types.DefaultTimeout,
}},
},
Proxy: proxy.Proxy{
Strategy: proxy.ProxyTypeSingle,
},
}

h, err := jsonReader.CreateHammer()

if err != nil {
t.Errorf("TestCreateHammerManualLoad error occurred: %v", err)
}

if !reflect.DeepEqual(expectedHammer, h) {
t.Errorf("Expected: %v, Found: %v", expectedHammer, h)
}
}

func TestCreateHammerManualLoadOverrideOthers(t *testing.T) {
t.Parallel()

jsonReader, _ := NewConfigReader(readConfigFile("config_testdata/config_manual_load_override.json"), ConfigTypeJson)
expectedHammer := types.Hammer{
TotalReqCount: 35,
LoadType: types.DefaultLoadType,
TestDuration: 18,
TimeRunCountMap: types.TimeRunCount{{Duration: 5, Count: 5}, {Duration: 6, Count: 10}, {Duration: 7, Count: 20}},
ReportDestination: types.DefaultOutputType,
Scenario: types.Scenario{
Scenario: []types.ScenarioItem{{
ID: 1,
URL: strings.ToLower(types.DefaultProtocol) + "://test.com",
Protocol: types.DefaultProtocol,
Method: types.DefaultMethod,
Timeout: types.DefaultTimeout,
}},
},
Proxy: proxy.Proxy{
Strategy: proxy.ProxyTypeSingle,
},
}

h, err := jsonReader.CreateHammer()

if err != nil {
t.Errorf("TestCreateHammerManualLoad error occurred: %v", err)
}

if !reflect.DeepEqual(expectedHammer, h) {
t.Errorf("Expected: %v, Found: %v", expectedHammer, h)
}
}

func TestCreateHammerPayload(t *testing.T) {
t.Parallel()
jsonReader, _ := NewConfigReader(readConfigFile("config_testdata/config_payload.json"), ConfigTypeJson)
Expand Down
5 changes: 5 additions & 0 deletions config_examples/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
"request_count": 20,
"load_type": "linear",
"duration": 5,
"manual_load": [
{"duration": 5, "count": 5},
{"duration": 6, "count": 10},
{"duration": 7, "count": 20}
],
"proxy": "http://proxy_host.com:proxy_port",
"output": "stdout",
"steps": [
Expand Down
28 changes: 22 additions & 6 deletions core/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ package core

import (
"context"
"fmt"
"math"
"reflect"
"sync"
Expand Down Expand Up @@ -185,12 +184,12 @@ func (e *engine) stop() {
}

func (e *engine) initReqCountArr() {
if e.hammer.TimeReqCountMap != nil {
fmt.Println("initReqCountArr from TimeReqCountMap")
} else {
length := int(e.hammer.TestDuration * int(time.Second/(tickerInterval*time.Millisecond)))
e.reqCountArr = make([]int, length)
length := int(e.hammer.TestDuration * int(time.Second/(tickerInterval*time.Millisecond)))
e.reqCountArr = make([]int, length)

if e.hammer.TimeRunCountMap != nil {
e.createManualReqCountArr()
} else {
switch e.hammer.LoadType {
case types.LoadTypeLinear:
e.createLinearReqCountArr()
Expand All @@ -202,6 +201,23 @@ func (e *engine) initReqCountArr() {
}
}

func (e *engine) createManualReqCountArr() {
tickPerSecond := int(time.Second / (tickerInterval * time.Millisecond))
stepStartIndex := 0
for _, t := range e.hammer.TimeRunCountMap {
steps := make([]int, t.Duration)
createLinearDistArr(t.Count, steps)

for i := range steps {
tickArrStartIndex := (i * tickPerSecond) + stepStartIndex
tickArrEndIndex := tickArrStartIndex + tickPerSecond
segment := e.reqCountArr[tickArrStartIndex:tickArrEndIndex]
createLinearDistArr(steps[i], segment)
}
stepStartIndex += len(steps) * tickPerSecond
}
}

func (e *engine) createLinearReqCountArr() {
steps := make([]int, e.hammer.TestDuration)
createLinearDistArr(e.hammer.TotalReqCount, steps)
Expand Down
39 changes: 27 additions & 12 deletions core/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,38 +114,52 @@ func TestRequestCount(t *testing.T) {
loadType string
duration int
reqCount int
timeRunCount types.TimeRunCount
expectedReqArr []int
delta int
}{
{"Linear1", types.LoadTypeLinear, 1, 100, []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, 1},
{"Linear2", types.LoadTypeLinear, 1, 5, []int{1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 0},
{"Linear3", types.LoadTypeLinear, 2, 4, []int{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, 0},
{"Linear4", types.LoadTypeLinear, 2, 23,
{"Linear1", types.LoadTypeLinear, 1, 100, nil, []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, 1},
{"Linear2", types.LoadTypeLinear, 1, 5, nil, []int{1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 0},
{"Linear3", types.LoadTypeLinear, 2, 4, nil,
[]int{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, 0},
{"Linear4", types.LoadTypeLinear, 2, 23, nil,
[]int{2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1}, 0},
{"Incremental1", types.LoadTypeIncremental, 1, 5,
{"Incremental1", types.LoadTypeIncremental, 1, 5, nil,
[]int{1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 2},
{"Incremental2", types.LoadTypeIncremental, 3, 1022,
{"Incremental2", types.LoadTypeIncremental, 3, 1022, nil,
[]int{17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 35, 34, 34, 34,
34, 34, 34, 34, 34, 34, 52, 51, 51, 51, 51, 51, 51, 51, 51, 51}, 2},
{"Incremental3", types.LoadTypeIncremental, 5, 10,
{"Incremental3", types.LoadTypeIncremental, 5, 10, nil,
[]int{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, 0},
{"Incremental4", types.LoadTypeIncremental, 4, 10,
{"Incremental4", types.LoadTypeIncremental, 4, 10, nil,
[]int{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, 0},
{"Waved1", types.LoadTypeWaved, 1, 5,
{"Waved1", types.LoadTypeWaved, 1, 5, nil,
[]int{1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 0},
{"Waved2", types.LoadTypeWaved, 4, 32,
{"Waved2", types.LoadTypeWaved, 4, 32, nil,
[]int{1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 0},
{"Waved3", types.LoadTypeWaved, 5, 10,
{"Waved3", types.LoadTypeWaved, 5, 10, nil,
[]int{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0},
{"Waved4", types.LoadTypeWaved, 9, 1000,
{"Waved4", types.LoadTypeWaved, 9, 1000, nil,
[]int{6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 17, 17, 17, 17,
17, 17, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 16, 16, 16, 16, 12, 11, 11, 11, 11, 11,
11, 11, 11, 11, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 12, 11, 11,
11, 11, 11, 11, 11, 11, 11, 17, 17, 17, 17, 17, 17, 17, 16, 16, 16}, 1},
{"TimeRunCount1", "", 1, 100, types.TimeRunCount{{Duration: 1, Count: 100}},
[]int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, 1},
{"TimeRunCount2", "", 1, 5, types.TimeRunCount{{Duration: 1, Count: 5}},
[]int{1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 0},
{"TimeRunCount3", "", 6, 55,
types.TimeRunCount{{Duration: 1, Count: 20}, {Duration: 2, Count: 30}, {Duration: 3, Count: 5}},
[]int{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0},
{"TimeRunCount4", "", 5, 40,
types.TimeRunCount{{Duration: 1, Count: 20}, {Duration: 2, Count: 0}, {Duration: 2, Count: 20}},
[]int{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, 0},
}

for _, tc := range tests {
Expand All @@ -171,6 +185,7 @@ func TestRequestCount(t *testing.T) {
h := newDummyHammer()
h.LoadType = test.loadType
h.TestDuration = test.duration
h.TimeRunCountMap = test.timeRunCount
h.TotalReqCount = test.reqCount
h.Scenario.Scenario[0].URL = server.URL

Expand Down
16 changes: 15 additions & 1 deletion core/types/hammer.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ const (

var loadTypes = [...]string{LoadTypeLinear, LoadTypeIncremental, LoadTypeWaved}

// TimeRunCount is the data structure to store manual load type data.
type TimeRunCount []struct {
Duration int
Count int
}

// Hammer is like a lighter for the engine.
// It includes attack metadata and all necessary data to initialize the internal services in the engine.
type Hammer struct {
Expand All @@ -63,7 +69,7 @@ type Hammer struct {
TestDuration int

// Duration (in second) - Request count map. Example: {10: 1500, 50: 400, ...}
TimeReqCountMap map[int]int
TimeRunCountMap TimeRunCount

// Test Scenario
Scenario Scenario
Expand All @@ -90,5 +96,13 @@ func (h *Hammer) Validate() error {
return fmt.Errorf("unsupported LoadType: %s", h.LoadType)
}

if len(h.TimeRunCountMap) > 0 {
for _, t := range h.TimeRunCountMap {
if t.Duration < 1 {
return fmt.Errorf("duration in manual_load should be greater than 0")
}
}
}

return nil
}
24 changes: 24 additions & 0 deletions core/types/hammer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,27 @@ func TestHammerEmptyScenarioItemID(t *testing.T) {
t.Errorf("TestHammerInvalidScenarioItemID errored")
}
}

func TestHammerInvalidManualLoadDuration(t *testing.T) {
// Duration = 0
h := newDummyHammer()
h.TimeRunCountMap = TimeRunCount{
{Duration: 10, Count: 10},
{Duration: 0, Count: 10},
}

if err := h.Validate(); err == nil {
t.Errorf("TestHammerInvalidManualLoadDuration errored")
}

// Duration is negatie
h = newDummyHammer()
h.TimeRunCountMap = TimeRunCount{
{Duration: 10, Count: 10},
{Duration: -1, Count: 10},
}

if err := h.Validate(); err == nil {
t.Errorf("TestHammerInvalidManualLoadDuration errored")
}
}

0 comments on commit 0234490

Please sign in to comment.