181 lines
3.7 KiB
Go
181 lines
3.7 KiB
Go
package perf
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.handmade.network/hmn/hmn/src/jobs"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
type RequestPerf struct {
|
|
Route string
|
|
Path string // the path actually matched
|
|
Method string
|
|
Start time.Time
|
|
End time.Time
|
|
Blocks []PerfBlock
|
|
}
|
|
|
|
func MakeNewRequestPerf(route string, method string, path string) *RequestPerf {
|
|
return &RequestPerf{
|
|
Start: time.Now(),
|
|
Route: route,
|
|
Path: path,
|
|
Method: method,
|
|
}
|
|
}
|
|
|
|
func (rp *RequestPerf) EndRequest() {
|
|
if rp == nil {
|
|
return
|
|
}
|
|
|
|
for rp.EndBlock() {
|
|
}
|
|
rp.End = time.Now()
|
|
}
|
|
|
|
func (rp *RequestPerf) Checkpoint(category, description string) {
|
|
if rp == nil {
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
checkpoint := PerfBlock{
|
|
Start: now,
|
|
End: now,
|
|
Category: category,
|
|
Description: description,
|
|
}
|
|
rp.Blocks = append(rp.Blocks, checkpoint)
|
|
}
|
|
|
|
func (rp *RequestPerf) StartBlock(category, description string) {
|
|
if rp == nil {
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
checkpoint := PerfBlock{
|
|
Start: now,
|
|
End: time.Time{},
|
|
Category: category,
|
|
Description: description,
|
|
}
|
|
rp.Blocks = append(rp.Blocks, checkpoint)
|
|
}
|
|
|
|
func (rp *RequestPerf) EndBlock() bool {
|
|
if rp == nil {
|
|
return false
|
|
}
|
|
|
|
for i := len(rp.Blocks) - 1; i >= 0; i -= 1 {
|
|
if rp.Blocks[i].End.Equal(time.Time{}) {
|
|
rp.Blocks[i].End = time.Now()
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (rp *RequestPerf) MsFromStart(block *PerfBlock) float64 {
|
|
if rp == nil {
|
|
return 0
|
|
}
|
|
|
|
return float64(block.Start.Sub(rp.Start).Nanoseconds()) / 1000 / 1000
|
|
}
|
|
|
|
type PerfBlock struct {
|
|
Start time.Time
|
|
End time.Time
|
|
Category string
|
|
Description string
|
|
}
|
|
|
|
func (pb *PerfBlock) Duration() time.Duration {
|
|
return pb.End.Sub(pb.Start)
|
|
}
|
|
|
|
func (pb *PerfBlock) DurationMs() float64 {
|
|
return float64(pb.Duration().Nanoseconds()) / 1000 / 1000
|
|
}
|
|
|
|
type PerfStorage struct {
|
|
AllRequests []RequestPerf
|
|
}
|
|
|
|
type PerfCollector struct {
|
|
In chan<- RequestPerf
|
|
Job jobs.Job
|
|
RequestCopy chan<- (chan<- PerfStorage)
|
|
}
|
|
|
|
func RunPerfCollector(ctx context.Context) *PerfCollector {
|
|
in := make(chan RequestPerf)
|
|
job := jobs.New()
|
|
requestCopy := make(chan (chan<- PerfStorage))
|
|
|
|
var storage PerfStorage
|
|
// TODO(asaf): Load history from file
|
|
|
|
go func() {
|
|
defer job.Done()
|
|
|
|
for {
|
|
select {
|
|
case perf := <-in:
|
|
storage.AllRequests = append(storage.AllRequests, perf)
|
|
// TODO(asaf): Write to file
|
|
case resultChan := <-requestCopy:
|
|
resultChan <- storage
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
perfCollector := PerfCollector{
|
|
In: in,
|
|
Job: job,
|
|
RequestCopy: requestCopy,
|
|
}
|
|
return &perfCollector
|
|
}
|
|
|
|
func (perfCollector *PerfCollector) SubmitRun(run *RequestPerf) {
|
|
perfCollector.In <- *run
|
|
}
|
|
|
|
func (perfCollector *PerfCollector) GetPerfCopy() *PerfStorage {
|
|
resultChan := make(chan PerfStorage)
|
|
perfCollector.RequestCopy <- resultChan
|
|
perfStorageCopy := <-resultChan
|
|
return &perfStorageCopy
|
|
}
|
|
|
|
func LogPerf(perf *RequestPerf, log *zerolog.Event) {
|
|
blockStack := make([]time.Time, 0)
|
|
for i, block := range perf.Blocks {
|
|
for len(blockStack) > 0 && block.End.After(blockStack[len(blockStack)-1]) {
|
|
blockStack = blockStack[:len(blockStack)-1]
|
|
}
|
|
log.Str(fmt.Sprintf("[%4.d] At %9.2fms", i, perf.MsFromStart(&block)), fmt.Sprintf("%*.s[%s] %s (%.4fms)", len(blockStack)*2, "", block.Category, block.Description, block.DurationMs()))
|
|
blockStack = append(blockStack, block.End)
|
|
}
|
|
log.Msg(fmt.Sprintf("Served [%s] %s in %.4fms", perf.Method, perf.Path, float64(perf.End.Sub(perf.Start).Nanoseconds())/1000/1000))
|
|
}
|
|
|
|
const PerfContextKey = "HMNPerf"
|
|
|
|
func ExtractPerf(ctx context.Context) *RequestPerf {
|
|
iperf := ctx.Value(PerfContextKey)
|
|
if iperf == nil {
|
|
return nil
|
|
}
|
|
return iperf.(*RequestPerf)
|
|
}
|