Одной из самых популярных причин использования Go является то, что он поддерживает широкий спектр встроенных пакетов. Одним из таких пакетов является Httptrace. Например, если вам нужно позвонить на сервер с использованием протокола HTTP, необходимо выполнить промежуточные шаги, такие как установление соединения, разрешение DNS и выполнение рукопожатия TLS перед выполнением запроса.

Рассмотрим сценарий, в котором ваш API должен обращаться к серверу во время пиковой нагрузки, а клиент выходит из строя из-за тайм-аутов, отказов в соединении и других проблем. В таких сценариях мы можем использовать Httptrace для отслеживания ошибок и прошедшего времени промежуточных операций HTTP, чтобы проанализировать, где возникают ошибки и где именно HTTP занимает больше времени, чем ожидалось.

Как только вы определили виновника с помощью Httptrace, вы можете соответствующим образом улучшить свою конфигурацию. Например, если получение соединения с сервером занимает больше времени, чем ожидалось, вы можете обновить MaxIdleConns в зависимости от пиковой нагрузки. Точно так же вы можете обновить MaxIdleConnsPerHost и IdleConnTimeout на основе выявленных вами несоответствий.

Для лучшего понимания таймаутов перейдите по этой ссылке https://cs.opensource.google/go/go/+/refs/tags/go1.20.3:src/net/http/transport.go

Трассировка HTTP:-

func getHttpTrace() *httptrace.ClientTrace {

// variables to track time/duration 
 var (
  dnsStart, dnsEnd, connStart,
  connEnd, connectStart, connectEnd,
  tlsHandShakeStart, tlsHandShakeEnd time.Time
 )

 trace := &httptrace.ClientTrace{
  GetConn: func(hostPort string) {
   connStart = time.Now()
  },
  GotConn: func(info httptrace.GotConnInfo) {
   connEnd = time.Now()

   if info.Reused {
    log.Println("connection reused")
   } else {
    log.Println("time elapsed for Getting connection in micro seconds ", connEnd.Sub(connStart).Microseconds())

   }

  },
  ConnectStart: func(network, addr string) {
   connectStart = time.Now()

  },
  ConnectDone: func(network, addr string, err error) {
   connectEnd = time.Now()
   if err != nil {
    log.Println("error at ConnectDone", err)

   } else {
    log.Println("time elapsed to  connect  in micro seconds ", connectEnd.Sub(connectStart).Microseconds())
   }
  },
  DNSStart: func(info httptrace.DNSStartInfo) {
   dnsStart = time.Now()
  },
  DNSDone: func(info httptrace.DNSDoneInfo) {
   dnsEnd = time.Now()
   log.Println("time elapsed to resolve DNS in micro seconds ", dnsEnd.Sub(dnsStart).Microseconds())

  },
  TLSHandshakeStart: func() {
   tlsHandShakeStart = time.Now()
  },
  TLSHandshakeDone: func(state tls.ConnectionState, err error) {
   if err != nil {
    log.Println("tls error", err)

   } else {
    tlsHandShakeEnd = time.Now()
    log.Println("time elapsed for TLS Handshake in micro seconds ", tlsHandShakeEnd.Sub(tlsHandShakeStart).Microseconds())

   }

  },
  PutIdleConn: func(err error) {
   if err != nil {
    log.Println("error at putIdleConn", err)
   } else {
    log.Println("put idle connection")
   }

  },
 }

 return trace

}

мы можем изменить приведенный выше код, например, добавить идентификатор трассировки, найти среднее значение всех вызовов и т. д.

пример для нахождения среднего значения каждой операции:-

func getHttpTrace() *httptrace.ClientTrace {
 var (
  dnsStart, dnsEnd, connStart,
  connEnd, connectStart, connectEnd,
  tlsHandShakeStart, tlsHandShakeEnd time.Time
 )

 trace := &httptrace.ClientTrace{
  GetConn: func(hostPort string) {
   connStart = time.Now()
  },
  GotConn: func(info httptrace.GotConnInfo) {
   connEnd = time.Now()

   if info.Reused {
    log.Println("connection reused")
   } else {
    avgGotConn = append(avgGotConn, connEnd.Sub(connStart).Microseconds())

   }

  },
  ConnectStart: func(network, addr string) {
   connectStart = time.Now()

  },
  ConnectDone: func(network, addr string, err error) {
   connectEnd = time.Now()
   if err != nil {
    log.Println("error at ConnectDone", err)

   } else {
    avgConnect = append(avgConnect, connectEnd.Sub(connectStart).Microseconds())
   }
  },
  DNSStart: func(info httptrace.DNSStartInfo) {
   dnsStart = time.Now()
  },
  DNSDone: func(info httptrace.DNSDoneInfo) {
   dnsEnd = time.Now()
   avgDns = append(avgDns, dnsEnd.Sub(dnsStart).Microseconds())

  },
  TLSHandshakeStart: func() {
   tlsHandShakeStart = time.Now()
  },
  TLSHandshakeDone: func(state tls.ConnectionState, err error) {
   if err != nil {
    log.Println("tls error", err)

   } else {
    tlsHandShakeEnd = time.Now()
    avgTlsHandShake = append(avgTlsHandShake, tlsHandShakeEnd.Sub(tlsHandShakeStart).Microseconds())

   }

  },
  PutIdleConn: func(err error) {
   if err != nil {
    log.Println("error at putIdleConn", err)
   } else {
    log.Println("put idle connection")
   }

  },
 }

 return trace

}

// finding average of each operation
func findAvg() {
 var (
  gotConn, connect, dns, tlsHandshake int64
 )
 for _, v := range avgGotConn {
  gotConn += v
 }
 log.Println("avg got conn", float64(gotConn)/float64(len(avgGotConn)))

 for _, v := range avgConnect {
  connect += v
 }
 log.Println("avg got connect", float64(connect)/float64(len(avgConnect)))

 for _, v := range avgDns {
  dns += v
 }
 log.Println("avg dns", float64(dns)/float64(len(avgDns)))

 for _, v := range avgTlsHandShake {
  tlsHandshake += v
 }
 log.Println("avg tls handshake", float64(tlsHandshake)/float64(len(avgTlsHandShake)))
}

Пример использования: –

   cli := http.Client{}
   trace := getHttpTrace()
   req, _ := http.NewRequest("GET", "http://localhost:8888/", bytes.NewBuffer([]byte))
   req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

   response,err:= cli.Do(req)

Пожалуйста, не стесняйтесь добавлять любые комментарии/предложения/ошибки и т. д.…👍