maxprocs.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. // Copyright (c) 2017 Uber Technologies, Inc.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. //
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. // THE SOFTWARE.
  20. // Package maxprocs lets Go programs easily configure runtime.GOMAXPROCS to
  21. // match the configured Linux CPU quota. Unlike the top-level automaxprocs
  22. // package, it lets the caller configure logging and handle errors.
  23. package maxprocs // import "go.uber.org/automaxprocs/maxprocs"
  24. import (
  25. "os"
  26. "runtime"
  27. iruntime "go.uber.org/automaxprocs/internal/runtime"
  28. )
  29. const _maxProcsKey = "GOMAXPROCS"
  30. func currentMaxProcs() int {
  31. return runtime.GOMAXPROCS(0)
  32. }
  33. type config struct {
  34. printf func(string, ...interface{})
  35. procs func(int) (int, iruntime.CPUQuotaStatus, error)
  36. minGOMAXPROCS int
  37. }
  38. func (c *config) log(fmt string, args ...interface{}) {
  39. if c.printf != nil {
  40. c.printf(fmt, args...)
  41. }
  42. }
  43. // An Option alters the behavior of Set.
  44. type Option interface {
  45. apply(*config)
  46. }
  47. // Logger uses the supplied printf implementation for log output. By default,
  48. // Set doesn't log anything.
  49. func Logger(printf func(string, ...interface{})) Option {
  50. return optionFunc(func(cfg *config) {
  51. cfg.printf = printf
  52. })
  53. }
  54. // Min sets the minimum GOMAXPROCS value that will be used.
  55. // Any value below 1 is ignored.
  56. func Min(n int) Option {
  57. return optionFunc(func(cfg *config) {
  58. if n >= 1 {
  59. cfg.minGOMAXPROCS = n
  60. }
  61. })
  62. }
  63. type optionFunc func(*config)
  64. func (of optionFunc) apply(cfg *config) { of(cfg) }
  65. // Set GOMAXPROCS to match the Linux container CPU quota (if any), returning
  66. // any error encountered and an undo function.
  67. //
  68. // Set is a no-op on non-Linux systems and in Linux environments without a
  69. // configured CPU quota.
  70. func Set(opts ...Option) (func(), error) {
  71. cfg := &config{
  72. procs: iruntime.CPUQuotaToGOMAXPROCS,
  73. minGOMAXPROCS: 1,
  74. }
  75. for _, o := range opts {
  76. o.apply(cfg)
  77. }
  78. undoNoop := func() {
  79. cfg.log("maxprocs: No GOMAXPROCS change to reset")
  80. }
  81. // Honor the GOMAXPROCS environment variable if present. Otherwise, amend
  82. // `runtime.GOMAXPROCS()` with the current process' CPU quota if the OS is
  83. // Linux, and guarantee a minimum value of 1. The minimum guaranteed value
  84. // can be overridden using `maxprocs.Min()`.
  85. if max, exists := os.LookupEnv(_maxProcsKey); exists {
  86. cfg.log("maxprocs: Honoring GOMAXPROCS=%q as set in environment", max)
  87. return undoNoop, nil
  88. }
  89. maxProcs, status, err := cfg.procs(cfg.minGOMAXPROCS)
  90. if err != nil {
  91. return undoNoop, err
  92. }
  93. if status == iruntime.CPUQuotaUndefined {
  94. cfg.log("maxprocs: Leaving GOMAXPROCS=%v: CPU quota undefined", currentMaxProcs())
  95. return undoNoop, nil
  96. }
  97. prev := currentMaxProcs()
  98. undo := func() {
  99. cfg.log("maxprocs: Resetting GOMAXPROCS to %v", prev)
  100. runtime.GOMAXPROCS(prev)
  101. }
  102. switch status {
  103. case iruntime.CPUQuotaMinUsed:
  104. cfg.log("maxprocs: Updating GOMAXPROCS=%v: using minimum allowed GOMAXPROCS", maxProcs)
  105. case iruntime.CPUQuotaUsed:
  106. cfg.log("maxprocs: Updating GOMAXPROCS=%v: determined from CPU quota", maxProcs)
  107. }
  108. runtime.GOMAXPROCS(maxProcs)
  109. return undo, nil
  110. }