beb3fbea8d9c7d5d48107a0e89cc535dbe201c01
[osm/vim-emu.git] / src / emuvim / dcemulator / resourcemodel / upb / simple.py
1 """
2 Playground for resource models created by University of Paderborn.
3 """
4 import time
5 import json
6 import logging
7 from emuvim.dcemulator.resourcemodel import BaseResourceModel, NotEnoughResourcesAvailable
8
9 LOG = logging.getLogger("rm.upb.simple")
10 LOG.setLevel(logging.DEBUG)
11
12 CPU_PERIOD = 1000000
13
14
15 class UpbSimpleCloudDcRM(BaseResourceModel):
16 """
17 This will be an example resource model that limits the overall
18 resources that can be deployed per data center.
19 No over provisioning. Resources are fixed throughout entire container
20 lifetime.
21 """
22
23 def __init__(self, max_cu=32, max_mu=1024,
24 deactivate_cpu_limit=False,
25 deactivate_mem_limit=False):
26 """
27 Initialize model.
28 :param max_cu: Maximum number of compute units available in this DC.
29 :param max_mu: Maximum memory of entire dc.
30 :return:
31 """
32 self.dc_max_cu = max_cu
33 self.dc_max_mu = max_mu
34 self.dc_alloc_cu = 0
35 self.dc_alloc_mu = 0
36 self.deactivate_cpu_limit = deactivate_cpu_limit
37 self.deactivate_mem_limit = deactivate_mem_limit
38 self.single_cu = 0
39 self.single_mu = 0
40 self.cpu_op_factor = 1.0 # over provisioning factor
41 self.mem_op_factor = 1.0
42 self.raise_no_cpu_resources_left = True
43 self.raise_no_mem_resources_left = True
44 super(UpbSimpleCloudDcRM, self).__init__()
45
46 def allocate(self, d):
47 """
48 Allocate resources for the given container.
49 Defined by d.flavor_name
50 :param d: container
51 :return:
52 """
53 self._allocated_compute_instances[d.name] = d
54 if not self.deactivate_cpu_limit:
55 self._allocate_cpu(d)
56 if not self.deactivate_mem_limit:
57 self._allocate_mem(d)
58 self._apply_limits()
59
60 def _allocate_cpu(self, d):
61 """
62 Actually allocate (bookkeeping)
63 :param d: container
64 :return:
65 """
66 fl_cu = self._get_flavor(d).get("compute")
67 # check for over provisioning
68 if self.dc_alloc_cu + fl_cu > self.dc_max_cu and self.raise_no_cpu_resources_left:
69 raise NotEnoughResourcesAvailable("Not enough compute resources left.")
70 self.dc_alloc_cu += fl_cu
71
72 def _allocate_mem(self, d):
73 """
74 Actually allocate (bookkeeping)
75 :param d: container
76 :return:
77 """
78 fl_mu = self._get_flavor(d).get("memory")
79 # check for over provisioning
80 if self.dc_alloc_mu + fl_mu > self.dc_max_mu and self.raise_no_mem_resources_left:
81 raise NotEnoughResourcesAvailable("Not enough memory resources left.")
82 self.dc_alloc_mu += fl_mu
83
84 def free(self, d):
85 """
86 Free resources allocated to the given container.
87 :param d: container
88 :return:
89 """
90 del self._allocated_compute_instances[d.name]
91 if not self.deactivate_cpu_limit:
92 self._free_cpu(d)
93 if not self.deactivate_mem_limit:
94 self._free_mem(d)
95 self._apply_limits()
96
97 def _free_cpu(self, d):
98 """
99 Free resources.
100 :param d: container
101 :return:
102 """
103 self.dc_alloc_cu -= self._get_flavor(d).get("compute")
104
105 def _free_mem(self, d):
106 """
107 Free resources.
108 :param d: container
109 :return:
110 """
111 self.dc_alloc_mu -= self._get_flavor(d).get("memory")
112
113 def _apply_limits(self):
114 """
115 Recalculate real resource limits for all allocated containers and apply them
116 to their cgroups.
117 We have to recalculate for all containers to allow e.g. over provisioning models.
118 :return:
119 """
120 for d in self._allocated_compute_instances.itervalues():
121 if not self.deactivate_cpu_limit:
122 self._apply_cpu_limits(d)
123 if not self.deactivate_mem_limit:
124 self._apply_mem_limits(d)
125
126 def _apply_cpu_limits(self, d):
127 """
128 Calculate real CPU limit (CFS bandwidth) and apply.
129 :param d: container
130 :return:
131 """
132 number_cu = self._get_flavor(d).get("compute")
133 # calculate cpu time fraction of a single compute unit
134 self.single_cu = self._compute_single_cu()
135 # calculate cpu time fraction for container with given flavor
136 cpu_time_percentage = self.single_cu * number_cu
137 # calculate input values for CFS scheduler bandwidth limitation
138 cpu_period, cpu_quota = self._calculate_cpu_cfs_values(cpu_time_percentage)
139 # apply limits to container if changed
140 if d.cpu_period != cpu_period or d.cpu_quota != cpu_quota:
141 LOG.debug("Setting CPU limit for %r: cpu_quota = cpu_period * limit = %f * %f = %f (op_factor=%f)" % (
142 d.name, cpu_period, cpu_time_percentage, cpu_quota, self.cpu_op_factor))
143 d.updateCpuLimit(cpu_period=int(cpu_period), cpu_quota=int(cpu_quota))
144
145 def _compute_single_cu(self):
146 """
147 Calculate percentage of CPU time of a singe CU unit.
148 :return:
149 """
150 # get cpu time fraction for entire emulation
151 e_cpu = self.registrar.e_cpu
152 # calculate
153 return float(e_cpu) / sum([rm.dc_max_cu for rm in list(self.registrar.resource_models)])
154
155 def _calculate_cpu_cfs_values(self, cpu_time_percentage):
156 """
157 Calculate cpu period and quota for CFS
158 :param cpu_time_percentage: percentage of overall CPU to be used
159 :return: cpu_period, cpu_quota
160 """
161 # (see: https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt)
162 # Attention minimum cpu_quota is 1ms (micro)
163 cpu_period = CPU_PERIOD # lets consider a fixed period of 1000000 microseconds for now
164 cpu_quota = cpu_period * cpu_time_percentage # calculate the fraction of cpu time for this container
165 # ATTENTION >= 1000 to avoid a invalid argument system error ... no idea why
166 if cpu_quota < 1000:
167 cpu_quota = 1000
168 LOG.warning("Increased CPU quota to avoid system error.")
169 return cpu_period, cpu_quota
170
171 def _apply_mem_limits(self, d):
172 """
173 Calculate real mem limit and apply.
174 :param d: container
175 :return:
176 """
177 number_mu = self._get_flavor(d).get("memory")
178 # get memory amount for entire emulation
179 e_mem = self.registrar.e_mem
180 # calculate amount of memory for a single mu
181 self.single_mu = float(e_mem) / sum([rm.dc_max_mu for rm in list(self.registrar.resource_models)])
182 # calculate mem for given flavor
183 mem_limit = self.single_mu * number_mu
184 mem_limit = self._calculate_mem_limit_value(mem_limit)
185 # apply to container if changed
186 if d.mem_limit != mem_limit:
187 LOG.debug("Setting MEM limit for %r: mem_limit = %f MB (op_factor=%f)" %
188 (d.name, mem_limit/1024/1024, self.mem_op_factor))
189 d.updateMemoryLimit(mem_limit=mem_limit)
190
191 def _calculate_mem_limit_value(self, mem_limit):
192 """
193 Calculate actual mem limit as input for cgroup API
194 :param mem_limit: abstract mem limit
195 :return: concrete mem limit
196 """
197 # ATTENTION minimum mem_limit per container is 4MB
198 if mem_limit < 4:
199 mem_limit = 4
200 LOG.warning("Increased MEM limit because it was less than 4.0 MB.")
201 # to byte!
202 return int(mem_limit*1024*1024)
203
204 def get_state_dict(self):
205 """
206 Return the state of the resource model as simple dict.
207 Helper method for logging functionality.
208 :return:
209 """
210 # collect info about all allocated instances
211 allocation_state = dict()
212 for k, d in self._allocated_compute_instances.iteritems():
213 s = dict()
214 s["cpu_period"] = d.cpu_period
215 s["cpu_quota"] = d.cpu_quota
216 s["cpu_shares"] = d.cpu_shares
217 s["mem_limit"] = d.mem_limit
218 s["memswap_limit"] = d.memswap_limit
219 allocation_state[k] = s
220 # final result
221 r = dict()
222 r["e_cpu"] = self.registrar.e_cpu
223 r["e_mem"] = self.registrar.e_mem
224 r["dc_max_cu"] = self.dc_max_cu
225 r["dc_max_mu"] = self.dc_max_mu
226 r["dc_alloc_cu"] = self.dc_alloc_cu
227 r["dc_alloc_mu"] = self.dc_alloc_mu
228 r["single_cu_percentage"] = self.single_cu
229 r["single_mu_percentage"] = self.single_mu
230 r["cpu_op_factor"] = self.cpu_op_factor
231 r["mem_op_factor"] = self.mem_op_factor
232 r["allocation_state"] = allocation_state
233 return r
234
235 def _get_flavor(self, d):
236 """
237 Get flavor assigned to given container.
238 Identified by d.flavor_name.
239 :param d: container
240 :return:
241 """
242 if d.flavor_name not in self._flavors:
243 raise Exception("Flavor %r does not exist" % d.flavor_name)
244 return self._flavors.get(d.flavor_name)
245
246 def _write_log(self, d, path, action):
247 """
248 Helper to log RM info for experiments.
249 :param d: container
250 :param path: log path
251 :param action: allocate or free
252 :return:
253 """
254 if path is None:
255 return
256 # we have a path: write out RM info
257 l = dict()
258 l["t"] = time.time()
259 l["container_state"] = d.getStatus()
260 l["action"] = action
261 l["rm_state"] = self.get_state_dict()
262 # append to logfile
263 with open(path, "a") as f:
264 f.write("%s\n" % json.dumps(l))
265
266
267 class UpbOverprovisioningCloudDcRM(UpbSimpleCloudDcRM):
268 """
269 This will be an example resource model that limits the overall
270 resources that can be deployed per data center.
271 Allows over provisioning. Might result in reducing resources of single
272 containers whenever a data-center is over provisioned.
273 """
274 # TODO add parts for memory
275 def __init__(self, *args, **kvargs):
276 super(UpbOverprovisioningCloudDcRM, self).__init__(*args, **kvargs)
277 self.raise_no_cpu_resources_left = False
278
279 def _compute_single_cu(self):
280 """
281 Calculate percentage of CPU time of a singe CU unit.
282 Take scale-down facte for over provisioning into account.
283 :return:
284 """
285 # get cpu time fraction for entire emulation
286 e_cpu = self.registrar.e_cpu
287 # calculate over provisioning scale factor
288 self.cpu_op_factor = float(self.dc_max_cu) / (max(self.dc_max_cu, self.dc_alloc_cu))
289 # calculate
290 return float(e_cpu) / sum([rm.dc_max_cu for rm in list(self.registrar.resource_models)]) * self.cpu_op_factor
291
292
293 class UpbDummyRM(UpbSimpleCloudDcRM):
294 """
295 No limits. But log allocations.
296 """
297 def __init__(self, *args, **kvargs):
298 super(UpbDummyRM, self).__init__(*args, **kvargs)
299 self.raise_no_cpu_resources_left = False
300
301 def _apply_limits(self):
302 # do nothing here
303 pass
304