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