2015-05-29 9 views
7

W moim programie Go wykonałem kilka żądań HTTP i potrzebowałem czasu czasu odpowiedzi (i nie wymagać czasu).Czasowa odpowiedź HTTP w Go

Oto mój aktualny kod (timing czas request):

func Get() int { 
    start := time.Now() 
    result, err := http.Get("http://www.google.com") 
    if err != nil { 
     log.Fatal(err) 
    } 
    defer result.Body.Close() 
    elapsed := time.Since(start).Seconds() 
    log.Println(elapsed) 

    return result.StatusCode 
} 

Właściwie ten kod pokaże coś o czasie 5s żądanie, w tym rozpoznawania nazw DNS i innych rzeczy ... Jeśli mogę wykonać ten sam test z narzędzie takie jak Apache JMeter, czas wynosi około 100ms (co jest rzeczywistym czasem odpowiedzi serwera, bez dbania o czas żądania).

To, czego naprawdę chcę, to obliczyć rzeczywisty czas reakcji serwera. Jak mogę to obliczyć w Go?

+3

Co masz na myśli czas reakcji? Czas od momentu zakończenia przetwarzania żądania przez serwer do czasu otrzymania go przez klienta? Nie jestem pewien, jak to będzie możliwe - sama wycieczka, dns i wszystko, co jest nieodłączną częścią cyklu HTTP ... Najlepiej będzie, jeśli zdążysz to zrobić na serwerze. –

+0

OK, to jest to, co myślałem. Właściwie to nie wiem, jak to jest teraz narzędzie JMeter, ale chciałbym czegoś takiego. JMeter nie sprawdza serwera (w mojej wiedzy). Testowałem z wget, a czas jest taki sam jak ten obliczony przez mój program (około 5 sekund włącznie z rozpoznawaniem DNS). – Devatoria

Odpowiedz

8

można mierzyć poprzez otwarcie połączenia TCP i "mówienie" surowego HTTP (wikipedia, rfc7230, rfc7231, rfc7232, rfc7233, rfc7234, rfc7235). Nawiązujesz połączenie, wysyłasz prośbę i uruchamiasz stoper tutaj. I poczekaj na odpowiedź. W ten sposób wykluczone zostanie rozstrzyganie DNS i czas wymagany do nawiązania połączenia.

Ponieważ nie masz wglądu, czy serwer rozpoczyna wysyłanie danych od razu, czy tylko wtedy, gdy wszystko jest gotowe, a nie masz informacji o opóźnieniach sieciowych, nie będzie to dokładne, a jedynie oszacowanie.

chciałbym mierzyć na 2 punkty: po pierwsze bajty mogą być odczytywane i gdy wszystko jest czytany:

conn, err := net.Dial("tcp", "google.com:80") 
if err != nil { 
    panic(err) 
} 
defer conn.Close() 
conn.Write([]byte("GET/HTTP/1.0\r\n\r\n")) 

start := time.Now() 
oneByte := make([]byte,1) 
_, err = conn.Read(oneByte) 
if err != nil { 
    panic(err) 
} 
log.Println("First byte:", time.Since(start)) 

_, err = ioutil.ReadAll(conn) 
if err != nil { 
    panic(err) 
} 
log.Println("Everything:", time.Since(start)) 

Uwaga:

To może być uzasadnione mierzyć w 3 punkcie: po odczytaniu wszystkich nagłówków odpowiedzi. Jest to wykrywane jako odczytanie pełnych linii z odpowiedzi (połączenia) i napotkanie pustej linii. Ta pusta linia oddziela nagłówki odpowiedzi od treści odpowiedzi.

+0

Dziękuję bardzo za tę odpowiedź. Tego właśnie szukałem.Mam zamiar trochę go poprawić, ale nie potrzebuję perfekcji :) – Devatoria

12

Aby nie odbierać niczego poza doskonale przyjętą, zaakceptowaną odpowiedzią, jedną z alternatyw, o których należy pamiętać, jest zaimplementowanie niestandardowego RoundTrippera, który otacza domyślny http.Transport i net.Dialer. Może to być przydatne, jeśli oprzyrządowanie kodu używa http.Client lub jeśli potrzebujesz obsługi proxy, TLS, keep-alive lub innych możliwości HTTP, ale nie chcesz/potrzebujesz ich ponownego wdrożenia. Nie będziesz miał tak dużej kontroli, jak Ty, korzystając z całkowicie spersonalizowanego klienta, ale warto mieć go w swoim zestawie narzędzi.

Przykład runda Tripper:

type customTransport struct { 
    rtp  http.RoundTripper 
    dialer *net.Dialer 
    connStart time.Time 
    connEnd time.Time 
    reqStart time.Time 
    reqEnd time.Time 
} 

func newTransport() *customTransport { 
    tr := &customTransport{ 
     dialer: &net.Dialer{ 
      Timeout: 30 * time.Second, 
      KeepAlive: 30 * time.Second, 
     }, 
    } 
    tr.rtp = &http.Transport{ 
     Proxy:    http.ProxyFromEnvironment, 
     Dial:    tr.dial, 
     TLSHandshakeTimeout: 10 * time.Second, 
    } 
    return tr 
} 

func (tr *customTransport) RoundTrip(r *http.Request) (*http.Response, error) { 
    tr.reqStart = time.Now() 
    resp, err := tr.rtp.RoundTrip(r) 
    tr.reqEnd = time.Now() 
    return resp, err 
} 

func (tr *customTransport) dial(network, addr string) (net.Conn, error) { 
    tr.connStart = time.Now() 
    cn, err := tr.dialer.Dial(network, addr) 
    tr.connEnd = time.Now() 
    return cn, err 
} 

func (tr *customTransport) ReqDuration() time.Duration { 
    return tr.Duration() - tr.ConnDuration() 
} 

func (tr *customTransport) ConnDuration() time.Duration { 
    return tr.connEnd.Sub(tr.connStart) 
} 

func (tr *customTransport) Duration() time.Duration { 
    return tr.reqEnd.Sub(tr.reqStart) 
} 

Mam spadła, że ​​w prosty przykład programu tutaj: https://github.com/skyec/go-instrumented-roundtripper/blob/master/main.go

+0

Dziękuję za tę ciekawą alternatywę, która może mi pomóc w dalszych krokach w moim projekcie :) – Devatoria