Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

autoscale.py 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.worker.autoscale
  4. ~~~~~~~~~~~~~~~~~~~~~~~
  5. This module implements the internal thread responsible
  6. for growing and shrinking the pool according to the
  7. current autoscale settings.
  8. The autoscale thread is only enabled if :option:`--autoscale`
  9. has been enabled on the command-line.
  10. """
  11. from __future__ import absolute_import
  12. import os
  13. import threading
  14. from time import sleep
  15. from kombu.async.semaphore import DummyLock
  16. from celery import bootsteps
  17. from celery.five import monotonic
  18. from celery.utils.log import get_logger
  19. from celery.utils.threads import bgThread
  20. from . import state
  21. from .components import Pool
  22. __all__ = ['Autoscaler', 'WorkerComponent']
  23. logger = get_logger(__name__)
  24. debug, info, error = logger.debug, logger.info, logger.error
  25. AUTOSCALE_KEEPALIVE = float(os.environ.get('AUTOSCALE_KEEPALIVE', 30))
  26. class WorkerComponent(bootsteps.StartStopStep):
  27. label = 'Autoscaler'
  28. conditional = True
  29. requires = (Pool, )
  30. def __init__(self, w, **kwargs):
  31. self.enabled = w.autoscale
  32. w.autoscaler = None
  33. def create(self, w):
  34. scaler = w.autoscaler = self.instantiate(
  35. w.autoscaler_cls,
  36. w.pool, w.max_concurrency, w.min_concurrency,
  37. worker=w, mutex=DummyLock() if w.use_eventloop else None,
  38. )
  39. return scaler if not w.use_eventloop else None
  40. def register_with_event_loop(self, w, hub):
  41. w.consumer.on_task_message.add(w.autoscaler.maybe_scale)
  42. hub.call_repeatedly(
  43. w.autoscaler.keepalive, w.autoscaler.maybe_scale,
  44. )
  45. class Autoscaler(bgThread):
  46. def __init__(self, pool, max_concurrency,
  47. min_concurrency=0, worker=None,
  48. keepalive=AUTOSCALE_KEEPALIVE, mutex=None):
  49. super(Autoscaler, self).__init__()
  50. self.pool = pool
  51. self.mutex = mutex or threading.Lock()
  52. self.max_concurrency = max_concurrency
  53. self.min_concurrency = min_concurrency
  54. self.keepalive = keepalive
  55. self._last_action = None
  56. self.worker = worker
  57. assert self.keepalive, 'cannot scale down too fast.'
  58. def body(self):
  59. with self.mutex:
  60. self.maybe_scale()
  61. sleep(1.0)
  62. def _maybe_scale(self, req=None):
  63. procs = self.processes
  64. cur = min(self.qty, self.max_concurrency)
  65. if cur > procs:
  66. self.scale_up(cur - procs)
  67. return True
  68. elif cur < procs:
  69. self.scale_down((procs - cur) - self.min_concurrency)
  70. return True
  71. def maybe_scale(self, req=None):
  72. if self._maybe_scale(req):
  73. self.pool.maintain_pool()
  74. def update(self, max=None, min=None):
  75. with self.mutex:
  76. if max is not None:
  77. if max < self.max_concurrency:
  78. self._shrink(self.processes - max)
  79. self.max_concurrency = max
  80. if min is not None:
  81. if min > self.min_concurrency:
  82. self._grow(min - self.min_concurrency)
  83. self.min_concurrency = min
  84. return self.max_concurrency, self.min_concurrency
  85. def force_scale_up(self, n):
  86. with self.mutex:
  87. new = self.processes + n
  88. if new > self.max_concurrency:
  89. self.max_concurrency = new
  90. self.min_concurrency += 1
  91. self._grow(n)
  92. def force_scale_down(self, n):
  93. with self.mutex:
  94. new = self.processes - n
  95. if new < self.min_concurrency:
  96. self.min_concurrency = max(new, 0)
  97. self._shrink(min(n, self.processes))
  98. def scale_up(self, n):
  99. self._last_action = monotonic()
  100. return self._grow(n)
  101. def scale_down(self, n):
  102. if n and self._last_action and (
  103. monotonic() - self._last_action > self.keepalive):
  104. self._last_action = monotonic()
  105. return self._shrink(n)
  106. def _grow(self, n):
  107. info('Scaling up %s processes.', n)
  108. self.pool.grow(n)
  109. self.worker.consumer._update_prefetch_count(n)
  110. def _shrink(self, n):
  111. info('Scaling down %s processes.', n)
  112. try:
  113. self.pool.shrink(n)
  114. except ValueError:
  115. debug("Autoscaler won't scale down: all processes busy.")
  116. except Exception as exc:
  117. error('Autoscaler: scale_down: %r', exc, exc_info=True)
  118. self.worker.consumer._update_prefetch_count(-n)
  119. def info(self):
  120. return {'max': self.max_concurrency,
  121. 'min': self.min_concurrency,
  122. 'current': self.processes,
  123. 'qty': self.qty}
  124. @property
  125. def qty(self):
  126. return len(state.reserved_requests)
  127. @property
  128. def processes(self):
  129. return self.pool.num_processes