-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from lightpanda-io/bench-history
add bench history
- Loading branch information
Showing
6 changed files
with
361 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.