Skip to content

Commit

Permalink
Merge pull request #9 from lightpanda-io/bench-history
Browse files Browse the repository at this point in the history
add bench history
  • Loading branch information
krichprollsch authored Dec 7, 2023
2 parents a2da742 + 5a9d97f commit 9e8788e
Show file tree
Hide file tree
Showing 6 changed files with 361 additions and 10 deletions.
16 changes: 9 additions & 7 deletions bench/bench.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ import (
"github.com/lightpanda-io/perf-fmt/git"
)

type InItem struct {
Duration int `json:"duration"`
AllocSize int `json:"alloc_size"`
AllocNb int `json:"alloc_nb"`
ReallocNb int `json:"realloc_nb"`
FreeNb int `json:"free_nb"`
}

type InResult struct {
Name string `json:"name"`
Bench struct {
Duration int `json:"duration"`
AllocSize int `json:"alloc_size"`
AllocNb int `json:"alloc_nb"`
ReallocNb int `json:"realloc_nb"`
FreeNb int `json:"free_nb"`
} `json:"bench"`
Bench InItem `json:"bench"`
}

type OutItem struct {
Expand Down
127 changes: 127 additions & 0 deletions bench/text.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package bench

import (
"bytes"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"

"github.com/lightpanda-io/perf-fmt/git"
)

var (
ErrBadData = errors.New("bad data format")
ErrBadName = errors.New("bad name format")
)

func ParseTxtData(filename string, data []byte) (OutResult, error) {
datetime, commit, err := parseTxtName(filename)
if err != nil {
return OutResult{}, fmt.Errorf("parse filename: %s", err)
}

res := OutResult{
Hash: commit,
Time: datetime,
}

var (
i = 0
rest = data
ok bool
v []byte
)
LOOP:
for {
v, rest, ok = bytes.Cut(rest, []byte("\n"))
if !ok {
return OutResult{}, ErrBadData
}

switch i {
case 6:
item, err := parseLine(v)
if err != nil {
return OutResult{}, fmt.Errorf("parse line with isolate: %w", err)
}
res.Data.WithIsolate = item
case 8:
item, err := parseLine(v)
if err != nil {
return OutResult{}, fmt.Errorf("parse line w/o isolate: %w", err)
}
res.Data.WithoutIsolate = item
break LOOP
}
i += 1
}

return res, nil
}

var linerxp = regexp.MustCompile(`^ *\|[^|]+\| +(\d+)us[^|]+\| +(\d+)[^|]+\|(| +(\d+)[^|]+\|)? +(\d+)kb[^|]+\|$`)

func parseLine(data []byte) (OutItem, error) {
b := linerxp.FindSubmatch(data)
if len(b) != 6 {
return OutItem{}, ErrBadData
}

duration, err := strconv.Atoi(string(b[1]))
if err != nil {
return OutItem{}, fmt.Errorf("bad data format: %w", err)
}

reallocnb := 0
if len(b[4]) > 0 {
reallocnb, err = strconv.Atoi(string(b[4]))
if err != nil {
return OutItem{}, fmt.Errorf("bad data format: %w", err)
}
}

allocnb, err := strconv.Atoi(string(b[2]))
if err != nil {
return OutItem{}, fmt.Errorf("bad data format: %w", err)
}

allocsize, err := strconv.Atoi(string(b[5]))
if err != nil {
return OutItem{}, fmt.Errorf("bad data format: %w", err)
}

return OutItem{
Duration: duration,
ReallocNb: reallocnb,
AllocNb: allocnb,
AllocSize: allocsize,
}, nil
}

func parseTxtName(filename string) (time.Time, git.CommitHash, error) {
strdate, rest, ok := strings.Cut(filename, "_")
if !ok {
return time.Time{}, "", ErrBadName
}

strtime, rest, ok := strings.Cut(rest, "_")
if !ok {
return time.Time{}, "", ErrBadName
}

commit, rest, ok := strings.Cut(rest, "_")
if !ok {
return time.Time{}, "", ErrBadName
}

// parse time
date, err := time.Parse("2006-01-02 15-04", strdate+" "+strtime)
if err != nil {
return time.Time{}, "", fmt.Errorf("bad date format: %w", err)
}

return date, git.CommitHash(commit), nil
}
37 changes: 37 additions & 0 deletions bench/text_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package bench

import "testing"

func TestParseFilename(t *testing.T) {
for _, tc := range []string{
"2022-11-07_22-58_0883be2_main.txt", "2022-11-07_13-29_0dd2b63_optional_arg.txt",
} {
name := tc
t.Run(tc, func(t *testing.T) {
dt, c, err := parseTxtName(name)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
t.Log(dt, c)
})
}
}

func TestParseLine(t *testing.T) {
for _, tc := range []string{
" | Without Isolateªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª | 178usªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª | 2821ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª | 48kbªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª |",
" | With Isolateªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª | 736usªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª | 2908ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª | 54kbªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª |",
" | With Isolate | 850us | 3 | 1084 | 72kb |",
" | Without Isolate | 328us | 2 | 977 | 24kb |",
} {
data := []byte(tc)
t.Run("", func(t *testing.T) {
v, err := parseLine(data)
if err != nil {
t.Errorf("unexpected error: %v", err)
}

t.Log(v)
})
}
}
184 changes: 184 additions & 0 deletions cmd/history/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package main

import (
"bytes"
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
"sort"
"syscall"

"github.com/lightpanda-io/perf-fmt/bench"
"github.com/lightpanda-io/perf-fmt/s3"
)

const (
exitOK = 0
exitFail = 1
)

// main starts interruptable context and runs the program.
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
defer cancel()

err := run(ctx, os.Args, os.Stdout, os.Stderr)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(exitFail)
}

os.Exit(exitOK)
}

const (
SourceBench = "bench"
SourceWPT = "wpt"

AWSRegion = "eu-west-3"
AWSBucket = "lpd-perf"
)

// run configures the flags and starts the HTTP API server.
func run(ctx context.Context, args []string, stdout, stderr io.Writer) error {
// declare runtime flag parameters.
flags := flag.NewFlagSet(args[0], flag.ExitOnError)
// usage func declaration.
exec := args[0]
flags.Usage = func() {
fmt.Fprintf(stderr, "usage: %s <dir>\n", exec)
fmt.Fprintf(stderr, "\nRead, format and save perf bench results history.\n")
fmt.Fprintf(stderr, "\nTo upload data in AWS S3, the program uses env var:\n")
fmt.Fprintf(stderr, "\tAWS_ACCESS_KEY_ID\t\trequired\n")
fmt.Fprintf(stderr, "\tAWS_SECRET_ACCESS_KEY\t\trequired\n")
fmt.Fprintf(stderr, "\tAWS_REGION\t\t\tdefault value: %s\n", AWSRegion)
fmt.Fprintf(stderr, "\tAWS_BUCKET\t\t\tdefault value: %s\n", AWSBucket)
}
if err := flags.Parse(args[1:]); err != nil {
return err
}

args = flags.Args()
if len(args) != 1 {
flags.Usage()
return errors.New("bad arguments")
}

dirname := args[0]
files, err := os.ReadDir(dirname)
if err != nil {
return fmt.Errorf("opendir: %w", err)
}

var res []bench.OutResult
for _, file := range files {
fmt.Fprintln(os.Stderr, file.Name())
// ignore subdirs
if file.IsDir() {
continue
}

b, err := os.ReadFile(filepath.Join(dirname, file.Name()))
if err != nil {
return fmt.Errorf("readfile: %w", err)
}

out, err := bench.ParseTxtData(file.Name(), b)
if err != nil {
return fmt.Errorf("parse file: %w", err)
}

res = append(res, out)
}

// pull hitory.json file
fio, err := s3.NewS3IO(env("AWS_BUCKET", AWSBucket), "bench/history.json")
if err != nil {
return fmt.Errorf("news3io single result: %w", err)
}

// pull the whole data
ball, err := fio.Pull(ctx)
if err != nil {
return fmt.Errorf("pull all file: %w", err)
}
defer ball.Close()

// decode the result
dec := json.NewDecoder(ball)
var all []bench.OutResult
if err := dec.Decode(&all); err != nil {
return fmt.Errorf("decode all history: %w", err)
}

// let merge all and res
APPEND:
for _, in := range res {
for _, a := range all {
if in.Hash == a.Hash {
// the commit already exists in all
continue APPEND
}
}

all = append(all, in)
}

// reorder all slice
sort.Slice(all, func(i, j int) bool {
return all[i].Time.Before(all[j].Time)
})

// push the history.json
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
if err := enc.Encode(all); err != nil {
return fmt.Errorf("json encode: %w", err)
}

// push output
if err := fio.Push(ctx, &buf); err != nil {
return fmt.Errorf("push single result : %w", err)
}

for _, v := range res {
in := []bench.InResult{
{Name: "With Isolate", Bench: bench.InItem(v.Data.WithIsolate)},
{Name: "Without Isolate", Bench: bench.InItem(v.Data.WithoutIsolate)},
}

bin, err := json.Marshal(in)
if err != nil {
return fmt.Errorf("json encode single result: %w", err)
}

// upload all individual file to s3
filename := fmt.Sprintf("%s_%v.json", v.Time.Format("2006-01-02_15-04"), v.Hash)
fio, err := s3.NewS3IO(env("AWS_BUCKET", AWSBucket), "bench/"+filename)
if err != nil {
return fmt.Errorf("news3io single result: %w", err)
}

// push output
if err := fio.Push(ctx, bytes.NewReader(bin)); err != nil {
return fmt.Errorf("push single result : %w", err)
}
}

return nil
}

func env(key, dflt string) string {
val, ok := os.LookupEnv(key)
if !ok {
return dflt
}

return val
}
Loading

0 comments on commit 9e8788e

Please sign in to comment.