Skip to content

Commit

Permalink
tenantrate: add "test" that reports IOPS estimations
Browse files Browse the repository at this point in the history
This change adds a "test" facility which takes the description of a
uniform workload (read percentage, read size, write size) and prints
out an estimation of the sustained IOPS and burst IO. This will allow
a better understanding of how changes to the settings or the mechanism
translate into IOPS changes.

Release note: None
  • Loading branch information
RaduBerinde committed Mar 31, 2021
1 parent ea3c263 commit e0efca8
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 1 deletion.
1 change: 1 addition & 0 deletions pkg/kv/kvserver/tenantrate/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ go_test(
"//pkg/util/timeutil",
"@com_github_cockroachdb_datadriven//:datadriven",
"@com_github_cockroachdb_errors//:errors",
"@com_github_dustin_go_humanize//:go-humanize",
"@com_github_stretchr_testify//require",
"@in_gopkg_yaml_v2//:yaml_v2",
],
Expand Down
23 changes: 23 additions & 0 deletions pkg/kv/kvserver/tenantrate/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,26 @@ func OverrideSettingsWithRateLimits(settings *cluster.Settings, rl LimitConfigs)
writeRateLimit.Override(&settings.SV, int64(rl.WriteBytes.Rate))
writeBurstLimit.Override(&settings.SV, rl.WriteBytes.Burst)
}

// DefaultLimitConfigs returns the configuration that corresponds to the default
// setting values.
func DefaultLimitConfigs() LimitConfigs {
return LimitConfigs{
ReadRequests: LimitConfig{
Rate: Limit(readRequestRateLimit.Default()),
Burst: readRequestBurstLimit.Default(),
},
WriteRequests: LimitConfig{
Rate: Limit(writeRequestRateLimit.Default()),
Burst: writeRequestBurstLimit.Default(),
},
ReadBytes: LimitConfig{
Rate: Limit(readRateLimit.Default()),
Burst: readBurstLimit.Default(),
},
WriteBytes: LimitConfig{
Rate: Limit(writeRateLimit.Default()),
Burst: writeBurstLimit.Default(),
},
}
}
78 changes: 77 additions & 1 deletion pkg/kv/kvserver/tenantrate/limiter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"bytes"
"context"
"fmt"
"math"
"regexp"
"sort"
"strings"
Expand All @@ -30,6 +31,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
"github.com/cockroachdb/datadriven"
"github.com/cockroachdb/errors"
"github.com/dustin/go-humanize"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
)
Expand Down Expand Up @@ -105,10 +107,11 @@ var testStateCommands = map[string]func(*testState, *testing.T, *datadriven.Test
"metrics": (*testState).metrics,
"get_tenants": (*testState).getTenants,
"release_tenants": (*testState).releaseTenants,
"estimate_iops": (*testState).estimateIOPS,
}

func (ts *testState) run(t *testing.T, d *datadriven.TestData) string {
if !ts.initialized && d.Cmd != "init" {
if !ts.initialized && d.Cmd != "init" && d.Cmd != "estimate_iops" {
d.Fatalf(t, "expected init as first command, got %q", d.Cmd)
}
if f, ok := testStateCommands[d.Cmd]; ok {
Expand Down Expand Up @@ -486,6 +489,79 @@ func (ts *testState) releaseTenants(t *testing.T, d *datadriven.TestData) string
return ts.FormatTenants()
}

// estimateIOPS takes in the description of a workload and produces an estimate
// of the IOPS for that workload (under the default settings).
//
// For example:
//
// estimate_iops
// readpercentage: 50
// readsize: 4096
// writesize: 4096
// ----
// Mixed workload (50% reads; 4.0 KiB reads; 4.0 KiB writes): 256 sustained IOPS, 256 burst.
//
func (ts *testState) estimateIOPS(t *testing.T, d *datadriven.TestData) string {
var workload struct {
ReadPercentage int
ReadSize int
WriteSize int
}
if err := yaml.UnmarshalStrict([]byte(d.Input), &workload); err != nil {
d.Fatalf(t, "failed to parse workload information: %v", err)
}
if workload.ReadPercentage < 0 || workload.ReadPercentage > 100 {
d.Fatalf(t, "Invalid read percentage %d", workload.ReadPercentage)
}
limits := tenantrate.DefaultLimitConfigs()

calculateIOPS := func(readRate, readBytesRate, writeRate, writeBytesRate float64) float64 {
readIOPS := math.Min(readRate, readBytesRate/float64(workload.ReadSize))
writeIOPS := math.Min(writeRate, writeBytesRate/float64(workload.WriteSize))
// The reads and writes are rate-limited separately; our workload will be
// bottlenecked on one of them.
return math.Min(
writeIOPS*100.0/float64(100-workload.ReadPercentage),
readIOPS*100.0/float64(workload.ReadPercentage),
)
}

sustained := calculateIOPS(
float64(limits.ReadRequests.Rate), float64(limits.ReadBytes.Rate),
float64(limits.WriteRequests.Rate), float64(limits.WriteBytes.Rate),
)

burst := calculateIOPS(
float64(limits.ReadRequests.Burst), float64(limits.ReadBytes.Burst),
float64(limits.WriteRequests.Burst), float64(limits.WriteBytes.Burst),
)
fmtFloat := func(val float64) string {
if val < 10 {
return fmt.Sprintf("%.1f", val)
}
return fmt.Sprintf("%.0f", val)
}
switch workload.ReadPercentage {
case 0:
return fmt.Sprintf(
"Write-only workload (%s writes): %s sustained IOPS, %s burst.",
humanize.IBytes(uint64(workload.WriteSize)), fmtFloat(sustained), fmtFloat(burst),
)
case 100:
return fmt.Sprintf(
"Read-only workload (%s reads): %s sustained IOPS, %s burst.",
humanize.IBytes(uint64(workload.ReadSize)), fmtFloat(sustained), fmtFloat(burst),
)
default:
return fmt.Sprintf(
"Mixed workload (%d%% reads; %s reads; %s writes): %s sustained IOPS, %s burst.",
workload.ReadPercentage,
humanize.IBytes(uint64(workload.ReadSize)), humanize.IBytes(uint64(workload.WriteSize)),
fmtFloat(sustained), fmtFloat(burst),
)
}
}

func (rs *testState) FormatRunning() string {
var states []string
for _, ls := range rs.running {
Expand Down
49 changes: 49 additions & 0 deletions pkg/kv/kvserver/tenantrate/testdata/estimate_iops
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
estimate_iops
readpercentage: 100
readsize: 4096
----
Read-only workload (4.0 KiB reads): 128 sustained IOPS, 512 burst.

estimate_iops
readpercentage: 100
readsize: 65536
----
Read-only workload (64 KiB reads): 16 sustained IOPS, 256 burst.

estimate_iops
readpercentage: 100
readsize: 1048576
----
Read-only workload (1.0 MiB reads): 1.0 sustained IOPS, 16 burst.

estimate_iops
readpercentage: 0
writesize: 4096
----
Write-only workload (4.0 KiB writes): 128 sustained IOPS, 512 burst.

estimate_iops
readpercentage: 0
writesize: 65536
----
Write-only workload (64 KiB writes): 8.0 sustained IOPS, 128 burst.

estimate_iops
readpercentage: 0
writesize: 1048576
----
Write-only workload (1.0 MiB writes): 0.5 sustained IOPS, 8.0 burst.

estimate_iops
readpercentage: 50
readsize: 4096
writesize: 4096
----
Mixed workload (50% reads; 4.0 KiB reads; 4.0 KiB writes): 256 sustained IOPS, 1024 burst.

estimate_iops
readpercentage: 90
readsize: 4096
writesize: 4096
----
Mixed workload (90% reads; 4.0 KiB reads; 4.0 KiB writes): 142 sustained IOPS, 569 burst.

0 comments on commit e0efca8

Please sign in to comment.