From 7745bc97380b62a1b05f7c922d746d0de778d8c8 Mon Sep 17 00:00:00 2001 From: Ned Palacios Date: Sun, 10 Mar 2024 22:40:55 +0800 Subject: [PATCH] feat(server/cli): add analyze-log --- server/cli/main.go | 150 +++++++++++++++++++++++++++++++++++++++++++++ server/go.mod | 1 + server/go.sum | 2 + 3 files changed, 153 insertions(+) diff --git a/server/cli/main.go b/server/cli/main.go index 8106a7d..73aa456 100644 --- a/server/cli/main.go +++ b/server/cli/main.go @@ -6,16 +6,24 @@ import ( "io" "log" "os" + "path/filepath" "strings" + "time" "github.com/nedpals/bugbuddy/server/daemon" "github.com/nedpals/bugbuddy/server/daemon/types" "github.com/nedpals/bugbuddy/server/executor" "github.com/nedpals/bugbuddy/server/helpers" + "github.com/nedpals/bugbuddy/server/logger" + log_analyzer "github.com/nedpals/bugbuddy/server/logger/analyzer" + errorquotient "github.com/nedpals/bugbuddy/server/logger/analyzer/error_quotient" + red "github.com/nedpals/bugbuddy/server/logger/analyzer/repeated_error_density" + timetosolve "github.com/nedpals/bugbuddy/server/logger/analyzer/time_to_solve" "github.com/nedpals/bugbuddy/server/lsp_server" "github.com/nedpals/bugbuddy/server/release" "github.com/nedpals/errgoengine" "github.com/spf13/cobra" + "github.com/tealeg/xlsx" ) var rootCmd = &cobra.Command{ @@ -196,12 +204,154 @@ var runCommandCmd = &cobra.Command{ }, } +type analyzeResults struct { + FilePath string + ErrorQuotient float64 + RepeatedErrorDensity float64 + TimeToSolve time.Duration +} + +var analyzeLogCmd = &cobra.Command{ + Use: "analyze-log", + Short: "Analyzes a set of log files. The results will be saved to an excel file.", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + loggerLoaders := []log_analyzer.LoggerLoader{} + + for _, path := range args { + matches, err := filepath.Glob(path) + if err != nil { + log.Fatalln(err) + } + + for _, match := range matches { + // check if match is a directory + fi, err := os.Stat(match) + if err != nil { + log.Fatalln(err) + } + + if fi.IsDir() { + log.Fatalln("directories are not supported") + } + + loggerLoaders = append(loggerLoaders, func() (*logger.Logger, error) { + return logger.NewLoggerFromPath(match) + }) + + log.Println("loaded", match) + } + } + + // TODO: set as configurable flags + analyzers := []string{"eq", "red", "tts"} + + // map[analyzer]map[participantId]map[filePath]struct{FilePath string, ErrorQuotient float64, RepeatedErrorDensity float64, TimeToSolve time.Duration} + results := map[string]map[string]*analyzeResults{} + + for _, analyzer := range analyzers { + switch analyzer { + case "eq": + eqa := log_analyzer.New[errorquotient.Analyzer](loggerLoaders...) + if err := eqa.Analyze(); err != nil { + log.Fatalln(err) + } + + for participantId, filePaths := range eqa.ResultsByParticipant { + if _, ok := results[participantId]; !ok { + results[participantId] = map[string]*analyzeResults{} + } + + for filePath, eq := range filePaths { + if _, ok := results[participantId][filePath]; !ok { + results[participantId][filePath] = &analyzeResults{FilePath: filePath} + } + + results[participantId][filePath].ErrorQuotient = eq + } + } + case "red": + red := log_analyzer.New[red.Analyzer](loggerLoaders...) + if err := red.Analyze(); err != nil { + log.Fatalln(err) + } + + for participantId, filePaths := range red.ResultsByParticipant { + if _, ok := results[participantId]; !ok { + results[participantId] = map[string]*analyzeResults{} + } + + for filePath, red := range filePaths { + if _, ok := results[participantId][filePath]; !ok { + results[participantId][filePath] = &analyzeResults{FilePath: filePath} + } + + results[participantId][filePath].RepeatedErrorDensity = red + } + } + case "tts": + tts := log_analyzer.New[timetosolve.Analyzer](loggerLoaders...) + if err := tts.Analyze(); err != nil { + log.Fatalln(err) + } + + for participantId, filePaths := range tts.ResultsByParticipant { + if _, ok := results[participantId]; !ok { + results[participantId] = map[string]*analyzeResults{} + } + + for filePath, tts := range filePaths { + if _, ok := results[participantId][filePath]; !ok { + results[participantId][filePath] = &analyzeResults{FilePath: filePath} + } + + results[participantId][filePath].TimeToSolve = tts + } + } + } + } + + // save the results to an excel file + wb := xlsx.NewFile() + + for participantId, filePaths := range results { + sheet, err := wb.AddSheet(participantId) + if err != nil { + log.Fatalln(err) + } + + // write the header + row := sheet.AddRow() + row.AddCell().SetValue("File Path") + row.AddCell().SetValue("Error Quotient") + row.AddCell().SetValue("Repeated Error Density") + row.AddCell().SetValue("Time To Solve") + + for _, result := range filePaths { + row = sheet.AddRow() + row.AddCell().SetValue(result.FilePath) + row.AddCell().SetValue(result.ErrorQuotient) + row.AddCell().SetValue(result.RepeatedErrorDensity) + row.AddCell().SetValue(result.TimeToSolve.String()) + } + } + + // TODO: set as configurable flag + if err := wb.Save("results.xlsx"); err != nil { + log.Fatalln(err) + } + + return nil + }, +} + func init() { rootCmd.AddCommand(lspCmd) rootCmd.AddCommand(daemonCmd) rootCmd.AddCommand(analyzeCmd) rootCmd.AddCommand(participantIdCmd) rootCmd.AddCommand(runCommandCmd) + rootCmd.AddCommand(analyzeLogCmd) participantIdCmd.PersistentFlags().Bool("generate", false, "generate a new participant ID") rootCmd.AddCommand(resetCmd) rootCmd.PersistentFlags().IntP("port", "p", daemon.DEFAULT_PORT, "the port to use for the daemon") diff --git a/server/go.mod b/server/go.mod index 5c90ba4..ea3e794 100644 --- a/server/go.mod +++ b/server/go.mod @@ -22,6 +22,7 @@ require ( github.com/google/uuid v1.5.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/tealeg/xlsx v1.0.5 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/tools v0.16.1 // indirect lukechampine.com/uint128 v1.3.0 // indirect diff --git a/server/go.sum b/server/go.sum index ec55647..c94c888 100644 --- a/server/go.sum +++ b/server/go.sum @@ -65,6 +65,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE= +github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM= go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI= go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac= go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE=