From mboxrd@z Thu Jan 1 00:00:00 1970 From: Dario Faggioli Subject: Re: [PATCH 1/4] xen: credit2: implement utilization cap Date: Mon, 12 Jun 2017 15:19:24 +0200 Message-ID: <1497273564.26212.18.camel@citrix.com> References: <149692186557.9605.11625777539060264052.stgit@Solace.fritz.box> <149692372627.9605.8252407697848997058.stgit@Solace.fritz.box> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="===============3903065754236692301==" Return-path: Received: from mail6.bemta5.messagelabs.com ([195.245.231.135]) by lists.xenproject.org with esmtp (Exim 4.84_2) (envelope-from ) id 1dKPFq-0006Qm-Kq for xen-devel@lists.xenproject.org; Mon, 12 Jun 2017 13:19:38 +0000 In-Reply-To: List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Errors-To: xen-devel-bounces@lists.xen.org Sender: "Xen-devel" To: Anshul Makkar , xen-devel@lists.xenproject.org Cc: George Dunlap , Andrew Cooper , Wei Liu , Ian Jackson , Jan Beulich List-Id: xen-devel@lists.xenproject.org --===============3903065754236692301== Content-Type: multipart/signed; micalg=pgp-sha256; protocol="application/pgp-signature"; boundary="=-S2vhWiz3ihwpdFTy39uq" --=-S2vhWiz3ihwpdFTy39uq Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hey, Thanks for looking at the patch! :-) On Mon, 2017-06-12 at 12:16 +0100, Anshul Makkar wrote: > On 08/06/2017 13:08, Dario Faggioli wrote: > > This commit implements the Xen part of the cap mechanism for > > Credit2. > >=20 > > A cap is how much, in terms of % of physical CPU time, a domain > > can execute at most. > >=20 > > For instance, a domain that must not use more than 1/4 of one > > physical CPU, must have a cap of 25%; one that must not use more > > than 1+1/2 of physical CPU time, must be given a cap of 150%. > >=20 > > Caps are per domain, so it is all a domain's vCPUs, cumulatively, > > that will be forced to execute no more than the decided amount. > >=20 > > This is implemented by giving each domain a 'budget', and using > > a (per-domain again) periodic timer. Values of budget and 'period' > > are chosen so that budget/period is equal to the cap itself. > >=20 > > Budget is burned by the domain's vCPUs, in a similar way to how > > credits are. > >=20 > > When a domain runs out of budget, its vCPUs can't run any longer. >=20 > if the vcpus of a domain have credit and if budget has run out, will > the=C2=A0 > vcpus won't be scheduled. > Is this a question? Assuming it is, what do you mean with "domain have credit"? Domains always have credits, and they never run out of them. There's no such thing as a domain not being runnable because it does not have credits. About budget, a domain with <=3D 0 budget means all its vcpus are not runnable, and hence won't be scheduler, no matter their credits. > > @@ -92,6 +92,82 @@ > > =C2=A0 */ > >=20 > > =C2=A0/* > > + * Utilization cap: > > + * > > + * Setting an pCPU utilization cap for a domain means the > > following: > > + * > > + * - a domain can have a cap, expressed in terms of % of physical > > CPU time. > > + *=C2=A0=C2=A0=C2=A0A domain that must not use more than 1/4 of _one_ = physical > > CPU, will > > + *=C2=A0=C2=A0=C2=A0be given a cap of 25%; a domain that must not use = more than > > 1+1/2 of > > + *=C2=A0=C2=A0=C2=A0physical CPU time, will be given a cap of 150%; > > + * > > + * - caps are per-domain (not per-vCPU). If a domain has only 1 > > vCPU, and > > + *=C2=A0=C2=A0=C2=A0a 40% cap, that one vCPU will use 40% of one pCPU.= If a > > somain has 4 > > + *=C2=A0=C2=A0=C2=A0vCPUs, and a 200% cap, all its 4 vCPUs are allowed= to run for > > (the > > + *=C2=A0=C2=A0=C2=A0equivalent of) 100% time on 2 pCPUs. How much each= of the > > various 4 > > + *=C2=A0=C2=A0=C2=A0vCPUs will get, is unspecified (will depend on var= ious > > aspects: workload, > > + *=C2=A0=C2=A0=C2=A0system load, etc.). >=20 > or the ratio can vary eg. 4 vcpus are allowed to run got 50% of the > time=C2=A0 > if on 4 pcpus ? > Yes, this is just an example. From the cap mechanism point of view, running for 4x50% is exactly the same as of running fo 2x100%, and there is no way to control what the actual distribution of runtime will be, on a multi-vcpu guest. I can try to make the wording a bit clearer... I can certainly add more examples (but I have no chance being exhaustive, as the combinations are virtually infinite! :-P). > > + * > > + * For implementing this, we use the following approach: > > + * > > + * - each domain is given a 'budget', an each domain has a timer, > > which > > + *=C2=A0=C2=A0=C2=A0replenishes the domain's budget periodically. The = budget is > > the amount > > + *=C2=A0=C2=A0=C2=A0of time the vCPUs of the domain can use every 'per= iod'; > > + * > > + * - the period is CSCHED2_BDGT_REPL_PERIOD, and is the same for > > all domains > > + *=C2=A0=C2=A0=C2=A0(but each domain has its own timer; so the all are= periodic > > by the same > > + *=C2=A0=C2=A0=C2=A0period, but replenishment of the budgets of the va= rious > > domains, at > > + *=C2=A0=C2=A0=C2=A0periods boundaries, are not synchronous); > > + * > > + * - when vCPUs run, they consume budget. When they don't run, > > they don't > > + *=C2=A0=C2=A0=C2=A0consume budget. If there is no budget left for the= domain, no > > vCPU of > > + *=C2=A0=C2=A0=C2=A0that domain can run. If a vCPU tries to run and fi= nds that > > there is no > > + *=C2=A0=C2=A0=C2=A0budget, it blocks. > > + *=C2=A0=C2=A0=C2=A0Budget never expires, so at whatever time a vCPU w= ants to > > run, it can > > + *=C2=A0=C2=A0=C2=A0check the domain's budget, and if there is some, i= t can use > > it. > > + * > > + * - budget is replenished to the top of the capacity for the > > domain once > > + *=C2=A0=C2=A0=C2=A0per period. Even if there was some leftover budget= from > > previous period, > > + *=C2=A0=C2=A0=C2=A0though, the budget after a replenishment will alwa= ys be at > > most equal > > + *=C2=A0=C2=A0=C2=A0to the total capacify of the domain ('tot_budget')= ; > > + * >=20 > budget is replenished but credits not available ? > Still not getting this. > budget is finished but not vcpu has not reached the rate limit > boundary ? >=20 Budget takes precedence over ratelimiting. This is important to keep cap working "regularly", rather then in some kind of permanent "trying- to-keep-up-with-overruns-in-previous-periods" state. And, ideally, a vcpu cap and ratelimiting should be set in such a way that they don't step on each other toe (or do that only rarely). I can see about trying to print a warning when I detect potential tricky values (but it's not easy, considering budget is per-domain, so I can't be sure about how much each vcpu will actually get, and whether or not that will reveal to be significantly less than ratelimiting the most of the times). > > + * - when a budget replenishment occurs, if there are vCPUs that > > had been > > + *=C2=A0=C2=A0=C2=A0blocked because of lack of budget, they'll be unbl= ocked, and > > they will > > + *=C2=A0=C2=A0=C2=A0(potentially) be able to run again. > > + * > > + * Finally, some even more implementation related detail: > > + * > > + * - budget is stored in a domain-wide pool. vCPUs of the domain > > that want > > + *=C2=A0=C2=A0=C2=A0to run go to such pool, and grub some. When they d= o so, the > > amount > > + *=C2=A0=C2=A0=C2=A0they grabbed is _immediately_ removed from the poo= l. This > > happens in > > + *=C2=A0=C2=A0=C2=A0vcpu_try_to_get_budget(); > > + * > > + * - when vCPUs stop running, if they've not consumed all the > > budget they > > + *=C2=A0=C2=A0=C2=A0took, the leftover is put back in the pool. This h= appens in > > + *=C2=A0=C2=A0=C2=A0vcpu_give_budget_back(); >=20 > 200% budget, 4 vcpus to run on 4 pcpus each allowed only 50% of > budget.=C2=A0 > This is a static allocation . > Err... again, are you telling or asking? > for eg. 2 vcpus running on 2 pvpus at 20%=C2=A0 > budgeted time, if vcpu3 wants to execute some cpu intensive task, > then=C2=A0 > it won't be allowed to allowed to use more than 50% of the pcpus. >=20 With what parameters? You mean with these ones you cite here (i.e., a 200% budget)? If the VM has 200%, and vcpu1 and vcpu2 consumes 20%+20%=3D40%, there's 160% free for vcpu3 and vcpu4. > I checked the implenation below and I believe we can allow for these=C2= =A0 > type of dynamic budget_quota allocation per vcpu. Not for initial=C2=A0 > version, but certainly we can consider it for future versions. > But... it's already totally dynamic. > > @@ -408,6 +505,10 @@ struct csched2_vcpu { > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0unsigned int residual; > >=20 > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0int credit; > > + > > +=C2=A0=C2=A0=C2=A0=C2=A0s_time_t budget; >=20 > it's confusing, please can we have different member names for budget > in=C2=A0 > domain and vcpu structure. > Mmm.. I don't think it is. It's "how much budget this _thing_ have", where "thing" can be the domain or a vcpu, and you can tell that by looking at the containing structure. Most of the time, that's rather explicit, the former being sdom->budget, the latter being svc->budget. What different names did you have in mind? The only alternative that I can come up with would be something like: struct csched2_dom { ... dom_budget; ... }; struct csched2_vcpu { ... vcpu_budget; ... }; Which I don't like (because of the repetition). > > @@ -1354,7 +1469,16 @@ static void reset_credit(const struct > > scheduler *ops, int cpu, s_time_t now, > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0* that the = credit it has spent so far get accounted. > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0*/ > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0if ( svc->vcpu = =3D=3D curr_on_cpu(svc_cpu) ) > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0{ > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0burn_credits(rqd, svc, now); > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0/* > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0* And, similarly, in case it has run out of budget, > > as a > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0* consequence of this round of accounting, we also > > must inform > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0* its pCPU that it's time to park it, and pick up > > someone else. > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0*/ > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0if ( unlikely(svc->budget <=3D 0) ) >=20 > use of unlikely here is not saving much of cpu cycles. > Well, considering that not all domains will have a cap, and that we don't expect, even for the domains with caps, all their vcpus to exhaust their budget at every reset event, I think annotating this as an unlikely event makes sense. It's not a super big deal, though, and I can get rid of it, if people don't like/are not convinced about it. :-) > > @@ -1410,27 +1534,35 @@ void burn_credits(struct > > csched2_runqueue_data *rqd, > >=20 > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0delta =3D now - svc->start_time; > >=20 > > -=C2=A0=C2=A0=C2=A0=C2=A0if ( likely(delta > 0) ) > > -=C2=A0=C2=A0=C2=A0=C2=A0{ > > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0SCHED_STAT_CRANK(burn_= credits_t2c); > > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0t2c_update(rqd, delta,= svc); > > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0svc->start_time =3D no= w; > > -=C2=A0=C2=A0=C2=A0=C2=A0} > > -=C2=A0=C2=A0=C2=A0=C2=A0else if ( delta < 0 ) > > +=C2=A0=C2=A0=C2=A0=C2=A0if ( unlikely(delta <=3D 0) ) > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0{ > > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0d2printk("WARNING: %s:= Time went backwards? now > > %"PRI_stime" start_time %"PRI_stime"\n", > > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0__func__, now, svc->start_time); > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0if ( unlikely(delta < = 0) ) > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0d2printk("WARNING: %s: Time went backwards? now > > %"PRI_stime > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0" start_time %"PRI= _stime"\n", __func__, now, > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0svc->start_time); > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0goto out; > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0} > >=20 > > +=C2=A0=C2=A0=C2=A0=C2=A0SCHED_STAT_CRANK(burn_credits_t2c); > > +=C2=A0=C2=A0=C2=A0=C2=A0t2c_update(rqd, delta, svc); > > + > > +=C2=A0=C2=A0=C2=A0=C2=A0if ( unlikely(svc->budget !=3D STIME_MAX) ) >=20 > not clear, what is this check about ? > Ah, good catch. This should have been has_cap()! I'll fix it. > > @@ -1438,6 +1570,217 @@ void burn_credits(struct > >=20 > > +static bool vcpu_try_to_get_budget(struct csched2_vcpu *svc) > > +{ > > +=C2=A0=C2=A0=C2=A0=C2=A0struct csched2_dom *sdom =3D svc->sdom; > > +=C2=A0=C2=A0=C2=A0=C2=A0unsigned int cpu =3D svc->vcpu->processor; > > + > > +=C2=A0=C2=A0=C2=A0=C2=A0ASSERT(spin_is_locked(per_cpu(schedule_data, > > cpu).schedule_lock)); > > + > > +=C2=A0=C2=A0=C2=A0=C2=A0if ( svc->budget > 0 ) > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0return true; > > + > > +=C2=A0=C2=A0=C2=A0=C2=A0/* budget_lock nests inside runqueue lock. */ > > +=C2=A0=C2=A0=C2=A0=C2=A0spin_lock(&sdom->budget_lock); > > + > > +=C2=A0=C2=A0=C2=A0=C2=A0/* > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0* Here, svc->budget is <=3D 0 (as, if it= was > 0, we'd have > > taken the if > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0* above!). That basically means the vCPU= has overrun a bit -- > > because of > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0* various reasons-- and we want to take = that into account. > > With the +=3D, > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0* we are actually subtracting the amount= of budget the vCPU > > has > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0* overconsumed, from the total domain bu= dget. > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0*/ > > +=C2=A0=C2=A0=C2=A0=C2=A0sdom->budget +=3D svc->budget; > > + > > +=C2=A0=C2=A0=C2=A0=C2=A0if ( sdom->budget > 0 ) > > +=C2=A0=C2=A0=C2=A0=C2=A0{ > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0svc->budget =3D sdom->= budget; >=20 > why are you assigning the remaining sdom->budge to only this svc. > svc=C2=A0 > should be assigned a proportionate budget. > Each vcpu is assigned a %age of the domain budget based on the cap > and=C2=A0 > number of vcpus. > There is difference in the code that's here and the code in branch=C2=A0 > git://xenbits.xen.org/people/dariof/xen.git (fetch)=C2=A0 > rel/sched/credti2-caps branch. Logic in the branch code looks fine > where=C2=A0 > you are taking svc->budget_quota into considration.. > Yeah... maybe look at patch 3/4. :-P > > @@ -2423,14 +2785,22 @@ csched2_runtime(const struct scheduler > > *ops, int cpu, > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0* credit values of MIN,MAX per vcpu= , since each vcpu burns > > credit > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0* at a different rate. > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0*/ > > -=C2=A0=C2=A0=C2=A0=C2=A0if (rt_credit > 0) > > +=C2=A0=C2=A0=C2=A0=C2=A0if ( rt_credit > 0 ) > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0time =3D c2t(rqd,= rt_credit, snext); > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0else > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0time =3D 0; > >=20 > > -=C2=A0=C2=A0=C2=A0=C2=A0/* 3) But never run longer than MAX_TIMER or l= ess than > > MIN_TIMER or > > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0* the rate_limit time. */ > > -=C2=A0=C2=A0=C2=A0=C2=A0if ( time < min_time) > > +=C2=A0=C2=A0=C2=A0=C2=A0/* > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0* 3) But, if capped, never run more than= our budget. > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0*/ > > +=C2=A0=C2=A0=C2=A0=C2=A0if ( unlikely(has_cap(snext)) ) > > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0time =3D snext->budget= < time ? snext->budget : time; > > + >=20 > does the budget takes precedence over rate and credit ? > It does takes precedence over ratelimiting, yes. There's no precedence relationship between budget and credits, and, anyway, the code here has nothing to do with that. In fact, all this code is saying is that, if this vcpu has 748us of budget left, I want csched2_schedule() to be called again no farther than 748us from now. > please replace snext->budget with something which is less confusing > eg=C2=A0 > snext->budget_allocated.. > How would budget_allocated be less confusing? > > @@ -2544,11 +2914,13 @@ runq_candidate(struct csched2_runqueue_data > > *rqd, > > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0} > >=20 >=20 > In runq candidate we have a code base > /* > =C2=A0 * Return the current vcpu if it has executed for less than > ratelimit. > =C2=A0 * Adjuststment for the selected vcpu's credit and decision > =C2=A0 * for how long it will run will be taken in csched2_runtime. > =C2=A0 * > =C2=A0 * Note that, if scurr is yielding, we don't let rate limiting kick > in. > =C2=A0 * In fact, it may be the case that scurr is about to spin, and > there's > =C2=A0 * no point forcing it to do so until rate limiting expires. > =C2=A0 */ > =C2=A0 if ( !yield && prv->ratelimit_us && !is_idle_vcpu(scurr->vcpu) && > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0vcpu_runnable(scurr->vcpu) && > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0(now - scurr->vcpu->runstate.state_en= try_time) < > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0MICROSECS(prv->ratelimit_= us) ) > In this codeblock we return scurr. Here there is no check for vcpu- > >budget. > Even if the scurr vcpu has executed for less than rate limit and > scurr=C2=A0 > is not yielding, we need to check for its budget before returning > scurr. >=20 But we check vcpu_runnable(scurr). And we've already called, in csched2_schedule(), vcpu_try_to_get_budget(scurr). And if scurr could not get any budget, we called park_vcpu(scurr), which sets scurr up in such a way that vcpu_runnable(scurr) is false. Thanks and Regards, Dario --=20 <> (Raistlin Majere) ----------------------------------------------------------------- Dario Faggioli, Ph.D, http://about.me/dario.faggioli Senior Software Engineer, Citrix Systems R&D Ltd., Cambridge (UK) --=-S2vhWiz3ihwpdFTy39uq Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part Content-Transfer-Encoding: 7bit -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABCAAGBQJZPpTcAAoJEBZCeImluHPuLQAP/iGYc+LRsOTOD3K9lMoBPq0Z MvbhmrDY35uOxlB48FGgePF0+vfVv4z7ZizxtDPpqYHqV9xRJvj9QhTzudz4eSTg BiG4ASmylcCf+5jybh3eUQLScgyABu0oc7W9K8itPKvGzuLQd8Nf/MGgk5c1r3Qn hvPtY2Cu9qUy3CTCz05QPG0sZabw5G1CMlWWKW3iyZYvC5Z+FO5gbP2R8SLcknDJ WO+ppHbp7kmz07emAtVfLfgiR2d8epn1+YE+wFh6KLAC8qKWosFY8h5fbFf2GJLq +V0CSOeT4X2nzclfyZGbYYH2/mzNg+zjy3ZkLz4p2jZK13I4PCvALoSg4T6VjsKu msJT+gpv/Taec94PWjJZ51pY7dPV2d2T03ipHLdNhJTwTiWaF6kEJTq1IQiePNNQ yRC2pmO1DUR5GACnLGlwbaxX3y++XYBEHWlZUXqf6zj6VHROp6tWStHfdFk+X2wG NxteVNI6XdYtcEO/LRobUjKqnYPcLXrqBKZGcPVi6rJ0Yp6ksLM47uA0KSCHjtIx EbR3OKFPAMnx+kOaUosV+NOLpqTplejTH/douq9XU8nJKQi8UJ4eO0LEhPB8QZtt eH6m4pqiHxt1NquvtWuCjUw2eY1SLO5LTGqSF6mTBTbQd7IqRjW2JNBPoh19Pi20 u84tNjlfFVZFe+BPuaoK =b0Fd -----END PGP SIGNATURE----- --=-S2vhWiz3ihwpdFTy39uq-- --===============3903065754236692301== Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: base64 Content-Disposition: inline X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KWGVuLWRldmVs IG1haWxpbmcgbGlzdApYZW4tZGV2ZWxAbGlzdHMueGVuLm9yZwpodHRwczovL2xpc3RzLnhlbi5v cmcveGVuLWRldmVsCg== --===============3903065754236692301==--