diff options
author | Patrick Steinhardt <psteinhardt@gitlab.com> | 2021-06-18 12:04:16 +0300 |
---|---|---|
committer | Patrick Steinhardt <psteinhardt@gitlab.com> | 2021-06-30 13:03:43 +0300 |
commit | 75ccb2ef11efbf803c8b4b9fb457dfb5ea78eb32 (patch) | |
tree | 119470ef9dd2ab2aed62ad7b9250d55baf84e9ff | |
parent | 5694344d3ee87c4f6ab663197596ba860f63ef50 (diff) |
stats: Split up HTTP stats code
Split up the code which handles generation of HTTP stats into multiple
files and prefix relevant public structures with `HTTP` to make their
scope clear. This is in preparation for a new probe type for pushes,
which will add additional HTTP logic.
-rw-r--r-- | cmd/gitaly-debug/analyzehttp.go | 2 | ||||
-rw-r--r-- | internal/blackbox/blackbox.go | 2 | ||||
-rw-r--r-- | internal/git/stats/http_clone.go | 49 | ||||
-rw-r--r-- | internal/git/stats/http_clone_test.go (renamed from internal/git/stats/clone_test.go) | 4 | ||||
-rw-r--r-- | internal/git/stats/http_fetch_pack.go (renamed from internal/git/stats/clone.go) | 147 | ||||
-rw-r--r-- | internal/git/stats/http_reference_discovery.go | 113 |
6 files changed, 166 insertions, 151 deletions
diff --git a/cmd/gitaly-debug/analyzehttp.go b/cmd/gitaly-debug/analyzehttp.go index dda0cb11e..805094ac7 100644 --- a/cmd/gitaly-debug/analyzehttp.go +++ b/cmd/gitaly-debug/analyzehttp.go @@ -8,7 +8,7 @@ import ( ) func analyzeHTTPClone(cloneURL string) { - st, err := stats.PerformClone(context.Background(), cloneURL, "", "", true) + st, err := stats.PerformHTTPClone(context.Background(), cloneURL, "", "", true) noError(err) fmt.Println("\n--- Reference discovery metrics:") diff --git a/internal/blackbox/blackbox.go b/internal/blackbox/blackbox.go index dd77d60f8..bffcca8b2 100644 --- a/internal/blackbox/blackbox.go +++ b/internal/blackbox/blackbox.go @@ -105,7 +105,7 @@ func (b Blackbox) doProbe(probe Probe) { entry := log.WithField("probe", probe.Name) entry.Info("starting probe") - clone, err := stats.PerformClone(ctx, probe.URL, probe.User, probe.Password, false) + clone, err := stats.PerformHTTPClone(ctx, probe.URL, probe.User, probe.Password, false) if err != nil { entry.WithError(err).Error("probe failed") return diff --git a/internal/git/stats/http_clone.go b/internal/git/stats/http_clone.go new file mode 100644 index 000000000..e47ecf06d --- /dev/null +++ b/internal/git/stats/http_clone.go @@ -0,0 +1,49 @@ +package stats + +import ( + "context" + "fmt" +) + +// HTTPClone hosts information about a typical HTTP-based clone. +type HTTPClone struct { + // ReferenceDiscovery is the reference discovery performed as part of the clone. + ReferenceDiscovery HTTPReferenceDiscovery + // FetchPack is the response to a git-fetch-pack(1) request which computes and transmits the + // packfile. + FetchPack HTTPFetchPack +} + +// PerformHTTPClone does a Git HTTP clone, discarding cloned data to /dev/null. +func PerformHTTPClone(ctx context.Context, url, user, password string, interactive bool) (HTTPClone, error) { + printInteractive := func(format string, a ...interface{}) { + if interactive { + // Ignore any errors returned by this given that we only use it as a + // debugging aid to write to stdout. + fmt.Printf(format, a...) + } + } + + referenceDiscovery, err := performHTTPReferenceDiscovery(ctx, url, user, password, printInteractive) + if err != nil { + return HTTPClone{}, ctxErr(ctx, err) + } + + fetchPack, err := performFetchPack(ctx, url, user, password, + referenceDiscovery.Refs(), printInteractive) + if err != nil { + return HTTPClone{}, ctxErr(ctx, err) + } + + return HTTPClone{ + ReferenceDiscovery: referenceDiscovery, + FetchPack: fetchPack, + }, nil +} + +func ctxErr(ctx context.Context, err error) error { + if ctx.Err() != nil { + return ctx.Err() + } + return err +} diff --git a/internal/git/stats/clone_test.go b/internal/git/stats/http_clone_test.go index 4e25cea93..1c287fa50 100644 --- a/internal/git/stats/clone_test.go +++ b/internal/git/stats/http_clone_test.go @@ -24,7 +24,7 @@ func TestClone(t *testing.T) { require.NoError(t, stopGitServer()) }() - clone, err := PerformClone(ctx, fmt.Sprintf("http://localhost:%d/%s", serverPort, filepath.Base(repoPath)), "", "", false) + clone, err := PerformHTTPClone(ctx, fmt.Sprintf("http://localhost:%d/%s", serverPort, filepath.Base(repoPath)), "", "", false) require.NoError(t, err, "perform analysis clone") const expectedRequests = 90 // based on contents of _support/gitlab-test.git-packed-refs @@ -100,7 +100,7 @@ func TestCloneWithAuth(t *testing.T) { require.NoError(t, stopGitServer()) }() - _, err := PerformClone( + _, err := PerformHTTPClone( ctx, fmt.Sprintf("http://localhost:%d/%s", serverPort, filepath.Base(repoPath)), user, diff --git a/internal/git/stats/clone.go b/internal/git/stats/http_fetch_pack.go index 18f881e9a..a688e7c19 100644 --- a/internal/git/stats/clone.go +++ b/internal/git/stats/http_fetch_pack.go @@ -5,8 +5,6 @@ import ( "compress/gzip" "context" "fmt" - "io" - "io/ioutil" "net/http" "strings" "time" @@ -14,151 +12,6 @@ import ( "gitlab.com/gitlab-org/gitaly/v14/internal/git/pktline" ) -// Clone hosts information about a typical HTTP-based clone. -type Clone struct { - // ReferenceDiscovery is the reference discovery performed as part of the clone. - ReferenceDiscovery HTTPReferenceDiscovery - // FetchPack is the response to a git-fetch-pack(1) request which computes transmits the - // packfile. - FetchPack HTTPFetchPack -} - -// PerformClone does a Git HTTP clone, discarding cloned data to /dev/null. -func PerformClone(ctx context.Context, url, user, password string, interactive bool) (Clone, error) { - printInteractive := func(format string, a ...interface{}) { - if interactive { - // Ignore any errors returned by this given that we only use it as a - // debugging aid to write to stdout. - fmt.Printf(format, a...) - } - } - - referenceDiscovery, err := performReferenceDiscovery(ctx, url, user, password, printInteractive) - if err != nil { - return Clone{}, ctxErr(ctx, err) - } - - fetchPack, err := performFetchPack(ctx, url, user, password, - referenceDiscovery.Refs(), printInteractive) - if err != nil { - return Clone{}, ctxErr(ctx, err) - } - - return Clone{ - ReferenceDiscovery: referenceDiscovery, - FetchPack: fetchPack, - }, nil -} - -func ctxErr(ctx context.Context, err error) error { - if ctx.Err() != nil { - return ctx.Err() - } - return err -} - -// HTTPReferenceDiscovery is a ReferenceDiscovery obtained via a clone of a target repository via -// HTTP. It contains additional information about the cloning process like status codes and -// timings. -type HTTPReferenceDiscovery struct { - start time.Time - responseHeader time.Duration - httpStatus int - stats ReferenceDiscovery -} - -// ResponseHeader returns how long it took to receive the response header. -func (d HTTPReferenceDiscovery) ResponseHeader() time.Duration { return d.responseHeader } - -// HTTPStatus returns the HTTP status code. -func (d HTTPReferenceDiscovery) HTTPStatus() int { return d.httpStatus } - -// FirstGitPacket returns how long it took to receive the first Git packet. -func (d HTTPReferenceDiscovery) FirstGitPacket() time.Duration { - return d.stats.FirstPacket.Sub(d.start) -} - -// ResponseBody returns how long it took to receive the first bytes of the response body. -func (d HTTPReferenceDiscovery) ResponseBody() time.Duration { - return d.stats.LastPacket.Sub(d.start) -} - -// Refs returns all announced references. -func (d HTTPReferenceDiscovery) Refs() []Reference { return d.stats.Refs } - -// Packets returns the number of Git packets received. -func (d HTTPReferenceDiscovery) Packets() int { return d.stats.Packets } - -// PayloadSize returns the total size of all pktlines' data. -func (d HTTPReferenceDiscovery) PayloadSize() int64 { return d.stats.PayloadSize } - -// Caps returns all announced capabilities. -func (d HTTPReferenceDiscovery) Caps() []string { return d.stats.Caps } - -func performReferenceDiscovery( - ctx context.Context, - url, user, password string, - reportProgress func(string, ...interface{}), -) (HTTPReferenceDiscovery, error) { - var referenceDiscovery HTTPReferenceDiscovery - - req, err := http.NewRequest("GET", url+"/info/refs?service=git-upload-pack", nil) - if err != nil { - return HTTPReferenceDiscovery{}, err - } - - req = req.WithContext(ctx) - if user != "" { - req.SetBasicAuth(user, password) - } - - for k, v := range map[string]string{ - "User-Agent": "gitaly-debug", - "Accept": "*/*", - "Accept-Encoding": "deflate, gzip", - "Pragma": "no-cache", - } { - req.Header.Set(k, v) - } - - referenceDiscovery.start = time.Now() - reportProgress("---\n") - reportProgress("--- GET %v\n", req.URL) - reportProgress("---\n") - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return HTTPReferenceDiscovery{}, err - } - defer func() { - io.Copy(ioutil.Discard, resp.Body) - resp.Body.Close() - }() - - if code := resp.StatusCode; code < 200 || code >= 400 { - return HTTPReferenceDiscovery{}, fmt.Errorf("git http get: unexpected http status: %d", code) - } - - referenceDiscovery.responseHeader = time.Since(referenceDiscovery.start) - referenceDiscovery.httpStatus = resp.StatusCode - reportProgress("response code: %d\n", resp.StatusCode) - reportProgress("response header: %v\n", resp.Header) - - body := resp.Body - if resp.Header.Get("Content-Encoding") == "gzip" { - body, err = gzip.NewReader(body) - if err != nil { - return HTTPReferenceDiscovery{}, err - } - } - - if err := referenceDiscovery.stats.Parse(body); err != nil { - return HTTPReferenceDiscovery{}, err - } - - return referenceDiscovery, nil -} - // HTTPFetchPack is a FetchPack obtained via a clone of a target repository via HTTP. It contains // additional information about the cloning process like status codes and timings. type HTTPFetchPack struct { diff --git a/internal/git/stats/http_reference_discovery.go b/internal/git/stats/http_reference_discovery.go new file mode 100644 index 000000000..7792f49f3 --- /dev/null +++ b/internal/git/stats/http_reference_discovery.go @@ -0,0 +1,113 @@ +package stats + +import ( + "compress/gzip" + "context" + "fmt" + "io" + "io/ioutil" + "net/http" + "time" +) + +// HTTPReferenceDiscovery is a ReferenceDiscovery obtained via a clone of a target repository via +// HTTP. It contains additional information about the cloning process like status codes and +// timings. +type HTTPReferenceDiscovery struct { + start time.Time + responseHeader time.Duration + httpStatus int + stats ReferenceDiscovery +} + +// ResponseHeader returns how long it took to receive the response header. +func (d HTTPReferenceDiscovery) ResponseHeader() time.Duration { return d.responseHeader } + +// HTTPStatus returns the HTTP status code. +func (d HTTPReferenceDiscovery) HTTPStatus() int { return d.httpStatus } + +// FirstGitPacket returns how long it took to receive the first Git packet. +func (d HTTPReferenceDiscovery) FirstGitPacket() time.Duration { + return d.stats.FirstPacket.Sub(d.start) +} + +// ResponseBody returns how long it took to receive the first bytes of the response body. +func (d HTTPReferenceDiscovery) ResponseBody() time.Duration { + return d.stats.LastPacket.Sub(d.start) +} + +// Refs returns all announced references. +func (d HTTPReferenceDiscovery) Refs() []Reference { return d.stats.Refs } + +// Packets returns the number of Git packets received. +func (d HTTPReferenceDiscovery) Packets() int { return d.stats.Packets } + +// PayloadSize returns the total size of all pktlines' data. +func (d HTTPReferenceDiscovery) PayloadSize() int64 { return d.stats.PayloadSize } + +// Caps returns all announced capabilities. +func (d HTTPReferenceDiscovery) Caps() []string { return d.stats.Caps } + +func performHTTPReferenceDiscovery( + ctx context.Context, + url, user, password string, + reportProgress func(string, ...interface{}), +) (HTTPReferenceDiscovery, error) { + var referenceDiscovery HTTPReferenceDiscovery + + req, err := http.NewRequest("GET", url+"/info/refs?service=git-upload-pack", nil) + if err != nil { + return HTTPReferenceDiscovery{}, err + } + + req = req.WithContext(ctx) + if user != "" { + req.SetBasicAuth(user, password) + } + + for k, v := range map[string]string{ + "User-Agent": "gitaly-debug", + "Accept": "*/*", + "Accept-Encoding": "deflate, gzip", + "Pragma": "no-cache", + } { + req.Header.Set(k, v) + } + + referenceDiscovery.start = time.Now() + reportProgress("---\n") + reportProgress("--- GET %v\n", req.URL) + reportProgress("---\n") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return HTTPReferenceDiscovery{}, err + } + defer func() { + io.Copy(ioutil.Discard, resp.Body) + resp.Body.Close() + }() + + if code := resp.StatusCode; code < 200 || code >= 400 { + return HTTPReferenceDiscovery{}, fmt.Errorf("git http get: unexpected http status: %d", code) + } + + referenceDiscovery.responseHeader = time.Since(referenceDiscovery.start) + referenceDiscovery.httpStatus = resp.StatusCode + reportProgress("response code: %d\n", resp.StatusCode) + reportProgress("response header: %v\n", resp.Header) + + body := resp.Body + if resp.Header.Get("Content-Encoding") == "gzip" { + body, err = gzip.NewReader(body) + if err != nil { + return HTTPReferenceDiscovery{}, err + } + } + + if err := referenceDiscovery.stats.Parse(body); err != nil { + return HTTPReferenceDiscovery{}, err + } + + return referenceDiscovery, nil +} |