Skip to content

Commit

Permalink
vcsim: Add CSV format support to QueryPerf API
Browse files Browse the repository at this point in the history
  • Loading branch information
jsleblanc committed Apr 17, 2023
1 parent c523284 commit 78a979c
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 8 deletions.
63 changes: 55 additions & 8 deletions simulator/performance_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package simulator
import (
"math/rand"
"strconv"
"strings"
"time"

"github.com/vmware/govmomi/simulator/esx"
Expand Down Expand Up @@ -174,9 +175,6 @@ func (p *PerformanceManager) QueryPerf(ctx *Context, req *types.QueryPerf) soap.
body.Res.Returnval = make([]types.BasePerfEntityMetricBase, len(req.QuerySpec))

for i, qs := range req.QuerySpec {
metrics := new(types.PerfEntityMetric)
metrics.Entity = qs.Entity

// Get metric data for this entity type
metricData, ok := p.metricData[qs.Entity.Type]
if !ok {
Expand Down Expand Up @@ -206,17 +204,21 @@ func (p *PerformanceManager) QueryPerf(ctx *Context, req *types.QueryPerf) soap.
n = qs.MaxSample
}

metrics := new(types.PerfEntityMetric)
metrics.Entity = qs.Entity

// Loop through each interval "tick"
metrics.SampleInfo = make([]types.PerfSampleInfo, n)
metrics.Value = make([]types.BasePerfMetricSeries, len(qs.MetricId))
for tick := int32(0); tick < n; tick++ {
metrics.SampleInfo[tick] = types.PerfSampleInfo{Timestamp: end.Add(time.Duration(-interval*tick) * time.Second), Interval: interval}
}

series := make([]*types.PerfMetricIntSeries, len(qs.MetricId))
for j, mid := range qs.MetricId {
// Create list of metrics for this tick
series := &types.PerfMetricIntSeries{Value: make([]int64, n)}
series.Id = mid
series[j] = &types.PerfMetricIntSeries{Value: make([]int64, n)}
series[j].Id = mid
points := metricData[mid.CounterId]
offset := int64(start.Unix()) / int64(interval)

Expand All @@ -237,11 +239,56 @@ func (p *PerformanceManager) QueryPerf(ctx *Context, req *types.QueryPerf) soap.
} else {
p = 0
}
series.Value[tick] = p
series[j].Value[tick] = p
}
metrics.Value[j] = series
metrics.Value[j] = series[j]
}

if qs.Format == string(types.PerfFormatCsv) {
metricsCsv := new(types.PerfEntityMetricCSV)
metricsCsv.Entity = qs.Entity

//PerfSampleInfo encoded in the following CSV format: [interval1], [date1], [interval2], [date2], and so on.
metricsCsv.SampleInfoCSV = sampleInfoCSV(metrics)
metricsCsv.Value = make([]types.PerfMetricSeriesCSV, len(qs.MetricId))

for j, mid := range qs.MetricId {
seriesCsv := &types.PerfMetricSeriesCSV{Value: ""}
seriesCsv.Id = mid
seriesCsv.Value = valueCSV(series[j])
metricsCsv.Value[j] = *seriesCsv
}

body.Res.Returnval[i] = metricsCsv
} else {
body.Res.Returnval[i] = metrics
}
body.Res.Returnval[i] = metrics
}
return body
}

// sampleInfoCSV converts the SampleInfo field to a CSV string
func sampleInfoCSV(m *types.PerfEntityMetric) string {
values := make([]string, len(m.SampleInfo)*2)

i := 0
for _, s := range m.SampleInfo {
values[i] = strconv.Itoa(int(s.Interval))
i++
values[i] = s.Timestamp.Format(time.RFC3339)
i++
}

return strings.Join(values, ",")
}

// valueCSV converts the Value field to a CSV string
func valueCSV(s *types.PerfMetricIntSeries) string {
values := make([]string, len(s.Value))

for i := range s.Value {
values[i] = strconv.FormatInt(s.Value[i], 10)
}

return strings.Join(values, ",")
}
68 changes: 68 additions & 0 deletions simulator/performance_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"testing"

"github.com/vmware/govmomi/performance"
Expand Down Expand Up @@ -306,6 +307,53 @@ func testPerfQuery(ctx context.Context, m *Model, e mo.Entity, interval int32, m
return nil
}

func testPerfQueryCSV(ctx context.Context, m *Model, e mo.Entity, interval int32, maxSample int32) error {
c := m.Service.client

p := performance.NewManager(c)

// Single metric, single VM
//
qs := []types.PerfQuerySpec{
{
MaxSample: maxSample,
IntervalId: interval,
MetricId: []types.PerfMetricId{{CounterId: 1, Instance: ""}},
Entity: e.Reference(),
Format: string(types.PerfFormatCsv),
},
}
series, err := p.Query(ctx, qs)
if err != nil {
return err
}
if len(series) == 0 {
return errors.New("Empty result set")
}
for i := range series {
s, ok := series[i].(*types.PerfEntityMetricCSV)
if !ok {
panic(fmt.Errorf("expected type %T, got: %T", s, series[i]))
}
if len(s.SampleInfoCSV) == 0 {
return errors.New("Empty SampleInfoCSV")
}
if len(strings.Split(s.SampleInfoCSV, ",")) == 0 {
return errors.New("SampleInfoCSV not in CSV format")
}
for _, v := range s.Value {
if len(v.Value) == 0 {
return errors.New("Empty PerfEntityMetricCSV.Value")
}
if len(strings.Split(v.Value, ",")) == 0 {
return errors.New("PerfEntityMetricCSV.Value not in CSV format")
}
}
}

return nil
}

func TestQueryPerf(t *testing.T) {
ctx := context.Background()

Expand Down Expand Up @@ -337,5 +385,25 @@ func TestQueryPerf(t *testing.T) {
if err := testPerfQuery(ctx, m, Map.Any("ResourcePool"), 300, maxSample); err != nil {
t.Fatal(err)
}

//csv format
if err := testPerfQueryCSV(ctx, m, Map.Any("VirtualMachine"), 20, maxSample); err != nil {
t.Fatal(err)
}
if err := testPerfQueryCSV(ctx, m, Map.Any("HostSystem"), 20, maxSample); err != nil {
t.Fatal(err)
}
if err := testPerfQueryCSV(ctx, m, Map.Any("ClusterComputeResource"), 300, maxSample); err != nil {
t.Fatal(err)
}
if err := testPerfQueryCSV(ctx, m, Map.Any("Datastore"), 300, maxSample); err != nil {
t.Fatal(err)
}
if err := testPerfQueryCSV(ctx, m, Map.Any("Datacenter"), 300, maxSample); err != nil {
t.Fatal(err)
}
if err := testPerfQueryCSV(ctx, m, Map.Any("ResourcePool"), 300, maxSample); err != nil {
t.Fatal(err)
}
}
}

0 comments on commit 78a979c

Please sign in to comment.