1
0
mirror of https://github.com/Oxalide/vsphere-influxdb-go.git synced 2023-10-10 11:36:51 +00:00

add vendoring with go dep

This commit is contained in:
Adrian Todorov
2017-10-25 20:52:40 +00:00
parent 704f4d20d1
commit a59409f16b
1627 changed files with 489673 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
package stressClient
import (
"log"
"time"
"github.com/influxdata/influxdb/models"
)
// Communes are a method for passing points between InsertStatements and QueryStatements.
type commune struct {
ch chan string
storedPoint models.Point
}
// NewCommune creates a new commune with a buffered chan of length n
func newCommune(n int) *commune {
return &commune{ch: make(chan string, n)}
}
func (c *commune) point(precision string) models.Point {
pt := []byte(<-c.ch)
p, err := models.ParsePointsWithPrecision(pt, time.Now().UTC(), precision)
if err != nil {
log.Fatalf("Error parsing point for commune\n point: %v\n error: %v\n", pt, err)
}
if len(p) == 0 {
return c.storedPoint
}
c.storedPoint = p[0]
return p[0]
}
// SetCommune creates a new commune on the StressTest
func (st *StressTest) SetCommune(name string) chan<- string {
com := newCommune(10)
st.communes[name] = com
return com.ch
}
// GetPoint is called by a QueryStatement and retrieves a point sent by the associated InsertStatement
func (st *StressTest) GetPoint(name, precision string) models.Point {
p := st.communes[name].point(precision)
// Function needs to return a point. Panic if it doesn't
if p == nil {
log.Fatal("Commune not returning point")
}
return p
}

View File

@@ -0,0 +1,57 @@
package stressClient
import (
"testing"
)
func TestCommunePoint(t *testing.T) {
comm := newCommune(5)
pt := "write,tag=tagVal fooField=5 1460912595"
comm.ch <- pt
point := comm.point("s")
if string(point.Name()) != "write" {
t.Errorf("expected: write\ngot: %v", string(point.Name()))
}
if point.Tags().GetString("tag") != "tagVal" {
t.Errorf("expected: tagVal\ngot: %v", point.Tags().GetString("tag"))
}
fields, err := point.Fields()
if err != nil {
t.Fatal(err)
}
if int(fields["fooField"].(float64)) != 5 {
t.Errorf("expected: 5\ngot: %v\n", fields["fooField"])
}
// Make sure commune returns the prev point
comm.ch <- ""
point = comm.point("s")
if string(point.Name()) != "write" {
t.Errorf("expected: write\ngot: %v", string(point.Name()))
}
if point.Tags().GetString("tag") != "tagVal" {
t.Errorf("expected: tagVal\ngot: %v", point.Tags().GetString("tag"))
}
if int(fields["fooField"].(float64)) != 5 {
t.Errorf("expected: 5\ngot: %v\n", fields["fooField"])
}
}
func TestSetCommune(t *testing.T) {
sf, _, _ := NewTestStressTest()
ch := sf.SetCommune("foo_name")
ch <- "write,tag=tagVal fooField=5 1460912595"
pt := sf.GetPoint("foo_name", "s")
if string(pt.Name()) != "write" {
t.Errorf("expected: write\ngot: %v", string(pt.Name()))
}
if pt.Tags().GetString("tag") != "tagVal" {
t.Errorf("expected: tagVal\ngot: %v", pt.Tags().GetString("tag"))
}
fields, err := pt.Fields()
if err != nil {
t.Fatal(err)
}
if int(fields["fooField"].(float64)) != 5 {
t.Errorf("expected: 5\ngot: %v\n", fields["fooField"])
}
}

View File

@@ -0,0 +1,19 @@
package stressClient
// Directive is a struct to enable communication between SetStatements and the stressClient backend
// Directives change state for the stress test
type Directive struct {
Property string
Value string
Tracer *Tracer
}
// NewDirective creates a new instance of a Directive with the appropriate state variable to change
func NewDirective(property string, value string, tracer *Tracer) Directive {
d := Directive{
Property: property,
Value: value,
Tracer: tracer,
}
return d
}

View File

@@ -0,0 +1,20 @@
package stressClient
import (
"testing"
)
func TestNewDirective(t *testing.T) {
tr := NewTracer(map[string]string{})
prop := "foo_prop"
val := "foo_value"
dir := NewDirective(prop, val, tr)
got := dir.Property
if prop != got {
t.Errorf("expected: %v\ngot: %v\n", prop, got)
}
got = dir.Value
if val != got {
t.Errorf("expected: %v\ngot: %v\n", val, got)
}
}

View File

@@ -0,0 +1,22 @@
package stressClient
// Package is a struct to enable communication between InsertStatements, QueryStatements and InfluxQLStatements and the stressClient backend
// Packages carry either writes or queries in the []byte that makes up the Body
type Package struct {
T Type
Body []byte
StatementID string
Tracer *Tracer
}
// NewPackage creates a new package with the appropriate payload
func NewPackage(t Type, body []byte, statementID string, tracer *Tracer) Package {
p := Package{
T: t,
Body: body,
StatementID: statementID,
Tracer: tracer,
}
return p
}

View File

@@ -0,0 +1,16 @@
package stressClient
import (
"testing"
)
func TestNewPackage(t *testing.T) {
qry := []byte("SELECT * FROM foo")
statementID := "foo_id"
tr := NewTracer(map[string]string{})
pkg := NewPackage(Query, qry, statementID, tr)
got := string(pkg.Body)
if string(qry) != got {
t.Errorf("expected: %v\ngot: %v\n", qry, got)
}
}

View File

@@ -0,0 +1,95 @@
package stressClient
import (
"log"
"strconv"
"time"
influx "github.com/influxdata/influxdb/client/v2"
)
// reporting.go contains functions to emit tags and points from various parts of stressClient
// These points are then written to the ("_%v", sf.TestName) database
// These are the tags that stressClient adds to any response points
func (sc *stressClient) tags(statementID string) map[string]string {
tags := map[string]string{
"number_targets": fmtInt(len(sc.addresses)),
"precision": sc.precision,
"writers": fmtInt(sc.wconc),
"readers": fmtInt(sc.qconc),
"test_id": sc.testID,
"statement_id": statementID,
"write_interval": sc.wdelay,
"query_interval": sc.qdelay,
}
return tags
}
// These are the tags that the StressTest adds to any response points
func (st *StressTest) tags() map[string]string {
tags := map[string]string{
"precision": st.Precision,
"batch_size": fmtInt(st.BatchSize),
}
return tags
}
// This function makes a *client.Point for reporting on writes
func (sc *stressClient) writePoint(retries int, statementID string, statusCode int, responseTime time.Duration, addedTags map[string]string, writeBytes int) *influx.Point {
tags := sumTags(sc.tags(statementID), addedTags)
fields := map[string]interface{}{
"status_code": statusCode,
"response_time_ns": responseTime.Nanoseconds(),
"num_bytes": writeBytes,
}
point, err := influx.NewPoint("write", tags, fields, time.Now())
if err != nil {
log.Fatalf("Error creating write results point\n error: %v\n", err)
}
return point
}
// This function makes a *client.Point for reporting on queries
func (sc *stressClient) queryPoint(statementID string, body []byte, statusCode int, responseTime time.Duration, addedTags map[string]string) *influx.Point {
tags := sumTags(sc.tags(statementID), addedTags)
fields := map[string]interface{}{
"status_code": statusCode,
"num_bytes": len(body),
"response_time_ns": responseTime.Nanoseconds(),
}
point, err := influx.NewPoint("query", tags, fields, time.Now())
if err != nil {
log.Fatalf("Error creating query results point\n error: %v\n", err)
}
return point
}
// Adds two map[string]string together
func sumTags(tags1, tags2 map[string]string) map[string]string {
tags := make(map[string]string)
// Add all tags from first map to return map
for k, v := range tags1 {
tags[k] = v
}
// Add all tags from second map to return map
for k, v := range tags2 {
tags[k] = v
}
return tags
}
// Turns an int into a string
func fmtInt(i int) string {
return strconv.FormatInt(int64(i), 10)
}

View File

@@ -0,0 +1,100 @@
package stressClient
import (
"testing"
"time"
)
func TestNewStressClientTags(t *testing.T) {
pe, _, _ := newTestStressClient("localhost:8086")
tags := pe.tags("foo_id")
expected := fmtInt(len(pe.addresses))
got := tags["number_targets"]
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
expected = pe.precision
got = tags["precision"]
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
expected = pe.wdelay
got = tags["write_interval"]
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
expected = "foo_id"
got = tags["statement_id"]
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func TestNewStressTestTags(t *testing.T) {
sf, _, _ := NewTestStressTest()
tags := sf.tags()
expected := sf.Precision
got := tags["precision"]
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
expected = fmtInt(sf.BatchSize)
got = tags["batch_size"]
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func TestWritePoint(t *testing.T) {
pe, _, _ := newTestStressClient("localhost:8086")
statementID := "foo_id"
responseCode := 200
responseTime := time.Duration(10 * time.Millisecond)
addedTags := map[string]string{"foo_tag": "foo_tag_value"}
writeBytes := 28051
pt := pe.writePoint(1, statementID, responseCode, responseTime, addedTags, writeBytes)
got := pt.Tags()["statement_id"]
if statementID != got {
t.Errorf("expected: %v\ngot: %v\n", statementID, got)
}
fields, err := pt.Fields()
if err != nil {
t.Fatal(err)
}
got2 := int(fields["status_code"].(int64))
if responseCode != got2 {
t.Errorf("expected: %v\ngot: %v\n", responseCode, got2)
}
expected := "write"
got = pt.Name()
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func TestQueryPoint(t *testing.T) {
pe, _, _ := newTestStressClient("localhost:8086")
statementID := "foo_id"
responseCode := 200
body := []byte{12}
responseTime := time.Duration(10 * time.Millisecond)
addedTags := map[string]string{"foo_tag": "foo_tag_value"}
pt := pe.queryPoint(statementID, body, responseCode, responseTime, addedTags)
got := pt.Tags()["statement_id"]
if statementID != got {
t.Errorf("expected: %v\ngot: %v\n", statementID, got)
}
fields, err := pt.Fields()
if err != nil {
t.Fatal(err)
}
got2 := int(fields["status_code"].(int64))
if responseCode != got2 {
t.Errorf("expected: %v\ngot: %v\n", responseCode, got2)
}
expected := "query"
got = pt.Name()
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}

View File

@@ -0,0 +1,50 @@
package stressClient
import (
"log"
influx "github.com/influxdata/influxdb/client/v2"
)
// Response holds data scraped from InfluxDB HTTP responses turned into a *influx.Point for reporting
// See reporting.go for more information
// The Tracer contains a wait group sent from the statement. It needs to be decremented when the Response is consumed
type Response struct {
Point *influx.Point
Tracer *Tracer
}
// NewResponse creates a new instance of Response
func NewResponse(pt *influx.Point, tr *Tracer) Response {
return Response{
Point: pt,
Tracer: tr,
}
}
// AddTags adds additional tags to the point held in Response and returns the point
func (resp Response) AddTags(newTags map[string]string) (*influx.Point, error) {
// Pull off the current tags
tags := resp.Point.Tags()
// Add the new tags to the current tags
for tag, tagValue := range newTags {
tags[tag] = tagValue
}
// Make a new point
fields, err := resp.Point.Fields()
if err != nil {
return nil, err
}
pt, err := influx.NewPoint(resp.Point.Name(), tags, fields, resp.Point.Time())
// panic on error
if err != nil {
log.Fatalf("Error adding tags to response point\n point: %v\n tags:%v\n error: %v\n", resp.Point, newTags, err)
}
return pt, nil
}

View File

@@ -0,0 +1,20 @@
package stressClient
import (
"testing"
)
func TestNewResponse(t *testing.T) {
pt := NewBlankTestPoint()
tr := NewTracer(map[string]string{})
r := NewResponse(pt, tr)
expected := "another_tag_value"
test, err := r.AddTags(map[string]string{"another_tag": "another_tag_value"})
if err != nil {
t.Fatal(err)
}
got := test.Tags()["another_tag"]
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}

View File

@@ -0,0 +1,175 @@
package stressClient
import (
"fmt"
"log"
"sync"
influx "github.com/influxdata/influxdb/client/v2"
)
// NewStressTest creates the backend for the stress test
func NewStressTest() *StressTest {
packageCh := make(chan Package, 0)
directiveCh := make(chan Directive, 0)
responseCh := make(chan Response, 0)
clnt, _ := influx.NewHTTPClient(influx.HTTPConfig{
Addr: fmt.Sprintf("http://%v/", "localhost:8086"),
})
s := &StressTest{
TestDB: "_stressTest",
Precision: "s",
StartDate: "2016-01-02",
BatchSize: 5000,
packageChan: packageCh,
directiveChan: directiveCh,
ResultsClient: clnt,
ResultsChan: responseCh,
communes: make(map[string]*commune),
TestID: randStr(10),
}
// Start the client service
startStressClient(packageCh, directiveCh, responseCh, s.TestID)
// Listen for Results coming in
s.resultsListen()
return s
}
// NewTestStressTest returns a StressTest to be used for testing Statements
func NewTestStressTest() (*StressTest, chan Package, chan Directive) {
packageCh := make(chan Package, 0)
directiveCh := make(chan Directive, 0)
s := &StressTest{
TestDB: "_stressTest",
Precision: "s",
StartDate: "2016-01-02",
BatchSize: 5000,
directiveChan: directiveCh,
packageChan: packageCh,
communes: make(map[string]*commune),
TestID: randStr(10),
}
return s, packageCh, directiveCh
}
// The StressTest is the Statement facing API that consumes Statement output and coordinates the test results
type StressTest struct {
TestID string
TestDB string
Precision string
StartDate string
BatchSize int
sync.WaitGroup
sync.Mutex
packageChan chan<- Package
directiveChan chan<- Directive
ResultsChan chan Response
communes map[string]*commune
ResultsClient influx.Client
}
// SendPackage is the public facing API for to send Queries and Points
func (st *StressTest) SendPackage(p Package) {
st.packageChan <- p
}
// SendDirective is the public facing API to set state variables in the test
func (st *StressTest) SendDirective(d Directive) {
st.directiveChan <- d
}
// Starts a go routine that listens for Results
func (st *StressTest) resultsListen() {
st.createDatabase(st.TestDB)
go func() {
bp := st.NewResultsPointBatch()
for resp := range st.ResultsChan {
switch resp.Point.Name() {
case "done":
st.ResultsClient.Write(bp)
resp.Tracer.Done()
default:
// Add the StressTest tags
pt, err := resp.AddTags(st.tags())
if err != nil {
panic(err)
}
// Add the point to the batch
bp = st.batcher(pt, bp)
resp.Tracer.Done()
}
}
}()
}
// NewResultsPointBatch creates a new batch of points for the results
func (st *StressTest) NewResultsPointBatch() influx.BatchPoints {
bp, _ := influx.NewBatchPoints(influx.BatchPointsConfig{
Database: st.TestDB,
Precision: "ns",
})
return bp
}
// Batches incoming Result.Point and sends them if the batch reaches 5k in size
func (st *StressTest) batcher(pt *influx.Point, bp influx.BatchPoints) influx.BatchPoints {
if len(bp.Points()) <= 5000 {
bp.AddPoint(pt)
} else {
err := st.ResultsClient.Write(bp)
if err != nil {
log.Fatalf("Error writing performance stats\n error: %v\n", err)
}
bp = st.NewResultsPointBatch()
}
return bp
}
// Convinence database creation function
func (st *StressTest) createDatabase(db string) {
query := fmt.Sprintf("CREATE DATABASE %v", db)
res, err := st.ResultsClient.Query(influx.Query{Command: query})
if err != nil {
log.Fatalf("error: no running influx server at localhost:8086")
if res.Error() != nil {
log.Fatalf("error: no running influx server at localhost:8086")
}
}
}
// GetStatementResults is a convinence function for fetching all results given a StatementID
func (st *StressTest) GetStatementResults(sID, t string) (res []influx.Result) {
qryStr := fmt.Sprintf(`SELECT * FROM "%v" WHERE statement_id = '%v'`, t, sID)
return st.queryTestResults(qryStr)
}
// Runs given qry on the test results database and returns the results or nil in case of error
func (st *StressTest) queryTestResults(qry string) (res []influx.Result) {
response, err := st.ResultsClient.Query(influx.Query{Command: qry, Database: st.TestDB})
if err == nil {
if response.Error() != nil {
log.Fatalf("Error sending results query\n error: %v\n", response.Error())
}
}
if response.Results[0].Series == nil {
return nil
}
return response.Results
}

View File

@@ -0,0 +1,32 @@
package stressClient
import (
"testing"
"time"
influx "github.com/influxdata/influxdb/client/v2"
)
func NewBlankTestPoint() *influx.Point {
meas := "measurement"
tags := map[string]string{"fooTag": "fooTagValue"}
fields := map[string]interface{}{"value": 5920}
utc, _ := time.LoadLocation("UTC")
timestamp := time.Date(2016, time.Month(4), 20, 0, 0, 0, 0, utc)
pt, _ := influx.NewPoint(meas, tags, fields, timestamp)
return pt
}
func TestStressTestBatcher(t *testing.T) {
sf, _, _ := NewTestStressTest()
bpconf := influx.BatchPointsConfig{
Database: sf.TestDB,
Precision: "ns",
}
bp, _ := influx.NewBatchPoints(bpconf)
pt := NewBlankTestPoint()
bp = sf.batcher(pt, bp)
if len(bp.Points()) != 1 {
t.Fail()
}
}

View File

@@ -0,0 +1,175 @@
package stressClient
import (
"strings"
"sync"
)
// Type refers to the different Package types
type Type int
// There are two package types, Write and Query
const (
Write Type = iota
Query
)
func startStressClient(packageCh <-chan Package, directiveCh <-chan Directive, responseCh chan<- Response, testID string) {
c := &stressClient{
testID: testID,
addresses: []string{"localhost:8086"},
ssl: false,
username: "",
password: "",
precision: "ns",
database: "stress",
startDate: "2016-01-01",
qdelay: "0s",
wdelay: "0s",
wconc: 10,
qconc: 5,
packageChan: packageCh,
directiveChan: directiveCh,
responseChan: responseCh,
}
// start listening for writes and queries
go c.listen()
// start listening for state changes
go c.directiveListen()
}
type stressClient struct {
testID string
// State for the Stress Test
addresses []string
precision string
startDate string
database string
wdelay string
qdelay string
username string
password string
ssl bool
// Channels from statements
packageChan <-chan Package
directiveChan <-chan Directive
// Response channel
responseChan chan<- Response
// Concurrency utilities
sync.WaitGroup
sync.Mutex
// Concurrency Limit for Writes and Reads
wconc int
qconc int
// Manage Read and Write concurrency seperately
wc *ConcurrencyLimiter
rc *ConcurrencyLimiter
}
// NewTestStressClient returns a blank stressClient for testing
func newTestStressClient(url string) (*stressClient, chan Directive, chan Package) {
pkgChan := make(chan Package)
dirChan := make(chan Directive)
pe := &stressClient{
testID: "foo_id",
addresses: []string{url},
precision: "s",
startDate: "2016-01-01",
database: "fooDatabase",
wdelay: "50ms",
qdelay: "50ms",
ssl: false,
username: "",
password: "",
wconc: 5,
qconc: 5,
packageChan: pkgChan,
directiveChan: dirChan,
wc: NewConcurrencyLimiter(1),
rc: NewConcurrencyLimiter(1),
}
return pe, dirChan, pkgChan
}
// stressClient starts listening for Packages on the main channel
func (sc *stressClient) listen() {
defer sc.Wait()
sc.wc = NewConcurrencyLimiter(sc.wconc)
sc.rc = NewConcurrencyLimiter(sc.qconc)
l := NewConcurrencyLimiter((sc.wconc + sc.qconc) * 2)
counter := 0
for p := range sc.packageChan {
l.Increment()
go func(p Package) {
defer l.Decrement()
switch p.T {
case Write:
sc.spinOffWritePackage(p, (counter % len(sc.addresses)))
case Query:
sc.spinOffQueryPackage(p, (counter % len(sc.addresses)))
}
}(p)
counter++
}
}
// Set handles all SET requests for test state
func (sc *stressClient) directiveListen() {
for d := range sc.directiveChan {
sc.Lock()
switch d.Property {
// addresses is a []string of target InfluxDB instance(s) for the test
// comes in as a "|" seperated array of addresses
case "addresses":
addr := strings.Split(d.Value, "|")
sc.addresses = addr
// percison is the write precision for InfluxDB
case "precision":
sc.precision = d.Value
// writeinterval is an optional delay between batches
case "writeinterval":
sc.wdelay = d.Value
// queryinterval is an optional delay between the batches
case "queryinterval":
sc.qdelay = d.Value
// database is the InfluxDB database to target for both writes and queries
case "database":
sc.database = d.Value
// username for the target database
case "username":
sc.username = d.Value
// username for the target database
case "password":
sc.password = d.Value
// use https if sent true
case "ssl":
if d.Value == "true" {
sc.ssl = true
}
// concurrency is the number concurrent writers to the database
case "writeconcurrency":
conc := parseInt(d.Value)
sc.wconc = conc
sc.wc.NewMax(conc)
// concurrentqueries is the number of concurrent queriers database
case "queryconcurrency":
conc := parseInt(d.Value)
sc.qconc = conc
sc.rc.NewMax(conc)
}
d.Tracer.Done()
sc.Unlock()
}
}

View File

@@ -0,0 +1,74 @@
package stressClient
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"time"
)
func (sc *stressClient) spinOffQueryPackage(p Package, serv int) {
sc.Add(1)
sc.rc.Increment()
go func() {
// Send the query
sc.prepareQuerySend(p, serv)
sc.Done()
sc.rc.Decrement()
}()
}
// Prepares to send the GET request
func (sc *stressClient) prepareQuerySend(p Package, serv int) {
var queryTemplate string
if sc.ssl {
queryTemplate = "https://%v/query?db=%v&q=%v&u=%v&p=%v"
} else {
queryTemplate = "http://%v/query?db=%v&q=%v&u=%v&p=%v"
}
queryURL := fmt.Sprintf(queryTemplate, sc.addresses[serv], sc.database, url.QueryEscape(string(p.Body)), sc.username, sc.password)
// Send the query
sc.makeGet(queryURL, p.StatementID, p.Tracer)
// Query Interval enforcement
qi, _ := time.ParseDuration(sc.qdelay)
time.Sleep(qi)
}
// Sends the GET request, reads it, and handles errors
func (sc *stressClient) makeGet(addr, statementID string, tr *Tracer) {
// Make GET request
t := time.Now()
resp, err := http.Get(addr)
elapsed := time.Since(t)
if err != nil {
log.Printf("Error making Query HTTP request\n error: %v\n", err)
}
defer resp.Body.Close()
// Read body and return it for Reporting
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error reading Query response body\n error: %v\n", err)
}
if resp.StatusCode != 200 {
log.Printf("Query returned non 200 status\n status: %v\n error: %v\n", resp.StatusCode, string(body))
}
// Send the response
sc.responseChan <- NewResponse(sc.queryPoint(statementID, body, resp.StatusCode, elapsed, tr.Tags), tr)
}
func success(r *http.Response) bool {
// ADD success for tcp, udp, etc
return r != nil && (r.StatusCode == 204 || r.StatusCode == 200)
}

View File

@@ -0,0 +1,112 @@
package stressClient
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
)
// ###############################################
// A selection of methods to manage the write path
// ###############################################
// Packages up Package from channel in goroutine
func (sc *stressClient) spinOffWritePackage(p Package, serv int) {
sc.Add(1)
sc.wc.Increment()
go func() {
sc.retry(p, time.Duration(time.Nanosecond), serv)
sc.Done()
sc.wc.Decrement()
}()
}
// Implements backoff and retry logic for 500 responses
func (sc *stressClient) retry(p Package, backoff time.Duration, serv int) {
// Set Backoff Interval to 500ms
backoffInterval := time.Duration(500 * time.Millisecond)
// Arithmetic backoff for kicks
bo := backoff + backoffInterval
// Make the write request
resp, elapsed, err := sc.prepareWrite(p.Body, serv)
// Find number of times request has been retried
numBackoffs := int(bo/backoffInterval) - 1
// On 500 responses, resp == nil. This logic keeps program for panicing
var statusCode int
if resp == nil {
statusCode = 500
} else {
statusCode = resp.StatusCode
}
// Make a point for reporting
point := sc.writePoint(numBackoffs, p.StatementID, statusCode, elapsed, p.Tracer.Tags, len(p.Body))
// Send the Response(point, tracer)
sc.responseChan <- NewResponse(point, p.Tracer)
// BatchInterval enforcement
bi, _ := time.ParseDuration(sc.wdelay)
time.Sleep(bi)
// Retry if the statusCode was not 204 or the err != nil
if !(statusCode == 204) || err != nil {
// Increment the *Tracer waitgroup if we are going to retry the request
p.Tracer.Add(1)
// Log the error if there is one
fmt.Println(err)
// Backoff enforcement
time.Sleep(bo)
sc.retry(p, bo, serv)
}
}
// Prepares to send the POST request
func (sc *stressClient) prepareWrite(points []byte, serv int) (*http.Response, time.Duration, error) {
// Construct address string
var writeTemplate string
if sc.ssl {
writeTemplate = "https://%v/write?db=%v&precision=%v&u=%v&p=%v"
} else {
writeTemplate = "http://%v/write?db=%v&precision=%v&u=%v&p=%v"
}
address := fmt.Sprintf(writeTemplate, sc.addresses[serv], sc.database, sc.precision, sc.username, sc.password)
// Start timer
t := time.Now()
resp, err := makePost(address, bytes.NewBuffer(points))
elapsed := time.Since(t)
return resp, elapsed, err
}
// Send POST request, read it, and handle errors
func makePost(url string, points io.Reader) (*http.Response, error) {
resp, err := http.Post(url, "text/plain", points)
if err != nil {
return resp, fmt.Errorf("Error making write POST request\n error: %v\n url: %v\n", err, url)
}
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != 204 {
return resp, fmt.Errorf("Write returned non-204 status code\n StatusCode: %v\n InfluxDB Error: %v\n", resp.StatusCode, string(body))
}
resp.Body.Close()
return resp, nil
}

View File

@@ -0,0 +1,19 @@
package stressClient
import (
"sync"
)
// The Tracer carrys tags and a waitgroup from the statements through the package life cycle
type Tracer struct {
Tags map[string]string
sync.WaitGroup
}
// NewTracer returns a Tracer with tags attached
func NewTracer(tags map[string]string) *Tracer {
return &Tracer{
Tags: tags,
}
}

View File

@@ -0,0 +1,17 @@
package stressClient
import (
"testing"
)
func TestNewTracer(t *testing.T) {
tagValue := "foo_tag_value"
tracer := NewTracer(map[string]string{"foo_tag_key": tagValue})
got := tracer.Tags["foo_tag_key"]
if got != tagValue {
t.Errorf("expected: %v\ngot: %v", tagValue, got)
}
tracer.Add(1)
tracer.Done()
tracer.Wait()
}

View File

@@ -0,0 +1,89 @@
package stressClient
import (
"crypto/rand"
"fmt"
"log"
"strconv"
"sync"
)
// ###########################################
// ConcurrencyLimiter and associated methods #
// ###########################################
// ConcurrencyLimiter ensures that no more than a specified
// max number of goroutines are running.
type ConcurrencyLimiter struct {
inc chan chan struct{}
dec chan struct{}
max int
count int
sync.Mutex
}
// NewConcurrencyLimiter returns a configured limiter that will
// ensure that calls to Increment will block if the max is hit.
func NewConcurrencyLimiter(max int) *ConcurrencyLimiter {
c := &ConcurrencyLimiter{
inc: make(chan chan struct{}),
dec: make(chan struct{}, max),
max: max,
}
go c.handleLimits()
return c
}
// Increment will increase the count of running goroutines by 1.
// if the number is currently at the max, the call to Increment
// will block until another goroutine decrements.
func (c *ConcurrencyLimiter) Increment() {
r := make(chan struct{})
c.inc <- r
<-r
}
// Decrement will reduce the count of running goroutines by 1
func (c *ConcurrencyLimiter) Decrement() {
c.dec <- struct{}{}
}
// NewMax resets the max of a ConcurrencyLimiter.
func (c *ConcurrencyLimiter) NewMax(i int) {
c.Lock()
defer c.Unlock()
c.max = i
}
// handleLimits runs in a goroutine to manage the count of
// running goroutines.
func (c *ConcurrencyLimiter) handleLimits() {
for {
r := <-c.inc
c.Lock()
if c.count >= c.max {
<-c.dec
c.count--
}
c.Unlock()
c.count++
r <- struct{}{}
}
}
// Utility interger parsing function
func parseInt(s string) int {
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
log.Fatalf("Error parsing integer:\n String: %v\n Error: %v\n", s, err)
}
return int(i)
}
// Utility for making random strings of length n
func randStr(n int) string {
b := make([]byte, n/2)
_, _ = rand.Read(b)
return fmt.Sprintf("%x", b)
}