send/recv: job-level bandwidth limiting
Sponsored-by: Prominic.NET, Inc. fixes #339
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package datasizeunit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Bits struct {
|
||||
bits float64
|
||||
}
|
||||
|
||||
func (b Bits) ToBits() float64 { return b.bits }
|
||||
func (b Bits) ToBytes() float64 { return b.bits / 8 }
|
||||
func FromBytesInt64(i int64) Bits { return Bits{float64(i) * 8} }
|
||||
|
||||
var datarateRegex = regexp.MustCompile(`^([-0-9\.]*)\s*(bit|(|K|Ki|M|Mi|G|Gi|T|Ti)([bB]))$`)
|
||||
|
||||
func (r *Bits) UnmarshalYAML(u func(interface{}, bool) error) (_ error) {
|
||||
|
||||
var s string
|
||||
err := u(&s, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genericErr := func(err error) error {
|
||||
var buf strings.Builder
|
||||
fmt.Fprintf(&buf, "cannot parse %q using regex %s", s, datarateRegex)
|
||||
if err != nil {
|
||||
fmt.Fprintf(&buf, ": %s", err)
|
||||
}
|
||||
return errors.New(buf.String())
|
||||
}
|
||||
|
||||
match := datarateRegex.FindStringSubmatch(s)
|
||||
if match == nil {
|
||||
return genericErr(nil)
|
||||
}
|
||||
|
||||
bps, err := strconv.ParseFloat(match[1], 64)
|
||||
if err != nil {
|
||||
return genericErr(err)
|
||||
}
|
||||
|
||||
if match[2] == "bit" {
|
||||
if math.Round(bps) != bps {
|
||||
return genericErr(fmt.Errorf("unit bit must be an integer value"))
|
||||
}
|
||||
r.bits = bps
|
||||
return nil
|
||||
}
|
||||
|
||||
factorMap := map[string]uint64{
|
||||
"": 1,
|
||||
|
||||
"K": 1e3,
|
||||
"M": 1e6,
|
||||
"G": 1e9,
|
||||
"T": 1e12,
|
||||
|
||||
"Ki": 1 << 10,
|
||||
"Mi": 1 << 20,
|
||||
"Gi": 1 << 30,
|
||||
"Ti": 1 << 40,
|
||||
}
|
||||
factor, ok := factorMap[match[3]]
|
||||
if !ok {
|
||||
panic(match)
|
||||
}
|
||||
|
||||
baseUnitFactorMap := map[string]uint64{
|
||||
"b": 1,
|
||||
"B": 8,
|
||||
}
|
||||
baseUnitFactor, ok := baseUnitFactorMap[match[4]]
|
||||
if !ok {
|
||||
panic(match)
|
||||
}
|
||||
|
||||
r.bits = bps * float64(factor) * float64(baseUnitFactor)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package datasizeunit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zrepl/yaml-config"
|
||||
)
|
||||
|
||||
func TestBits(t *testing.T) {
|
||||
|
||||
tcs := []struct {
|
||||
input string
|
||||
expectRate float64
|
||||
expectErr string
|
||||
}{
|
||||
{`23 bit`, 23, ""}, // bit special case works
|
||||
{`23bit`, 23, ""}, // also without space
|
||||
|
||||
{`10MiB`, 10 * (1 << 20) * 8, ""}, // integer unit without space
|
||||
{`10 MiB`, 8 * 10 * (1 << 20), ""}, // integer unit with space
|
||||
|
||||
{`10.5 Kib`, 10.5 * (1 << 10), ""}, // floating point with bit unit works with space
|
||||
{`10.5Kib`, 10.5 * (1 << 10), ""}, // floating point with bit unit works without space
|
||||
|
||||
// unit checks
|
||||
{`1 bit`, 1, ""},
|
||||
{`1 B`, 1 * 8, ""},
|
||||
{`1 Kb`, 1e3, ""},
|
||||
{`1 Kib`, 1 << 10, ""},
|
||||
{`1 Mb`, 1e6, ""},
|
||||
{`1 Mib`, 1 << 20, ""},
|
||||
{`1 Gb`, 1e9, ""},
|
||||
{`1 Gib`, 1 << 30, ""},
|
||||
{`1 Tb`, 1e12, ""},
|
||||
{`1 Tib`, 1 << 40, ""},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.input, func(t *testing.T) {
|
||||
|
||||
var bits Bits
|
||||
err := yaml.Unmarshal([]byte(tc.input), &bits)
|
||||
if tc.expectErr != "" {
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, tc.expectErr, err.Error())
|
||||
assert.Zero(t, bits.bits)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectRate, bits.bits)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user