Loading init/Android.bp +10 −0 Original line number Diff line number Diff line Loading @@ -287,6 +287,9 @@ cc_binary { "make_f2fs", "mke2fs", "sload_f2fs", // TODO: Revert after go/android-memcgv2-exp b/386797433 "memcgv2_activation_depth", ], } Loading Loading @@ -685,3 +688,10 @@ phony { default: ["init_first_stage"], }), } // TODO: Revert after go/android-memcgv2-exp b/386797433 sh_binary { name: "memcgv2_activation_depth", src: "memcgv2_activation_depth.sh", filename_from_src: true, } init/memcgv2_activation_depth.sh 0 → 100644 +86 −0 Original line number Diff line number Diff line #!/bin/sh # This script adjusts overrides of the memcg v2 MaxActivationDepth value at runtime. # The override value needs to be accessible starting very early in the Android boot, where aconfig # flags and system properties do not work. A file on /metadata is used instead. # The kernel allows this to be as high as 65535, but our Android hierarchy is never that deep. MAX_ALLOWED_DEPTH=5 # Store overridden MaxActivationDepths here for libprocessgroup to find them OVERRIDE_FILE_PATH="/metadata/libprocessgroup/memcg_v2_max_activation_depth" if [ "$#" -ne 1 ] then echo "Usage: $0 <memcg v2 MaxActivationDepth value>" exit 99 fi max_activation_depth=$1 if [[ $max_activation_depth != +([0-9]) ]] then echo "MaxActivationDepth value must be a positive integer: $max_activation_depth" exit 98 fi if [ $max_activation_depth -lt 0 ] then echo "Negative MaxActivationDepth is invalid: $max_activation_depth" exit 97 fi if [ $max_activation_depth -gt $MAX_ALLOWED_DEPTH ] then echo "MaxActivationDepth is too large: $max_activation_depth" exit 96 fi grep memory /sys/fs/cgroup/cgroup.controllers if [ $? -ne 0 ] then echo "memcg v2 is not available on this device!" exit 95 fi current_activation_depth=$(cat $OVERRIDE_FILE_PATH) if [ $? -ne 0 ] then # Find the default activation depth in the absence of any properties / overrides. # # To do this 100% correctly requires JSON parsing which we don't really want to do here. # We know that this will be called only for Pixel (for a limited-duration experiment), and that # Pixel does not override cgroups.json, therefore we can assume that the system cgroups.json has # only a single MaxActivationDepth entry which corresponds to the v2 memory controller. So we # can just grep for the default value. default_activation_depth=$(grep MaxActivationDepth /system/etc/cgroups.json | tr -dc '0-9') if [ $? -ne 0 -o $default_activation_depth -gt $MAX_ALLOWED_DEPTH ] then # If MaxActivationDepth is not present, libprocessgroup does not limit how deep it will activate default_activation_depth=$MAX_ALLOWED_DEPTH fi current_activation_depth=$default_activation_depth fi # libprocessgroup will pick this up for all future cgroup creations, including on the next boot echo $max_activation_depth > $OVERRIDE_FILE_PATH if [ $max_activation_depth -lt $current_activation_depth ] then # We can deactivate memcgs which are deeper than the new depth value, however that would leave # behind zombie memcgs which would ruin the metrics produced from this device. The only way to # eliminate those zombies is to remove the entire cgroup, which we cannot do without killing # all the contained processes. So the only real option we have is to reboot here. # This should only happen once at the end of the experiment. echo "Rebooting due to decreased memcg v2 MaxActivationDepth" reboot elif [ $max_activation_depth -gt $current_activation_depth ] then for d in $(seq $max_activation_depth) do for f in $(find /sys/fs/cgroup/ -mindepth $d -maxdepth $d -name cgroup.subtree_control) do echo "+memory" > $f done done fi libprocessgroup/util/util.cpp +40 −1 Original line number Diff line number Diff line Loading @@ -18,15 +18,19 @@ #include <algorithm> #include <iterator> #include <mutex> #include <optional> #include <string_view> #include <mntent.h> #include <unistd.h> #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/parseint.h> #include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> #include <json/reader.h> #include <json/value.h> Loading Loading @@ -174,6 +178,38 @@ static std::optional<std::map<MountDir, MountOpts>> ReadCgroupV1Mounts() { return mounts; } // Keep the override file open to reduce open syscalls, but read it every time. // Note that memcgv2_activation_depth.sh can race with us here. std::optional<unsigned int> ReadMaxActivationDepthMetadataOverride() { static const char* OVERRIDE_FILE_PATH = "/metadata/libprocessgroup/memcg_v2_max_activation_depth"; static int override_fd = open(OVERRIDE_FILE_PATH, O_RDONLY | O_CLOEXEC); static std::mutex mtx; std::unique_lock lock(mtx); if (override_fd < 0) { override_fd = open(OVERRIDE_FILE_PATH, O_RDONLY | O_CLOEXEC); if (override_fd < 0) return std::nullopt; } std::string depth_str; const bool ret = android::base::ReadFdToString(override_fd, &depth_str); lseek(override_fd, 0, SEEK_SET); lock.unlock(); if (!ret) { PLOG(ERROR) << "Failed to read max activation depth override"; return std::nullopt; } unsigned int depth; if (!android::base::ParseUint(android::base::Trim(depth_str), &depth)) { PLOG(ERROR) << "Failed to convert max activation depth override (" << depth_str << ')'; return std::nullopt; } return depth; } } // anonymous namespace Loading Loading @@ -235,7 +271,10 @@ bool ReadDescriptors(CgroupDescriptorMap* descriptors) { bool ActivateControllers(const std::string& path, const CgroupDescriptorMap& descriptors) { for (const auto& [name, descriptor] : descriptors) { const uint32_t flags = descriptor.controller()->flags(); const uint32_t max_activation_depth = descriptor.controller()->max_activation_depth(); uint32_t max_activation_depth; std::optional<unsigned int> metadataMaxDepth = ReadMaxActivationDepthMetadataOverride(); if (metadataMaxDepth) max_activation_depth = *metadataMaxDepth; else max_activation_depth = descriptor.controller()->max_activation_depth(); const unsigned int depth = GetCgroupDepth(descriptor.controller()->path(), path); if (flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION && depth < max_activation_depth) { Loading rootdir/init.rc +9 −0 Original line number Diff line number Diff line Loading @@ -614,6 +614,9 @@ on post-fs mkdir /metadata/staged-install 0770 root system # TODO: Revert after go/android-memcgv2-exp b/386797433 mkdir /metadata/libprocessgroup 0770 root system on late-fs # Ensure that tracefs has the correct permissions. # This does not work correctly if it is called in post-fs. Loading Loading @@ -1318,6 +1321,9 @@ on init && property:ro.debuggable=1 # Multi-Gen LRU Experiment on property:persist.device_config.mglru_native.lru_gen_config=none write /sys/kernel/mm/lru_gen/enabled 0 # Memcg v2 Experiment # TODO: Revert after go/android-memcgv2-exp b/386797433 exec - system system -- /system/bin/memcgv2_activation_depth.sh 0 on property:persist.device_config.mglru_native.lru_gen_config=core write /sys/kernel/mm/lru_gen/enabled 1 on property:persist.device_config.mglru_native.lru_gen_config=core_and_mm_walk Loading @@ -1326,6 +1332,9 @@ on property:persist.device_config.mglru_native.lru_gen_config=core_and_nonleaf_y write /sys/kernel/mm/lru_gen/enabled 5 on property:persist.device_config.mglru_native.lru_gen_config=all write /sys/kernel/mm/lru_gen/enabled 7 # Memcg v2 Experiment # TODO: Revert after go/android-memcgv2-exp b/386797433 exec - system system -- /system/bin/memcgv2_activation_depth.sh 3 # Allow other processes to run `snapshotctl` through `init`. This requires # `set_prop` permission on `snapshotctl_prop`. Loading Loading
init/Android.bp +10 −0 Original line number Diff line number Diff line Loading @@ -287,6 +287,9 @@ cc_binary { "make_f2fs", "mke2fs", "sload_f2fs", // TODO: Revert after go/android-memcgv2-exp b/386797433 "memcgv2_activation_depth", ], } Loading Loading @@ -685,3 +688,10 @@ phony { default: ["init_first_stage"], }), } // TODO: Revert after go/android-memcgv2-exp b/386797433 sh_binary { name: "memcgv2_activation_depth", src: "memcgv2_activation_depth.sh", filename_from_src: true, }
init/memcgv2_activation_depth.sh 0 → 100644 +86 −0 Original line number Diff line number Diff line #!/bin/sh # This script adjusts overrides of the memcg v2 MaxActivationDepth value at runtime. # The override value needs to be accessible starting very early in the Android boot, where aconfig # flags and system properties do not work. A file on /metadata is used instead. # The kernel allows this to be as high as 65535, but our Android hierarchy is never that deep. MAX_ALLOWED_DEPTH=5 # Store overridden MaxActivationDepths here for libprocessgroup to find them OVERRIDE_FILE_PATH="/metadata/libprocessgroup/memcg_v2_max_activation_depth" if [ "$#" -ne 1 ] then echo "Usage: $0 <memcg v2 MaxActivationDepth value>" exit 99 fi max_activation_depth=$1 if [[ $max_activation_depth != +([0-9]) ]] then echo "MaxActivationDepth value must be a positive integer: $max_activation_depth" exit 98 fi if [ $max_activation_depth -lt 0 ] then echo "Negative MaxActivationDepth is invalid: $max_activation_depth" exit 97 fi if [ $max_activation_depth -gt $MAX_ALLOWED_DEPTH ] then echo "MaxActivationDepth is too large: $max_activation_depth" exit 96 fi grep memory /sys/fs/cgroup/cgroup.controllers if [ $? -ne 0 ] then echo "memcg v2 is not available on this device!" exit 95 fi current_activation_depth=$(cat $OVERRIDE_FILE_PATH) if [ $? -ne 0 ] then # Find the default activation depth in the absence of any properties / overrides. # # To do this 100% correctly requires JSON parsing which we don't really want to do here. # We know that this will be called only for Pixel (for a limited-duration experiment), and that # Pixel does not override cgroups.json, therefore we can assume that the system cgroups.json has # only a single MaxActivationDepth entry which corresponds to the v2 memory controller. So we # can just grep for the default value. default_activation_depth=$(grep MaxActivationDepth /system/etc/cgroups.json | tr -dc '0-9') if [ $? -ne 0 -o $default_activation_depth -gt $MAX_ALLOWED_DEPTH ] then # If MaxActivationDepth is not present, libprocessgroup does not limit how deep it will activate default_activation_depth=$MAX_ALLOWED_DEPTH fi current_activation_depth=$default_activation_depth fi # libprocessgroup will pick this up for all future cgroup creations, including on the next boot echo $max_activation_depth > $OVERRIDE_FILE_PATH if [ $max_activation_depth -lt $current_activation_depth ] then # We can deactivate memcgs which are deeper than the new depth value, however that would leave # behind zombie memcgs which would ruin the metrics produced from this device. The only way to # eliminate those zombies is to remove the entire cgroup, which we cannot do without killing # all the contained processes. So the only real option we have is to reboot here. # This should only happen once at the end of the experiment. echo "Rebooting due to decreased memcg v2 MaxActivationDepth" reboot elif [ $max_activation_depth -gt $current_activation_depth ] then for d in $(seq $max_activation_depth) do for f in $(find /sys/fs/cgroup/ -mindepth $d -maxdepth $d -name cgroup.subtree_control) do echo "+memory" > $f done done fi
libprocessgroup/util/util.cpp +40 −1 Original line number Diff line number Diff line Loading @@ -18,15 +18,19 @@ #include <algorithm> #include <iterator> #include <mutex> #include <optional> #include <string_view> #include <mntent.h> #include <unistd.h> #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/parseint.h> #include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> #include <json/reader.h> #include <json/value.h> Loading Loading @@ -174,6 +178,38 @@ static std::optional<std::map<MountDir, MountOpts>> ReadCgroupV1Mounts() { return mounts; } // Keep the override file open to reduce open syscalls, but read it every time. // Note that memcgv2_activation_depth.sh can race with us here. std::optional<unsigned int> ReadMaxActivationDepthMetadataOverride() { static const char* OVERRIDE_FILE_PATH = "/metadata/libprocessgroup/memcg_v2_max_activation_depth"; static int override_fd = open(OVERRIDE_FILE_PATH, O_RDONLY | O_CLOEXEC); static std::mutex mtx; std::unique_lock lock(mtx); if (override_fd < 0) { override_fd = open(OVERRIDE_FILE_PATH, O_RDONLY | O_CLOEXEC); if (override_fd < 0) return std::nullopt; } std::string depth_str; const bool ret = android::base::ReadFdToString(override_fd, &depth_str); lseek(override_fd, 0, SEEK_SET); lock.unlock(); if (!ret) { PLOG(ERROR) << "Failed to read max activation depth override"; return std::nullopt; } unsigned int depth; if (!android::base::ParseUint(android::base::Trim(depth_str), &depth)) { PLOG(ERROR) << "Failed to convert max activation depth override (" << depth_str << ')'; return std::nullopt; } return depth; } } // anonymous namespace Loading Loading @@ -235,7 +271,10 @@ bool ReadDescriptors(CgroupDescriptorMap* descriptors) { bool ActivateControllers(const std::string& path, const CgroupDescriptorMap& descriptors) { for (const auto& [name, descriptor] : descriptors) { const uint32_t flags = descriptor.controller()->flags(); const uint32_t max_activation_depth = descriptor.controller()->max_activation_depth(); uint32_t max_activation_depth; std::optional<unsigned int> metadataMaxDepth = ReadMaxActivationDepthMetadataOverride(); if (metadataMaxDepth) max_activation_depth = *metadataMaxDepth; else max_activation_depth = descriptor.controller()->max_activation_depth(); const unsigned int depth = GetCgroupDepth(descriptor.controller()->path(), path); if (flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION && depth < max_activation_depth) { Loading
rootdir/init.rc +9 −0 Original line number Diff line number Diff line Loading @@ -614,6 +614,9 @@ on post-fs mkdir /metadata/staged-install 0770 root system # TODO: Revert after go/android-memcgv2-exp b/386797433 mkdir /metadata/libprocessgroup 0770 root system on late-fs # Ensure that tracefs has the correct permissions. # This does not work correctly if it is called in post-fs. Loading Loading @@ -1318,6 +1321,9 @@ on init && property:ro.debuggable=1 # Multi-Gen LRU Experiment on property:persist.device_config.mglru_native.lru_gen_config=none write /sys/kernel/mm/lru_gen/enabled 0 # Memcg v2 Experiment # TODO: Revert after go/android-memcgv2-exp b/386797433 exec - system system -- /system/bin/memcgv2_activation_depth.sh 0 on property:persist.device_config.mglru_native.lru_gen_config=core write /sys/kernel/mm/lru_gen/enabled 1 on property:persist.device_config.mglru_native.lru_gen_config=core_and_mm_walk Loading @@ -1326,6 +1332,9 @@ on property:persist.device_config.mglru_native.lru_gen_config=core_and_nonleaf_y write /sys/kernel/mm/lru_gen/enabled 5 on property:persist.device_config.mglru_native.lru_gen_config=all write /sys/kernel/mm/lru_gen/enabled 7 # Memcg v2 Experiment # TODO: Revert after go/android-memcgv2-exp b/386797433 exec - system system -- /system/bin/memcgv2_activation_depth.sh 3 # Allow other processes to run `snapshotctl` through `init`. This requires # `set_prop` permission on `snapshotctl_prop`. Loading