Skip to content

Commit

Permalink
added chart creation
Browse files Browse the repository at this point in the history
  • Loading branch information
lkumarjain committed Oct 25, 2024
1 parent c8d57fc commit f2dfb0a
Show file tree
Hide file tree
Showing 3 changed files with 294 additions and 0 deletions.
52 changes: 52 additions & 0 deletions result/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"os"
"path/filepath"
"slices"

"github.com/lkumarjain/benchmark/result/parser"
"github.com/lkumarjain/benchmark/result/plotter"
)

func main() {
path := os.Args[1]
records, applications, scenarios, err := parser.Parse(path)
if err != nil {
panic(err)
}

directory := filepath.Dir(path)

allocationPerOperation := plotter.Plot{Time: false, Title: "allocationPerOperation", Directory: directory, FileName: "allocationPerOperation.png", Options: applications, LegendLabels: scenarios, Values: make([][]float64, len(scenarios))}
timePerOperation := plotter.Plot{Time: true, Title: "timePerOperation", Directory: directory, FileName: "timePerOperation.png", Options: applications, LegendLabels: scenarios, Values: make([][]float64, len(scenarios))}

for _, v := range records {
index := slices.Index(scenarios, v.ScenarioName)
allocations := allocationPerOperation.Values[index]
if allocations == nil {
allocations = make([]float64, len(applications))
allocationPerOperation.Values[index] = allocations
}

allocations[slices.Index(applications, v.ApplicationName)] = v.AllocationPerOperation

times := timePerOperation.Values[index]
if times == nil {
times = make([]float64, len(applications))
timePerOperation.Values[index] = times
}

times[slices.Index(applications, v.ApplicationName)] = v.TimePerOperation
}

err = allocationPerOperation.Generate()
if err != nil {
panic(err)
}

err = timePerOperation.Generate()
if err != nil {
panic(err)
}
}
118 changes: 118 additions & 0 deletions result/parser/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package parser

import (
"bufio"
"errors"
"fmt"
"math"
"os"
"slices"
"strconv"
"strings"
)

type Benchmark struct {
ApplicationName string
ScenarioName string
NumberOfCPU string
Iterations int
TimePerOperation float64
TimeUnit string
AllocationPerOperation float64
AllocationUnit string
}

func (b *Benchmark) Parse(line string) error {
if strings.HasPrefix(line, "Benchmark") {
fields := strings.Fields(line[9:])

names := strings.Split(fields[0], "/")
b.ApplicationName = names[0]
b.ScenarioName = strings.ReplaceAll(names[1][:len(names[1])-2], "_", " ")
b.NumberOfCPU = names[1][len(names[1])-1:]
b.Iterations, _ = strconv.Atoi(fields[1])
b.TimePerOperation, _ = strconv.ParseFloat(fields[2], 64)
b.TimeUnit = fields[3]
b.AllocationPerOperation, _ = strconv.ParseFloat(fields[4], 64)
b.AllocationUnit = fields[5]

return nil
}

return fmt.Errorf("invalid line: %s", line)
}

func Parse(path string) ([]Benchmark, []string, []string, error) {
file, err := os.Open(path)
if err != nil {
return nil, nil, nil, err
}

defer file.Close()

scanner := bufio.NewScanner(file)
var benchmarks = make(map[string]Benchmark)

for scanner.Scan() {
line := scanner.Text()
b := &Benchmark{}

err := b.Parse(line)
if err != nil {
continue
}

key := b.ApplicationName + "/" + b.ScenarioName
benchmark, ok := benchmarks[key]
if !ok {
benchmark = *b
} else {
benchmark.Iterations = (benchmark.Iterations + b.Iterations) / 2
benchmark.TimePerOperation = (benchmark.TimePerOperation + b.TimePerOperation) / 2
benchmark.AllocationPerOperation = (benchmark.AllocationPerOperation + b.AllocationPerOperation) / 2
}

benchmarks[key] = benchmark

key = b.ApplicationName
benchmark, ok = benchmarks[key]
if !ok {
benchmark = *b
benchmark.ScenarioName = "All"
} else {
benchmark.Iterations = (benchmark.Iterations + b.Iterations) / 2
benchmark.TimePerOperation = (benchmark.TimePerOperation + b.TimePerOperation) / 2
benchmark.AllocationPerOperation = (benchmark.AllocationPerOperation + b.AllocationPerOperation) / 2
}

benchmarks[key] = benchmark
}

if err := scanner.Err(); err != nil {
return nil, nil, nil, errors.New("failed to scan file")
}

var records []Benchmark
var applications []string
var scenarios []string

for _, v := range benchmarks {
v.TimePerOperation = math.Round(v.TimePerOperation*100) / 100
v.AllocationPerOperation = math.Round(v.AllocationPerOperation*100) / 100

records = append(records, v)

if !slices.Contains(applications, v.ApplicationName) {
applications = append(applications, v.ApplicationName)
}

if !slices.Contains(scenarios, v.ScenarioName) {
scenarios = append(scenarios, v.ScenarioName)
}
}

slices.Sort(applications)
slices.Sort(scenarios)

return records, applications, scenarios, nil
}
124 changes: 124 additions & 0 deletions result/plotter/plotter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package plotter

import (
"fmt"
"os"
"path/filepath"
"time"

"github.com/vicanso/go-charts/v2"
)

var (
markLineOption = charts.MarkLineOptionFunc(0, charts.SeriesMarkDataTypeAverage)
markPointOption = charts.MarkPointOptionFunc(0, charts.SeriesMarkDataTypeMax, charts.SeriesMarkDataTypeMin)
imageType = "png"
theme = "grafana"
width = 4096
height = 2160
fontSize = float64(30)
padding = charts.Box{Top: 100, Bottom: 100, Left: 100, Right: 300}
)

type Plot struct {
Title string
Values [][]float64
Options []string
LegendLabels []string
Directory string
FileName string
Time bool
}

func (p *Plot) writeFile(buf []byte) error {
err := os.MkdirAll(p.Directory, 0700)
if err != nil {
return err
}

file := filepath.Join(p.Directory, p.FileName)

err = os.WriteFile(file, buf, 0600)
if err != nil {
return err
}

return nil
}

func (p *Plot) valueFormatter(f float64) string {
if p.Time {
return time.Duration(int64(f)).String()
}

return allocationUnitValue(f)
}

func (p *Plot) options(opt *charts.ChartOption) {
if len(opt.SeriesList) > 0 {
opt.SeriesList[1].MarkPoint = charts.NewMarkPoint(charts.SeriesMarkDataTypeMax, charts.SeriesMarkDataTypeMin)
opt.SeriesList[1].MarkLine = charts.NewMarkLine(charts.SeriesMarkDataTypeAverage)
opt.SeriesList[1].MarkLine.Width = 10
}

opt.Type = imageType
opt.Theme = theme
opt.Width = width
opt.Height = height
opt.Padding = padding
opt.XAxis.FontSize = fontSize
opt.Legend.FontSize = fontSize
opt.BarMargin = 5
opt.Title = charts.TitleOption{
Text: p.Title,
Subtext: "Benchmark Created by github.com/lkumarjain/benchmark",
Left: charts.PositionRight,
Top: charts.PositionTop,
FontSize: 50,
SubtextFontSize: 25,
}

opt.YAxisOptions = []charts.YAxisOption{
{
FontSize: fontSize,
Show: charts.TrueFlag(),
SplitLineShow: charts.TrueFlag(),
},
}

opt.ValueFormatter = p.valueFormatter
}

func (p *Plot) Generate() error {
xAxisOptions := charts.XAxisDataOptionFunc(p.Options)
legendLabelsOption := charts.LegendLabelsOptionFunc(p.LegendLabels, charts.PositionLeft)

chart, err := charts.BarRender(p.Values, xAxisOptions, legendLabelsOption, markLineOption, markPointOption, p.options)
if err != nil {
return err
}

buf, err := chart.Bytes()
if err != nil {
return err
}

err = p.writeFile(buf)
if err != nil {
return err
}

return nil
}

func allocationUnitValue(value float64) string {
if value < 1024 {
return fmt.Sprintf("%.0f B", value)
}

if value < 1048576 {
return fmt.Sprintf("%.0f KB", value/1024)
}

return fmt.Sprintf("%.0f MB", value/1048576)
}

0 comments on commit f2dfb0a

Please sign in to comment.