Skip to content

Commit 7d31ab8

Browse files
authored
Merge pull request #23 from MZC-CSC/develop
add result sampling and update docs for usage
2 parents 2951be7 + 4bcfd97 commit 7d31ab8

File tree

6 files changed

+186
-31
lines changed

6 files changed

+186
-31
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ These functionalities are integrated with other subsystems, namely `CB-Tumblebug
5959
- Container: Docker 25.0.0
6060

6161
### Subsystem Dependency
62-
- CB-Spider : v0.9.0 <- **cost explorer anycall handler not yet implemented version.**
63-
- CB-Tumblebug : v0.9.7
62+
- CB-Spider : v0.10.0
63+
- CB-Tumblebug : v0.10.0
6464

6565
---
6666

@@ -108,13 +108,13 @@ Follow the guide for initializing CB-Tumblebug to configure multi-cloud informat
108108
### Pre-Configuration for Performance Evaluation ⭐⭐
109109
To correctly use the performance evaluation features provided by CM-ANT, the following steps are required:
110110

111-
- Register appropriate permissions for VM provisioning with the registered credentials. [TBD]
111+
- Register appropriate permissions for VM provisioning with the registered credentials.
112112

113113
### Pre-Configuration for Price and Cost Features ⭐⭐
114114
To correctly use the price and cost features provided by CM-ANT, the following steps are required:
115115

116116
- Enable AWS Cost Explorer and set up daily granularity resource-level data.
117-
- Register appropriate permissions for price and cost retrieval with the registered credentials. [TBD]
117+
- Register appropriate permissions for price and cost retrieval with the registered credentials.
118118

119119

120120

@@ -137,7 +137,7 @@ To correctly use the price and cost features provided by CM-ANT, the following
137137

138138
## How to Use 🔍
139139
#### 👉 [CM-ANT Swagger API Doc](https://cloud-barista.github.io/api/?url=https://raw.githubusercontent.com/cloud-barista/cm-ant/main/api/swagger.yaml)
140-
[TBD]
140+
#### 👉 [Simple and Sample guide](https://github.com/cloud-barista/cm-ant/wiki/CM%E2%80%90ANT-Simple--&-Sample-API-Usage-guide)
141141

142142

143143

docker-compose.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ services:
4444
- cm-ant-net
4545
healthcheck:
4646
test: [ "CMD", "pg_isready", "-U", "cm-ant-user", "-d", "cm-ant-db" ]
47-
interval: 30s
47+
interval: 1m
4848
timeout: 5s
4949
retries: 5
5050
start_period: 10s
5151
restart: unless-stopped
5252

5353
cb-tumblebug:
54-
image: cloudbaristaorg/cb-tumblebug:0.9.21
54+
image: cloudbaristaorg/cb-tumblebug:0.10.0
5555
container_name: cb-tumblebug
5656
platform: linux/amd64
5757
ports:
@@ -148,7 +148,7 @@ services:
148148
restart: unless-stopped
149149

150150
cb-spider:
151-
image: cloudbaristaorg/cb-spider:0.9.8
151+
image: cloudbaristaorg/cb-spider:0.10.0
152152
container_name: cb-spider
153153
platform: linux/amd64
154154
networks:

internal/core/load/load_generator_install_service.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ func (l *LoadService) GetAllLoadGeneratorInstallInfo(param GetAllLoadGeneratorIn
600600

601601
result.LoadGeneratorInstallInfoResults = infos
602602
result.TotalRows = totalRows
603-
log.Info().Msgf("Fetched %d load generator install info results. length: %d", len(infos))
603+
log.Info().Msgf("Fetched %d load generator install info results.", len(infos))
604604

605605
return result, nil
606606
}

internal/core/load/performance_evaluation_result_service.go

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ func (l *LoadService) GetLoadTestResult(param GetLoadTestResultParam) (interface
8181
return nil, err
8282
}
8383

84-
var resultSummaries []ResultSummary
84+
var resultSummaries []*ResultSummary
8585

8686
for label, results := range resultMap {
87-
resultSummaries = append(resultSummaries, ResultSummary{
87+
resultSummaries = append(resultSummaries, &ResultSummary{
8888
Label: label,
8989
Results: results,
9090
})
@@ -159,10 +159,10 @@ func (l *LoadService) GetLastLoadTestResult(param GetLastLoadTestResultParam) (i
159159
return nil, err
160160
}
161161

162-
var resultSummaries []ResultSummary
162+
var resultSummaries []*ResultSummary
163163

164164
for label, results := range resultMap {
165-
resultSummaries = append(resultSummaries, ResultSummary{
165+
resultSummaries = append(resultSummaries, &ResultSummary{
166166
Label: label,
167167
Results: results,
168168
})
@@ -360,6 +360,71 @@ func appendResultRawData(filePath string) (map[string][]*ResultRawData, error) {
360360
return resultMap, nil
361361
}
362362

363+
func SampleDataByTimeInterval(data []*ResultRawData, intervalMs int) []*ResultRawData {
364+
if len(data) == 0 {
365+
return nil
366+
}
367+
368+
sort.Slice(data, func(i, j int) bool {
369+
return data[i].Timestamp.Before(data[j].Timestamp)
370+
})
371+
372+
var sampledData []*ResultRawData
373+
startTime := data[0].Timestamp
374+
var currentInterval []*ResultRawData
375+
376+
for _, record := range data {
377+
elapsedTime := record.Timestamp.Sub(startTime).Milliseconds()
378+
if elapsedTime < int64(intervalMs) {
379+
currentInterval = append(currentInterval, record)
380+
} else {
381+
if len(currentInterval) > 0 {
382+
sampledData = append(sampledData, calculateRepresentative(currentInterval))
383+
}
384+
startTime = record.Timestamp
385+
currentInterval = []*ResultRawData{record}
386+
}
387+
}
388+
389+
if len(currentInterval) > 0 {
390+
sampledData = append(sampledData, calculateRepresentative(currentInterval))
391+
}
392+
393+
return sampledData
394+
}
395+
396+
func calculateRepresentative(data []*ResultRawData) *ResultRawData {
397+
if len(data) == 0 {
398+
return nil
399+
}
400+
401+
var totalElapsed int
402+
for _, record := range data {
403+
totalElapsed += record.Elapsed
404+
}
405+
avgElapsed := totalElapsed / len(data)
406+
407+
// Find the record closest to the average elapsed time
408+
var closestRecord *ResultRawData
409+
minDiff := int(^uint(0) >> 1) // Max int value
410+
for _, record := range data {
411+
diff := abs(record.Elapsed - avgElapsed)
412+
if diff < minDiff {
413+
minDiff = diff
414+
closestRecord = record
415+
}
416+
}
417+
418+
return closestRecord
419+
}
420+
421+
func abs(a int) int {
422+
if a < 0 {
423+
return -a
424+
}
425+
return a
426+
}
427+
363428
func appendMetricsRawData(mrds map[string][]*MetricsRawData, filePath string) (map[string][]*MetricsRawData, error) {
364429
csvRows, err := utils.ReadCSV(filePath)
365430
if err != nil || csvRows == nil {
@@ -420,15 +485,29 @@ func appendMetricsRawData(mrds map[string][]*MetricsRawData, filePath string) (m
420485
}
421486
}
422487

488+
for _, vals := range mrds {
489+
if len(vals) > 0 {
490+
sort.Slice(vals, func(i, j int) bool {
491+
return vals[i].Timestamp.Before(vals[j].Timestamp)
492+
})
493+
}
494+
}
495+
423496
return mrds, nil
424497
}
425498

426-
func aggregate(resultRawDatas []ResultSummary) []*LoadTestStatistics {
499+
func aggregate(resultRawDatas []*ResultSummary) []*LoadTestStatistics {
427500
var statistics []*LoadTestStatistics
428501

429502
for i := range resultRawDatas {
430503

431504
record := resultRawDatas[i]
505+
506+
if record == nil {
507+
log.Warn().Msgf("record must not be nil; %d", i)
508+
continue
509+
}
510+
432511
var requestCount, totalElapsed, totalBytes, totalSentBytes, errorCount int
433512
var elapsedList []int
434513
if len(record.Results) < 1 {
@@ -500,14 +579,23 @@ func aggregate(resultRawDatas []ResultSummary) []*LoadTestStatistics {
500579
return statistics
501580
}
502581

503-
func resultFormat(format constant.ResultFormat, resultSummaries []ResultSummary) (any, error) {
582+
func resultFormat(format constant.ResultFormat, resultSummaries []*ResultSummary) (any, error) {
504583
if resultSummaries == nil {
505584
return nil, nil
506585
}
507586

508587
switch format {
509588
case constant.Aggregate:
510589
return aggregate(resultSummaries), nil
590+
case constant.Normal:
591+
592+
for i := range resultSummaries {
593+
rs := resultSummaries[i]
594+
if len(rs.Results) > 0 {
595+
rs.Results = SampleDataByTimeInterval(rs.Results, 100)
596+
}
597+
}
598+
511599
}
512600

513601
return resultSummaries, nil
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package load
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"os"
7+
"testing"
8+
"time"
9+
10+
"github.com/rs/zerolog/log"
11+
"github.com/stretchr/testify/assert"
12+
"golang.org/x/exp/rand"
13+
)
14+
15+
func generateCSVData(numRecords int) (string, error) {
16+
file, err := os.CreateTemp("/tmp", "large_data_*.csv")
17+
if err != nil {
18+
return "", err
19+
}
20+
defer file.Close()
21+
22+
writer := bufio.NewWriter(file)
23+
24+
_, err = writer.WriteString("timeStamp,elapsed,label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect\n")
25+
if err != nil {
26+
return "", err
27+
}
28+
29+
// Generate random data and write to CSV
30+
for i := 0; i < numRecords; i++ {
31+
timestamp := time.Now().Add(time.Duration(i) * time.Millisecond).UnixMilli()
32+
elapsed := rand.Intn(1000) // Random elapsed time
33+
label := fmt.Sprintf("Label%d", rand.Intn(10)) // Random label
34+
responseCode := rand.Intn(500) // Random response code
35+
responseMessage := fmt.Sprintf("Message%d", rand.Intn(10)) // Random response message
36+
threadName := fmt.Sprintf("Thread%d", rand.Intn(10)) // Random thread name
37+
dataType := rand.Intn(10) // Random data type
38+
success := rand.Intn(2) == 1 // Random success (true or false)
39+
failureMessage := "" // Can be filled with some failure message if needed
40+
bytes := rand.Intn(1000) // Random bytes
41+
sentBytes := rand.Intn(1000) // Random sent bytes
42+
grpThreads := rand.Intn(100) // Random group threads
43+
allThreads := rand.Intn(200) // Random all threads
44+
url := "https://example.com" // Example URL
45+
latency := rand.Intn(500) // Random latency
46+
idleTime := rand.Intn(200) // Random idle time
47+
connection := rand.Intn(100) // Random connection
48+
49+
_, err := writer.WriteString(fmt.Sprintf("%d,%d,%s,%d,%s,%s,%d,%t,%s,%d,%d,%d,%d,%s,%d,%d,%d\n",
50+
timestamp, elapsed, label, responseCode, responseMessage, threadName, dataType, success, failureMessage,
51+
bytes, sentBytes, grpThreads, allThreads, url, latency, idleTime, connection))
52+
if err != nil {
53+
return "", err
54+
}
55+
}
56+
57+
err = writer.Flush()
58+
if err != nil {
59+
return "", err
60+
}
61+
62+
return file.Name(), nil
63+
}
64+
65+
// {"level":"info","time":"2024-11-20T14:46:21+09:00","message":"Time taken to process CSV: 10.501558875s"}
66+
func TestAppendResultRawData(t *testing.T) {
67+
numRecords := 100_000_00 // 10 million
68+
69+
filePath, err := generateCSVData(numRecords)
70+
assert.NoError(t, err)
71+
defer os.Remove(filePath)
72+
start := time.Now()
73+
74+
_, err = appendResultRawData(filePath)
75+
assert.NoError(t, err)
76+
77+
duration := time.Since(start)
78+
log.Info().Msgf("Time taken to process CSV: %v", duration)
79+
}

internal/utils/ioes.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package utils
33
import (
44
"encoding/csv"
55
"fmt"
6-
"io"
76
"os"
87
"strings"
98

@@ -59,21 +58,10 @@ func ReadCSV(filename string) (*[][]string, error) {
5958
defer file.Close()
6059

6160
reader := csv.NewReader(file)
62-
63-
var parsedCsv [][]string
64-
for {
65-
line, err := reader.Read()
66-
67-
if err != nil {
68-
if err == io.EOF {
69-
return &parsedCsv, nil
70-
}
71-
72-
log.Error().Msgf("failed to read csv file from path %s; %v", filename, err)
73-
break
74-
}
75-
76-
parsedCsv = append(parsedCsv, line)
61+
parsedCsv, err := reader.ReadAll()
62+
if err != nil {
63+
log.Printf("Failed to read CSV file from path %s; %v", filename, err)
64+
return nil, err
7765
}
7866

7967
return &parsedCsv, nil

0 commit comments

Comments
 (0)