The io/ioutil package has been deprecated as of Go 1.16, see https://golang.org/doc/go1.16#ioutil. This commit replaces the existing io/ioutil functions with their new definitions in io and os packages. Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
243 lines
4.6 KiB
Go
243 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/wcharczuk/go-chart"
|
|
)
|
|
|
|
var (
|
|
title *string
|
|
file *string
|
|
outputFile *string
|
|
outputFormat *string
|
|
)
|
|
|
|
const (
|
|
PNG = "png"
|
|
SVG = "svg"
|
|
)
|
|
|
|
type (
|
|
Tags struct {
|
|
Group string `json:"group"`
|
|
Iter string `json:"iter"`
|
|
Method string `json:"method"`
|
|
Name string `json:"name"`
|
|
Proto string `json:"proto"`
|
|
Status string `json:"status"`
|
|
URL string `json:"url"`
|
|
Vu string `json:"vu"`
|
|
}
|
|
|
|
Data struct {
|
|
Time time.Time `json:"time"`
|
|
Value float64 `json:"value"`
|
|
Tags Tags `json:"tags"`
|
|
}
|
|
|
|
MetricPoint struct {
|
|
Type string `json:"type"`
|
|
Data Data `json:"data"`
|
|
Metric string `json:"metric"`
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
title = flag.String("title", "Fission Benchmark", "Chart title")
|
|
file = flag.String("file", "", "Metric json file")
|
|
outputFile = flag.String("o", "chart.png", "Output file name")
|
|
outputFormat = flag.String("format", PNG, "Format of output file (png or svg)")
|
|
flag.Parse()
|
|
}
|
|
|
|
func generateContinuousSeries(file string) chart.Series {
|
|
f, err := os.OpenFile(file, os.O_RDONLY, 0644)
|
|
if err != nil {
|
|
fmt.Printf("Failed to open file metric point: %v", err)
|
|
return nil
|
|
}
|
|
defer f.Close()
|
|
|
|
reader := bufio.NewReader(f)
|
|
|
|
var xVals []float64
|
|
var yVals []float64
|
|
|
|
var initTime *time.Time
|
|
|
|
for {
|
|
l, _, err := reader.ReadLine()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
|
|
point := &MetricPoint{}
|
|
err = json.Unmarshal(l, point)
|
|
if err != nil {
|
|
fmt.Printf("Failed to parse metric point: %v -> %v", err, string(l))
|
|
return nil
|
|
}
|
|
|
|
if point.Type != "Point" || point.Metric != "http_req_duration" {
|
|
continue
|
|
}
|
|
|
|
if initTime == nil {
|
|
initTime = &point.Data.Time
|
|
}
|
|
|
|
timeSinceStart := point.Data.Time.Sub(*initTime).Seconds()
|
|
xVals = append(xVals, timeSinceStart)
|
|
yVals = append(yVals, point.Data.Value)
|
|
}
|
|
|
|
return chart.ContinuousSeries{
|
|
Style: chart.Style{
|
|
Show: true,
|
|
StrokeColor: chart.GetDefaultColor(0).WithAlpha(64),
|
|
FillColor: chart.GetDefaultColor(0).WithAlpha(64),
|
|
},
|
|
XValues: xVals,
|
|
YValues: yVals,
|
|
}
|
|
}
|
|
|
|
func generateChart(title string, file string, format chart.RendererProvider, series []chart.Series) error {
|
|
if series == nil {
|
|
return errors.New("series cannot be nil")
|
|
}
|
|
|
|
cs := chart.ConcatSeries(series)
|
|
|
|
graph := chart.Chart{
|
|
Title: title,
|
|
TitleStyle: chart.StyleShow(),
|
|
Background: chart.Style{
|
|
Padding: chart.Box{
|
|
Top: 50,
|
|
Left: 25,
|
|
Right: 25,
|
|
Bottom: 10,
|
|
},
|
|
},
|
|
XAxis: chart.XAxis{
|
|
Name: "Time (s)",
|
|
NameStyle: chart.StyleShow(),
|
|
Style: chart.StyleShow(),
|
|
Range: &chart.ContinuousRange{
|
|
Min: 0,
|
|
},
|
|
ValueFormatter: func(v interface{}) string {
|
|
return fmt.Sprintf("%.2f s", v.(float64))
|
|
},
|
|
},
|
|
YAxis: chart.YAxis{
|
|
Name: "Response Time (ms)",
|
|
NameStyle: chart.StyleShow(),
|
|
Style: chart.StyleShow(),
|
|
Range: &chart.ContinuousRange{
|
|
Min: 0,
|
|
},
|
|
ValueFormatter: func(v interface{}) string {
|
|
return fmt.Sprintf("%d ms", int(v.(float64)))
|
|
},
|
|
},
|
|
Series: cs,
|
|
}
|
|
|
|
buffer := bytes.NewBuffer([]byte{})
|
|
err := graph.Render(format, buffer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = os.WriteFile(file, buffer.Bytes(), 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func listJsonFiles(path string) ([]string, error) {
|
|
fi, err := os.Stat(*file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if fi.Mode().IsRegular() {
|
|
return []string{path}, nil
|
|
}
|
|
|
|
var files []string
|
|
|
|
err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.HasSuffix(path, ".json") && !info.IsDir() {
|
|
files = append(files, path)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
return files, err
|
|
}
|
|
|
|
func main() {
|
|
|
|
if file == nil || len(*file) == 0 {
|
|
fmt.Println("Please provide metric json file name")
|
|
return
|
|
}
|
|
|
|
var format chart.RendererProvider
|
|
|
|
if outputFormat == nil {
|
|
format = chart.PNG
|
|
} else {
|
|
switch strings.ToLower(*outputFormat) {
|
|
case PNG:
|
|
format = chart.PNG
|
|
case SVG:
|
|
format = chart.SVG
|
|
default:
|
|
fmt.Println("Unknown format, use png as output format")
|
|
*outputFormat = PNG
|
|
}
|
|
}
|
|
|
|
if len(*outputFile) == 0 {
|
|
fmt.Println("Please output chart png file name")
|
|
return
|
|
}
|
|
|
|
var series []chart.Series
|
|
|
|
files, err := listJsonFiles(*file)
|
|
if err != nil {
|
|
fmt.Printf("Failed to get file information: %v", err)
|
|
return
|
|
}
|
|
|
|
for _, f := range files {
|
|
series = append(series, generateContinuousSeries(f))
|
|
}
|
|
|
|
err = generateChart(*title, *outputFile, format, series)
|
|
if err != nil {
|
|
fmt.Printf("Failed to generate chart: %v\n", err)
|
|
}
|
|
}
|