mirror of https://github.com/jetkvm/kvm.git
133 lines
3.2 KiB
Go
133 lines
3.2 KiB
Go
package timesync
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math/rand"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
var defaultHTTPUrls = []string{
|
|
"http://www.gstatic.com/generate_204",
|
|
"http://cp.cloudflare.com/",
|
|
"http://edge-http.microsoft.com/captiveportal/generate_204",
|
|
// Firefox, Apple, and Microsoft have inconsistent results, so we don't use it
|
|
// "http://detectportal.firefox.com/",
|
|
// "http://www.apple.com/library/test/success.html",
|
|
// "http://www.msftconnecttest.com/connecttest.txt",
|
|
}
|
|
|
|
func (t *TimeSync) queryAllHttpTime() (now *time.Time) {
|
|
chunkSize := 4
|
|
httpUrls := t.httpUrls
|
|
|
|
// shuffle the http urls to avoid always querying the same servers
|
|
rand.Shuffle(len(httpUrls), func(i, j int) { httpUrls[i], httpUrls[j] = httpUrls[j], httpUrls[i] })
|
|
|
|
for i := 0; i < len(httpUrls); i += chunkSize {
|
|
chunk := httpUrls[i:min(i+chunkSize, len(httpUrls))]
|
|
results := t.queryMultipleHttp(chunk, timeSyncTimeout)
|
|
if results != nil {
|
|
return results
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *TimeSync) queryMultipleHttp(urls []string, timeout time.Duration) (now *time.Time) {
|
|
results := make(chan *time.Time, len(urls))
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
defer cancel()
|
|
|
|
for _, url := range urls {
|
|
go func(url string) {
|
|
scopedLogger := t.l.With().
|
|
Str("http_url", url).
|
|
Logger()
|
|
|
|
metricHttpRequestCount.WithLabelValues(url).Inc()
|
|
metricHttpTotalRequestCount.Inc()
|
|
|
|
startTime := time.Now()
|
|
now, response, err := queryHttpTime(
|
|
ctx,
|
|
url,
|
|
timeout,
|
|
)
|
|
duration := time.Since(startTime)
|
|
|
|
metricHttpServerLastRTT.WithLabelValues(url).Set(float64(duration.Milliseconds()))
|
|
metricHttpServerRttHistogram.WithLabelValues(url).Observe(float64(duration.Milliseconds()))
|
|
|
|
status := 0
|
|
if response != nil {
|
|
status = response.StatusCode
|
|
}
|
|
metricHttpServerInfo.WithLabelValues(
|
|
url,
|
|
strconv.Itoa(status),
|
|
).Set(1)
|
|
|
|
if err == nil {
|
|
metricHttpTotalSuccessCount.Inc()
|
|
metricHttpSuccessCount.WithLabelValues(url).Inc()
|
|
|
|
requestId := response.Header.Get("X-Request-Id")
|
|
if requestId != "" {
|
|
requestId = response.Header.Get("X-Msedge-Ref")
|
|
}
|
|
if requestId == "" {
|
|
requestId = response.Header.Get("Cf-Ray")
|
|
}
|
|
scopedLogger.Info().
|
|
Str("time", now.Format(time.RFC3339)).
|
|
Int("status", status).
|
|
Str("request_id", requestId).
|
|
Str("time_taken", duration.String()).
|
|
Msg("HTTP server returned time")
|
|
|
|
cancel()
|
|
results <- now
|
|
} else if errors.Is(err, context.Canceled) {
|
|
metricHttpCancelCount.WithLabelValues(url).Inc()
|
|
metricHttpTotalCancelCount.Inc()
|
|
} else {
|
|
scopedLogger.Warn().
|
|
Str("error", err.Error()).
|
|
Int("status", status).
|
|
Msg("failed to query HTTP server")
|
|
}
|
|
}(url)
|
|
}
|
|
|
|
return <-results
|
|
}
|
|
|
|
func queryHttpTime(
|
|
ctx context.Context,
|
|
url string,
|
|
timeout time.Duration,
|
|
) (now *time.Time, response *http.Response, err error) {
|
|
client := http.Client{
|
|
Timeout: timeout,
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
dateStr := resp.Header.Get("Date")
|
|
parsedTime, err := time.Parse(time.RFC1123, dateStr)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return &parsedTime, resp, nil
|
|
}
|