assemblyline-core 4.7.5.dev52__tar.gz → 4.7.5.dev56__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/PKG-INFO +1 -1
  2. assemblyline_core-4.7.5.dev56/assemblyline_core/VERSION +1 -0
  3. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/__init__.py +1 -0
  4. assemblyline_core-4.7.5.dev56/assemblyline_core/scaler/base.py +327 -0
  5. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/scaler/collection.py +4 -3
  6. assemblyline_core-4.7.5.dev52/assemblyline_core/scaler/controllers/kubernetes_ctl.py → assemblyline_core-4.7.5.dev56/assemblyline_core/scaler/controllers/kubernetes.py +174 -412
  7. assemblyline_core-4.7.5.dev56/assemblyline_core/scaler/controllers/kubernetes_ctl.py +391 -0
  8. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/scaler/scaler_server.py +46 -323
  9. assemblyline_core-4.7.5.dev56/assemblyline_core/scaler_hpa/kubernetes.py +381 -0
  10. assemblyline_core-4.7.5.dev56/assemblyline_core/scaler_hpa/run_scaler.py +7 -0
  11. assemblyline_core-4.7.5.dev56/assemblyline_core/scaler_hpa/scaler_server.py +522 -0
  12. assemblyline_core-4.7.5.dev56/assemblyline_core/workflow/__init__.py +0 -0
  13. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core.egg-info/PKG-INFO +1 -1
  14. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core.egg-info/SOURCES.txt +6 -2
  15. assemblyline_core-4.7.5.dev52/assemblyline_core/VERSION +0 -1
  16. assemblyline_core-4.7.5.dev52/assemblyline_core/tasking_client.py +0 -537
  17. assemblyline_core-4.7.5.dev52/test/test_tasking_client.py +0 -77
  18. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/LICENCE.md +0 -0
  19. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/README.md +0 -0
  20. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/alerter/__init__.py +0 -0
  21. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/alerter/processing.py +0 -0
  22. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/alerter/run_alerter.py +0 -0
  23. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/archiver/__init__.py +0 -0
  24. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/archiver/run_archiver.py +0 -0
  25. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/badlist_client.py +0 -0
  26. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/dispatching/__init__.py +0 -0
  27. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/dispatching/client.py +0 -0
  28. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/dispatching/dispatcher.py +0 -0
  29. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/dispatching/schedules.py +0 -0
  30. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/expiry/__init__.py +0 -0
  31. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/expiry/run_expiry.py +0 -0
  32. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/ingester/__init__.py +0 -0
  33. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/ingester/constants.py +0 -0
  34. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/ingester/ingester.py +0 -0
  35. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/metrics/__init__.py +0 -0
  36. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/metrics/es_metrics.py +0 -0
  37. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/metrics/heartbeat_formatter.py +0 -0
  38. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/metrics/helper.py +0 -0
  39. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/metrics/metrics_server.py +0 -0
  40. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/metrics/run_heartbeat_manager.py +0 -0
  41. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/metrics/run_metrics_aggregator.py +0 -0
  42. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/metrics/run_statistics_aggregator.py +0 -0
  43. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/replay/__init__.py +0 -0
  44. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/replay/client.py +0 -0
  45. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/replay/creator/__init__.py +0 -0
  46. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/replay/creator/run.py +0 -0
  47. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/replay/creator/run_worker.py +0 -0
  48. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/replay/loader/__init__.py +0 -0
  49. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/replay/loader/run.py +0 -0
  50. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/replay/loader/run_worker.py +0 -0
  51. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/replay/replay.py +0 -0
  52. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/safelist_client.py +0 -0
  53. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/scaler/__init__.py +0 -0
  54. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/scaler/controllers/__init__.py +0 -0
  55. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/scaler/controllers/docker_ctl.py +0 -0
  56. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/scaler/controllers/interface.py +0 -0
  57. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/scaler/run_scaler.py +0 -0
  58. {assemblyline_core-4.7.5.dev52/assemblyline_core/updater → assemblyline_core-4.7.5.dev56/assemblyline_core/scaler_hpa}/__init__.py +0 -0
  59. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/server_base.py +0 -0
  60. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/signature_client.py +0 -0
  61. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/submission_client.py +0 -0
  62. {assemblyline_core-4.7.5.dev52/assemblyline_core/vacuum → assemblyline_core-4.7.5.dev56/assemblyline_core/updater}/__init__.py +0 -0
  63. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/updater/helper.py +0 -0
  64. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/updater/run_updater.py +0 -0
  65. {assemblyline_core-4.7.5.dev52/assemblyline_core/workflow → assemblyline_core-4.7.5.dev56/assemblyline_core/vacuum}/__init__.py +0 -0
  66. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/vacuum/crawler.py +0 -0
  67. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/vacuum/department_map.py +0 -0
  68. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/vacuum/safelist.py +0 -0
  69. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/vacuum/stream_map.py +0 -0
  70. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/vacuum/worker.py +0 -0
  71. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core/workflow/run_workflow.py +0 -0
  72. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core.egg-info/dependency_links.txt +0 -0
  73. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core.egg-info/requires.txt +0 -0
  74. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/assemblyline_core.egg-info/top_level.txt +0 -0
  75. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/setup.cfg +0 -0
  76. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/setup.py +0 -0
  77. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/test/test_alerter.py +0 -0
  78. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/test/test_badlist_client.py +0 -0
  79. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/test/test_expiry.py +0 -0
  80. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/test/test_replay.py +0 -0
  81. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/test/test_safelist_client.py +0 -0
  82. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/test/test_scaler.py +0 -0
  83. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/test/test_scheduler.py +0 -0
  84. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/test/test_signature_client.py +0 -0
  85. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/test/test_updater_helper.py +0 -0
  86. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/test/test_vacuum.py +0 -0
  87. {assemblyline_core-4.7.5.dev52 → assemblyline_core-4.7.5.dev56}/test/test_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: assemblyline-core
3
- Version: 4.7.5.dev52
3
+ Version: 4.7.5.dev56
4
4
  Summary: Assemblyline 4 - Core components
5
5
  Home-page: https://github.com/CybercentreCanada/assemblyline-core/
6
6
  Author: CCCS Assemblyline development team
@@ -0,0 +1 @@
1
+ 4.7.5.dev56
@@ -1,5 +1,6 @@
1
1
  PAUSABLE_COMPONENTS = ['ingester', 'dispatcher']
2
2
 
3
+
3
4
  def normalize_hashlist_item(tag_type: str, tag_value: str) -> str:
4
5
  # Normalize tag data pertaining to domains or URIs
5
6
  if tag_type.endswith('.domain'):
@@ -0,0 +1,327 @@
1
+ import os
2
+ import platform
3
+ import threading
4
+ import concurrent.futures
5
+ import functools
6
+ from typing import Optional
7
+ from contextlib import contextmanager
8
+
9
+ import elasticapm
10
+
11
+ from assemblyline.common.constants import (
12
+ SCALER_TIMEOUT_QUEUE,
13
+ SERVICE_STATE_HASH,
14
+ ServiceStatus,
15
+ )
16
+ from assemblyline.common.forge import get_apm_client
17
+ from assemblyline.remote.datatypes.exporting_counter import export_metrics_once
18
+ from assemblyline.odm.messages.changes import Operation, ServiceChange
19
+ from assemblyline.odm.messages.scaler_heartbeat import Metrics
20
+ from assemblyline.odm.messages.scaler_status_heartbeat import Status
21
+ from assemblyline.remote.datatypes.events import EventSender, EventWatcher
22
+ from assemblyline.remote.datatypes.hash import ExpiringHash, Hash
23
+ from assemblyline.remote.datatypes.queues.named import NamedQueue
24
+ from assemblyline_core.scaler.controllers.interface import ControllerInterface, ServiceControlError
25
+ from assemblyline_core.server_base import ServiceStage, ThreadedCoreBase
26
+
27
+
28
+ # How often (in seconds) to download new service data, try to scale managed services,
29
+ # and download more metrics data respectively
30
+ SERVICE_SYNC_INTERVAL = 60 * 30 # Every half hour
31
+ HEARTBEAT_INTERVAL = 5
32
+
33
+ CONTAINER_EVENTS_LOG_INTERVAL = 2
34
+ HOSTNAME = os.getenv('HOSTNAME', platform.node())
35
+ APM_SPAN_TYPE = 'scaler'
36
+
37
+
38
+ @contextmanager
39
+ def apm_span(client: Optional[elasticapm.Client], span_name: str):
40
+ try:
41
+ if client:
42
+ client.begin_transaction(APM_SPAN_TYPE)
43
+ yield None
44
+ if client:
45
+ client.end_transaction(span_name, 'success')
46
+ except Exception:
47
+ if client:
48
+ client.end_transaction(span_name, 'exception')
49
+ raise
50
+
51
+
52
+ class ProfileBase:
53
+ def __init__(self, name: str, target_queue_length: int) -> None:
54
+ self.name = name
55
+ # How long does a backlog need to be before we are concerned
56
+ self.target_queue_length = target_queue_length
57
+
58
+ # Number of instances kubernetes expects to be running
59
+ self.target_instances: int = 0
60
+
61
+ # The number of instances running based on feedback metrics
62
+ self.running_instances: int = 0
63
+
64
+ # Last reported length of the service task queue
65
+ self.queue_length: int = 0
66
+
67
+
68
+ class ScalerBase(ThreadedCoreBase):
69
+ """Common scaler methods."""
70
+ def __init__(self, config=None, datastore=None, redis=None, redis_persist=None) -> None:
71
+ super().__init__('assemblyline.scaler', config=config, datastore=datastore,
72
+ redis=redis, redis_persist=redis_persist)
73
+
74
+ self.scaler_timeout_queue: NamedQueue[dict] = NamedQueue(SCALER_TIMEOUT_QUEUE, host=self.redis_persist)
75
+ self.status_table = ExpiringHash(SERVICE_STATE_HASH, host=self.redis, ttl=30*60)
76
+ self.service_event_sender: EventSender[dict] = EventSender('changes.services', host=self.redis)
77
+ self.service_watcher_wakeup = threading.Event()
78
+ self.service_change_watcher = EventWatcher(self.redis, deserializer=ServiceChange.deserialize)
79
+ self.service_change_watcher.register('changes.services.*', self._handle_service_change_event)
80
+
81
+ self.controller: ControllerInterface
82
+
83
+ # Information about services
84
+ self.profiles: dict[str, ProfileBase] = {}
85
+ self.profiles_lock = threading.RLock()
86
+
87
+ # Load the APM connection if any
88
+ self.apm_client = None
89
+ if self.config.core.metrics.apm_server.server_url:
90
+ elasticapm.instrument()
91
+ self.apm_client = get_apm_client("scaler")
92
+
93
+ def try_run(self):
94
+ self.service_change_watcher.start()
95
+ self.maintain_threads({
96
+ 'Log Container Events': self.log_container_events,
97
+ 'Process Timeouts': self.process_timeouts,
98
+ 'Service Configuration Sync': self.sync_services,
99
+ 'Service Adjuster': self.update_scaling,
100
+ 'Import Metrics': self.sync_metrics,
101
+ 'Export Metrics': self.export_metrics,
102
+ })
103
+
104
+ def stop(self):
105
+ super().stop()
106
+ self.service_change_watcher.stop()
107
+ self.service_watcher_wakeup.set()
108
+ self.controller.stop()
109
+
110
+ def _handle_service_change_event(self, data: Optional[ServiceChange]):
111
+ if data is None:
112
+ self.service_watcher_wakeup.set()
113
+ else:
114
+ if data.operation == Operation.Removed:
115
+ self.log.info(f'Service appears to be deleted, removing {data.name}')
116
+ stage = self.get_service_stage(data.name)
117
+ self.stop_service(data.name, stage)
118
+ elif data.operation == Operation.Incompatible:
119
+ return
120
+ else:
121
+ service = self.datastore.get_service_with_delta(data.name)
122
+ if not service:
123
+ self.log.warning(f'Received change event for non-existent service: {data.name}. Ignoring..')
124
+ return
125
+ self._sync_service(service)
126
+
127
+ def sync_services(self) -> None:
128
+ last_synced_profiles = None
129
+ while self.running:
130
+ with apm_span(self.apm_client, 'sync_services'):
131
+ self.log.info('Synchronizing service configuration')
132
+ with self.profiles_lock:
133
+ current_services = set(self.profiles.keys())
134
+
135
+ # Check to see if the service is progressing since it's last sync
136
+ if last_synced_profiles:
137
+ for service, profile in self.profiles.items():
138
+ # Assume there was no backlog initially if the service is new since last sync
139
+ last_synced_backlog = 0
140
+ if last_synced_profiles.get(service):
141
+ last_synced_backlog = last_synced_profiles[service].queue_length
142
+
143
+ # Check to see if the backlog has increased and if the service has been running since
144
+ backlog = profile.queue_length
145
+ trying_to_start = profile.running_instances == 0 and profile.target_instances > 0
146
+ if backlog and backlog >= last_synced_backlog and trying_to_start:
147
+ # Restart the service in an attempt to resolve intermittent issues with container/pod
148
+ self.controller.restart(profile)
149
+
150
+ # Update the last synced profiles for next time
151
+ last_synced_profiles = self.profiles
152
+
153
+ discovered_services: list[str] = []
154
+
155
+ # Get all the service data
156
+ for service in self.datastore.list_all_services(full=True):
157
+ self._sync_service(service)
158
+ discovered_services.append(service.name)
159
+
160
+ # Find any services we have running, that are no longer in the database and remove them
161
+ for stray_service in current_services - set(discovered_services):
162
+ self.log.info('Service appears to be deleted, removing stray %s', stray_service)
163
+ stage = self.get_service_stage(stray_service)
164
+ self.stop_service(stray_service, stage)
165
+ self.log.info('Finish synchronizing service configuration')
166
+
167
+ # Wait for the interval or until someone wakes us up
168
+ self.service_watcher_wakeup.wait(timeout=SERVICE_SYNC_INTERVAL)
169
+ self.service_watcher_wakeup.clear()
170
+
171
+ def _sync_service(self, service):
172
+ raise NotImplementedError()
173
+
174
+ @elasticapm.capture_span(span_type=APM_SPAN_TYPE)
175
+ def stop_service(self, name: str, current_stage: ServiceStage):
176
+ if current_stage != ServiceStage.Off:
177
+ # Disable this service's dependencies
178
+ self.controller.stop_containers(labels={
179
+ 'dependency_for': name
180
+ })
181
+
182
+ # Clear related dependency caching from Redis
183
+ Hash(f'service-updates-{name}', self.redis_persist).delete()
184
+
185
+ # Mark this service as not running in the shared record
186
+ self._service_stage_hash.set(name, ServiceStage.Off)
187
+
188
+ # Stop any running disabled services
189
+ if name in self.profiles or self.controller.get_target(name) > 0:
190
+ self.log.info(f'Removing {name} from scaling')
191
+ with self.profiles_lock:
192
+ self.profiles.pop(name, None)
193
+ self.controller.set_target(name, 0)
194
+
195
+ def log_crashes(self, fn):
196
+ @functools.wraps(fn)
197
+ def with_logs(*args, **kwargs):
198
+ # noinspection PyBroadException
199
+ try:
200
+ fn(*args, **kwargs)
201
+ except ServiceControlError as error:
202
+ self.log.exception(f"Error while managing service: {error.service_name}")
203
+ except Exception:
204
+ self.log.exception(f'Crash in scaler: {fn.__name__}')
205
+ return with_logs
206
+
207
+ def _timeout_kill(self, service, container):
208
+ with apm_span(self.apm_client, 'timeout_kill'):
209
+ self.controller.stop_container(service, container)
210
+ self.status_table.pop(container)
211
+
212
+ def process_timeouts(self):
213
+ with concurrent.futures.ThreadPoolExecutor(10) as pool:
214
+ futures = []
215
+
216
+ while self.running:
217
+ message = self.scaler_timeout_queue.pop(blocking=True, timeout=1)
218
+ if not message:
219
+ continue
220
+
221
+ with apm_span(self.apm_client, 'process_timeouts'):
222
+ # Process new messages
223
+ self.log.info(f"Killing service container: {message['container']} running: {message['service']}")
224
+ futures.append(pool.submit(self._timeout_kill, message['service'], message['container']))
225
+
226
+ # Process finished
227
+ finished = [_f for _f in futures if _f.done()]
228
+ futures = [_f for _f in futures if _f not in finished]
229
+ for _f in finished:
230
+ exception = _f.exception()
231
+ if exception is not None:
232
+ self.log.error("Exception trying to stop timed out service container: %s", exception)
233
+
234
+ def get_cpu_overallocation(self) -> float:
235
+ node_limit = self.config.core.scaler.overallocation_node_limit
236
+ if node_limit is not None and node_limit <= self.controller.node_count:
237
+ return 1
238
+ return self.config.core.scaler.cpu_overallocation
239
+
240
+ def get_memory_overallocation(self) -> float:
241
+ node_limit = self.config.core.scaler.overallocation_node_limit
242
+ if node_limit is not None and node_limit <= self.controller.node_count:
243
+ return 1
244
+ return self.config.core.scaler.memory_overallocation
245
+
246
+ def get_cpu_info(self, overallocation: bool) -> tuple[float, float]:
247
+ # Get the raw used resource numbers
248
+ free_cpu, total_cpu = self.controller.cpu_info()
249
+
250
+ # Recalculate the amount of free resources expanding the total quantity by the overallocation
251
+ if overallocation:
252
+ used_cpu = total_cpu - free_cpu
253
+ free_cpu = total_cpu * self.get_cpu_overallocation() - used_cpu
254
+
255
+ # Include the service containers not counted in the raw numbers because they are pending
256
+ for name, pending in self.controller.get_unavailable().items():
257
+ profile = self.profiles.get(name)
258
+ if not profile or not pending:
259
+ continue
260
+
261
+ free_cpu = free_cpu - profile.container_config.cpu_cores * pending
262
+
263
+ return (free_cpu, total_cpu)
264
+
265
+ def get_memory_info(self, overallocation: bool) -> tuple[float, float]:
266
+ # Get the raw used resource numbers
267
+ free_memory, total_memory = self.controller.memory_info()
268
+
269
+ # Recalculate the amount of free resources expanding the total quantity by the overallocation
270
+ if overallocation:
271
+ used_memory = total_memory - free_memory
272
+ free_memory = total_memory * self.get_memory_overallocation() - used_memory
273
+
274
+ # Include the service containers not counted in the raw numbers because they are pending
275
+ for name, pending in self.controller.get_unavailable().items():
276
+ profile = self.profiles.get(name)
277
+ if not profile or not pending:
278
+ continue
279
+
280
+ free_memory = free_memory - profile.container_config.ram_mb * pending
281
+
282
+ return (free_memory, total_memory)
283
+
284
+ def log_container_events(self):
285
+ """The service status table may have references to containers that have crashed. Try to remove them all."""
286
+ while self.sleep(CONTAINER_EVENTS_LOG_INTERVAL):
287
+ with apm_span(self.apm_client, 'log_container_events'):
288
+ for message in self.controller.new_events():
289
+ self.log.warning("Container Event :: " + message)
290
+
291
+ def export_metrics(self):
292
+ while self.sleep(self.config.logging.export_interval):
293
+ with apm_span(self.apm_client, 'export_metrics'):
294
+ service_metrics = {}
295
+ with self.profiles_lock:
296
+ for service_name, profile in self.profiles.items():
297
+ service_metrics[service_name] = {
298
+ 'running': profile.running_instances,
299
+ 'target': profile.target_instances,
300
+ 'minimum': profile.min_instances,
301
+ 'maximum': profile.instance_limit,
302
+ 'dynamic_maximum': profile.max_instances,
303
+ 'queue': profile.queue_length,
304
+ 'duty_cycle': profile.duty_cycle,
305
+ 'pressure': profile.pressure,
306
+ }
307
+
308
+ for service_name, metrics in service_metrics.items():
309
+ export_metrics_once(service_name, Status, metrics, host=HOSTNAME,
310
+ counter_type='scaler_status', config=self.config, redis=self.redis)
311
+
312
+ memory, memory_total = self.get_memory_info(overallocation=False)
313
+ cpu, cpu_total = self.get_cpu_info(overallocation=False)
314
+ metrics = {
315
+ 'memory_total': memory_total,
316
+ 'cpu_total': cpu_total,
317
+ 'memory_free': memory,
318
+ 'cpu_free': cpu
319
+ }
320
+ export_metrics_once('scaler', Metrics, metrics, host=HOSTNAME,
321
+ counter_type='scaler', config=self.config, redis=self.redis)
322
+
323
+ def update_scaling(self):
324
+ raise NotImplementedError()
325
+
326
+ def sync_metrics(self):
327
+ raise NotImplementedError()
@@ -12,10 +12,9 @@ Row = namedtuple('Row', ['timestamp', 'busy', 'throughput'])
12
12
 
13
13
 
14
14
  class Collection:
15
- def __init__(self, period, ttl=None):
15
+ """A buffer for metrics data from multiple instances of multiple services."""
16
+ def __init__(self, period, ttl=None) -> None:
16
17
  """
17
- A buffer for metrics data from multiple instances of multiple services.
18
-
19
18
  :param period: Expected seconds between updates
20
19
  :param ttl: Seconds before a message is dropped from the buffer
21
20
  """
@@ -24,6 +23,7 @@ class Collection:
24
23
  self.services: Dict[str, Dict[str, Row]] = {}
25
24
 
26
25
  def update(self, service, host, busy_seconds, throughput):
26
+ """Store a new record for a given host."""
27
27
  # Load the sequence of data points that
28
28
  try:
29
29
  hosts = self.services[service]
@@ -34,6 +34,7 @@ class Collection:
34
34
  hosts[host] = Row(time.time(), busy_seconds, throughput)
35
35
 
36
36
  def read(self, service):
37
+ """Read how many instances of a service are known and how busy they are."""
37
38
  now = time.time()
38
39
 
39
40
  # Load the last messages from this service