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