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