Skip to content

Instantly share code, notes, and snippets.

@segfault88
Created November 9, 2017 01:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save segfault88/7a876e3346aba7e6d2d72b54dd71db14 to your computer and use it in GitHub Desktop.
Save segfault88/7a876e3346aba7e6d2d72b54dd71db14 to your computer and use it in GitHub Desktop.
package main
import (
"crypto/tls"
"encoding/csv"
"fmt"
"log"
"net/http"
"net/http/httptrace"
"os"
"os/signal"
"strconv"
"sync"
"time"
"github.com/davecgh/go-spew/spew"
)
type transport struct {
current *http.Request
}
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
t.current = req
return http.DefaultTransport.RoundTrip(req)
}
func fromStart(start time.Time, end time.Time) string {
duration := float64(end.Sub(start).Nanoseconds()) / float64(time.Millisecond)
if duration <= 0 {
return "0.0"
}
return strconv.FormatFloat(duration, 'f', -1, 64)
}
func makeRequest(url string) []string {
t := &transport{}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Fatal(err)
}
start := time.Now()
var reused bool
var tDNSStart,
tDNSDone,
tFirstBytes,
tConnectStart,
tConnectDone,
tTLSStart,
tTLSDone,
tWroteHeaders,
tWroteRequest,
tDone time.Time
trace := &httptrace.ClientTrace{
GotConn: func(info httptrace.GotConnInfo) {
reused = info.Reused
},
DNSStart: func(_ httptrace.DNSStartInfo) {
tDNSStart = time.Now()
},
DNSDone: func(_ httptrace.DNSDoneInfo) {
tDNSDone = time.Now()
},
GotFirstResponseByte: func() {
tFirstBytes = time.Now()
},
ConnectStart: func(_, _ string) {
tConnectStart = time.Now()
},
ConnectDone: func(_, _ string, _ error) {
tConnectDone = time.Now()
},
TLSHandshakeStart: func() {
tTLSStart = time.Now()
},
TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
tTLSDone = time.Now()
},
WroteHeaders: func() {
tWroteHeaders = time.Now()
},
WroteRequest: func(_ httptrace.WroteRequestInfo) {
tWroteRequest = time.Now()
},
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
req.Close = true
client := &http.Client{Transport: t}
d, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
tDone = time.Now()
r := []string{
start.String(),
url,
strconv.FormatBool(reused),
fromStart(start, tDNSStart),
fromStart(start, tDNSDone),
fromStart(start, tConnectStart),
fromStart(start, tConnectDone),
fromStart(start, tTLSStart),
fromStart(start, tTLSDone),
fromStart(start, tWroteHeaders),
fromStart(start, tWroteRequest),
fromStart(start, tFirstBytes),
fromStart(start, tDone),
d.Header["X-Amzn-Requestid"][0],
d.Header["X-Amz-Cf-Id"][0],
d.Header["Via"][0],
}
d.Body.Close()
return r
}
var lock = sync.Mutex{}
var wg = sync.WaitGroup{}
var columns = []string{
"At",
"URL",
"Conn Reused",
"DNS Start",
"DNS Done",
"Connect Start",
"Connect Done",
"TLS Start",
"TLS Done",
"Wrote Headers",
"Wrote Request",
"First Response Byte",
"Done",
"X-Amzn-Requestid",
"X-Amz-Cf-Id",
"Via",
}
func monitor(writer *csv.Writer, shutdown chan bool, url string) {
i := 0
c := time.Tick(30 * time.Second)
loop:
for {
i++
result := makeRequest(url)
spew.Printf("Request # %d done to %s at %s\n", i, url, result[0])
lock.Lock()
err := writer.Write(result)
if err != nil {
log.Fatal(err)
}
writer.Flush()
lock.Unlock()
select {
case <-shutdown:
break loop
case <-c:
continue loop
}
}
wg.Done()
}
func main() {
file, err := os.Create("result.csv")
if err != nil {
log.Fatal(err)
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
writer.Write(columns)
urls := []string{"https://url/health"}
shutdown := make(chan bool)
for _, u := range urls {
wg.Add(1)
go monitor(writer, shutdown, u)
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
close(shutdown)
fmt.Printf("Waiting to finish.\n")
wg.Wait()
fmt.Printf("Done.\n")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment