diff --git a/cli/analyze.go b/cli/analyze.go index e6640451..0fbaa614 100644 --- a/cli/analyze.go +++ b/cli/analyze.go @@ -463,7 +463,7 @@ func printRequestAnalysis(ctx *cli.Context, ops aggregate.Operation, details boo console.Println(" * TTFB:", reqs.FirstByte) } - if reqs.FirstAccess != nil { + if details && reqs.FirstAccess != nil { reqs := reqs.FirstAccess console.Print( " * First Access: Avg: ", time.Duration(reqs.DurAvgMillis)*time.Millisecond, @@ -476,7 +476,20 @@ func printRequestAnalysis(ctx *cli.Context, ops aggregate.Operation, details boo if reqs.FirstByte != nil { console.Print(" * First Access TTFB: ", reqs.FirstByte) } - console.Println("") + } + if details && reqs.LastAccess != nil { + reqs := reqs.LastAccess + console.Print( + " * Last Access: Avg: ", time.Duration(reqs.DurAvgMillis)*time.Millisecond, + ", 50%: ", time.Duration(reqs.DurMedianMillis)*time.Millisecond, + ", 90%: ", time.Duration(reqs.Dur90Millis)*time.Millisecond, + ", 99%: ", time.Duration(reqs.Dur99Millis)*time.Millisecond, + ", Fastest: ", time.Duration(reqs.FastestMillis)*time.Millisecond, + ", Slowest: ", time.Duration(reqs.SlowestMillis)*time.Millisecond, + "\n") + if reqs.FirstByte != nil { + console.Print(" * Last Access TTFB: ", reqs.FirstByte) + } } if eps := reqs.ByHost; len(eps) > 1 && details { diff --git a/pkg/aggregate/requests.go b/pkg/aggregate/requests.go index d9ffe558..21e1330e 100644 --- a/pkg/aggregate/requests.go +++ b/pkg/aggregate/requests.go @@ -49,6 +49,7 @@ type SingleSizedRequests struct { // FirstAccess is filled if the same object is accessed multiple times. // This records the first touch of the object. FirstAccess *SingleSizedRequests `json:"first_access,omitempty"` + LastAccess *SingleSizedRequests `json:"last_access,omitempty"` // Host names, sorted. HostNames []string // Request times by host. @@ -69,14 +70,17 @@ func (a *SingleSizedRequests) fill(ops bench.Operations) { a.FirstByte = TtfbFromBench(ops.TTFB(start, end)) } -func (a *SingleSizedRequests) fillFirst(ops bench.Operations) { +func (a *SingleSizedRequests) fillFirstLast(ops bench.Operations) { if !ops.IsMultiTouch() { return } - r := SingleSizedRequests{} - ops = ops.FilterFirst() - r.fill(ops) - a.FirstAccess = &r + var first, last SingleSizedRequests + o := ops.FilterFirst() + first.fill(o) + a.FirstAccess = &first + o = ops.FilterLast() + last.fill(o) + a.LastAccess = &last } type RequestSizeRange struct { @@ -195,7 +199,7 @@ func RequestAnalysisSingleSized(o bench.Operations, allThreads bool) *SingleSize return &res } res.fill(active) - res.fillFirst(o) + res.fillFirstLast(o) res.HostNames = o.Endpoints() res.ByHost = RequestAnalysisHostsSingleSized(o) diff --git a/pkg/bench/ops.go b/pkg/bench/ops.go index cc3332a5..1a61677d 100644 --- a/pkg/bench/ops.go +++ b/pkg/bench/ops.go @@ -922,6 +922,7 @@ func (o Operations) FilterFirst() Operations { if len(o) == 0 { return nil } + o.SortByStartTime() ok := make(Operations, 0, 1000) seen := make(map[string]struct{}, len(o)) for _, op := range o { @@ -935,6 +936,26 @@ func (o Operations) FilterFirst() Operations { return ok } +// FilterLast returns the last operation on any file. +func (o Operations) FilterLast() Operations { + if len(o) == 0 { + return nil + } + o.SortByStartTime() + ok := make(Operations, 0, 1000) + seen := make(map[string]struct{}, len(o)) + for i := len(o) - 1; i >= 0; i-- { + op := o[i] + if _, ok := seen[op.File]; ok { + continue + } + seen[op.File] = struct{}{} + ok = append(ok, op) + } + + return ok +} + // Errors returns the errors found. func (o Operations) FilterErrors() Operations { if len(o) == 0 {