Skip to content

Commit

Permalink
all: collect metrics
Browse files Browse the repository at this point in the history
all: fix CR issues

all: fix CR issues

all: fix CR issues

all: fix CR issues

all: fix CR issues

all: collect metrics

all: collect metrics

all: collect metrics
  • Loading branch information
seilagamo committed Nov 8, 2023
1 parent 3d37036 commit 1f62d88
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 5 deletions.
19 changes: 17 additions & 2 deletions cmd/lava/internal/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import (
"fmt"
"os"
"path/filepath"
"time"

"github.com/fatih/color"

"github.com/adevinta/lava/cmd/lava/internal/base"
"github.com/adevinta/lava/internal/config"
"github.com/adevinta/lava/internal/engine"
"github.com/adevinta/lava/internal/metrics"
"github.com/adevinta/lava/internal/report"
)

Expand Down Expand Up @@ -56,17 +58,21 @@ func run(args []string) error {
color.NoColor = false
}

executionTime := time.Now()
metrics.Collect("execution_time", executionTime)

cfg, err := config.ParseFile(*cfgfile)
if err != nil {
return fmt.Errorf("parse config file: %w", err)
}

if err := os.Chdir(filepath.Dir(*cfgfile)); err != nil {
if err = os.Chdir(filepath.Dir(*cfgfile)); err != nil {
return fmt.Errorf("change directory: %w", err)
}
metrics.Collect("lava_version", cfg.LavaVersion)
metrics.Collect("targets", cfg.Targets)

base.LogLevel.Set(cfg.LogLevel)

er, err := engine.Run(cfg.ChecktypesURLs, cfg.Targets, cfg.AgentConfig)
if err != nil {
return fmt.Errorf("run: %w", err)
Expand All @@ -83,6 +89,15 @@ func run(args []string) error {
return fmt.Errorf("render report: %w", err)
}

metrics.Collect("exit_code", exitCode)
duration := time.Since(executionTime)
metrics.Collect("duration", duration.String())

if cfg.ReportConfig.Metrics != "" {
if err = metrics.WriteFile(cfg.ReportConfig.Metrics); err != nil {
return fmt.Errorf("write metrics: %w", err)
}
}
os.Exit(int(exitCode))

return nil
Expand Down
7 changes: 6 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ type ReportConfig struct {
// Exclusions is a list of findings that will be ignored. For
// instance, accepted risks, false positives, etc.
Exclusions []Exclusion `yaml:"exclusions"`

// Metrics is the file where the metrics will be written.
// If Metrics is an empty string or not specified in the yaml file, then
// the metrics report is not saved.
Metrics string `yaml:"metrics"`
}

// Target represents the target of a scan.
Expand Down Expand Up @@ -220,7 +225,7 @@ func (s Severity) String() string {
}

// MarshalText encode a [Severity] as a text.
func (s *Severity) MarshalText() (text []byte, err error) {
func (s Severity) MarshalText() (text []byte, err error) {
if !s.IsValid() {
return nil, ErrInvalidSeverity
}
Expand Down
70 changes: 70 additions & 0 deletions internal/metrics/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2023 Adevinta

// Package metrics collects Lava execution metrics.
package metrics

import (
"encoding/json"
"fmt"
"io"
"os"
"sync"
)

// DefaultCollector is the default [Collector].
var DefaultCollector = NewCollector()

// Collector represents a metrics collector.
type Collector struct {
mutex sync.Mutex
metrics map[string]any
}

// NewCollector returns a new metrics collector.
func NewCollector() *Collector {
return &Collector{
metrics: make(map[string]any),
}
}

// Collect records a metric with the provided name and value.
func (c *Collector) Collect(name string, value any) {
c.mutex.Lock()
defer c.mutex.Unlock()

c.metrics[name] = value
}

// Write writes the metrics to the specified [io.Writer].
func (c *Collector) Write(w io.Writer) error {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
if err := enc.Encode(c.metrics); err != nil {
return fmt.Errorf("encode JSON: %w", err)
}
return nil
}

// Collect records a metric with the provided name and value using
// [DefaultCollector].
func Collect(name string, value any) {
DefaultCollector.Collect(name, value)
}

// Write writes the collected metrics to the specified [io.Writer]
// using [DefaultCollector].
func Write(w io.Writer) error {
return DefaultCollector.Write(w)
}

// WriteFile writes the collected metrics into the specified file
// using [DefaultCollector].
func WriteFile(file string) error {
f, err := os.Create(file)
if err != nil {
return fmt.Errorf("create file: %w", err)
}
defer f.Close()

return Write(f)
}
118 changes: 118 additions & 0 deletions internal/metrics/metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2023 Adevinta

package metrics

import (
"bytes"
"encoding/json"
"os"
"path"
"testing"

"github.com/google/go-cmp/cmp"
)

var testdata = []struct {
name string
metrics map[string]any
want map[string]any
}{
{
name: "happy path",
metrics: map[string]any{
"metric 1": "metric value 1",
"metric 2": 12345,
"metric 3": 25.5,
"metric 4": map[string]int{
"key 1": 1,
"key 2": 2,
},
"metric 5": []string{
"one", "two", "three",
},
},
want: map[string]any{
"metric 1": "metric value 1",
"metric 2": float64(12345),
"metric 3": 25.5,
"metric 4": map[string]any{
"key 1": float64(1),
"key 2": float64(2),
},
"metric 5": []any{
"one", "two", "three",
},
},
},
}

func TestWrite(t *testing.T) {
for _, tt := range testdata {
t.Run(tt.name, func(t *testing.T) {
oldDefaultCollector := DefaultCollector
defer func() { DefaultCollector = oldDefaultCollector }()

DefaultCollector = NewCollector()

var buf bytes.Buffer

for key, value := range tt.metrics {
Collect(key, value)
}

if err := Write(&buf); err != nil {
t.Fatalf("error writing metrics: %v", err)
}

var got map[string]any
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
t.Errorf("error decoding JSON metrics: %v", err)
}

if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("metrics mismatch (-want +got):\n%v", diff)
}
})
}
}

func TestWriteFile(t *testing.T) {
for _, tt := range testdata {
t.Run(tt.name, func(t *testing.T) {
oldDefaultCollector := DefaultCollector
defer func() { DefaultCollector = oldDefaultCollector }()

DefaultCollector = NewCollector()

tmpPath, err := os.MkdirTemp("", "")
if err != nil {
t.Fatalf("error creating temp dir: %v", err)
}
defer os.RemoveAll(tmpPath)

file := path.Join(tmpPath, "metrics.json")

for key, value := range tt.metrics {
Collect(key, value)
}

if err = WriteFile(file); err != nil {
t.Fatalf("error writing metrics: %v", err)
}

data, err := os.ReadFile(file)
if err != nil {
t.Fatalf("error reading metrics file: %v", err)
}

var got map[string]any
if err := json.Unmarshal(data, &got); err != nil {
t.Errorf("error decoding JSON metrics: %v", err)
}

if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("metrics mismatch (-want +got):\n%v", diff)
}
})
}
}
7 changes: 5 additions & 2 deletions internal/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/adevinta/lava/internal/config"
"github.com/adevinta/lava/internal/engine"
"github.com/adevinta/lava/internal/metrics"
)

// Writer represents a Lava report writer.
Expand Down Expand Up @@ -65,13 +66,16 @@ func (writer Writer) Write(er engine.Report) (ExitCode, error) {
if err != nil {
return 0, fmt.Errorf("parse report: %w", err)
}

sum, err := mkSummary(vulns)
if err != nil {
return 0, fmt.Errorf("calculate summary: %w", err)
}

metrics.Collect("excluded", sum.excluded)
metrics.Collect("vulnerabilities", sum.count)
exitCode := writer.calculateExitCode(sum)
fvulns := writer.filterVulns(vulns)

if err = writer.prn.Print(writer.w, fvulns, sum); err != nil {
return exitCode, fmt.Errorf("print report: %w", err)
}
Expand All @@ -96,7 +100,6 @@ func (writer Writer) parseReport(er engine.Report) ([]vulnerability, error) {
for _, r := range er {
for _, vuln := range r.ResultData.Vulnerabilities {
severity := scoreToSeverity(vuln.Score)

excluded, err := writer.isExcluded(vuln, r.Target)
if err != nil {
return nil, fmt.Errorf("vulnerability exlusion: %w", err)
Expand Down

0 comments on commit 1f62d88

Please sign in to comment.