Fix: I somehow messed up some commits by clicking the "revert" button on the github...
[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
13 class UpbSimpleCloudDcRM(BaseResourceModel):
14 """
15 This will be an example resource model that limits the overall
16 resources that can be deployed per data center.
17 No over provisioning. Resources are fixed throughout entire container
18 lifetime.
19 """
20
21 def __init__(self, max_cu=32, max_mu=1024,
22 deactivate_cpu_limit=False,
23 deactivate_mem_limit=False):
24 """
25 Initialize model.
26 :param max_cu: Maximum number of compute units available in this DC.
27 :param max_mu: Maximum memory of entire dc.
28 :return:
29 """
30 self.dc_max_cu = max_cu
31 self.dc_max_mu = max_mu
32 self.dc_alloc_cu = 0
33 self.dc_alloc_mu = 0
34 self.deactivate_cpu_limit = deactivate_cpu_limit
35 self.deactivate_mem_limit = deactivate_mem_limit
36 self.single_cu = 0
37 self.single_mu = 0
38 super(self.__class__, self).__init__()
39
40 def allocate(self, d):
41 """
42 Allocate resources for the given container.
43 Defined by d.flavor_name
44 :param d: container
45 :return:
46 """
47 self._allocated_compute_instances[d.name] = d
48 if not self.deactivate_cpu_limit:
49 self._allocate_cpu(d)
50 if not self.deactivate_mem_limit:
51 self._allocate_mem(d)
52 self._apply_limits()
53
54 def _allocate_cpu(self, d):
55 """
56 Actually allocate (bookkeeping)
57 :param d: container
58 :return:
59 """
60 fl_cu = self._get_flavor(d).get("compute")
61 # check for over provisioning
62 if self.dc_alloc_cu + fl_cu > self.dc_max_cu:
63 raise Exception("Not enough compute resources left.")
64 self.dc_alloc_cu += fl_cu
65
66 def _allocate_mem(self, d):
67 """
68 Actually allocate (bookkeeping)
69 :param d: container
70 :return:
71 """
72 fl_mu = self._get_flavor(d).get("memory")
73 # check for over provisioning
74 if self.dc_alloc_mu + fl_mu > self.dc_max_mu:
75 raise Exception("Not enough memory resources left.")
76 self.dc_alloc_mu += fl_mu
77
78 def free(self, d):
79 """
80 Free resources allocated to the given container.
81 :param d: container
82 :return:
83 """
84 del self._allocated_compute_instances[d.name]
85 if not self.deactivate_cpu_limit:
86 self._free_cpu(d)
87 if not self.deactivate_mem_limit:
88 self._free_mem(d)
89 self._apply_limits()
90
91 def _free_cpu(self, d):
92 """
93 Free resources.
94 :param d: container
95 :return:
96 """
97 self.dc_alloc_cu -= self._get_flavor(d).get("compute")
98
99 def _free_mem(self, d):
100 """
101 Free resources.
102 :param d: container
103 :return:
104 """
105 self.dc_alloc_mu -= self._get_flavor(d).get("memory")
106
107 def _apply_limits(self):
108 """
109 Recalculate real resource limits for all allocated containers and apply them
110 to their cgroups.
111 We have to recalculate for all to allow e.g. overprovisioning models.
112 :return:
113 """
114 for d in self._allocated_compute_instances.itervalues():
115 if not self.deactivate_cpu_limit:
116 self._apply_cpu_limits(d)
117 if not self.deactivate_mem_limit:
118 self._apply_mem_limits(d)
119
120 def _apply_cpu_limits(self, d):
121 """
122 Calculate real CPU limit (CFS bandwidth) and apply.
123 :param d: container
124 :return:
125 """
126 number_cu = self._get_flavor(d).get("compute")
127 # get cpu time fraction for entire emulation
128 e_cpu = self.registrar.e_cpu
129 # calculate cpu time fraction of a single compute unit
130 self.single_cu = float(e_cpu) / sum([rm.dc_max_cu for rm in list(self.registrar.resource_models)])
131 # calculate cpu time fraction for container with given flavor
132 cpu_time_percentage = single_cu * number_cu
133 # calculate cpu period and quota for CFS
134 # (see: https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt)
135 # Attention minimum cpu_quota is 1ms (micro)
136 cpu_period = 1000000 # lets consider a fixed period of 1000000 microseconds for now
137 cpu_quota = cpu_period * cpu_time_percentage # calculate the fraction of cpu time for this container
138 # ATTENTION >= 1000 to avoid a invalid argument system error ... no idea why
139 if cpu_quota < 1000:
140 cpu_quota = 1000
141 LOG.warning("Increased CPU quota for %r to avoid system error." % d.name)
142 # apply to container if changed
143 if d.cpu_period != cpu_period or d.cpu_quota != cpu_quota:
144 LOG.debug("Setting CPU limit for %r: cpu_quota = cpu_period * limit = %f * %f = %f" % (
145 d.name, cpu_period, cpu_time_percentage, cpu_quota))
146 d.updateCpuLimit(cpu_period=int(cpu_period), cpu_quota=int(cpu_quota))
147
148 def _apply_mem_limits(self, d):
149 """
150 Calculate real mem limit and apply.
151 :param d: container
152 :return:
153 """
154 number_mu = self._get_flavor(d).get("memory")
155 # get memory amount for entire emulation
156 e_mem = self.registrar.e_mem
157 # calculate amount of memory for a single mu
158 self.single_mu = float(e_mem) / sum([rm.dc_max_mu for rm in list(self.registrar.resource_models)])
159 # calculate mem for given flavor
160 mem_limit = single_mu * number_mu
161 # ATTENTION minimum mem_limit per container is 4MB
162 if mem_limit < 4:
163 mem_limit = 4
164 LOG.warning("Increased MEM limit for %r because it was less than 4.0 MB." % d.name)
165 # to byte!
166 mem_limit = int(mem_limit*1024*1024)
167 # apply to container if changed
168 if d.mem_limit != mem_limit:
169 LOG.debug("Setting MEM limit for %r: mem_limit = %f MB" % (d.name, mem_limit/1024/1024))
170 d.updateMemoryLimit(mem_limit=mem_limit)
171
172 def get_state_dict(self):
173 """
174 Return the state of the resource model as simple dict.
175 Helper method for logging functionality.
176 :return:
177 """
178 # collect info about all allocated instances
179 allocation_state = dict()
180 for k, d in self._allocated_compute_instances.iteritems():
181 s = dict()
182 s["cpu_period"] = d.cpu_period
183 s["cpu_quota"] = d.cpu_quota
184 s["cpu_shares"] = d.cpu_shares
185 s["mem_limit"] = d.mem_limit
186 s["memswap_limit"] = d.memswap_limit
187 allocation_state[k] = s
188 # final result
189 r = dict()
190 r["e_cpu"] = self.registrar.e_cpu
191 r["e_mem"] = self.registrar.e_mem
192 r["dc_max_cu"] = self.dc_max_cu
193 r["dc_max_mu"] = self.dc_max_mu
194 r["dc_alloc_cu"] = self.dc_alloc_cu
195 r["dc_alloc_mu"] = self.dc_alloc_mu
196 r["single_cu_percentage"] = self.single_cu
197 r["single_mu_percentage"] = self.single_mu
198 r["allocation_state"] = allocation_state
199 return r
200
201 def _get_flavor(self, d):
202 """
203 Get flavor assigned to given container.
204 Identified by d.flavor_name.
205 :param d: container
206 :return:
207 """
208 if d.flavor_name not in self._flavors:
209 raise Exception("Flavor %r does not exist" % d.flavor_name)
210 return self._flavors.get(d.flavor_name)
211
212 def _write_log(self, d, path, action):
213 """
214 Helper to log RM info for experiments.
215 :param d: container
216 :param path: log path
217 :param action: allocate or free
218 :return:
219 """
220 if path is None:
221 return
222 # we have a path: write out RM info
223 l = dict()
224 l["t"] = time.time()
225 l["container_state"] = d.getStatus()
226 l["action"] = action
227 l["rm_state"] = self.get_state_dict()
228 # append to logfile
229 with open(path, "a") as f:
230 f.write("%s\n" % json.dumps(l))