Skip to content

Commit 48e1e6d

Browse files
authored
WIP. Added some stuff for dhcpinform. (#4)
* WIP. Added some stuff for dhcpinform. * Removing stats that make no sense for dhcpinform. * Moved the zero flags out for release and inform. * Adding in some stats changes * Adding config changes and handlers to allow rps option changes during run-time. * Switched to httpway for graceful shutdown. * Small http status change. * Quick pass at auto-tuning. Need changes in generator to really make this work right/well. * Quick stats method rename, options check in generator, and switched to options loop in handler. * Some small adjustments to wrap up. Not an amazing example, but a working one.
1 parent a0aba91 commit 48e1e6d

File tree

8 files changed

+361
-36
lines changed

8 files changed

+361
-36
lines changed

AutoTuner.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/python3
2+
3+
import requests
4+
import json
5+
import logging
6+
import os
7+
import time
8+
import math
9+
10+
class AutoTuner:
11+
"""A class for using the dhammer API to find the optimal rps rate."""
12+
13+
def __init__(self, options):
14+
self._options = options
15+
self._run = True
16+
self._rps = 1
17+
18+
self._previous_ramped_up_rps = 1
19+
20+
def _tune(self):
21+
logging.debug("Going to tune...")
22+
23+
response = requests.get(f"http://{self._options.api_address}:{self._options.api_port}/stats")
24+
response.raise_for_status()
25+
stats = json.loads(response.text)
26+
27+
target_stat = None
28+
compare_stat = None
29+
30+
for stat in stats:
31+
if stat['stat_name'] == self._options.tune_stat_name:
32+
target_stat = stat
33+
elif stat['stat_name'] == self._options.tune_stat_compare_name:
34+
compare_stat = stat
35+
36+
diff_perc = target_stat['stat_rate_per_second'] / compare_stat['stat_rate_per_second']
37+
38+
info = {}
39+
40+
if diff_perc >= self._options.tune_compare_min_percentage:
41+
self._previous_ramped_up_rps = self._rps
42+
self._rps = self._rps * self._options.ramp_up_factor
43+
44+
response = requests.get(f"http://{self._options.api_address}:{self._options.api_port}/update/rps/{math.floor(self._rps)}")
45+
response.raise_for_status()
46+
47+
print(f"{target_stat['stat_name']} / {compare_stat['stat_name']} = {diff_perc}: ramped up. Target RPS is {self._rps}.")
48+
49+
else:
50+
51+
self._options.ramp_up_factor = self._options.ramp_up_factor * self._options.ramp_down_factor
52+
53+
if self._options.ramp_up_factor <= 1:
54+
print(f"{target_stat['stat_name']} / {compare_stat['stat_name']} = {diff_perc}: Ramp down triggered, but next ramp-up difference too low.")
55+
print(f"Target reached. Optimal RPS is approximately {math.floor(self._previous_ramped_up_rps)}.")
56+
return(False)
57+
58+
self._rps = self._previous_ramped_up_rps * self._options.ramp_up_factor
59+
60+
response = requests.get(f"http://{self._options.api_address}:{self._options.api_port}/update/rps/{math.floor(self._rps)}")
61+
response.raise_for_status()
62+
print(f"{target_stat['stat_name']} / {compare_stat['stat_name']} = {diff_perc}: ramped down. Target RPS is {self._rps}.")
63+
64+
return(True)
65+
66+
def prepare(self):
67+
response = requests.get(f"http://{self._options.api_address}:{self._options.api_port}/update/rps/1")
68+
response.raise_for_status()
69+
time.sleep(self._options.refresh_rate_seconds)
70+
71+
72+
def stop(self): # GIL for now ;D
73+
self._run = False
74+
return(True)
75+
76+
77+
def start(self):
78+
while self._run and self._tune(): # GIL for now ;D
79+
time.sleep(self._options.refresh_rate_seconds)
80+
81+

autotune.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/python3
2+
3+
import argparse
4+
import signal
5+
import sys
6+
import AutoTuner
7+
8+
"""
9+
Example script for interacting with the dhammer API
10+
11+
Usage: autotune.py --tune-stat-name OfferReceived --tune-stat-compare-name DiscoverSent
12+
"""
13+
14+
##### Install a signal handler for CTRL+C #####
15+
16+
def signal_handler(signal, frame, tuner):
17+
try:
18+
# Try to shut things down gracefully.
19+
print('Stopping...')
20+
tuner.stop()
21+
except BaseException as e:
22+
print(str(e))
23+
pass
24+
25+
print('Shutting down...')
26+
sys.exit(0) # Raise a SystemExit exception
27+
28+
def main():
29+
30+
##### Get command-line arguments
31+
parser = argparse.ArgumentParser(description='Find the max request rate.')
32+
33+
parser.add_argument('--api-address','-a', dest='api_address', default='localhost',
34+
help='Address for stats API.')
35+
36+
parser.add_argument('--api-port','-p', dest='api_port', default=8080,
37+
help='Port for stats API')
38+
39+
parser.add_argument('--tune-stat-name','-t', dest='tune_stat_name', default=None, required=True,
40+
help='Stat field used to decide if ramp up or down is necessary.')
41+
42+
parser.add_argument('--tune-stat-compare-name','-c', dest='tune_stat_compare_name', default=None, required=True,
43+
help='Stat used for comparison to determine if goal is being reached.')
44+
45+
parser.add_argument('--tune-compare-min-percentage','-cp', dest='tune_compare_min_percentage', default=0.95,
46+
help='The maximum percentage difference between the tuning stat and the comparison stat.')
47+
48+
parser.add_argument('--ramp-up-factor','-ru', dest='ramp_up_factor', default=2, type=int,
49+
help='Factor by which to ramp up the target RPS.')
50+
51+
parser.add_argument('--ramp-down-factor','-rd', dest='ramp_down_factor', default=0.9, type=int,
52+
help='Factor by which to reduce the ramp-up factor.')
53+
54+
parser.add_argument('--refresh-rate_seconds','-rr', dest='refresh_rate_seconds', default=6, type=int,
55+
help='Rate to check stats. Should be slightly longer than the dhammer refresh rate.')
56+
57+
args = parser.parse_args()
58+
59+
60+
##### Prep the result handler
61+
tuner = AutoTuner.AutoTuner(args)
62+
63+
# Register our signal handler.
64+
signal.signal(signal.SIGINT, lambda signal, frame: signal_handler(signal, frame, tuner))
65+
66+
try:
67+
tuner.prepare()
68+
tuner.start()
69+
except SystemExit:
70+
pass
71+
except BaseException as e:
72+
print("Tuner broke down: %s" % str(e))
73+
74+
try:
75+
tuner.stop()
76+
except:
77+
pass
78+
79+
return(0)
80+
81+
if __name__ == "__main__":
82+
main()

config/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,7 @@ type Options struct {
4545

4646
InterfaceName *string
4747
GatewayMAC net.HardwareAddr
48+
49+
ApiPort *int
50+
ApiAddress *string
4851
}

dhammer.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func main() {
1515
options := &config.Options{}
1616

1717
options.Handshake = flag.Bool("handshake", true, "Attempt full handshakes")
18-
options.DhcpInfo = flag.Bool("dhcpinfo", false, "Blast DHCPINFO packets, but don't complete the handshake. NOT YET IMPLEMENTED.")
18+
options.DhcpInfo = flag.Bool("info", false, "Send DHCPINFO packets. This requires a full handshake.")
1919
options.DhcpBroadcast = flag.Bool("dhcp-broadcast", true, "Set the broadcast bit.")
2020
options.EthernetBroadcast = flag.Bool("ethernet-broadcast", true, "Use ethernet broadcasting.")
2121
options.DhcpRelease = flag.Bool("release", false, "Release leases after acquiring them.")
@@ -38,6 +38,10 @@ func main() {
3838

3939
options.InterfaceName = flag.String("interface", "eth0", "Interface name for listening and sending.")
4040
gatewayMAC := flag.String("gateway-mac", "de:ad:be:ef:f0:0d", "MAC of the gateway.")
41+
42+
options.ApiAddress = flag.String("api-address", "", "IP for the API server to listen on.")
43+
options.ApiPort = flag.Int("api-port", 8080, "Port for the API server to listen on.")
44+
4145
flag.Parse()
4246

4347
var err error

generator/generator.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type GeneratorV4 struct {
2424
addStat func(stats.StatValue) bool
2525
finishChannel chan struct{}
2626
doneChannel chan struct{}
27+
rpsChannel chan int
2728
}
2829

2930
func NewV4(o *config.Options, iface *net.Interface, logFunc func(string) bool, errFunc func(error) bool, payloadFunc func([]byte) bool, statFunc func(stats.StatValue) bool) *GeneratorV4 {
@@ -37,6 +38,7 @@ func NewV4(o *config.Options, iface *net.Interface, logFunc func(string) bool, e
3738
addStat: statFunc,
3839
finishChannel: make(chan struct{}, 1),
3940
doneChannel: make(chan struct{}),
41+
rpsChannel: make(chan int, 1),
4042
}
4143

4244
return &g
@@ -56,6 +58,23 @@ func (g *GeneratorV4) Stop() error {
5658
return nil
5759
}
5860

61+
func (g *GeneratorV4) Update(attr interface{}, val interface{}) error {
62+
63+
if a, ok := attr.(string); ok {
64+
if a == "rps" {
65+
if v, ok := val.(string); ok {
66+
if vI, err := strconv.Atoi(v); err != nil {
67+
return err
68+
} else {
69+
g.rpsChannel <- vI
70+
}
71+
}
72+
}
73+
}
74+
75+
return nil
76+
}
77+
5978
func (g *GeneratorV4) Run() {
6079

6180
macs := g.generateMacList()
@@ -93,6 +112,9 @@ func (g *GeneratorV4) Run() {
93112
if err != nil {
94113
g.addError(err)
95114
continue
115+
} else if aOption > 255 {
116+
g.addLog("DHCP option codes greater than 255 are not supported. Skipping " + optionValCombo[0])
117+
continue
96118
}
97119

98120
aValue, err := base64.StdEncoding.DecodeString(optionValCombo[1])
@@ -166,11 +188,19 @@ func (g *GeneratorV4) Run() {
166188
default:
167189
}
168190

191+
select {
192+
case mRps, _ = <-g.rpsChannel:
193+
sent = 0
194+
start = time.Now()
195+
time.Sleep(1)
196+
default:
197+
}
198+
169199
t = time.Now()
170200
elapsed = t.Sub(start).Seconds()
171201
rps = int(float64(sent) / elapsed)
172202

173-
if rps > mRps {
203+
if rps >= mRps {
174204
runtime.Gosched()
175205
continue
176206
}

0 commit comments

Comments
 (0)