1
0
mirror of https://github.com/k8sgpt-ai/k8sgpt.git synced 2025-05-06 07:06:51 +00:00

feat: add stats option to analyze command for performance insights ()

* feat: add stats option to analyze command for performance insights

Introduced a new feature to the analyze command that enables users to print detailed performance statistics of each analyzer. This enhancement aids in debugging and understanding the time taken by various components during analysis, providing valuable insights for performance optimization.

Signed-off-by: Matthis Holleville <matthish29@gmail.com>

* feat: enhance analysis command with statistics option

Refactored the analysis command to support an enhanced statistics option, enabling users to opt-in for detailed performance metrics of the analysis process. This change introduces a more flexible approach to handling statistics, allowing for a clearer separation between the analysis output and performance metrics, thereby improving the usability and insights provided to the user.

Signed-off-by: Matthis Holleville <matthish29@gmail.com>

---------

Signed-off-by: Matthis Holleville <matthish29@gmail.com>
Co-authored-by: Alex Jones <alexsimonjones@gmail.com>
Co-authored-by: Aris Boutselis <arisboutselis08@gmail.com>
This commit is contained in:
Matthis 2024-10-30 14:58:18 +01:00 committed by GitHub
parent 87565a0bcc
commit 3eec9bbb05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 96 additions and 42 deletions
README.md
cmd/analyze
pkg
analysis
common
server/analyze

View File

@ -319,6 +319,22 @@ _Analysis with custom headers_
k8sgpt analyze --explain --custom-headers CustomHeaderKey:CustomHeaderValue
```
_Print analysis stats_
```
k8sgpt analyze -s
The stats mode allows for debugging and understanding the time taken by an analysis by displaying the statistics of each analyzer.
- Analyzer Ingress took 47.125583ms
- Analyzer PersistentVolumeClaim took 53.009167ms
- Analyzer CronJob took 57.517792ms
- Analyzer Deployment took 156.6205ms
- Analyzer Node took 160.109833ms
- Analyzer ReplicaSet took 245.938333ms
- Analyzer StatefulSet took 448.0455ms
- Analyzer Pod took 5.662594708s
- Analyzer Service took 38.583359166s
```
</details>
## LLM AI Backends

View File

@ -40,6 +40,7 @@ var (
interactiveMode bool
customAnalysis bool
customHeaders []string
withStats bool
)
// AnalyzeCmd represents the problems command
@ -63,6 +64,7 @@ var AnalyzeCmd = &cobra.Command{
withDoc,
interactiveMode,
customHeaders,
withStats,
)
if err != nil {
@ -88,6 +90,12 @@ var AnalyzeCmd = &cobra.Command{
color.Red("Error: %v", err)
os.Exit(1)
}
if withStats {
statsData := config.PrintStats()
fmt.Println(string(statsData))
}
fmt.Println(string(output_data))
if interactiveMode && explain {
@ -146,4 +154,6 @@ func init() {
AnalyzeCmd.Flags().StringSliceVarP(&customHeaders, "custom-headers", "r", []string{}, "Custom Headers, <key>:<value> (e.g CustomHeaderKey:CustomHeaderValue AnotherHeader:AnotherValue)")
// label selector flag
AnalyzeCmd.Flags().StringVarP(&labelSelector, "selector", "L", "", "Label selector (label query) to filter on, supports '=', '==', and '!='. (e.g. -L key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints.")
// print stats
AnalyzeCmd.Flags().BoolVarP(&withStats, "with-stat", "s", false, "Print analysis stats. This option disables errors display.")
}

View File

@ -18,9 +18,9 @@ import (
"encoding/base64"
"errors"
"fmt"
"reflect"
"strings"
"sync"
"time"
"github.com/fatih/color"
openapi_v2 "github.com/google/gnostic/openapiv2"
@ -50,6 +50,8 @@ type Analysis struct {
MaxConcurrency int
AnalysisAIProvider string // The name of the AI Provider used for this analysis
WithDoc bool
WithStats bool
Stats []common.AnalysisStats
}
type (
@ -82,6 +84,7 @@ func NewAnalysis(
withDoc bool,
interactiveMode bool,
httpHeaders []string,
withStats bool,
) (*Analysis, error) {
// Get kubernetes client from viper.
kubecontext := viper.GetString("kubecontext")
@ -112,6 +115,7 @@ func NewAnalysis(
Explain: explain,
MaxConcurrency: maxConcurrency,
WithDoc: withDoc,
WithStats: withStats,
}
if !explain {
// Return early if AI use was not requested.
@ -243,22 +247,10 @@ func (a *Analysis) RunAnalysis() {
var mutex sync.Mutex
// if there are no filters selected and no active_filters then run coreAnalyzer
if len(a.Filters) == 0 && len(activeFilters) == 0 {
for _, analyzer := range coreAnalyzerMap {
for name, analyzer := range coreAnalyzerMap {
wg.Add(1)
semaphore <- struct{}{}
go func(analyzer common.IAnalyzer, wg *sync.WaitGroup, semaphore chan struct{}) {
defer wg.Done()
results, err := analyzer.Analyze(analyzerConfig)
if err != nil {
mutex.Lock()
a.Errors = append(a.Errors, fmt.Sprintf("[%s] %s", reflect.TypeOf(analyzer).Name(), err))
mutex.Unlock()
}
mutex.Lock()
a.Results = append(a.Results, results...)
mutex.Unlock()
<-semaphore
}(analyzer, &wg, semaphore)
go a.executeAnalyzer(analyzer, name, analyzerConfig, semaphore, &wg, &mutex)
}
wg.Wait()
@ -270,19 +262,7 @@ func (a *Analysis) RunAnalysis() {
if analyzer, ok := analyzerMap[filter]; ok {
semaphore <- struct{}{}
wg.Add(1)
go func(analyzer common.IAnalyzer, filter string) {
defer wg.Done()
results, err := analyzer.Analyze(analyzerConfig)
if err != nil {
mutex.Lock()
a.Errors = append(a.Errors, fmt.Sprintf("[%s] %s", filter, err))
mutex.Unlock()
}
mutex.Lock()
a.Results = append(a.Results, results...)
mutex.Unlock()
<-semaphore
}(analyzer, filter)
go a.executeAnalyzer(analyzer, filter, analyzerConfig, semaphore, &wg, &mutex)
} else {
a.Errors = append(a.Errors, fmt.Sprintf("\"%s\" filter does not exist. Please run k8sgpt filters list.", filter))
}
@ -296,24 +276,52 @@ func (a *Analysis) RunAnalysis() {
if analyzer, ok := analyzerMap[filter]; ok {
semaphore <- struct{}{}
wg.Add(1)
go func(analyzer common.IAnalyzer, filter string) {
defer wg.Done()
results, err := analyzer.Analyze(analyzerConfig)
if err != nil {
mutex.Lock()
a.Errors = append(a.Errors, fmt.Sprintf("[%s] %s", filter, err))
mutex.Unlock()
}
mutex.Lock()
a.Results = append(a.Results, results...)
mutex.Unlock()
<-semaphore
}(analyzer, filter)
go a.executeAnalyzer(analyzer, filter, analyzerConfig, semaphore, &wg, &mutex)
}
}
wg.Wait()
}
func (a *Analysis) executeAnalyzer(analyzer common.IAnalyzer, filter string, analyzerConfig common.Analyzer, semaphore chan struct{}, wg *sync.WaitGroup, mutex *sync.Mutex) {
defer wg.Done()
var startTime time.Time
var elapsedTime time.Duration
// Start the timer
if a.WithStats {
startTime = time.Now()
}
// Run the analyzer
results, err := analyzer.Analyze(analyzerConfig)
// Measure the time taken
if a.WithStats {
elapsedTime = time.Since(startTime)
}
stat := common.AnalysisStats{
Analyzer: filter,
DurationTime: elapsedTime,
}
mutex.Lock()
defer mutex.Unlock()
if err != nil {
if a.WithStats {
a.Stats = append(a.Stats, stat)
}
a.Errors = append(a.Errors, fmt.Sprintf("[%s] %s", filter, err))
} else {
if a.WithStats {
a.Stats = append(a.Stats, stat)
}
a.Results = append(a.Results, results...)
}
<-semaphore
}
func (a *Analysis) GetAIResults(output string, anonymize bool) error {
if len(a.Results) == 0 {
return nil

View File

@ -55,6 +55,18 @@ func (a *Analysis) jsonOutput() ([]byte, error) {
return output, nil
}
func (a *Analysis) PrintStats() []byte {
var output strings.Builder
output.WriteString(color.YellowString("The stats mode allows for debugging and understanding the time taken by an analysis by displaying the statistics of each analyzer.\n"))
for _, stat := range a.Stats {
output.WriteString(fmt.Sprintf("- Analyzer %s took %s \n", color.YellowString(stat.Analyzer), stat.DurationTime))
}
return []byte(output.String())
}
func (a *Analysis) textOutput() ([]byte, error) {
var output strings.Builder

View File

@ -15,6 +15,7 @@ package common
import (
"context"
"time"
trivy "github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
openapi_v2 "github.com/google/gnostic/openapiv2"
@ -80,6 +81,11 @@ type Result struct {
ParentObject string `json:"parentObject"`
}
type AnalysisStats struct {
Analyzer string `json:"analyzer"`
DurationTime time.Duration `json:"durationTime"`
}
type Failure struct {
Text string
KubernetesDoc string

View File

@ -1,9 +1,10 @@
package analyze
import (
schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
"context"
json "encoding/json"
schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
)
@ -31,6 +32,7 @@ func (h *Handler) Analyze(ctx context.Context, i *schemav1.AnalyzeRequest) (
false, // Kubernetes Doc disabled in server mode
false, // Interactive mode disabled in server mode
[]string{}, //TODO: add custom http headers in server mode
false, // with stats disable
)
config.Context = ctx // Replace context for correct timeouts.
if err != nil {