-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP Timeline Support #12831
WIP Timeline Support #12831
Changes from 30 commits
dee25ff
a3a7cbd
72fab36
22e7c12
aab5d8a
b46f81d
e90a046
cdeac64
077be58
b06323f
a371f6e
9c8dbe5
3e4e8ea
c9b39cb
a00aa0f
e1d0c65
da1fde1
4e08b3d
51bffaa
f74c85d
1a7c1f4
0150408
95acb3c
d565042
7b2162a
0b28b6f
8feb085
9232921
7bf22b5
89563ea
9cadda9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"math/rand" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
"sync" | ||
"sync/atomic" | ||
"time" | ||
) | ||
|
||
// An URL like /pattern?r='200x50,404x20,200|500x30' | ||
// The above pattern would return 50 200 responses, then 20 404s, then randomly return a mix of 200 and 500 | ||
// responses 30 times | ||
|
||
func main() { | ||
states := &sync.Map{} | ||
|
||
var reqs uint64 = 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should drop = 0 from declaration of var reqs; it is the zero value |
||
|
||
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { | ||
atomic.AddUint64(&reqs, 1) | ||
|
||
writer.Write([]byte("Dummy HTTP Server")) | ||
}) | ||
|
||
http.HandleFunc("/pattern", func(writer http.ResponseWriter, request *http.Request) { | ||
atomic.AddUint64(&reqs, 1) | ||
|
||
status, body := handlePattern(states, request.URL) | ||
writer.WriteHeader(status) | ||
writer.Write([]byte(body)) | ||
}) | ||
|
||
go func() { | ||
for { | ||
time.Sleep(time.Second * 10) | ||
r := atomic.LoadUint64(&reqs) | ||
fmt.Printf("Processed %d reqs\n", r) | ||
} | ||
}() | ||
|
||
port := 5678 | ||
fmt.Printf("Starting server on port %d\n", port) | ||
err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil) | ||
if err != nil { | ||
fmt.Printf("Could not start server: %s", err) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
type responsePattern struct { | ||
httpStatuses []int | ||
httpStatusesLen int | ||
countLimit int | ||
} | ||
|
||
func (rp *responsePattern) next() (status int, body string) { | ||
var idx int | ||
if rp.httpStatusesLen > 1 { | ||
fmt.Printf("INTN %d\n", rp.httpStatusesLen) | ||
idx = rand.Intn(rp.httpStatusesLen) | ||
} else { | ||
idx = 0 | ||
} | ||
status = rp.httpStatuses[idx] | ||
return status, strconv.Itoa(status) | ||
} | ||
|
||
type responsePatternSequence struct { | ||
currentPatternIdx int | ||
currentPattern *responsePattern | ||
currentPatternCount int | ||
patterns []*responsePattern | ||
shuffle bool | ||
mtx sync.Mutex | ||
} | ||
|
||
func (ps *responsePatternSequence) next() (status int, body string) { | ||
ps.mtx.Lock() | ||
ps.mtx.Unlock() | ||
|
||
if ps.currentPatternCount >= ps.currentPattern.countLimit { | ||
ps.advancePattern() | ||
} | ||
|
||
ps.currentPatternCount = ps.currentPatternCount + 1 | ||
return ps.currentPattern.next() | ||
} | ||
|
||
func (ps *responsePatternSequence) advancePattern() { | ||
if ps.shuffle { | ||
ps.currentPatternIdx = rand.Intn(len(ps.patterns)) - 1 | ||
ps.currentPattern = ps.patterns[ps.currentPatternIdx] | ||
} else { | ||
var nextIdx = ps.currentPatternIdx + 1 | ||
if nextIdx == len(ps.patterns) { | ||
nextIdx = 0 | ||
} | ||
ps.currentPatternIdx = nextIdx | ||
ps.currentPattern = ps.patterns[nextIdx] | ||
} | ||
|
||
ps.currentPatternCount = 0 | ||
} | ||
|
||
var statusListRegexp = regexp.MustCompile("^[|\\d]+$") | ||
|
||
func handlePattern(states *sync.Map, url *url.URL) (status int, body string) { | ||
query := url.Query() | ||
|
||
rpsInter, ok := states.Load(url.RawQuery) | ||
var rps *responsePatternSequence | ||
if !ok { | ||
patterns, err := compilePatterns(query.Get("r")) | ||
if err != nil { | ||
return 400, err.Error() | ||
} | ||
rps = NewResponsePatternSequence(patterns, query.Get("shuffle") == "true") | ||
states.Store(url.RawQuery, rps) | ||
} else { | ||
rps = rpsInter.(*responsePatternSequence) | ||
} | ||
|
||
return rps.next() | ||
} | ||
|
||
func NewResponsePatternSequence(patterns []*responsePattern, shuffle bool) *responsePatternSequence { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. exported func NewResponsePatternSequence returns unexported type *main.responsePatternSequence, which can be annoying to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. exported func NewResponsePatternSequence returns unexported type *main.responsePatternSequence, which can be annoying to use |
||
ps := responsePatternSequence{ | ||
currentPatternIdx: 0, | ||
currentPattern: patterns[0], | ||
currentPatternCount: 0, | ||
patterns: patterns, | ||
shuffle: shuffle, | ||
mtx: sync.Mutex{}, | ||
} | ||
|
||
return &ps | ||
} | ||
|
||
func compilePatterns(patternsStr string) (patterns []*responsePattern, err error) { | ||
splitPatterns := strings.Split(patternsStr, ",") | ||
|
||
for _, patternStr := range splitPatterns { | ||
rp, err := compilePattern(patternStr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
patterns = append(patterns, rp) | ||
} | ||
|
||
return patterns, nil | ||
} | ||
|
||
func compilePattern(patternStr string) (*responsePattern, error) { | ||
rp := responsePattern{} | ||
|
||
splitPattern := strings.Split(patternStr, "x") | ||
if len(splitPattern) != 2 { | ||
return nil, fmt.Errorf("Bad pattern '%s', expected a STATUSxCOUNT as pattern. Got %s") | ||
} | ||
|
||
statusDefStr := splitPattern[0] | ||
if statusListRegexp.MatchString(statusDefStr) { | ||
statuses := strings.Split(statusDefStr, "|") | ||
for _, statusStr := range statuses { | ||
status, _ := strconv.Atoi(statusStr) | ||
rp.httpStatuses = append(rp.httpStatuses, status) | ||
} | ||
rp.httpStatusesLen = len(rp.httpStatuses) | ||
} else { | ||
return nil, fmt.Errorf("Expected a | separated list of numbers for status code def, got '%s'", statusDefStr) | ||
|
||
} | ||
|
||
count, err := strconv.Atoi(splitPattern[1]) | ||
if err != nil { | ||
return nil, fmt.Errorf("Repeat def should be an int, got '%s'", splitPattern[1]) | ||
} | ||
rp.countLimit = count | ||
|
||
return &rp, nil | ||
} |
Large diffs are not rendered by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should drop = 0 from declaration of var reqs; it is the zero value