Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuang-Minh Nguyen <qmnguyen@gitlab.com>2023-11-22 19:09:34 +0300
committerQuang-Minh Nguyen <qmnguyen@gitlab.com>2023-11-22 19:09:34 +0300
commitaab593720ebff0d3022ccf2e420760850062de8a (patch)
treebcc948a7b2dc02ae8b03746b8f91622f3688f6f8
parent6835085898eb8d3881ee98c476a7bfc2981f0067 (diff)
parentb17bce5fcee8f38578568a7232e978b39633bb04 (diff)
Merge branch 'qmnguyen0711/cgroup-allow-spawning-command-use-up-to-m-cgroups' into 'master'
cgroup: Allow a repository to use up to M repository cgroups instead of one See merge request https://gitlab.com/gitlab-org/gitaly/-/merge_requests/6537 Merged-by: Quang-Minh Nguyen <qmnguyen@gitlab.com> Approved-by: Christian Couder <chriscool@tuxfamily.org> Reviewed-by: Will Chandler <wchandler@gitlab.com> Reviewed-by: Quang-Minh Nguyen <qmnguyen@gitlab.com>
-rw-r--r--internal/cgroups/handler_linux_test.go105
-rw-r--r--internal/cgroups/manager_linux.go29
-rw-r--r--internal/cgroups/manager_linux_test.go83
-rw-r--r--internal/cgroups/testhelper_test.go8
-rw-r--r--internal/gitaly/config/cgroups/cgroups.go21
-rw-r--r--internal/gitaly/config/cgroups/cgroups_test.go31
-rw-r--r--internal/gitaly/config/config.go4
-rw-r--r--internal/gitaly/config/config_test.go87
8 files changed, 281 insertions, 87 deletions
diff --git a/internal/cgroups/handler_linux_test.go b/internal/cgroups/handler_linux_test.go
index cb2a701af..bce5c5f09 100644
--- a/internal/cgroups/handler_linux_test.go
+++ b/internal/cgroups/handler_linux_test.go
@@ -241,8 +241,7 @@ func TestRepoCgroups(t *testing.T) {
// is creating repository directories in the correct location.
requireShards(t, version, mock, manager, pid)
- groupID := calcGroupID(cmdArgs, cfg.Repositories.Count)
-
+ groupID := uint(0) // Fixed - generated from the command
mock.setupMockCgroupFiles(t, manager, []uint{groupID})
require.False(t, manager.Ready())
@@ -286,33 +285,29 @@ func TestRepoCgroups(t *testing.T) {
func TestAddCommand(t *testing.T) {
for _, version := range []int{1, 2} {
t.Run("cgroups-v"+strconv.Itoa(version), func(t *testing.T) {
- mock := newMock(t, version)
-
- config := defaultCgroupsConfig()
- config.Repositories.Count = 10
- config.Repositories.MemoryBytes = 1024
- config.Repositories.CPUShares = 16
- config.HierarchyRoot = "gitaly"
- config.Mountpoint = mock.rootPath()
-
pid := 1
- manager1 := mock.newCgroupManager(config, testhelper.SharedLogger(t), pid)
- mock.setupMockCgroupFiles(t, manager1, []uint{})
- require.NoError(t, manager1.Setup())
-
ctx := testhelper.Context(t)
- cmd2 := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...)
- require.NoError(t, cmd2.Run())
-
- groupID := calcGroupID(cmd2.Args, config.Repositories.Count)
+ t.Run("without overridden key", func(t *testing.T) {
+ mock := newMock(t, version)
+ config := defaultCgroupsConfig()
+ config.Repositories.Count = 10
+ config.Repositories.MemoryBytes = 1024
+ config.Repositories.CPUShares = 16
+ config.HierarchyRoot = "gitaly"
+ config.Mountpoint = mock.rootPath()
+
+ cmd := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...)
+ require.NoError(t, cmd.Run())
- manager2 := mock.newCgroupManager(config, testhelper.SharedLogger(t), pid)
+ manager := mock.newCgroupManager(config, testhelper.SharedLogger(t), pid)
+ mock.setupMockCgroupFiles(t, manager, []uint{})
+ require.NoError(t, manager.Setup())
- t.Run("without overridden key", func(t *testing.T) {
- _, err := manager2.AddCommand(cmd2)
+ groupID := uint(8) // Fixed - generated from the command
+ _, err := manager.AddCommand(cmd)
require.NoError(t, err)
- requireShards(t, version, mock, manager2, pid, groupID)
+ requireShards(t, version, mock, manager, pid, groupID)
for _, path := range mock.repoPaths(pid, groupID) {
procsPath := filepath.Join(path, "cgroup.procs")
@@ -321,27 +316,79 @@ func TestAddCommand(t *testing.T) {
cmdPid, err := strconv.Atoi(string(content))
require.NoError(t, err)
- require.Equal(t, cmd2.Process.Pid, cmdPid)
+ require.Equal(t, cmd.Process.Pid, cmdPid)
}
})
t.Run("with overridden key", func(t *testing.T) {
- overriddenGroupID := calcGroupID([]string{"foobar"}, config.Repositories.Count)
+ mock := newMock(t, version)
+ config := defaultCgroupsConfig()
+ config.Repositories.Count = 10
+ config.Repositories.MemoryBytes = 1024
+ config.Repositories.CPUShares = 16
+ config.HierarchyRoot = "gitaly"
+ config.Mountpoint = mock.rootPath()
+
+ cmd := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...)
+ require.NoError(t, cmd.Run())
+
+ manager := mock.newCgroupManager(config, testhelper.SharedLogger(t), pid)
+ mock.setupMockCgroupFiles(t, manager, []uint{})
+ require.NoError(t, manager.Setup())
- _, err := manager2.AddCommand(cmd2, WithCgroupKey("foobar"))
+ groupID := uint(9) // Fixed - generated from the key
+ _, err := manager.AddCommand(cmd, WithCgroupKey("foobar"))
require.NoError(t, err)
- requireShards(t, version, mock, manager2, pid, groupID, overriddenGroupID)
+ requireShards(t, version, mock, manager, pid, groupID)
- for _, path := range mock.repoPaths(pid, overriddenGroupID) {
+ for _, path := range mock.repoPaths(pid, groupID) {
procsPath := filepath.Join(path, "cgroup.procs")
content := readCgroupFile(t, procsPath)
cmdPid, err := strconv.Atoi(string(content))
require.NoError(t, err)
- require.Equal(t, cmd2.Process.Pid, cmdPid)
+ require.Equal(t, cmd.Process.Pid, cmdPid)
}
})
+
+ t.Run("when AllocationSet is set", func(t *testing.T) {
+ mock := newMock(t, version)
+ config := defaultCgroupsConfig()
+ config.Repositories.Count = 10
+ config.Repositories.MaxCgroupsPerRepo = 3
+ config.Repositories.MemoryBytes = 1024
+ config.Repositories.CPUShares = 16
+ config.HierarchyRoot = "gitaly"
+ config.Mountpoint = mock.rootPath()
+
+ manager := mock.newCgroupManager(config, testhelper.SharedLogger(t), pid)
+ mock.setupMockCgroupFiles(t, manager, []uint{})
+ require.NoError(t, manager.Setup())
+
+ rand := newMockRand(t, 3)
+ manager.rand = rand
+
+ groupIDs := []uint{8, 9, 0}
+ for i := 0; i < 3; i++ {
+ cmd := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...)
+ require.NoError(t, cmd.Run())
+ _, err := manager.AddCommand(cmd)
+ require.NoError(t, err)
+
+ for _, path := range mock.repoPaths(pid, groupIDs[i]) {
+ procsPath := filepath.Join(path, "cgroup.procs")
+ content := readCgroupFile(t, procsPath)
+
+ cmdPid, err := strconv.Atoi(string(content))
+ require.NoError(t, err)
+
+ require.Equal(t, cmd.Process.Pid, cmdPid)
+ }
+ }
+
+ requireShards(t, version, mock, manager, pid, groupIDs...)
+ })
})
}
}
diff --git a/internal/cgroups/manager_linux.go b/internal/cgroups/manager_linux.go
index 0ee67acc2..516b63fbc 100644
--- a/internal/cgroups/manager_linux.go
+++ b/internal/cgroups/manager_linux.go
@@ -7,6 +7,7 @@ import (
"fmt"
"hash/crc32"
"io"
+ "math/rand"
"os"
"os/exec"
"path/filepath"
@@ -14,6 +15,7 @@ import (
"sync"
"sync/atomic"
"syscall"
+ "time"
cgrps "github.com/containerd/cgroups/v3"
"github.com/opencontainers/runtime-spec/specs-go"
@@ -72,6 +74,12 @@ func (s *cgroupStatus) getLock(cgroupPath string) *cgroupLock {
return cgLock
}
+// randomizer is the interface of the Go random number generator.
+type randomizer interface {
+ // Intn returns a random integer in the range [0,n).
+ Intn(n int) int
+}
+
// CGroupManager is a manager class that implements specific methods related to cgroups
type CGroupManager struct {
cfg cgroupscfg.Config
@@ -79,8 +87,8 @@ type CGroupManager struct {
enabled bool
repoRes *specs.LinuxResources
status *cgroupStatus
-
handler cgroupHandler
+ rand randomizer
}
func newCgroupManager(cfg cgroupscfg.Config, logger log.Logger, pid int) *CGroupManager {
@@ -106,6 +114,7 @@ func newCgroupManagerWithMode(cfg cgroupscfg.Config, logger log.Logger, pid int,
handler: handler,
repoRes: configRepositoryResources(cfg),
status: newCgroupStatus(cfg, handler.repoPath),
+ rand: rand.New(rand.NewSource(time.Now().UnixNano())),
}
}
@@ -199,13 +208,21 @@ func (cgm *CGroupManager) cgroupPathForCommand(cmd *exec.Cmd, opts []AddCommandO
if key == "" {
key = strings.Join(cmd.Args, "/")
}
+ groupID := cgm.calcGroupID(cgm.rand, key, cgm.cfg.Repositories.Count, cgm.cfg.Repositories.MaxCgroupsPerRepo)
+ return cgm.handler.repoPath(int(groupID))
+}
- checksum := crc32.ChecksumIEEE(
- []byte(key),
- )
+func (cgm *CGroupManager) calcGroupID(rand randomizer, key string, count uint, allocationCount uint) uint {
+ checksum := crc32.ChecksumIEEE([]byte(key))
- groupID := uint(checksum) % cgm.cfg.Repositories.Count
- return cgm.handler.repoPath(int(groupID))
+ // Pick a starting point
+ groupID := uint(checksum) % count
+ if allocationCount <= 1 {
+ return groupID
+ }
+
+ // Shift random distance [0, allocation) from the starting point. Wrap-around if needed.
+ return (groupID + uint(rand.Intn(int(allocationCount)))) % count
}
// Describe is used to generate description information for each CGroupManager prometheus metric
diff --git a/internal/cgroups/manager_linux_test.go b/internal/cgroups/manager_linux_test.go
index 3c9878169..30068edc4 100644
--- a/internal/cgroups/manager_linux_test.go
+++ b/internal/cgroups/manager_linux_test.go
@@ -14,25 +14,47 @@ import (
"gitlab.com/gitlab-org/gitaly/v16/internal/testhelper"
)
+// mockRand is a mock for random number generator that implements randomizer interface of cgroups package.
+type mockRand struct {
+ t *testing.T
+ max int
+ n int
+}
+
+func newMockRand(t *testing.T, max int) *mockRand {
+ return &mockRand{t: t, max: max, n: -1}
+}
+
+// Intn returns 0 to inputMax-1 in turn then back to 0. It also verifies if the input argument matched the initialized max.
+func (r *mockRand) Intn(inputMax int) int {
+ require.Equalf(r.t, r.max, inputMax, "unexpected rand.Intn() argument, expected %d, actual %d", r.max, inputMax)
+
+ r.n++
+ if r.n >= r.max {
+ r.n = 0
+ }
+ return r.n
+}
+
func TestCloneIntoCgroup(t *testing.T) {
mountPoint := t.TempDir()
hierarchyRoot := filepath.Join(mountPoint, "gitaly")
// Create the files we expect the manager to open.
require.NoError(t, os.MkdirAll(filepath.Join(hierarchyRoot, "gitaly-1"), fs.ModePerm))
- require.NoError(t, os.MkdirAll(filepath.Join(hierarchyRoot, "gitaly-1", "repos-3"), fs.ModePerm))
- require.NoError(t, os.MkdirAll(filepath.Join(hierarchyRoot, "gitaly-1", "repos-5"), fs.ModePerm))
+ for i := 0; i < 10; i++ {
+ require.NoError(t, os.MkdirAll(filepath.Join(hierarchyRoot, "gitaly-1", fmt.Sprintf("repos-%d", i)), fs.ModePerm))
+ }
require.NoError(t, os.WriteFile(filepath.Join(hierarchyRoot, "gitaly-1", "cgroup.subtree_control"), nil, fs.ModePerm))
- mgr := NewManager(cgroups.Config{
- Mountpoint: mountPoint,
- HierarchyRoot: "gitaly",
- Repositories: cgroups.Repositories{
- Count: 10,
- },
- }, testhelper.NewLogger(t), 1)
-
t.Run("command args used as key", func(t *testing.T) {
+ mgr := NewManager(cgroups.Config{
+ Mountpoint: mountPoint,
+ HierarchyRoot: "gitaly",
+ Repositories: cgroups.Repositories{
+ Count: 10,
+ },
+ }, testhelper.NewLogger(t), 1)
for _, tc := range []struct {
desc string
existingSysProcAttr *syscall.SysProcAttr
@@ -74,6 +96,14 @@ func TestCloneIntoCgroup(t *testing.T) {
})
t.Run("key provided", func(t *testing.T) {
+ mgr := NewManager(cgroups.Config{
+ Mountpoint: mountPoint,
+ HierarchyRoot: "gitaly",
+ Repositories: cgroups.Repositories{
+ Count: 10,
+ },
+ }, testhelper.NewLogger(t), 1)
+
commandWithKey := exec.Command("command", "arg")
pathWithKey, closeWithKey, err := mgr.CloneIntoCgroup(commandWithKey, WithCgroupKey("some-key"))
require.NoError(t, err)
@@ -93,6 +123,39 @@ func TestCloneIntoCgroup(t *testing.T) {
require.NotEqual(t, pathWithKey, pathWithoutKey, "commands should be placed in different groups")
require.NotEqual(t, commandWithKey.SysProcAttr.CgroupFD, commandWithoutKey.SysProcAttr.CgroupFD)
})
+
+ t.Run("AllocationCount is set", func(t *testing.T) {
+ mgr := NewManager(cgroups.Config{
+ Mountpoint: mountPoint,
+ HierarchyRoot: "gitaly",
+ Repositories: cgroups.Repositories{
+ Count: 10,
+ MaxCgroupsPerRepo: 3,
+ },
+ }, testhelper.NewLogger(t), 1)
+
+ assertSpawnedCommand := func(key string, groupID string) {
+ commandWithKey := exec.Command("command", "arg")
+ pathWithKey, closeWithKey, err := mgr.CloneIntoCgroup(commandWithKey, WithCgroupKey(key))
+ require.NoError(t, err)
+ defer testhelper.MustClose(t, closeWithKey)
+ require.Equal(t, filepath.Join(hierarchyRoot, fmt.Sprintf("gitaly-1/%s", groupID)), pathWithKey)
+ require.True(t, commandWithKey.SysProcAttr.UseCgroupFD)
+ require.NotEqual(t, 0, commandWithKey.SysProcAttr.UseCgroupFD)
+ }
+
+ mgr.(*CGroupManager).rand = newMockRand(t, 3)
+ // Starting point is 3 (similar to the above test)
+ assertSpawnedCommand("some-key", "repos-3")
+ assertSpawnedCommand("some-key", "repos-4")
+ assertSpawnedCommand("some-key", "repos-5")
+
+ mgr.(*CGroupManager).rand = newMockRand(t, 3)
+ // Starting point is 8
+ assertSpawnedCommand("key-1", "repos-8")
+ assertSpawnedCommand("key-1", "repos-9")
+ assertSpawnedCommand("key-1", "repos-0") // Wrap-around
+ })
}
func TestNewCgroupStatus(t *testing.T) {
diff --git a/internal/cgroups/testhelper_test.go b/internal/cgroups/testhelper_test.go
index 25b7e008e..33f0b6b30 100644
--- a/internal/cgroups/testhelper_test.go
+++ b/internal/cgroups/testhelper_test.go
@@ -3,8 +3,6 @@
package cgroups
import (
- "hash/crc32"
- "strings"
"testing"
"gitlab.com/gitlab-org/gitaly/v16/internal/testhelper"
@@ -16,9 +14,3 @@ var cmdArgs = []string{"ls", "-hal", "."}
func TestMain(m *testing.M) {
testhelper.Run(m)
}
-
-// calcGroupID calculates the repository cgroup ID for the key provided.
-func calcGroupID(key []string, ct uint) uint {
- checksum := crc32.ChecksumIEEE([]byte(strings.Join(key, "/")))
- return uint(checksum) % ct
-}
diff --git a/internal/gitaly/config/cgroups/cgroups.go b/internal/gitaly/config/cgroups/cgroups.go
index ff19650f9..b1a437dc5 100644
--- a/internal/gitaly/config/cgroups/cgroups.go
+++ b/internal/gitaly/config/cgroups/cgroups.go
@@ -61,6 +61,26 @@ type Repositories struct {
// Count is the number of cgroups that will be created for repository-level isolation
// of git commands.
Count uint `toml:"count"`
+ // MaxCgroupsPerRepo specifies the maximum number of cgroups to which a single repository can allocate its
+ // processes.
+ // By default, a repository can spawn processes in at most one cgroups. If the number of repositories
+ // is more than the number of cgroups (likely), multiple repositories share the same one. This model works very
+ // well if the repositories under the management of Cgroup are equivalent in size or traffic. If a node has some
+ // enormous repositories (mono-repo, for example), the scoping cgroups become excessively large comparing to the
+ // rest. This imbalance situation might force the operators to lift the repository-level cgroups. As a result,
+ // the isolation effect is not effective.
+ // This config is designed to balance resource usage between cgroups, mitigate competition for resources
+ // within a single cgroup, and enhance memory usage efficiency and isolation. The value can be adjusted based on
+ // the specific workload and number of repository cgroups on the node.
+ // A Git process uses its target repository's relative path as the hash key to find the corresponding cgroup. It
+ // is allocated randomly to any of the consequent MaxCgroupsPerRepo cgroups. It wraps around if needed.
+ // repo-X
+ // ┌───────┐
+ // □ □ □ □ □ □ □ ■ ■ ■ ■ ■ ■ ■ ■ □ □ □ □
+ // └────────┘
+ // repo-Y
+ // The default value is "1".
+ MaxCgroupsPerRepo uint `toml:"max_cgroups_per_repo"`
// MemoryBytes is the memory limit for each cgroup. 0 implies no memory limit.
MemoryBytes int64 `toml:"memory_bytes"`
// CPUShares are the shares of CPU that each cgroup is allowed to utilize. A value of 1024
@@ -77,6 +97,7 @@ type Repositories struct {
// Validate runs validation on all fields and compose all found errors.
func (r *Repositories) Validate(memBytes int64, cpuShares uint64, cpuQuotaUs int64) error {
return cfgerror.New().
+ Append(cfgerror.InRange(0, r.Count, r.MaxCgroupsPerRepo, cfgerror.InRangeOptIncludeMin, cfgerror.InRangeOptIncludeMax), "max_cgroups_per_repo").
Append(cfgerror.InRange(0, memBytes, r.MemoryBytes, cfgerror.InRangeOptIncludeMin, cfgerror.InRangeOptIncludeMax), "memory_bytes").
Append(cfgerror.InRange(0, cpuShares, r.CPUShares, cfgerror.InRangeOptIncludeMin, cfgerror.InRangeOptIncludeMax), "cpu_shares").
Append(cfgerror.InRange(0, cpuQuotaUs, r.CPUQuotaUs, cfgerror.InRangeOptIncludeMin, cfgerror.InRangeOptIncludeMax), "cpu_quota_us").
diff --git a/internal/gitaly/config/cgroups/cgroups_test.go b/internal/gitaly/config/cgroups/cgroups_test.go
index 979a027b4..5527415ea 100644
--- a/internal/gitaly/config/cgroups/cgroups_test.go
+++ b/internal/gitaly/config/cgroups/cgroups_test.go
@@ -168,9 +168,21 @@ func TestRepositories_Validate(t *testing.T) {
{
name: "valid",
repositories: Repositories{
- Count: 2,
- MemoryBytes: 1024,
- CPUShares: 16,
+ Count: 2,
+ MaxCgroupsPerRepo: 2,
+ MemoryBytes: 1024,
+ CPUShares: 16,
+ },
+ memBytes: 2048,
+ cpuShares: 32,
+ },
+ {
+ name: "valid",
+ repositories: Repositories{
+ Count: 2,
+ MaxCgroupsPerRepo: 1,
+ MemoryBytes: 1024,
+ CPUShares: 16,
},
memBytes: 2048,
cpuShares: 32,
@@ -178,16 +190,21 @@ func TestRepositories_Validate(t *testing.T) {
{
name: "invalid",
repositories: Repositories{
- Count: 2,
- MemoryBytes: 1024,
- CPUShares: 16,
- CPUQuotaUs: 32000,
+ Count: 2,
+ MaxCgroupsPerRepo: 3,
+ MemoryBytes: 1024,
+ CPUShares: 16,
+ CPUQuotaUs: 32000,
},
memBytes: 256,
cpuShares: 2,
cpuQuotaUs: 16000,
expectedErr: cfgerror.ValidationErrors{
cfgerror.NewValidationError(
+ fmt.Errorf("%w: 3 out of [0, 2]", cfgerror.ErrNotInRange),
+ "max_cgroups_per_repo",
+ ),
+ cfgerror.NewValidationError(
fmt.Errorf("%w: 1024 out of [0, 256]", cfgerror.ErrNotInRange),
"memory_bytes",
),
diff --git a/internal/gitaly/config/config.go b/internal/gitaly/config/config.go
index edf978996..49280cd3d 100644
--- a/internal/gitaly/config/config.go
+++ b/internal/gitaly/config/config.go
@@ -801,6 +801,10 @@ func (cfg *Cfg) SetDefaults() error {
cfg.Cgroups.FallbackToOldVersion()
+ if cfg.Cgroups.Repositories.Count != 0 && cfg.Cgroups.Repositories.MaxCgroupsPerRepo == 0 {
+ cfg.Cgroups.Repositories.MaxCgroupsPerRepo = 1
+ }
+
if cfg.Backup.Layout == "" {
cfg.Backup.Layout = "pointer"
}
diff --git a/internal/gitaly/config/config_test.go b/internal/gitaly/config/config_test.go
index 5e7d45689..417047951 100644
--- a/internal/gitaly/config/config_test.go
+++ b/internal/gitaly/config/config_test.go
@@ -1246,9 +1246,10 @@ func TestValidateCgroups(t *testing.T) {
Shares: 512,
},
Repositories: cgroups.Repositories{
- Count: 10,
- MemoryBytes: 1024,
- CPUShares: 512,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ MemoryBytes: 1024,
+ CPUShares: 512,
},
},
},
@@ -1264,7 +1265,8 @@ func TestValidateCgroups(t *testing.T) {
Mountpoint: "/sys/fs/cgroup",
HierarchyRoot: "baz",
Repositories: cgroups.Repositories{
- Count: 10,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
},
},
},
@@ -1279,7 +1281,8 @@ func TestValidateCgroups(t *testing.T) {
Mountpoint: "/sys/fs/cgroup",
HierarchyRoot: "gitaly",
Repositories: cgroups.Repositories{
- Count: 10,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
},
},
},
@@ -1308,8 +1311,9 @@ func TestValidateCgroups(t *testing.T) {
Shares: 0,
},
Repositories: cgroups.Repositories{
- Count: 10,
- MemoryBytes: 1024,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ MemoryBytes: 1024,
},
},
},
@@ -1339,8 +1343,9 @@ func TestValidateCgroups(t *testing.T) {
Shares: 512,
},
Repositories: cgroups.Repositories{
- Count: 10,
- CPUShares: 512,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ CPUShares: 512,
},
},
},
@@ -1385,10 +1390,11 @@ func TestValidateCgroups(t *testing.T) {
Mountpoint: "/sys/fs/cgroup",
HierarchyRoot: "gitaly",
Repositories: cgroups.Repositories{
- Count: 10,
- MemoryBytes: 1024,
- CPUShares: 512,
- CPUQuotaUs: 500,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ MemoryBytes: 1024,
+ CPUShares: 512,
+ CPUQuotaUs: 500,
},
},
},
@@ -1427,9 +1433,10 @@ func TestValidateCgroups(t *testing.T) {
Mountpoint: "/sys/fs/cgroup",
HierarchyRoot: "gitaly",
Repositories: cgroups.Repositories{
- Count: 10,
- CPUShares: 512,
- CPUQuotaUs: 500,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ CPUShares: 512,
+ CPUQuotaUs: 500,
},
},
},
@@ -1454,10 +1461,11 @@ func TestValidateCgroups(t *testing.T) {
CPUShares: 1024,
CPUQuotaUs: 800,
Repositories: cgroups.Repositories{
- Count: 10,
- MemoryBytes: 2147483648,
- CPUShares: 128,
- CPUQuotaUs: 500,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ MemoryBytes: 2147483648,
+ CPUShares: 128,
+ CPUQuotaUs: 500,
},
},
validateErr: errors.New("cgroups.repositories: memory limit cannot exceed parent"),
@@ -1477,8 +1485,9 @@ func TestValidateCgroups(t *testing.T) {
HierarchyRoot: "gitaly",
CPUShares: 128,
Repositories: cgroups.Repositories{
- Count: 10,
- CPUShares: 512,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ CPUShares: 512,
},
},
validateErr: errors.New("cgroups.repositories: cpu shares cannot exceed parent"),
@@ -1498,8 +1507,9 @@ func TestValidateCgroups(t *testing.T) {
HierarchyRoot: "gitaly",
CPUQuotaUs: 225,
Repositories: cgroups.Repositories{
- Count: 10,
- CPUQuotaUs: 500,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ CPUQuotaUs: 500,
},
},
validateErr: errors.New("cgroups.repositories: cpu quota cannot exceed parent"),
@@ -1520,9 +1530,32 @@ func TestValidateCgroups(t *testing.T) {
HierarchyRoot: "gitaly",
MetricsEnabled: true,
Repositories: cgroups.Repositories{
- Count: 10,
- MemoryBytes: 1024,
- CPUShares: 512,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ MemoryBytes: 1024,
+ CPUShares: 512,
+ },
+ },
+ },
+ {
+ name: "max_cgroups_per_repo enabled",
+ rawCfg: `[cgroups]
+ mountpoint = "/sys/fs/cgroup"
+ hierarchy_root = "gitaly"
+ [cgroups.repositories]
+ count = 10
+ max_cgroups_per_repo = 5
+ memory_bytes = 1024
+ cpu_shares = 512
+ `,
+ expect: cgroups.Config{
+ Mountpoint: "/sys/fs/cgroup",
+ HierarchyRoot: "gitaly",
+ Repositories: cgroups.Repositories{
+ Count: 10,
+ MaxCgroupsPerRepo: 5,
+ MemoryBytes: 1024,
+ CPUShares: 512,
},
},
},