hmn/src/perf/perf.go

181 lines
3.7 KiB
Go
Raw Normal View History

2021-04-26 06:56:49 +00:00
package perf
import (
"context"
2021-08-08 20:05:52 +00:00
"fmt"
2021-04-26 06:56:49 +00:00
"time"
2021-08-08 20:05:52 +00:00
"git.handmade.network/hmn/hmn/src/jobs"
2021-08-08 20:05:52 +00:00
"github.com/rs/zerolog"
2021-04-26 06:56:49 +00:00
)
type RequestPerf struct {
Route string
Path string // the path actually matched
2021-07-23 03:09:46 +00:00
Method string
2021-04-26 06:56:49 +00:00
Start time.Time
End time.Time
Blocks []PerfBlock
}
2021-07-23 03:09:46 +00:00
func MakeNewRequestPerf(route string, method string, path string) *RequestPerf {
2021-04-26 06:56:49 +00:00
return &RequestPerf{
2021-07-23 03:09:46 +00:00
Start: time.Now(),
Route: route,
Path: path,
Method: method,
2021-04-26 06:56:49 +00:00
}
}
func (rp *RequestPerf) EndRequest() {
if rp == nil {
return
}
2021-04-26 06:56:49 +00:00
for rp.EndBlock() {
}
rp.End = time.Now()
}
func (rp *RequestPerf) Checkpoint(category, description string) {
if rp == nil {
return
}
2021-04-26 06:56:49 +00:00
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
}
2021-04-26 06:56:49 +00:00
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
}
2021-04-26 06:56:49 +00:00
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
}
2021-04-26 06:56:49 +00:00
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
2021-04-26 06:56:49 +00:00
RequestCopy chan<- (chan<- PerfStorage)
}
func RunPerfCollector(ctx context.Context) *PerfCollector {
in := make(chan RequestPerf)
job := jobs.New()
2021-04-26 06:56:49 +00:00
requestCopy := make(chan (chan<- PerfStorage))
var storage PerfStorage
// TODO(asaf): Load history from file
go func() {
defer job.Done()
2021-04-26 06:56:49 +00:00
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,
2021-04-26 06:56:49 +00:00
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
}
2021-08-08 20:05:52 +00:00
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)
}