While at it,
- change some functions to not be methods of CpusetCgroup as
they don't use any members;
- simplify isEmpty.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
fscommon.WriteFile is added specifically to work with cgroup files,
and the error it returns does not need to be wrapped.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This removes package dependency on cgroup, as following commits
make cgroup use fscommon, which would result in dependency cycle.
The code to find out memory cgroup root is not really needed,
as 99% of test envrionments will have it at /sys/fs/cgroup/memory.
If not, that means we're either on cgroupv2 or on some very custom
system, so just skip the test.
The code that checks if we're on cgroupv2 is replaced by the check
of the particular v1 control file.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
errors.Wrap(err, "some error") returns nil if err is nil, so it's
slightly clearer to just return early than to set the error to nil
and call errors.Wrap(). This is also somewhat defensive in case
we decide to replace `errors.Wrap()` for golang's native `%w`
wrapping.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This is a function to convert huge page sizes (obtained by reading
/sys/kernel/mm/hugepages directory entries) to strings user for hugetlb
cgroup controller resource files. Those strings are when used to get the
hugetlb resource statistics.
This function used external library, floating point numbers, and can
(theoretically) produce invalid values, since the kernel only uses KB,
MB, and GB suffixes.
Rewrite it to produce the same strings as used in the kernel (see [1]).
As a result, it's also faster, more future-proof (entries that do not
start with "hugepages-" and/or incorrect suffix are skipped), and does
more input sanity checks. As a side effect, libcontainer no longer
depends on docker/go-units.
While at it, add more test cases.
Before:
BenchmarkGetHugePageSize-8 187452 6265 ns/op
BenchmarkGetHugePageSizeImpl-8 396769 2998 ns/op
After:
BenchmarkGetHugePageSize-8 222898 4554 ns/op
BenchmarkGetHugePageSizeImpl-8 4738924 241 ns/op
NOTE on removing HugePageSizeUnitList -- this was added by commit
6f77e35da and was used by kubernetes code in [2], which was later
superceded by [3], so there are (hopefully) no external users.
If there are any, they should not be doing that.
[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/hugetlb_cgroup.c?id=eff48ddeab782e35e58ccc8853f7386bbae9dec4#n574
[2] https://github.com/kubernetes/kubernetes/pull/78495
[3] https://github.com/kubernetes/kubernetes/pull/84154
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
In case it takes more than 1 second for systemd to create a unit,
startUnit() times out with a warning and then runc proceeds
(to create cgroups using fs manager and so on).
Now runc and systemd are racing, and multiple scenarios are possible.
In one such scenario, by the time runc calls systemd manager's Apply()
the unit is not yet created, the dbusConnection.SetUnitProperties()
call fails with "unit xxx.scope not found", and the whole container
start also fails.
To eliminate the race, we need to return an error in case the timeout is
hit.
To reduce the chance to fail, increase the timeout from 1 to 30 seconds,
to not error out too early on a busy/slow system (and times like 3-5
seconds are not unrealistic).
While at it, as the timeout is quite long now, make sure to not leave
a stray timer.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
1. Use GetCgroupParamString as the initial part of both functions are
the same and we can reuse it. This also gives us whatever security
measures GetCgroupParamString has (see previous commit).
2. Fix the error wrapping to not add the value, as it is already a part
of the error returned by ParseUint.
3. Improve docstring.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
1. Use own ReadFile wrapper instead of ioutils.ReadFile.
This makes it use the security measures of ReadFile.
2. Improve doc.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2. Fix wrapping the error to not have the value as it's already
part of the error returned from ParseUint.
3. Fix/improve doc.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
1. Don't wrap the error from fscommon.GetCgroupParamUint as it already
contains the file name.
2. Don't put file name when wrapping the error from ioutil.ReadFile
since it already has it.
3. Don't reconstruct file name, use existing one since it's available.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
The names of these templates overlapped with some local
variables, which made reading the code somewhat confusing.
Changing them to a const, given that these were not updated anywere.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
ioutil.ReadFile does a stat() on every entry and returns a slice of
os.Stat structures. What we need here is just a file name.
This change both simplifies and speeds up the code a bit.
Before:
BenchmarkGetHugePageSize-8 115213 9400 ns/op
After:
BenchmarkGetHugePageSize-8 190326 6187 ns/op
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
On my laptop, I get
BenchmarkGetHugePageSize
BenchmarkGetHugePageSize-8 115213 9400 ns/op
BenchmarkGetHugePageSizeImpl
BenchmarkGetHugePageSizeImpl-8 397873 2971 ns/op
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
As the underlying error message from iotuils.WriteFile already contains
file name, there's no need to put it, otherwise we end up with something
like:
failed to write "val" to "/sys/fs/cgroup/.../file": open /sys/fs/cgroup/.../file: permission denied
With this patch, the error will be
failed to write "val": open /sys/fs/cgroup/.../file: permission denied
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
In all these cases, getSubsystemPath() was already called, and its
result stored in m.paths map. It makes no sense to not reuse it.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
We call joinCgroups() from Apply, and in there we iterate through the
list of subsystems, calling getSubsystemPath() for each. This is
expensive, since every getSubsystemPath() involves parsing mountinfo.
At the end of Apply(), we iterate through the list of subsystems to fill
the m.paths, again calling getSubsystemPath() for every subsystem.
As a result, we parse mountinfo about 20 times here.
Let's find the paths first and reuse m.paths in joinCgroups().
While at it, since join() is just two calls now, inline it.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
When paths are set, we only need to place the PID into proper
cgroups, and we do know all the paths already.
Both fs/d.path() and systemd/v1/getSubsystemPath() parse
/proc/self/mountinfo, and the only reason they are used
here is to check whether the subsystem is available.
Use a much simpler/faster check instead.
Frankly, I am not sure why the check is needed at all. Maybe it should
be dropped.
Also, for fs driver, since d is no longer used in this code path,
move its initialization to after it.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
All the test cases are doing the same checks, only input differs,
so we can unify those using a test data table.
While at it:
- use t.Fatalf where it makes sense (no further checks are possible);
- remove the "XXX" comments as we won't get rid of cgroup Name/Parent.
PS I tried using t.Parallel() as well but it did not result in any
noticeable speedup, so I dropped it for simplicity.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
...by checking the default path first.
Quick benchmark shows it's about 5x faster on an idle system, and the
gain should be much more on a system doing mounts etc.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
In case cgroupPath is under the default cgroup prefix, let's try to
guess the mount point by adding the subsystem name to the default
prefix, and resolving the resulting path in case it's a symlink.
In most cases, given the default cgroup setup, this trick
should result in returning the same result faster, and avoiding
/proc/self/mountinfo parsing which is relatively slow and problematic.
Be very careful with the default path, checking it is
- a directory;
- a mount point;
- has cgroup fstype.
If something is not right, fall back to parsing mountinfo.
While at it, remove the obsoleted comment about mountinfo parsing. The
comment belongs to findCgroupMountpointAndRootFromReader(), but rather
than moving it there, let's just remove it, since it does not add any
value in understanding the current code.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
In manager.Apply() method, a path to each subsystem is obtained by
calling d.path(sys.Name()), and the sys.Apply() is called that does
the same call to d.path() again.
d.path() is an expensive call, so rather than to call it twice, let's
reuse the result.
This results the number of times we parse mountinfo during container
start from 62 to 34 on my setup.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Instead of iterating over m.paths, iterate over subsystems and look up
the path for each. This is faster since a map lookup is faster than
iterating over the names in Get. A quick benchmark shows that the new
way is 2.5x faster than the old one.
Note though that this is not done to make things faster, as savings are
negligible, but to make things simpler by removing some code.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Half of controllers' GetStats just return nil, and most of the others
ignore ENOENT on files, so it will be cheaper to not check that the
path exists in the main GetStats method, offloading that to the
controllers.
Drop PathExists check from GetStats, add it to those controllers'
GetStats where it was missing.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>