From 91f3997ec09d6502f88471f384aad2fe3ddcecb9 Mon Sep 17 00:00:00 2001 From: "Joshua M. Clulow" Date: Mon, 28 Oct 2019 09:19:48 -0700 Subject: [PATCH] runtime: make NumCPU respect zone CPU cap on illumos On illumos systems, check for the "zone.cpu-cap" resource control when determining how many usable CPUs are available. If the resource control is not set, or we are unable to read it, ignore the failure and return the value we used to return; i.e., the CPU count from sysconf(_SC_NPROCESSORS_ONLN). Fixes golang/go#35199 Change-Id: Ic8a408f84cd140d544d128f1281baad527fb5e35 Reviewed-on: https://go-review.googlesource.com/c/go/+/203758 Run-TryBot: Brad Fitzpatrick Reviewed-by: Brad Fitzpatrick --- src/runtime/defs_illumos_amd64.go | 14 ++++ src/runtime/os3_solaris.go | 8 -- src/runtime/os_illumos.go | 132 ++++++++++++++++++++++++++++++ src/runtime/os_only_solaris.go | 18 ++++ 4 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 src/runtime/defs_illumos_amd64.go create mode 100644 src/runtime/os_illumos.go create mode 100644 src/runtime/os_only_solaris.go diff --git a/src/runtime/defs_illumos_amd64.go b/src/runtime/defs_illumos_amd64.go new file mode 100644 index 0000000000..9c5413bae3 --- /dev/null +++ b/src/runtime/defs_illumos_amd64.go @@ -0,0 +1,14 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package runtime + +const ( + _RCTL_LOCAL_DENY = 0x2 + + _RCTL_LOCAL_MAXIMAL = 0x80000000 + + _RCTL_FIRST = 0x0 + _RCTL_NEXT = 0x1 +) diff --git a/src/runtime/os3_solaris.go b/src/runtime/os3_solaris.go index 563e981d0f..373c682f05 100644 --- a/src/runtime/os3_solaris.go +++ b/src/runtime/os3_solaris.go @@ -119,14 +119,6 @@ var ( var sigset_all = sigset{[4]uint32{^uint32(0), ^uint32(0), ^uint32(0), ^uint32(0)}} -func getncpu() int32 { - n := int32(sysconf(__SC_NPROCESSORS_ONLN)) - if n < 1 { - return 1 - } - return n -} - func getPageSize() uintptr { n := int32(sysconf(__SC_PAGESIZE)) if n <= 0 { diff --git a/src/runtime/os_illumos.go b/src/runtime/os_illumos.go new file mode 100644 index 0000000000..c3c3e4e6d5 --- /dev/null +++ b/src/runtime/os_illumos.go @@ -0,0 +1,132 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package runtime + +import ( + "unsafe" +) + +//go:cgo_import_dynamic libc_getrctl getrctl "libc.so" +//go:cgo_import_dynamic libc_rctlblk_get_local_action rctlblk_get_local_action "libc.so" +//go:cgo_import_dynamic libc_rctlblk_get_local_flags rctlblk_get_local_flags "libc.so" +//go:cgo_import_dynamic libc_rctlblk_get_value rctlblk_get_value "libc.so" +//go:cgo_import_dynamic libc_rctlblk_size rctlblk_size "libc.so" + +//go:linkname libc_getrctl libc_getrctl +//go:linkname libc_rctlblk_get_local_action libc_rctlblk_get_local_action +//go:linkname libc_rctlblk_get_local_flags libc_rctlblk_get_local_flags +//go:linkname libc_rctlblk_get_value libc_rctlblk_get_value +//go:linkname libc_rctlblk_size libc_rctlblk_size + +var ( + libc_getrctl, + libc_rctlblk_get_local_action, + libc_rctlblk_get_local_flags, + libc_rctlblk_get_value, + libc_rctlblk_size libcFunc +) + +// Return the minimum value seen for the zone CPU cap, or 0 if no cap is +// detected. +func getcpucap() uint64 { + // The resource control block is an opaque object whose size is only + // known to libc. In practice, given the contents, it is unlikely to + // grow beyond 8KB so we'll use a static buffer of that size here. + const rblkmaxsize = 8 * 1024 + if rctlblk_size() > rblkmaxsize { + return 0 + } + + // The "zone.cpu-cap" resource control, as described in + // resource_controls(5), "sets a limit on the amount of CPU time that + // can be used by a zone. The unit used is the percentage of a single + // CPU that can be used by all user threads in a zone, expressed as an + // integer." A C string of the name must be passed to getrctl(2). + name := []byte("zone.cpu-cap\x00") + + // To iterate over the list of values for a particular resource + // control, we need two blocks: one for the previously read value and + // one for the next value. + var rblk0 [rblkmaxsize]byte + var rblk1 [rblkmaxsize]byte + rblk := &rblk0[0] + rblkprev := &rblk1[0] + + var flag uint32 = _RCTL_FIRST + var capval uint64 = 0 + + for { + if getrctl(unsafe.Pointer(&name[0]), unsafe.Pointer(rblkprev), unsafe.Pointer(rblk), flag) != 0 { + // The end of the sequence is reported as an ENOENT + // failure, but determining the CPU cap is not critical + // here. We'll treat any failure as if it were the end + // of sequence. + break + } + + lflags := rctlblk_get_local_flags(unsafe.Pointer(rblk)) + action := rctlblk_get_local_action(unsafe.Pointer(rblk)) + if (lflags&_RCTL_LOCAL_MAXIMAL) == 0 && action == _RCTL_LOCAL_DENY { + // This is a finite (not maximal) value representing a + // cap (deny) action. + v := rctlblk_get_value(unsafe.Pointer(rblk)) + if capval == 0 || capval > v { + capval = v + } + } + + // Swap the blocks around so that we can fetch the next value + t := rblk + rblk = rblkprev + rblkprev = t + flag = _RCTL_NEXT + } + + return capval +} + +func getncpu() int32 { + n := int32(sysconf(__SC_NPROCESSORS_ONLN)) + if n < 1 { + return 1 + } + + if cents := int32(getcpucap()); cents > 0 { + // Convert from a percentage of CPUs to a number of CPUs, + // rounding up to make use of a fractional CPU + // e.g., 336% becomes 4 CPUs + ncap := (cents + 99) / 100 + if ncap < n { + return ncap + } + } + + return n +} + +//go:nosplit +func getrctl(controlname, oldbuf, newbuf unsafe.Pointer, flags uint32) uintptr { + return sysvicall4(&libc_getrctl, uintptr(controlname), uintptr(oldbuf), uintptr(newbuf), uintptr(flags)) +} + +//go:nosplit +func rctlblk_get_local_action(buf unsafe.Pointer) uintptr { + return sysvicall2(&libc_rctlblk_get_local_action, uintptr(buf), uintptr(0)) +} + +//go:nosplit +func rctlblk_get_local_flags(buf unsafe.Pointer) uintptr { + return sysvicall1(&libc_rctlblk_get_local_flags, uintptr(buf)) +} + +//go:nosplit +func rctlblk_get_value(buf unsafe.Pointer) uint64 { + return uint64(sysvicall1(&libc_rctlblk_get_value, uintptr(buf))) +} + +//go:nosplit +func rctlblk_size() uintptr { + return sysvicall0(&libc_rctlblk_size) +} diff --git a/src/runtime/os_only_solaris.go b/src/runtime/os_only_solaris.go new file mode 100644 index 0000000000..e2f5409354 --- /dev/null +++ b/src/runtime/os_only_solaris.go @@ -0,0 +1,18 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Solaris code that doesn't also apply to illumos. + +// +build !illumos + +package runtime + +func getncpu() int32 { + n := int32(sysconf(__SC_NPROCESSORS_ONLN)) + if n < 1 { + return 1 + } + + return n +} -- 2.50.0