Skip to content

Commit 5424357

Browse files
feat: add backward-compatible Specs field to LoadProfile
Add Specs []LoadProfileSpec field alongside existing Spec field to enable time-series replay support in future. Update all code to use GetSpecs() accessor method for unified access to both old and new formats. Changes: - Add Specs field with omitempty tags to LoadProfile struct - Add GetSpecs() method to handle both single Spec and Specs formats - Update LoadProfile.Validate() to validate all specs - Update runner, runkperf bench, and warmup commands to use GetSpecs() - Maintain backward compatibility with existing configs All existing configs with 'spec:' continue to work unchanged. New configs can use 'specs:' array format. No functional changes in this commit.
1 parent f3f2d5a commit 5424357

File tree

4 files changed

+82
-30
lines changed

4 files changed

+82
-30
lines changed

api/types/load_traffic.go

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package types
66
import (
77
"encoding/json"
88
"fmt"
9+
"reflect"
910
"strings"
1011

1112
apitypes "k8s.io/apimachinery/pkg/types"
@@ -37,8 +38,29 @@ type LoadProfile struct {
3738
Version int `json:"version" yaml:"version"`
3839
// Description is a string value to describe this object.
3940
Description string `json:"description,omitempty" yaml:"description"`
40-
// Spec defines behavior of load profile.
41-
Spec LoadProfileSpec `json:"spec" yaml:"spec"`
41+
// Spec defines behavior of load profile (deprecated, use Specs for single or multiple specs).
42+
Spec LoadProfileSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
43+
// Specs defines behaviors of load profile for time-series replay support.
44+
Specs []LoadProfileSpec `json:"specs,omitempty" yaml:"specs,omitempty"`
45+
}
46+
47+
// GetSpecs returns specs as a slice, handling both old and new format.
48+
// If Specs is set, returns it. Otherwise, returns Spec as a single-element slice.
49+
func (lp *LoadProfile) GetSpecs() []LoadProfileSpec {
50+
if len(lp.Specs) > 0 {
51+
return lp.Specs
52+
}
53+
// Fallback to old single Spec field
54+
return []LoadProfileSpec{lp.Spec}
55+
}
56+
57+
// SetFirstSpec updates the first spec, handling both old and new format.
58+
func (lp *LoadProfile) SetFirstSpec(spec LoadProfileSpec) {
59+
if len(lp.Specs) > 0 {
60+
lp.Specs[0] = spec
61+
} else {
62+
lp.Spec = spec
63+
}
4264
}
4365

4466
// LoadProfileSpec defines the load traffic for traget resource.
@@ -198,7 +220,21 @@ func (lp LoadProfile) Validate() error {
198220
if lp.Version != 1 {
199221
return fmt.Errorf("version should be 1")
200222
}
201-
return lp.Spec.Validate()
223+
224+
// Validate that at least one format is provided
225+
if len(lp.Specs) == 0 && reflect.DeepEqual(lp.Spec, LoadProfileSpec{}) {
226+
return fmt.Errorf("either 'spec' or 'specs' must be provided")
227+
}
228+
229+
// Validate all specs
230+
specs := lp.GetSpecs()
231+
for i, spec := range specs {
232+
if err := spec.Validate(); err != nil {
233+
return fmt.Errorf("spec[%d]: %w", i, err)
234+
}
235+
}
236+
237+
return nil
202238
}
203239

204240
// Validate verifies fields of LoadProfileSpec.

cmd/kperf/commands/runner/runner.go

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -103,19 +103,20 @@ var runCommand = cli.Command{
103103
return err
104104
}
105105

106-
clientNum := profileCfg.Spec.Conns
106+
specs := profileCfg.GetSpecs()
107+
clientNum := specs[0].Conns
107108
restClis, err := request.NewClients(kubeCfgPath,
108109
clientNum,
109110
request.WithClientUserAgentOpt(cliCtx.String("user-agent")),
110-
request.WithClientQPSOpt(profileCfg.Spec.Rate),
111-
request.WithClientContentTypeOpt(profileCfg.Spec.ContentType),
112-
request.WithClientDisableHTTP2Opt(profileCfg.Spec.DisableHTTP2),
111+
request.WithClientQPSOpt(specs[0].Rate),
112+
request.WithClientContentTypeOpt(specs[0].ContentType),
113+
request.WithClientDisableHTTP2Opt(specs[0].DisableHTTP2),
113114
)
114115
if err != nil {
115116
return err
116117
}
117118

118-
stats, err := request.Schedule(context.TODO(), &profileCfg.Spec, restClis)
119+
stats, err := request.Schedule(context.TODO(), &specs[0], restClis)
119120
if err != nil {
120121
return err
121122
}
@@ -165,40 +166,44 @@ func loadConfig(cliCtx *cli.Context) (*types.LoadProfile, error) {
165166
return nil, fmt.Errorf("failed to unmarshal %s from yaml format: %w", cfgPath, err)
166167
}
167168

169+
specs := profileCfg.GetSpecs()
168170
// override value by flags
169171
if v := "rate"; cliCtx.IsSet(v) {
170-
profileCfg.Spec.Rate = cliCtx.Float64(v)
172+
specs[0].Rate = cliCtx.Float64(v)
171173
}
172-
if v := "conns"; cliCtx.IsSet(v) || profileCfg.Spec.Conns == 0 {
173-
profileCfg.Spec.Conns = cliCtx.Int(v)
174+
if v := "conns"; cliCtx.IsSet(v) || specs[0].Conns == 0 {
175+
specs[0].Conns = cliCtx.Int(v)
174176
}
175-
if v := "client"; cliCtx.IsSet(v) || profileCfg.Spec.Client == 0 {
176-
profileCfg.Spec.Client = cliCtx.Int(v)
177+
if v := "client"; cliCtx.IsSet(v) || specs[0].Client == 0 {
178+
specs[0].Client = cliCtx.Int(v)
177179
}
178180
if v := "total"; cliCtx.IsSet(v) {
179-
profileCfg.Spec.Total = cliCtx.Int(v)
181+
specs[0].Total = cliCtx.Int(v)
180182
}
181183
if v := "duration"; cliCtx.IsSet(v) {
182-
profileCfg.Spec.Duration = cliCtx.Int(v)
184+
specs[0].Duration = cliCtx.Int(v)
183185
}
184-
if profileCfg.Spec.Total > 0 && profileCfg.Spec.Duration > 0 {
185-
klog.Warningf("both total:%v and duration:%v are set, duration will be ignored\n", profileCfg.Spec.Total, profileCfg.Spec.Duration)
186-
profileCfg.Spec.Duration = 0
186+
if specs[0].Total > 0 && specs[0].Duration > 0 {
187+
klog.Warningf("both total:%v and duration:%v are set, duration will be ignored\n", specs[0].Total, specs[0].Duration)
188+
specs[0].Duration = 0
187189
}
188-
if profileCfg.Spec.Total == 0 && profileCfg.Spec.Duration == 0 {
190+
if specs[0].Total == 0 && specs[0].Duration == 0 {
189191
// Use default total value
190-
profileCfg.Spec.Total = cliCtx.Int("total")
192+
specs[0].Total = cliCtx.Int("total")
191193
}
192-
if v := "content-type"; cliCtx.IsSet(v) || profileCfg.Spec.ContentType == "" {
193-
profileCfg.Spec.ContentType = types.ContentType(cliCtx.String(v))
194+
if v := "content-type"; cliCtx.IsSet(v) || specs[0].ContentType == "" {
195+
specs[0].ContentType = types.ContentType(cliCtx.String(v))
194196
}
195197
if v := "disable-http2"; cliCtx.IsSet(v) {
196-
profileCfg.Spec.DisableHTTP2 = cliCtx.Bool(v)
198+
specs[0].DisableHTTP2 = cliCtx.Bool(v)
197199
}
198200
if v := "max-retries"; cliCtx.IsSet(v) {
199-
profileCfg.Spec.MaxRetries = cliCtx.Int(v)
201+
specs[0].MaxRetries = cliCtx.Int(v)
200202
}
201203

204+
// Update profileCfg with modified specs
205+
profileCfg.SetFirstSpec(specs[0])
206+
202207
if err := profileCfg.Validate(); err != nil {
203208
return nil, err
204209
}

contrib/cmd/runkperf/commands/bench/utils.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,10 @@ func newLoadProfileFromEmbed(cliCtx *cli.Context, name string) (_name string, _s
151151
return fmt.Errorf("invalid total-requests value: %v", reqs)
152152
}
153153
reqsTime := cliCtx.Int("duration")
154+
specs := spec.Profile.GetSpecs()
154155
if !cliCtx.IsSet("total") && reqsTime > 0 {
155156
reqs = 0
156-
spec.Profile.Spec.Duration = reqsTime
157+
specs[0].Duration = reqsTime
157158
}
158159

159160
rgAffinity := cliCtx.GlobalString("rg-affinity")
@@ -163,10 +164,14 @@ func newLoadProfileFromEmbed(cliCtx *cli.Context, name string) (_name string, _s
163164
}
164165

165166
if reqs != 0 {
166-
spec.Profile.Spec.Total = reqs
167+
specs[0].Total = reqs
167168
}
168169
spec.NodeAffinity = affinityLabels
169-
spec.Profile.Spec.ContentType = types.ContentType(cliCtx.String("content-type"))
170+
specs[0].ContentType = types.ContentType(cliCtx.String("content-type"))
171+
172+
// Update profile with modified specs
173+
spec.Profile.SetFirstSpec(specs[0])
174+
170175
data, _ := yaml.Marshal(spec)
171176

172177
// Tweak the load profile for read-update case
@@ -202,7 +207,8 @@ func tweakReadUpdateProfile(cliCtx *cli.Context, spec *types.RunnerGroupSpec) er
202207
configmapTotal := cliCtx.Int("read-update-configmap-total")
203208

204209
if namePattern != "" || ratio != 0 || namespace != "" || configmapTotal > 0 {
205-
for _, r := range spec.Profile.Spec.Requests {
210+
specs := spec.Profile.GetSpecs()
211+
for _, r := range specs[0].Requests {
206212
if r.Patch != nil {
207213
if namePattern != "" {
208214
r.Patch.Name = fmt.Sprintf("runkperf-cm-%s", namePattern)

contrib/cmd/runkperf/commands/warmup/command.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,13 @@ var Command = cli.Command{
102102
return fmt.Errorf("failed to parse %s affinity: %w", rgAffinity, err)
103103
}
104104

105-
spec.Profile.Spec.Total = reqs
106-
spec.Profile.Spec.Rate = rate
105+
specs := spec.Profile.GetSpecs()
106+
specs[0].Total = reqs
107+
specs[0].Rate = rate
108+
109+
// Update profile with modified specs
110+
spec.Profile.SetFirstSpec(specs[0])
111+
107112
spec.NodeAffinity = affinityLabels
108113

109114
data, _ := yaml.Marshal(spec)

0 commit comments

Comments
 (0)