From 451dc852aabb4ea771de7635214ba6d2251a71e0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 20 Dec 2025 09:13:49 +0900 Subject: [PATCH] Import Linux cgroup support From https://github.com/moznion/maxprocs-ruby --- LICENSE-maxprocs.txt | 44 ++++++++++++++++++++++++++++ ext/etc/lib/etc.rb | 69 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 LICENSE-maxprocs.txt create mode 100644 ext/etc/lib/etc.rb diff --git a/LICENSE-maxprocs.txt b/LICENSE-maxprocs.txt new file mode 100644 index 0000000..e9c3e33 --- /dev/null +++ b/LICENSE-maxprocs.txt @@ -0,0 +1,44 @@ +MIT License + +Copyright (c) 2025 Taiki Kawakami (a.k.a moznion) https://moznion.net + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-- + +Copyright (c) 2017 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/ext/etc/lib/etc.rb b/ext/etc/lib/etc.rb new file mode 100644 index 0000000..08a8632 --- /dev/null +++ b/ext/etc/lib/etc.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +# MIT License +# +# Copyright (c) 2025 Taiki Kawakami (a.k.a moznion) https://moznion.net +# https://github.com/moznion/maxprocs-ruby/ + +require "etc.so" + +return unless RUBY_PLATFORM.include?("linux") + +module Etc + # Maxprocs detects CPU quota from Linux cgroups and returns the appropriate + # number of processors for container environments. + module Maxprocs + CGROUP_FILE_PATH = "/proc/self/cgroup" + CGROUP_V1_QUOTA_PATH = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us" + CGROUP_V1_PERIOD_PATH = "/sys/fs/cgroup/cpu/cpu.cfs_period_us" + CGROUP_V2_CONTROLLERS_PATH = "/sys/fs/cgroup/cgroup.controllers" + CGROUP_V2_CPU_MAX_PATH = "/sys/fs/cgroup/cpu.max" + + def nprocessors + read_quota or super + end + + private + + def read_quota + return unless File.exist?(CGROUP_FILE_PATH) + + # Check for cgroup v2 first + if File.exist?(CGROUP_V2_CONTROLLERS_PATH) + read_quota_v2 + elsif File.exist?(CGROUP_V1_QUOTA_PATH) + read_quota_v1 + end + rescue Errno::ENOENT, Errno::EACCES, Errno::EINVAL + # File doesn't exist, permission denied, or invalid - fallback to unlimited + end + + def read_quota_v1 + quota = Integer(File.read(CGROUP_V1_QUOTA_PATH)) + return nil if quota == -1 # -1 means unlimited + + period = Integer(File.read(CGROUP_V1_PERIOD_PATH)) + return nil if period <= 0 + + quota / period + end + + def read_quota_v2 + max_str, period_str = File.read(CGROUP_V2_CPU_MAX_PATH).split(3) + + return nil if max_str == "max" # "max" means unlimited + return nil if period_str.nil? + + max = Integer(max_str) + period = Integer(period_str) + return nil if period <= 0 + + max / period + end + end + + prepend Maxprocs + class << self + prepend Maxprocs + end +end