Merge lp:~james-page/charm-helpers/worker-multipler-wsgi into lp:charm-helpers

Proposed by James Page
Status: Merged
Merged at revision: 738
Proposed branch: lp:~james-page/charm-helpers/worker-multipler-wsgi
Merge into: lp:charm-helpers
Diff against target: 263 lines (+98/-88)
2 files modified
charmhelpers/contrib/openstack/context.py (+43/-25)
tests/contrib/openstack/test_os_contexts.py (+55/-63)
To merge this branch: bzr merge lp:~james-page/charm-helpers/worker-multipler-wsgi
Reviewer Review Type Date Requested Status
Alex Kavanagh Approve
charmers Pending
Review via email: mp+323221@code.launchpad.net

Description of the change

Refactor worker multiplier code.

Ensure WSGI processes are capped by default when running in containers.

Skew public processes in favour of admin processes.

To post a comment you must log in.
739. By James Page

Tidy docstrings

Revision history for this message
Alex Kavanagh (ajkavanagh) wrote :

Looks good. Just a thought on Pythonic idiomatic code.

740. By James Page

Make things more pythonic

Revision history for this message
Alex Kavanagh (ajkavanagh) wrote :

LGTM.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/openstack/context.py'
2--- charmhelpers/contrib/openstack/context.py 2017-04-25 17:13:07 +0000
3+++ charmhelpers/contrib/openstack/context.py 2017-04-26 09:46:29 +0000
4@@ -1232,31 +1232,50 @@
5 DEFAULT_MULTIPLIER = 2
6
7
8+def _calculate_workers():
9+ '''
10+ Determine the number of worker processes based on the CPU
11+ count of the unit containing the application.
12+
13+ Workers will be limited to MAX_DEFAULT_WORKERS in
14+ container environments where no worker-multipler configuration
15+ option been set.
16+
17+ @returns int: number of worker processes to use
18+ '''
19+ multiplier = config('worker-multiplier') or DEFAULT_MULTIPLIER
20+ count = int(_num_cpus() * multiplier)
21+ if multiplier > 0 and count == 0:
22+ count = 1
23+
24+ if config('worker-multiplier') is None and is_container():
25+ # NOTE(jamespage): Limit unconfigured worker-multiplier
26+ # to MAX_DEFAULT_WORKERS to avoid insane
27+ # worker configuration in LXD containers
28+ # on large servers
29+ # Reference: https://pad.lv/1665270
30+ count = min(count, MAX_DEFAULT_WORKERS)
31+
32+ return count
33+
34+
35+def _num_cpus():
36+ '''
37+ Compatibility wrapper for calculating the number of CPU's
38+ a unit has.
39+
40+ @returns: int: number of CPU cores detected
41+ '''
42+ try:
43+ return psutil.cpu_count()
44+ except AttributeError:
45+ return psutil.NUM_CPUS
46+
47+
48 class WorkerConfigContext(OSContextGenerator):
49
50- @property
51- def num_cpus(self):
52- # NOTE: use cpu_count if present (16.04 support)
53- if hasattr(psutil, 'cpu_count'):
54- return psutil.cpu_count()
55- else:
56- return psutil.NUM_CPUS
57-
58 def __call__(self):
59- multiplier = config('worker-multiplier') or DEFAULT_MULTIPLIER
60- count = int(self.num_cpus * multiplier)
61- if multiplier > 0 and count == 0:
62- count = 1
63-
64- if config('worker-multiplier') is None and is_container():
65- # NOTE(jamespage): Limit unconfigured worker-multiplier
66- # to MAX_DEFAULT_WORKERS to avoid insane
67- # worker configuration in LXD containers
68- # on large servers
69- # Reference: https://pad.lv/1665270
70- count = min(count, MAX_DEFAULT_WORKERS)
71-
72- ctxt = {"workers": count}
73+ ctxt = {"workers": _calculate_workers()}
74 return ctxt
75
76
77@@ -1264,7 +1283,7 @@
78
79 def __init__(self, name=None, script=None, admin_script=None,
80 public_script=None, process_weight=1.00,
81- admin_process_weight=0.75, public_process_weight=0.25):
82+ admin_process_weight=0.25, public_process_weight=0.75):
83 self.service_name = name
84 self.user = name
85 self.group = name
86@@ -1276,8 +1295,7 @@
87 self.public_process_weight = public_process_weight
88
89 def __call__(self):
90- multiplier = config('worker-multiplier') or 1
91- total_processes = self.num_cpus * multiplier
92+ total_processes = _calculate_workers()
93 ctxt = {
94 "service_name": self.service_name,
95 "user": self.user,
96
97=== modified file 'tests/contrib/openstack/test_os_contexts.py'
98--- tests/contrib/openstack/test_os_contexts.py 2017-04-19 12:42:07 +0000
99+++ tests/contrib/openstack/test_os_contexts.py 2017-04-26 09:46:29 +0000
100@@ -2604,15 +2604,16 @@
101 }
102 self.assertEquals(result, expected)
103
104- @patch.object(context, 'psutil')
105+ @patch.object(context, '_calculate_workers')
106 @patch.object(context, 'git_determine_python_path')
107 @patch.object(context, 'git_determine_usr_bin')
108- def test_wsgi_worker_config_context(self, usr_bin, python_path, psutil):
109+ def test_wsgi_worker_config_context(self, usr_bin, python_path,
110+ _calculate_workers):
111 self.config.return_value = 2 # worker-multiplier=2
112 usr_bin_path = '/usr/bin'
113 usr_bin.return_value = usr_bin_path
114 python_path.return_value = None
115- psutil.cpu_count.return_value = 4
116+ _calculate_workers.return_value = 8
117 service_name = 'service-name'
118 script = '/usr/bin/script'
119 ctxt = context.WSGIWorkerConfigContext(name=service_name,
120@@ -2625,8 +2626,8 @@
121 "admin_script": None,
122 "public_script": None,
123 "processes": 8,
124- "admin_processes": 6,
125- "public_processes": 2,
126+ "admin_processes": 2,
127+ "public_processes": 6,
128 "threads": 1,
129 "usr_bin": usr_bin_path,
130 "python_path": None,
131@@ -2688,83 +2689,74 @@
132 {'notifications': 'True'})
133
134 @patch.object(context, 'psutil')
135- def test_workerconfig_context_xenial(self, _psutil):
136- self.config.side_effect = fake_config({
137- 'worker-multiplier': 1,
138- })
139+ def test_num_cpus_xenial(self, _psutil):
140 _psutil.cpu_count.return_value = 4
141- worker = context.WorkerConfigContext()
142- self.assertTrue(worker.num_cpus, 4)
143+ self.assertTrue(context._num_cpus(), 4)
144
145 @patch.object(context, 'psutil')
146- def test_workerconfig_context_trusty(self, _psutil):
147- self.config.side_effect = fake_config({
148- 'worker-multiplier': 2,
149- })
150+ def test_num_cpus_trusty(self, _psutil):
151 _psutil.NUM_CPUS = 4
152- worker = context.WorkerConfigContext()
153- self.assertTrue(worker.num_cpus, 2)
154+ self.assertTrue(context._num_cpus(), 4)
155
156- def test_workerconfig_context_float(self):
157+ @patch.object(context, '_num_cpus')
158+ def test_calculate_workers_float(self, _num_cpus):
159 self.config.side_effect = fake_config({
160 'worker-multiplier': 0.3
161 })
162- with patch.object(context.WorkerConfigContext, 'num_cpus', 4):
163- worker = context.WorkerConfigContext()
164- self.assertTrue(worker.num_cpus, 1)
165+ _num_cpus.return_value = 4
166+ self.assertTrue(context._calculate_workers(), 4)
167
168- def test_workerconfig_context_not_quite_0(self):
169+ @patch.object(context, '_num_cpus')
170+ def test_calculate_workers_not_quite_0(self, _num_cpus):
171 # Make sure that the multiplier evaluating to somewhere between
172 # 0 and 1 in the floating point range still has at least one
173 # worker.
174 self.config.side_effect = fake_config({
175 'worker-multiplier': 0.001
176 })
177- with patch.object(context.WorkerConfigContext, 'num_cpus', 100):
178- worker = context.WorkerConfigContext()
179- self.assertTrue(worker.num_cpus, 1)
180+ _num_cpus.return_value = 100
181+ self.assertTrue(context._calculate_workers(), 1)
182
183- def test_workerconfig_context_0(self):
184+ @patch.object(context, 'psutil')
185+ def test_calculate_workers_0(self, _psutil):
186 self.config.side_effect = fake_config({
187 'worker-multiplier': 0
188 })
189- with patch.object(context.WorkerConfigContext, 'num_cpus', 2):
190- worker = context.WorkerConfigContext()
191- self.assertTrue(worker.num_cpus, 0)
192-
193- def test_workerconfig_context_noconfig(self):
194- self.config.return_value = None
195- with patch.object(context.WorkerConfigContext, 'num_cpus', 1):
196- worker = context.WorkerConfigContext()
197- self.assertEqual({'workers': 2}, worker())
198-
199- def test_workerconfig_context_noconfig_container(self):
200- self.config.return_value = None
201- self.is_container.return_value = True
202- with patch.object(context.WorkerConfigContext, 'num_cpus', 1):
203- worker = context.WorkerConfigContext()
204- self.assertEqual({'workers': 2}, worker())
205-
206- def test_workerconfig_context_noconfig_lotsa_cpus_container(self):
207- self.config.return_value = None
208- self.is_container.return_value = True
209- with patch.object(context.WorkerConfigContext, 'num_cpus', 32):
210- worker = context.WorkerConfigContext()
211- self.assertEqual({'workers': 4}, worker())
212-
213- def test_workerconfig_context_noconfig_lotsa_cpus_not_container(self):
214- self.config.return_value = None
215- with patch.object(context.WorkerConfigContext, 'num_cpus', 32):
216- worker = context.WorkerConfigContext()
217- self.assertEqual({'workers': 64}, worker())
218-
219- def test_workerconfig_context_withconfig(self):
220- self.config.side_effect = fake_config({
221- 'worker-multiplier': 4,
222- })
223- with patch.object(context.WorkerConfigContext, 'num_cpus', 2):
224- worker = context.WorkerConfigContext()
225- self.assertEqual({'workers': 8}, worker())
226+ _psutil.cpu_count.return_value = 2
227+ self.assertTrue(context._calculate_workers(), 0)
228+
229+ @patch.object(context, '_num_cpus')
230+ def test_calculate_workers_noconfig(self, _num_cpus):
231+ self.config.return_value = None
232+ _num_cpus.return_value = 1
233+ self.assertTrue(context._calculate_workers(), 2)
234+
235+ @patch.object(context, '_num_cpus')
236+ def test_calculate_workers_noconfig_container(self, _num_cpus):
237+ self.config.return_value = None
238+ self.is_container.return_value = True
239+ _num_cpus.return_value = 1
240+ self.assertTrue(context._calculate_workers(), 2)
241+
242+ @patch.object(context, '_num_cpus')
243+ def test_calculate_workers_noconfig_lotsa_cpus_container(self,
244+ _num_cpus):
245+ self.config.return_value = None
246+ self.is_container.return_value = True
247+ _num_cpus.return_value = 32
248+ self.assertTrue(context._calculate_workers(), 4)
249+
250+ @patch.object(context, '_num_cpus')
251+ def test_calculate_workers_noconfig_lotsa_cpus_not_container(self,
252+ _num_cpus):
253+ self.config.return_value = None
254+ _num_cpus.return_value = 32
255+ self.assertTrue(context._calculate_workers(), 64)
256+
257+ @patch.object(context, '_calculate_workers', return_value=256)
258+ def test_worker_context(self, calculate_workers):
259+ self.assertEqual(context.WorkerConfigContext()(),
260+ {'workers': 256})
261
262 def test_apache_get_addresses_no_network_splits(self):
263 self.https.return_value = True

Subscribers

People subscribed via source and target branches