Skip to content

Commit

Permalink
complete export for markdown and csv
Browse files Browse the repository at this point in the history
  • Loading branch information
shravanasati committed Jan 21, 2024
1 parent 69789a9 commit b5501eb
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 51 deletions.
114 changes: 66 additions & 48 deletions internal/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,22 @@ func (result *PrintableResult) String() string {
}

// textify writes the benchmark summary of the Result struct to a text file.
func textify(results []*PrintableResult) {
func textify(results []*PrintableResult, filename string) {
// temporarily turn off colors so that [PrintableResult.String] used non-colored summary
origVal := NO_COLOR
NO_COLOR = true
defer func() {
NO_COLOR = origVal
absPath, err := filepath.Abs(filename)
if err != nil {
Log("red", "unable to get the absolute path for text file: "+err.Error())
return
} else {
Log("green", "Successfully wrote benchmark summary to `"+absPath+"`.")
}
}()

f, ferr := os.Create("atomic-summary.txt")
f, ferr := os.Create(filename)
if ferr != nil {
Log("red", "Failed to create the file.")
}
Expand All @@ -70,34 +77,27 @@ func textify(results []*PrintableResult) {
f.WriteString(r.String() + "\n")
}

absPath, err := filepath.Abs("atomic-summary.txt")
if err != nil {
Log("red", "unable to get the absolute path for text file: "+err.Error())
return
} else {
Log("green", "Successfully wrote benchmark summary to `"+absPath+"`.")
}
}

func markdownify(results []*SpeedResult) {
func markdownify(results []*SpeedResult, filename, timeUnit string) {
text := `
# atomic-summary
| Command | Runs | Average | User | System | Min | Max | Relative |
| Command | Runs | Average [${timeUnit}] | User [${timeUnit}] | System [${timeUnit}] | Min [${timeUnit}] | Max [${timeUnit}] | Relative |
| ------- | ---- | ------- | ---- | ------ | --- | --- | -------- |
`
f, ferr := os.Create("atomic-summary.md")
if ferr != nil {
Log("red", "Failed to create the file.")
}
defer f.Close()

text = format(text, map[string]string{"timeUnit": timeUnit})
for _, r := range results {
// todo add a row in the table
text += fmt.Sprintf("%v",r)
text += fmt.Sprintf("`%s` | %d | %.2f ± %.2f | %.2f | %.2f | %.2f | %.2f | %.2f ± %.2f \n", r.Command, len(r.Times), r.AverageElapsed, r.StandardDeviation, r.AverageUser, r.AverageSystem, r.Min, r.Max, r.RelativeMean, r.RelativeStddev)
}

err := writeToFile(text, filename)
if err != nil {
Log("red", "error in writing to file: "+filename+"\nerror: "+err.Error())
return
}

absPath, err := filepath.Abs("atomic-summary.md")
absPath, err := filepath.Abs(filename)
if err != nil {
Log("red", "unable to get the absolute path for markdown file: "+err.Error())
} else {
Expand All @@ -111,35 +111,29 @@ func jsonify(data any) ([]byte, error) {
}

// csvify converts the Result struct to CSV.
func csvify(r *PrintableResult) {
text := `
Executed Command,Total runs,Average time taken,Range
{{.Command}}, {{.Runs}}, {{.Average}} ± {{ .StandardDeviation }}, {{.Min}} ... {{.Max}}
`
tmpl, err := template.New("summary").Parse(text)
if err != nil {
panic(err)
func csvify(results []*SpeedResult, filename string) {
text := "command,runs,average_elapsed,stddev,average_user,average_system,min,max,relative_average,relative_stddev\n"

for _, r := range results {
text += fmt.Sprintf("%s,%d,%f,%f,%f,%f,%f,%f,%f,%f\n", r.Command, len(r.Times), r.AverageElapsed, r.StandardDeviation, r.AverageUser, r.AverageSystem, r.Min, r.Max, r.RelativeMean, r.RelativeStddev)
}

f, ferr := os.Create("atomic-summary.csv")
if ferr != nil {
Log("red", "Failed to create the file.")
err := writeToFile(text, filename)
if err != nil {
Log("red", "error in writing to file: "+filename+"\nerror: "+err.Error())
return
}
defer f.Close()
if terr := tmpl.Execute(f, r); terr != nil {
Log("red", "Failed to write to the file.")

absPath, err := filepath.Abs(filename)
if err != nil {
Log("red", "unable to get the absolute path for csv file: "+err.Error())
} else {
absPath, err := filepath.Abs("atomic-summary.csv")
if err != nil {
Log("red", "unable to get the absolute path for csv file: "+err.Error())
} else {
Log("green", "Successfully wrote benchmark summary to `"+absPath+"`.")
}
Log("green", "Successfully wrote benchmark summary to `"+absPath+"`.")
}
}

func VerifyExportFormats(formats string) ([]string, error) {
validFormats := []string{"csv", "markdown", "txt", "json"}
validFormats := []string{"csv", "markdown", "md", "txt", "json"}
formatList := strings.Split(strings.ToLower(formats), ",")
for _, f := range formatList {
if !slices.Contains(validFormats, f) {
Expand Down Expand Up @@ -170,7 +164,14 @@ func convertToTimeUnit(given float64, unit time.Duration) float64 {
}
}

func Export(formats []string, results []*SpeedResult, timeUnit time.Duration) {
func addExtension(filename, ext string) string {
if !strings.HasSuffix(filename, "."+ext) {
return filename + "." + ext
}
return filename
}

func Export(formats []string, filename string, results []*SpeedResult, timeUnit time.Duration) {
// first convert all speed results to the given time unit
// except for microseconds, because that's what used internally
if timeUnit != time.Microsecond {
Expand All @@ -196,19 +197,36 @@ func Export(formats []string, results []*SpeedResult, timeUnit time.Duration) {
for _, format := range formats {
switch format {
case "json":
jsonMap := map[string]any{"time_unit": timeUnit.String()[1:], "result": results}
jsonMap := map[string]any{"time_unit": timeUnit.String()[1:], "results": results}
jsonData, err := jsonify(jsonMap)
if err != nil {
panic("unable to convert to json: " + err.Error())
}
writeToFile(string(jsonData), "./atomic-summary.json")
filename := addExtension(filename, "json")
err = writeToFile(string(jsonData), filename)
if err != nil {
Log("red", "an unknown error occured in writing to file: "+err.Error())
return
}
absPath, err := filepath.Abs(filename)
if err != nil {
Log("red", "an unknown error occured in getting the full path to the file: "+filename+"\nerror: "+err.Error())
return
}
Log("green", fmt.Sprintf("Successfully wrote benchmark summary to `%s`.", absPath))

case "csv":
// csvify()
case "markdown":
// markdownify()
filename := addExtension(filename, "csv")
csvify(results, filename)

case "markdown", "md":
filename := addExtension(filename, "md")
markdownify(results, filename, timeUnit.String()[1:])

case "txt":
printables := MapFunc[[]*SpeedResult, []*PrintableResult](func(r *SpeedResult) *PrintableResult { return NewPrintableResult().FromSpeedResult(*r) }, results)
textify(printables)
filename := addExtension(filename, "txt")
textify(printables, filename)
}
}
}
2 changes: 1 addition & 1 deletion internal/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"time"
)

var ErrInvalidTimeUnit = errors.New("invalid time unit: ")
var ErrInvalidTimeUnit = errors.New("invalid time unit")

// formats the text in a javascript like syntax.
func format(text string, params map[string]string) string {
Expand Down
12 changes: 10 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,9 +501,10 @@ func main() {
AddFlag("shell,s", "Whether to use shell to execute the given command.", commando.Bool, false).
AddFlag("shell-path", "Path to the shell to use.", commando.String, defaultShellValue).
AddFlag("timeout,t", "The timeout for a single command.", commando.String, LargestDurationString).
AddFlag("export,e", "Comma separated list of benchmark export formats, including json, text, csv and markdown.", commando.String, "none").
AddFlag("verbose,V", "Enable verbose output.", commando.Bool, false).
AddFlag("no-color", "Disable colored output.", commando.Bool, false).
AddFlag("export,e", "Comma separated list of benchmark export formats, including json, text, csv and markdown.", commando.String, "none").
AddFlag("filename,f", "The filename to use in exports.", commando.String, "atomic-summary").
AddFlag("time-unit,u", "The time unit to use for exported results. Must be one of ns, us, ms, s, m, h.", commando.String, "ms").
AddFlag("outlier-threshold", "Minimum number of runs to be outliers for the outlier warning to be displayed, in percentage.", commando.String, "0").
SetAction(func(args map[string]commando.ArgValue, flags map[string]commando.FlagValue) {
Expand Down Expand Up @@ -644,6 +645,12 @@ func main() {
return
}

filename, err := flags["filename"].GetString()
if err != nil {
internal.Log("red", "Application error: cannot parse flag values.")
return
}

// * getting export values
exportFormatString, err := flags["export"].GetString()
if err != nil {
Expand Down Expand Up @@ -802,7 +809,8 @@ func main() {
internal.RelativeSummary(speedResults)

if exportFormatString != "none" {
internal.Export(exportFormats, speedResults, timeUnit)
fmt.Println()
internal.Export(exportFormats, filename, speedResults, timeUnit)
}

})
Expand Down

0 comments on commit b5501eb

Please sign in to comment.