Skip to content

Commit d58b313

Browse files
committed
k8s resources reading + latest commit finding
1 parent 0337c29 commit d58b313

File tree

1 file changed

+113
-81
lines changed

1 file changed

+113
-81
lines changed

wasp/comparator/report.go

Lines changed: 113 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,22 @@ import (
55
"context"
66
"encoding/json"
77
"fmt"
8-
"github.com/docker/docker/api/types/container"
9-
tc "github.com/testcontainers/testcontainers-go"
10-
"golang.org/x/sync/errgroup"
118
"io"
129
"net/url"
1310
"os"
11+
"os/exec"
1412
"path/filepath"
1513
"regexp"
1614
"strings"
1715
"time"
1816

17+
"github.com/docker/docker/api/types/container"
1918
"github.com/pkg/errors"
19+
tc "github.com/testcontainers/testcontainers-go"
20+
"golang.org/x/sync/errgroup"
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
"k8s.io/client-go/kubernetes"
23+
"k8s.io/client-go/rest"
2024

2125
"github.com/smartcontractkit/chainlink-testing-framework/lib/client"
2226
"github.com/smartcontractkit/chainlink-testing-framework/wasp"
@@ -38,14 +42,13 @@ type Report interface {
3842
Fetch() error
3943
// IsComparable checks whether both reports can be compared (e.g. test config is the same, app's resources are the same, queries or metrics used are the same, etc.), and returns a map of the differences and an error (if any difference is found)
4044
IsComparable(otherReport Report) (bool, map[string]string, error)
41-
//// Compare compares two reports and returns a map of the differences and an error (if any difference is found). You should call it only after IsComparable returns true
42-
//Compare(other Report) (bool, map[string]string, error)
4345
}
4446

4547
var directory = "performance_reports"
4648

4749
type BasicReport struct {
48-
TestName string `json:"test_name"`
50+
TestName string `json:"test_name"`
51+
// either k8s or docker
4952
ExecutionEnvironment ExecutionEnvironment `json:"execution_environment"`
5053

5154
// Test metrics
@@ -57,12 +60,13 @@ type BasicReport struct {
5760
GeneratorConfigs map[string]*wasp.Config `json:"generator_configs"`
5861

5962
// AUT metrics
60-
PodsResources map[string]*PodResources `json:"pods_resources"`
61-
ContainerResources map[string]*DockerResources `json:"container_resources"`
62-
ResourceSelectionPattern string `json:"resource_selection_pattern"`
63+
PodsResources map[string]*PodResources `json:"pods_resources"`
64+
ContainerResources map[string]*DockerResources `json:"container_resources"`
65+
// regex pattern to select the resources we want to fetch
66+
ResourceSelectionPattern string `json:"resource_selection_pattern"`
6367

6468
// Performance queries
65-
// a map of name to query template, ex: avg(rate(cpu_usage_seconds_total[5m])) @ 1663915200
69+
// a map of name to query template, ex: "average cpu usage": "avg(rate(cpu_usage_seconds_total[5m]))"
6670
LokiQueries map[string]string `json:"loki_queries"`
6771
// Performance queries results
6872
// can be anything, avg RPS, amount of errors, 95th percentile of CPU utilization, etc
@@ -81,8 +85,10 @@ type DockerResources struct {
8185
}
8286

8387
type PodResources struct {
84-
CPU int64
85-
Memory int64
88+
RequestsCPU int64
89+
RequestsMemory int64
90+
LimitsCPU int64
91+
LimitsMemory int64
8692
}
8793

8894
func (b *BasicReport) Store() (string, error) {
@@ -123,14 +129,19 @@ func (b *BasicReport) Load() error {
123129
return errors.New("test name is empty. Please set it and try again")
124130
}
125131

126-
var reportFilePath string
127132
if b.CommitOrTag == "" {
128-
// TODO load the latest one, but we need to think how to know which one is the latest one
129-
// with tags it is easy, just use semver, but with commits not so straightforward, although we could probably comb the repo to find the commit and then its commit date
130-
panic("not implemented")
131-
} else {
132-
reportFilePath = filepath.Join(directory, fmt.Sprintf("%s-%s.json", b.TestName, b.CommitOrTag))
133+
tagsOrCommits, tagErr := extractTagsOrCommits(directory)
134+
if tagErr != nil {
135+
return tagErr
136+
}
137+
138+
latestCommit, commitErr := findLatestCommit(tagsOrCommits)
139+
if commitErr != nil {
140+
return commitErr
141+
}
142+
b.CommitOrTag = latestCommit
133143
}
144+
reportFilePath := filepath.Join(directory, fmt.Sprintf("%s-%s.json", b.TestName, b.CommitOrTag))
134145

135146
reportFile, err := os.Open(reportFilePath)
136147
if err != nil {
@@ -145,6 +156,50 @@ func (b *BasicReport) Load() error {
145156
return nil
146157
}
147158

159+
func extractTagsOrCommits(directory string) ([]string, error) {
160+
pattern := regexp.MustCompile(`.+-(.+)\.json$`)
161+
162+
files, err := os.ReadDir(directory)
163+
if err != nil {
164+
return nil, errors.Wrapf(err, "failed to read directory %s", directory)
165+
}
166+
167+
var tagsOrCommits []string
168+
169+
for _, file := range files {
170+
if file.IsDir() {
171+
continue
172+
}
173+
174+
matches := pattern.FindStringSubmatch(file.Name())
175+
if len(matches) == 2 {
176+
tagsOrCommits = append(tagsOrCommits, matches[1])
177+
}
178+
}
179+
180+
return tagsOrCommits, nil
181+
}
182+
183+
func findLatestCommit(references []string) (string, error) {
184+
refList := strings.Join(references, " ")
185+
186+
cmd := exec.Command("git", "rev-list", "--topo-order", "--date-order", "-n", "1", refList)
187+
188+
var stdout, stderr bytes.Buffer
189+
cmd.Stdout = &stdout
190+
cmd.Stderr = &stderr
191+
if err := cmd.Run(); err != nil {
192+
return "", fmt.Errorf("failed to run git rev-list: %s, error: %v", stderr.String(), err)
193+
}
194+
195+
latestCommit := strings.TrimSpace(stdout.String())
196+
if latestCommit == "" {
197+
return "", fmt.Errorf("no latest commit found")
198+
}
199+
200+
return latestCommit, nil
201+
}
202+
148203
func (b *BasicReport) Fetch() error {
149204
if len(b.LokiQueries) == 0 {
150205
return errors.New("there are no Loki queries, there's nothing to fetch. Please set them and try again")
@@ -216,8 +271,10 @@ func (b *BasicReport) Fetch() error {
216271
func (b *BasicReport) fetchResources() error {
217272
//TODO in both cases we'd need to know some mask to filter out the resources we need
218273
if b.ExecutionEnvironment == ExecutionEnvironment_Docker {
219-
// fetch docker resources
220-
// get all containers and their resources
274+
err := b.fetchDockerResources()
275+
if err != nil {
276+
return err
277+
}
221278
} else {
222279
// fetch k8s resources
223280
// get all pods and their resources
@@ -226,6 +283,42 @@ func (b *BasicReport) fetchResources() error {
226283
return nil
227284
}
228285

286+
func (b *BasicReport) fetchK8sResources() error {
287+
config, err := rest.InClusterConfig()
288+
if err != nil {
289+
return errors.Wrapf(err, "failed to get in-cluster config, are you sure this is running in a k8s cluster?")
290+
}
291+
292+
clientset, err := kubernetes.NewForConfig(config)
293+
if err != nil {
294+
return errors.Wrapf(err, "failed to create k8s clientset")
295+
}
296+
297+
namespaceFile := "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
298+
namespace, err := os.ReadFile(namespaceFile)
299+
if err != nil {
300+
return errors.Wrapf(err, "failed to read namespace file %s", namespaceFile)
301+
}
302+
303+
pods, err := clientset.CoreV1().Pods(string(namespace)).List(context.TODO(), metav1.ListOptions{})
304+
if err != nil {
305+
panic(err)
306+
}
307+
308+
b.PodsResources = make(map[string]*PodResources)
309+
310+
for _, pod := range pods.Items {
311+
b.PodsResources[pod.Name] = &PodResources{
312+
RequestsCPU: pod.Spec.Containers[0].Resources.Requests.Cpu().MilliValue(),
313+
RequestsMemory: pod.Spec.Containers[0].Resources.Requests.Memory().Value(),
314+
LimitsCPU: pod.Spec.Containers[0].Resources.Limits.Cpu().MilliValue(),
315+
LimitsMemory: pod.Spec.Containers[0].Resources.Limits.Memory().Value(),
316+
}
317+
}
318+
319+
return nil
320+
}
321+
229322
func (b *BasicReport) fetchDockerResources() error {
230323
provider, err := tc.NewDockerProvider()
231324
if err != nil {
@@ -457,64 +550,3 @@ func mustMarshallSegment(segment *wasp.Segment) string {
457550

458551
return string(segmentBytes)
459552
}
460-
461-
//type DifferentResult struct {
462-
// Expected []string
463-
// Actual []string
464-
//}
465-
//
466-
//// this could also just be a generic var, so we don't couple Result with this struct
467-
//type ComparisonReport struct {
468-
// missingResults []string
469-
// unexpectedResults []string
470-
// differentResults map[string]DifferentResult
471-
//}
472-
//
473-
//func (c *ComparisonReport) HasDifferences() bool {
474-
// return len(c.missingResults) > 0 || len(c.unexpectedResults) > 0 || len(c.differentResults) > 0
475-
//}
476-
//
477-
//func (c *ComparisonReport) MissingResults() []string {
478-
// return c.missingResults
479-
//}
480-
//
481-
//func (c *ComparisonReport) UnexpectedResults() []string {
482-
// return c.unexpectedResults
483-
//}
484-
//
485-
//func (c *ComparisonReport) DifferentResults() map[string]DifferentResult {
486-
// return c.differentResults
487-
//}
488-
//
489-
//func (b *BasicReport) Compare(other BasicReport) (bool, ComparisonReport, error) {
490-
// // check if there are no errors
491-
//
492-
// // check if results are the same
493-
// if len(b.Results) != len(other.Results) {
494-
// return false, ComparisonReport{}, fmt.Errorf("result count is different. Expected %d, got %d", len(b.Results), len(other.Results))
495-
// }
496-
//
497-
// comparisonReport := ComparisonReport{
498-
// differentResults: make(map[string]DifferentResult),
499-
// }
500-
//
501-
// for name, result := range b.Results {
502-
// if otherResult, ok := other.Results[name]; !ok {
503-
// comparisonReport.missingResults = append(comparisonReport.missingResults, name)
504-
// } else {
505-
//
506-
// comparisonReport.differentResults[name] = DifferentResult{
507-
// Expected: result,
508-
// Actual: otherResult,
509-
// }
510-
// }
511-
// }
512-
//
513-
// for name, otherResult := range other.Results {
514-
// if _, ok := b.Results[name]; !ok {
515-
// comparisonReport.unexpectedResults[name] = otherResult
516-
// }
517-
// }
518-
//
519-
// return comparisonReport.HasDifferences(), comparisonReport, nil
520-
//}

0 commit comments

Comments
 (0)