summaryrefslogtreecommitdiff
path: root/tools/perf/scripts/python/flamegraph.py
blob: b6af1dd5f816e47c8a904cc9aca7d39894c81348 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# flamegraph.py - create flame graphs from perf samples
# SPDX-License-Identifier: GPL-2.0
#
# Usage:
#
#     perf record -a -g -F 99 sleep 60
#     perf script report flamegraph
#
# Combined:
#
#     perf script flamegraph -a -F 99 sleep 60
#
# Written by Andreas Gerstmayr <agerstmayr@redhat.com>
# Flame Graphs invented by Brendan Gregg <bgregg@netflix.com>
# Works in tandem with d3-flame-graph by Martin Spier <mspier@netflix.com>
#
# pylint: disable=missing-module-docstring
# pylint: disable=missing-class-docstring
# pylint: disable=missing-function-docstring

from __future__ import print_function
import sys
import os
import io
import argparse
import json
import subprocess

# pylint: disable=too-few-public-methods
class Node:
    def __init__(self, name, libtype):
        self.name = name
        # "root" | "kernel" | ""
        # "" indicates user space
        self.libtype = libtype
        self.value = 0
        self.children = []

    def to_json(self):
        return {
            "n": self.name,
            "l": self.libtype,
            "v": self.value,
            "c": self.children
        }


class FlameGraphCLI:
    def __init__(self, args):
        self.args = args
        self.stack = Node("all", "root")

        if self.args.format == "html" and \
                not os.path.isfile(self.args.template):
            print("Flame Graph template {} does not exist. Please install "
                  "the js-d3-flame-graph (RPM) or libjs-d3-flame-graph (deb) "
                  "package, specify an existing flame graph template "
                  "(--template PATH) or another output format "
                  "(--format FORMAT).".format(self.args.template),
                  file=sys.stderr)
            sys.exit(1)

    @staticmethod
    def get_libtype_from_dso(dso):
        """
        when kernel-debuginfo is installed,
        dso points to /usr/lib/debug/lib/modules/*/vmlinux
        """
        if dso and (dso == "[kernel.kallsyms]" or dso.endswith("/vmlinux")):
            return "kernel"

        return ""

    @staticmethod
    def find_or_create_node(node, name, libtype):
        for child in node.children:
            if child.name == name:
                return child

        child = Node(name, libtype)
        node.children.append(child)
        return child

    def process_event(self, event):
        pid = event.get("sample", {}).get("pid", 0)
        # event["dso"] sometimes contains /usr/lib/debug/lib/modules/*/vmlinux
        # for user-space processes; let's use pid for kernel or user-space distinction
        if pid == 0:
            comm = event["comm"]
            libtype = "kernel"
        else:
            comm = "{} ({})".format(event["comm"], pid)
            libtype = ""
        node = self.find_or_create_node(self.stack, comm, libtype)

        if "callchain" in event:
            for entry in reversed(event["callchain"]):
                name = entry.get("sym", {}).get("name", "[unknown]")
                libtype = self.get_libtype_from_dso(entry.get("dso"))
                node = self.find_or_create_node(node, name, libtype)
        else:
            name = event.get("symbol", "[unknown]")
            libtype = self.get_libtype_from_dso(event.get("dso"))
            node = self.find_or_create_node(node, name, libtype)
        node.value += 1

    def get_report_header(self):
        if self.args.input == "-":
            # when this script is invoked with "perf script flamegraph",
            # no perf.data is created and we cannot read the header of it
            return ""

        try:
            output = subprocess.check_output(["perf", "report", "--header-only"])
            return output.decode("utf-8")
        except Exception as err:  # pylint: disable=broad-except
            print("Error reading report header: {}".format(err), file=sys.stderr)
            return ""

    def trace_end(self):
        stacks_json = json.dumps(self.stack, default=lambda x: x.to_json())

        if self.args.format == "html":
            report_header = self.get_report_header()
            options = {
                "colorscheme": self.args.colorscheme,
                "context": report_header
            }
            options_json = json.dumps(options)

            try:
                with io.open(self.args.template, encoding="utf-8") as template:
                    output_str = (
                        template.read()
                        .replace("/** @options_json **/", options_json)
                        .replace("/** @flamegraph_json **/", stacks_json)
                    )
            except IOError as err:
                print("Error reading template file: {}".format(err), file=sys.stderr)
                sys.exit(1)
            output_fn = self.args.output or "flamegraph.html"
        else:
            output_str = stacks_json
            output_fn = self.args.output or "stacks.json"

        if output_fn == "-":
            with io.open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as out:
                out.write(output_str)
        else:
            print("dumping data to {}".format(output_fn))
            try:
                with io.open(output_fn, "w", encoding="utf-8") as out:
                    out.write(output_str)
            except IOError as err:
                print("Error writing output file: {}".format(err), file=sys.stderr)
                sys.exit(1)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Create flame graphs.")
    parser.add_argument("-f", "--format",
                        default="html", choices=["json", "html"],
                        help="output file format")
    parser.add_argument("-o", "--output",
                        help="output file name")
    parser.add_argument("--template",
                        default="/usr/share/d3-flame-graph/d3-flamegraph-base.html",
                        help="path to flame graph HTML template")
    parser.add_argument("--colorscheme",
                        default="blue-green",
                        help="flame graph color scheme",
                        choices=["blue-green", "orange"])
    parser.add_argument("-i", "--input",
                        help=argparse.SUPPRESS)

    cli_args = parser.parse_args()
    cli = FlameGraphCLI(cli_args)

    process_event = cli.process_event
    trace_end = cli.trace_end
cĬYyV\\_ c{Iڀ6,  ⌕2Y?׺"+۞[_-wHw:Pa-D0~ \ђ?v"/hMFͽI l8 !\D LdZT$_ o]\HP'9Gdi'{ Xvpt&U L`A~/}\4UJIl d*Wv:W];?g8|:>?Ŏ e8|XweCH:ncۈHP#q9 Lgqձ{(ZFMxoW8՚₆Ln~1򷎒?w.#o g FnxpHRv_W>urAޖ )mgGGrR)9u p'Ud֙j>)0{ ȑ{; GZ XaRum![҇.hVGkKe){kysES"P^[w1|s8ޘ&`wPG7eY? rőC3/-R>]:W#7X 6ut pAޗAoD~H\`VܜyLcnqЪdfUKf:;cޭlkmdq{dP?:Ro~і[9ȉ?Ҷr0C2ak{mf rm+٪+jD [S|nm2g?j5[_L3;K@gY3%yW:p^|[X<)PGlqMy4cTRN*s< m_{jۻ}yI #cL&U0%& yH=cxnAǑKS[Q@  QƐ˘W+צd 569x䧍Ƒ!!TIl֦ @Sh͉85GS{烕 8\(XHۺl;c"ºlxVjd,|;dS$v8q&KF٩-9qĹr/|Q#iϝI7bEsXshz_(Nf7ToNlcQYv_aǸUã*A5)ac׻l*MN8pGˈT>/7ŧ ./Zms_cPX"KUSӦYB_}R%tZˢ ν^OKo'N065}ʥa%رEg$=]Ѥ'_<čx@dC0,8zϋ|z(8n8fպFJƉ簹ō8‡ljY*A,~2(VO?Q8n RYp ZtSxJU& b}]h߃<㗰ٴ+~uTQ귐_V\S zD0؀ګ:c$lة"f0YTjt46c~4F|%ɶ]~[FM @c)z^~%_l$us%X˺}ⷼ NFzvkՌ3zFlqj ov%>5n8viV%1B͖!8G T{҂EKz^2Ϫ>/Bif9gl M `b$w ` ;Th%fozSA]wCrmOx{oaʠX[EAٝѸjVUe5D}S{= 8P"SCХ좥9'/h=зou_vug w럖 rA@R ' m`pL`7:pAqSj3ZՄD薏:KX\($Ԧ~cA1ɴ:1bzܨ0[~af3{^h 1PLSٙ!< 1 @";:TZd+%+2R[].;gd;t8eLhs4o3hZ3 QZ0&+RawDu`UcB}pJ6XnOwOଠ {ݠ튳= 2"IKgU G725>u[qKA0, 7wق^YlEx~5> bc;\W23&6d5szLKGP^p6Iݦj:d?. 9Luk1LYP#d"34z15l<^0jR2]I%-( q"X4~1y*y;Z yB#G r*ah ލH%H %~X]#vR LUu_gpIjz4TyBC tT ,wOF8p,T"4Lj|KK'ձPɨ&!ᢰl0;=Ӕu<#i~mO 퇇yE/2J x!fkt ^۵|>)a3: bnnW*dRcMhz|+Z&!o9_*{ܥ!BECbm[^*f+ZN`2nAMl:O 3xn_饉ȳ,Y ]thMbe2 gƒC; 9s;% wkӬ@jIX^|9L7bv%ɹAs1W(F= #J4)H[h^J"Ri9}ªSi  ԫv0Y*DDRd.ar7~F3G-z$~D#N|{FYIfQRҋG rΒCWe)Ö*<8F=?s"3CX(U%Fh#L-5 &'TCBZ _V$|kΖ`f_lQ*Z,Ils9m{S΋~2C[ 9 H^1CΖoȵ]kꮝ =ZkSR<37̠/{÷:8n Lh׮UU[rAS}5GcgJ Cƅ_/CΔ9'rPA*C6AM*RzN S8BgQ׆ffv*4/8 D M% ["ݝ0RP`>hu(18^ϩl,6P sF7C*jL*lNiOI?<,1DIU]**c2%)Y-ݕ^o3.ɱF2pqK%QUU݄wQ9mt;OoܰHUf|”\Bj<ʿY<;]^=.8=Ѽ!oXoiGӈ(OdG^(9K6l԰Dr;3fVjy,ưTW^D?n+p JBob^;8"|L]dl_dSt2˼m~f *3[vODH4!H t>]Ti p52i.LO;F3O4vHqh4&2M<,tvfv הR i|LϽ\eew h.EH񨚮hǯηxNdi T?"_d1lѤ]AȎ;L:v\:Eh.v9is$TP'=rIk k@juzG4|J'Vܵ&9KP&`X!.r D, ( eq\3"(df/1Jhi~)3%VNٮVi+&^asUmc0X>g7˱'\JLvA l`Ѧ*3kLq=.68ƪ{u`/CUXbUѼYp^b<Xօs`IY;-xɐIti8KQ7_8|?M0z+#ˀATj$;{_!V+F<$QXy Y[HrV @N(h jV.H]R `3BmX4B0Svz0d#kOhd(&%`4,QsHKcW]^w~@Tͭ+ܝJ|w6˖/zΛж[zS=0 9 gR ?rm,vDZ~\|ꏗ^-{whgա39{Lcy6z1ELNٴw7WSz,Rd/<8Ҧ^4U+eYByYPqo W` t*A 8orH;>!LZ _LF^.Wp$/lv3D>> 27#BxjI`? ~vt` )^k14BXt7٬>aU:"ITjUVAч 7dn) 8c-9!u !L"P/i_rd^`{OȗK Ns?3̑npN3fl?fj$]rF`9Z)9zU|ɖ@{b!;! C_ZBvڠ5i6PjsR Ρ\dG>oiX[9jMƙVacKu~5B>xON2Cvi׈M V5code!dZ|.[XFf=j׫1mֹT[JmU!_i0™@c6'%']!xr!CtR(|*V*iTW-d }*~U"ÔN؁F^+*Sd%Lη.rpҷ|8T ٥Љ S:{7}Ln܍> 7R*#fehŪY\Lv>u.F}'qj1@7^^UvhFszcZ8C]kho2RMA==|ĹCl.Ti|spWT >u POi;)faǛ[ڨxOɾil-o;V`ِ~Zodiԅ,!-ُe·B=?*U^5[d8F}:lstIR([ҝ6f#]I}iOU;$"P^6RDqjh SN`Ƀ-i2?,i?HN|ZdwNQf/GɢB又I"Cyy?66-[/&S/rk4FAm7$nK#A@"|-Od@&H$p)l,MCgQF*}Fr_k]^{jp{7#񖻭UV4e5H zpGHbAdd|&ap5F&rA7؏lskk]uvo-wLAfzl1\h7NZ#ͼn :$yprpkOCGČjbFa|~iCoriv+{glY @8p8Xd{7|[iXwZ;sqLl0lAZC1BH{1:K+=hd _"b(CS޶Od:n9d{W[7vsC Y@2 ?Э"33*x퍴=-hoΓP:v7 hUT`FRN56pҷ6冼92!]iiu$AVUesgA grTzMO[v\֖oWaߢF33eAЉ o˟[QAAbV{[PҿY˧H7Bd aǤZAПxV{ V.(٘-~"\kxghSm~%:`/c$tpIn!5Nok; ~rbPyD?$z}"l>N'Ol.KvFOh@Z@xRب6B H]+N[}]k 0tς4j՛UiTϜ]F}b<|݀;b==}13Q(@$`J[4Nb6HdXݯkwdW%eT.M'wOadpi%ŞN]!䎨tS)@/q<|S1|J(wWamu%$eiW$I¤c9 \{L(zb6+)x^]}m-I~I,vXmԻ%ӑ;ovpG]o=Y ̮[ 1͘CrXM36F Bt!L?(pK =K(7,w)eY>x.V6D3XmGs6SܑT8^bFIh e 5wOqL]xh{b`\g&iTĕ;/ ^!4k4j eB[N;2m^Ćm2aMf.g8ԇ8rLBi$ #2 {{  ɽg^tǖ |2ԇ# 5E<>XL- vV2 Ԅ?]HYOM==C8_+[#\Qj[T7]յmRҏo])I !/XvNl!!uQ%T/IA 9;tFևO7޷kM1l_V4GȈ$+"YĘͪJ= IU -7vw1|tP" |r ]Fjpa\K6Տvxiv+4RSwRc`,I~`+fqJǑ7v4w;ݳNwvkXDT;CO Z)o^5ش>V>XsK<0hV4S*G1G'i:F{+|=s,,]OfjjDP*'ݪE+(6;S6=ŇRWOk-îݺFia W #OdRކDۣeP¢P|S | + 5*Cܝirˍ5jz\}V v9IpFzN* ؟l'IbaAwO]3jo։pW75 )c} 99k\#0$vH!F^gtޗ~3<%kKlaSG4"GIà!\7mr 8uo4*H;W!|4)3ذQ@gi-X[މ<̓!sH_DЂMUuY2ٿ51ZJՃ[&yi?T/UsVh6OȐ!U z<n[Ya _)\0^n){-^k)("w ʸW+vv*7~鐂N tce2qPa_JR3@Ow/ZOْMA~:\!2IO9e/E571|uYUnn0Pf )f.)x . _O#Dw겚JrN j,JYUz@;EwHC7Q8Ajn(W{y' vi̜:lx0N&V6#p5 .ٱ}<[)ݒM^E㫜$S7NR4-rr1fZNch-ή(I)k,s= >td˰`Rbf6/ϵLS{r%R*鰍|xqjU5I38?e@b KI%K0|ً>c>iC|DИi2ލz6 d *CCD&=,B[7q,(o7^Tuoa칦m+:Ug`gܛ*G9ʗNj_$\-Q:>4|/Pzolq"v lxA 3 !8^ 8?x Ӓtk{H޴O^?I}ndA^՚,LݗM)X8KIʆ\CM&Ux76M^zdH׎˳N6:KU*Nү}D)(JxZ^c._,[ozwtXn*+_{jvB6?nݚE ,pFy S#e,nml6fIV!荲?/F5PXd2i F9fÂ^۴JI-92vmHwz/?6`HO孹51"+ ޮ|zwE5sFߌTO?H3Rf47-sKO Pg Z ;F@27\1oL9I; WU߁9u#] L FsTs}6+ HrhlF n6k `l3UCPpؠOt)ܥrva;!pn$LBA OL-mќL{+@},ȂMtYpPR jk;&_+P$%t7!bz#m"gLEj4sGbK^cș!#xc!ݏ\9=krObbm-qnlZ11 wsR ّ7r~'\ɾA ϒ܋"fjhHtrn; c_څlw@-!ϯү`['sɪl'KwQdjMϚeN=if^gz c~^^ k;8L3lɐа-Гbđlh {mrA1\n֫,KeV*A׵|XkwWKVP-Bri ; оQ>KZT(j𜄟z4YU¹ 5?|jb /يL:H&i)j!L sѧo܄Cd2mwI @&r£#f%*caX..Rs$͟lf)BREKGJ2fr<0sd+Rt WCL 9ԉ(n`Y$̩gfvlP?՚hqBdl`񝭒^E=1)*h0@ˣRufV0 gvSF[}BuH띋W~3ۋ/{F?bAsr*REԵ̛hYr.!&hrϟΓ8,aүR.yKs\ a<X;XP~o&yY>WU&^$&F';1TBTGˏŖST:E;a@цL92:hEkEZ;1+ߌ>52瞗./ǝ_9ؑ V~%VnVuv9{}\ZAyz^j/|CвH/rcMRث$4it])\s8,@5W3- ^r ƚpez9~sh?).)-s|:[&$0Cuͅ8]I3tor$En?Hj=*UWq{k^˄3].b &\O]?\n t@|*G뀤H&)ltJ""g5gx᙭KڢnrAm@kSV9/F$4xuLyYN]?.zy˔X1Kdt!PRwI@-~*Nm{] \3e4dbAB\2ħ5mdko4Hڞ;L93!G&< Ь,Lɫ$J&k4U8Ĺ]ܓyl+مu4]KՔ)H(x 7JI!S!I29$t@Fb~'or zRץ{[@`.c1daBG.`%hK+`ݵ%aTkH`4Phr^ -PiE|yљ賍ؗvG2by8ĚCk4^f,R;IytmXcs6%%a9?f4}1u6Q\bY=j@^+]]=٪#md#s-4wuCU*ޙ^׊^`>򑊡Q$&SV˝FSĀϜqeO֥P~mX6tfSmbr?\'~O""h;:_*|iͦZ[7@}h&4"6~ Hmm>aՙ)ٖ< R8SQQh4 ~ ܄^4 )Ԏ&A) 肁 a~l| x5R%jXV6oG{h8n8Lds.{ڧݡ}ϱ!Q,-Xpqz%haLqt'U~:Ƥ4$ =7&Uk{Ӿ8u!@("֦~$@QH(} 1LDr\z}'zIF6k6UOakTUNTgi[J> SR~|.guD%xr 55- VS]zEps:_شƀA9jKʪS=2kJj8~RU|LTE< 'ZQz쭕FUxpsV𓲝+$mLt6qwc|\ZQS@Poԏ%*3Af+_ 'N4 bga U\\n);)Tӓnw3[@Ӥ>jO1dt7+]E-mF]h=/Q|elv[7{ڤ'`+&r i)sq=.za9|W r)X6o#-b?;]ӟOrz@BBeZ󠤯\ 7H.`NaU dv$G$@L^Qش5v aCn}R{&jŏ9ji :qAzA}K6}k.wN'7 [b-pYO]_fǐ;7BXFss-۟P$b]W%欀wHVBJ. |?a@²GrG??`G}xft@SX:+ , t }HD$b&ll0]ɝ/)淘>5Mc뤿K@^gNߩ Pq=0}ZΊ&% K\3+EN!ZBH;{}zoq:&b88s `Ɏүt R|f:lCT*  a Cvja6h:Ha:հpq !6?'QT <S6v nkRF|'=t# &*XǣWtC-\֤/q'{KDNǏYV_TL?9;rqdOQ7vtG{7aOMt9BOxCZ{ NґR&$G iCUb&=-^UW(fD{xn_ VZ2^~"g;jD  q-ji~RFkҝĦ<6U>w ~Fv2e@ L̽~;ȍ6OqYm5!25_{;9"RCAcm i**U]raI 5p%2ɣ8WǗ iDCgC,'E)Oa- ^t~{&?yڞ*E4o]4Ud"5nMwW9W3U/m`MnOR$Ҋacjz;k8sn/-6͡ /1 Fg.Jȡ$Ht4,Q-&)zV*9"| G#؁1YfOF!׫f{PQÎB؎UOyMrjju7Ĉ=A4 Ys3@e$;]Or<2דaiYta΃1ڝwjxg̍$c..:iKxw7Q^[Ls,_оK b `sd:ADW*OiG`Tx';;s 3)=W.-ܟɬqlRMJH_KZ)!?+݃p0Y fR~ʣrKdSJeP-Ǵn#2;q)3䪧q"WkU0=ڐ$܅l6,5)q{7/dX?8!|UOlT ,"|G ,,Xf_ԤZxU8p ƬʃscC(&8 K{gw [b^u|?9X7WYԻ3E} \ !}l;b.N "ok]"Ӊ=Nܨܯ*7jU<\'I,k'1~EġLjF + GI6=wj,H%s~6FZn(i5:fsCUZOA#{(I"^Frfnt_eHyЫ]"%~ s|3ү6D?L9 aXiZ>{ЕbR9${amJD>&\EFxDN$$0xp~N^_/-QVXi<;*>[8;{3wÍΖ։,>zu@+nfʍ^Zi"Ya:R-$xbQ)n]i;$i&ٜW]W+)9.wa0q mAg&5pt$OgZ{3(͠NA(3(ZҰ=ȷBK^h6*'~G r4rM!Pdl$މFy$|kS!tʱunn0W%A~0XRɥ4XfHN\+2 hy:~O#Z #P'0JӮῄ;AZ*s1d?5S[Cj+HjY#PYQ:ݰx;wH{E/?9d{W!kpFӍ.2O2"'# j+;w\AB]Yd'օ|׽|.CAs0صfCnCM[4aKW33"*s>a;"'+Ue5nQ," Sݩ4@$@!s@xw+]tp}~sr3nx0jjG(kdj8u67ꀻ1P8c묥=/z?x0у6%J$jMgP)?=at߬|4*<ڨk劺Qr&+kr?zm[}ңr&:]{~7W0R@,6;{||.Vp9$4_ܗeNk#!֌rWMmmVl;od3M+ޝX́ Ym{sˎcM>YM'Yip2E$ R[=9Ry \pXtP/NyL3K _?t풱1dr,CwkQ¼ϊ /qC5*%Vߟ)PO"_l(t |+@Wee&}j?`@FJb!`"85H[lLo{G T@JZUB'^AZN]z3#G:B%\ˉ6ϖ6w|?}Tld"G.-0*vT?fF+Y Lu$ST#qwTng4o McuwG7ًmn&WL!ɼWD Ҏ`H^~+K^b\Ԑ91Mgco5H8k&:B|f/!$wEv;5C-սfTp~ԗ{$WIN-]KHL-J=Gff}uAa46 MYv>,R HT@e"O.v7-X~Y=X5ɶ@,!iZ{KHe2y8&Uhy243*`.sA ,f55̐՗[:^ _ E'Ise/|kw֎&K N$)7]Bf a ـO_+@;BgW{4oM) ~a-C`nǛȉjj `ޠ_f!F6²j?[rvW'#*\n@mU,  ȌK%Gӡۃ JXԭ-Ɉl\{a5. 㽫ƔөRZ D M|rh3!MZn[JGlrvkr沘x̞79r2Ш!G}qX)3MD"^\R$)0!ԟGOqipf8!ws>"$wdeL/+/Msc#}ux7uCe;'tC(sK&C5/ [qx=Wwdt$W 7ޟ>>DGW$'e$hg:|7̞+!VeAOdb{Ns|zE@j~$$V/EsLlpp66ybm"?œ 9EB$@zBW;~0ӔR5i$TPRN )x̟[Pm< \PFa w{Bj;\矧_o]괞Zܸh;E7^wt4ϷPү¥1#iVuuSW qY-:fu!4f{lv$͢Dmldi^1Ҡ1A_uJ PFUT -6T*yEQC e M-i,twY!08bnVmʒjVиvao)Y/_%W${4[-7 VxWI#򉪕!NXoK C!B U* kYhxhN@ *Qb[_Mofj8uz5%Q }$pr4$Eyu,37TBD?,R_$Vn(vV0JLU&A?IJ;{ #}C:Ot^%c?tP[ tn v\63iJ}SR\ O]8q}gJ%(i6[/О/8&!XZ9pD ұa,էsdo|T.?W x ֹtr#'XG@@$3qffeđId tEƷжÿIQ7O]i$ep)ӹaT-^N~'HRxTm<@X"ł a 6%9ruaGyƜb1{]TYZc|@(  m HnίBz$dK*X52˪@{0VރL0L*O8 1sww5n2SFpΩ!l/6,""E޺jyk wϑ3tOis(BKu7&67> LRgTVJؤ=+|9`ӳNw#' N]7v=mY/NC3Dl# DE-äx$9˳t5:IފGҡuiJuwm{|5ː{TD*ev x 6m[9B✒VR{ u9GfO^GK8v4/B]̟zR[^M#y],FI>C)&gcM`kf`SrE#rB׊C Oَ$ [x5`SjkC.WSDO@OG>T,JdqiA˷E;N+p/uC;dFK)I`6y+WCؘd6IH;6u-7P&2A{~b%g]!r1X}egdhF{}gS [`}u?ggٟՎ+b}V <5% ,wһ|[ڵ̪Y[ eLt$JrspN9L5L)`K=tc4n83t-yY^aiXZq_j6U*$U5[ [ȩvOHp) $ȅ8>t]orIzAdiOZYqNv?^I҆nXN:]L//)C "ޚDʪU/0SoTTр8]*tт]2SC3Jj*,T P Hp!Ri5s~KءO(2ac%ԓETT ZE)W}p^|kg_4|jCMkv!VTqpMmTI UDD|aYD-iH.=d=DJ8G՝OF@k"O{{8RQ&ԛmzwdE9T,}bB؜EkTJ>VG|䈙{A?oNIkIk#ęQ¸n$9VoFUk:xJjuEK`4Tj FmPC8*c v^Zz] 7ƬnXU*Vl6ō4dq1YC&NRH~)i9[wO OU$ $Ļ..P:4ҭ˲GnN.>GuaH排+8ZLfD=ӷhp}k,EqnrdD¾=(߾Ȧ{3ݍafl@3/&eH0-巒r aoV"c<=rϟ?1~naD<=6fSC/)m]!U8zW-|ְ2#Van;G)b;s5ÒT2Hv9+NZEuz=[&1$crl4P8%3+^@$-]Mc34ZyX8/i8]A75$3| sf;!0&+)|2γԫ xz~^>bGFc@ae 9jY'7ȱ_bM[bT!`Y0LndB/{Uw<F~)EҊve"Q+$.lGv@o5Oy&4lQIGxn{8TN5\o!^HRo~+籮W8dY& _L@_1HKo nWJxkt&g|99TbI21#f6l=ɋL FOp&v6<}5&6TϼHSd\s4%d!l=vM{IFK-s a奅bGGXo{'Ȓ]$Oڏ:#M IΆ;ܸg[Z[p N*$I֮ך -0f*:;HӯM\G9&}nF2IoN3铡 g.2{ OAHUu;"TPugǧK&3<i&1?].P$*|˙x-@2Ql,;gSrvdsfart7 '#uNN+*HNV0(\cg%QKw-FyTFPoZ0bWGf|[]FϛCQ+Ii6@t.5{C{lzW$xzү2|?׳bҁ3v3v}i̚ _|OV^ WPj;gefB.Ft<&dcʾ)ow6%C#8jVC!%`RsEndab&5KFg!-򀵡R[Ei LvqamczyKfh4Y[*aZ4C 0UA_&FqX=)&8Sn96onѧHJ8͠3= W!3S,zjǭX5M{; 0TeH4kzsG%liI3sz}5"沄|;rhftSdnf#F4{ i[uϽ+v=)uL lpo-sf cCVi j\F>V![#5 O:lFͱ$ʃQvЏѱ7WZPHHIWwz p{ ~Fǣ 錞}' / w.sօ_|(H؅_iUҴ=SЬo?KRP Ăg"}%s ERxCTK J^.U&IY?,v?VVJ7vWHzof1*s@ -RMg#soy H4ŰتjU52?!sȜq| I$\:$SӏCf;%ϘI tH|w5T%qujT?IZ؍tILx6k'e$'_,X6f7M]Ο_Fn$1iz.:-Qi.%4FD@nb?#b,rƿ?Iwo?}7rHoƣ٬U^ZE-oy:+sAIzv!-χO!7v=2VK]X1AoH|^`jHP&D+;TES(`?zt-J{bz3˓6s&8(f !ucAmCU, ;͛1YW9Dl';t޼QLv-%>a 88 g-Pқ7f9XMǛw3b!C}aؚGH[@bfVV*ȏ[E 4ut,"s4zҸ| winc[p+WV[Eٷ #,(1ib0@zWKE*wJ汃LC?^3ͰyŘ$./@Y• =.EN'xS%c]I^-PrlIM5\ݞ>>^Uq〴:QqߧTWUnMv;w&22 u(ݟj-jEVI+B{A|4rdjI8?|2H#\ =dv%U~I/gڳw4\Wǁv%O$YLIg㹙aƐv^LSiZme=녕n%xci[\W ofCvijxw#6ԥTtN21 lF~ZXƋ]#hr+ϙ O5? I߸w"=IHB2?D^OE*9H:iB0g`s2.'GX2\1R\ Yf}|ܦEqW$ t}]|F̚ lFt%B}G[ ȩNNbeWNFfİ$n(1__~ouVx7 T5Ey~dZ!u_mG(/'~t 4{жPƘ?$8 }Mzw1@.HkǴϔ7֊ДHR;FH{g6_-юJRk :Bdad,IR8G#W_lד"2aqct w\12v_."J(3LMxǀg>߭KqI%NqrѶ{duR/7dhJD2I51.Ձ-Ao'~%:C UJn/v(@s.BKd,^յ;[kwkcO~w5͇J .hzo6m,eX49\x17FAիSS'0E<]]H2}ZyI^1 ̌#[%s!e%KΑ3=1TTrj-%_ZZ%NEࣲ2Ϩ.kmT}pӋ YBs g !Q/x<hwdo{qU@,t$(hbj6bG ?5`&'tkAt(8UP)az$C ;YɺL =7lu?6vRT@p {:R85Ϛc){o Ўfs|6]U#& %5x:kME͖\/L o~5\]1+ֵ ,aU7t *DQ4,]Q 0?E8]/KGt-y^4E DObsཻ> c`(AxLC? bLnыMn,u˒=D4:e  2=[vG;'($u'M25ꮓ'cBJW#er8wL^gBWǁoqE/\T^e\ԍ^)ydmNfĴCIX4ka~ewq073{w>aj Pj{N|Iym%Il!dp Ѷu؄]R>+)_=+?nҧw%t+dt- r칛\^| @(yq;'9jCIxXۯڠRasX9.g*J1\D)dҮ`B)%OJn/4pG Z#s|,iNߦ o_r%)#T:FXzȫ;v^\hrrR8ˍ$tQ1f-_LqQv1(|42iv+c%Otk\؜ l掐19'f]$u:yxe W< ̓D/ Ȳs?'b8ef$';GǕͭLak֣?Z?߾yj7~'63t^YWҍmfMt_4ĥZ:ȅzUK'mO7({!W5~2IxGQ4LJ7+ߵrwϿ "}w٨^m;ÿSHuVg+5wG48M@dIP+%0H%C'ݲFKg!3=d]+/]`!أ!2hdadŻ%XϏH* V&\֛71~WH~{8%Lc<([s7ya<7f{ZaJozo @~ aA (33'̥b~=f:ߙ"gPo~ wQR]&r .ix^`ܣFfBId@4]@`m2]ln0 |8rg>e\O|XׯAJs9|0z:<(.ʍ_.j_P (7s6XC7m3/..τJ DO<%2C$c P.$lzpF )geGbUSB?]qy\?J<Mkww|x C];EЬ`CX`/J{x4dErHc^<3cKs}AIұM 푊UL*&C XY,/W6QUr9$xL@nMB 84Sߜȋ3'2:$#oP݃R:ŪX`~No2ID0Oi6yPidԛ̤7KBj" &S` rƮoSxB.̂l3#r+_&g쑼Qhk"{?Ѫ˜W?^nz7kArfXY(>%upI A!% <e aN)Ap&ΆAa&9Jj֫OSJKGU]o>OW95BX 3L!& E4 L eD"am R?3vnа|s¯LI. TZ5)|VFoM\#j0r9O2e)I|}rjBWs3}H; TՓ˥EK~*v^/քEc{] KӟYᦦV'84cc bEp%,ۉf ]h;+ {!) P9s3<8 \!$BC?r~I]0YHS8<8?Otބ\;N&x|9(H ;g&,%hyuX."-GMy$^bhm9O(R[@ H2?؟@H^*HI5~/lu ^`V<: 2&' \9hX֍g54Mpqk X gGPs8LK{:I2[Bwn]LwI蠭NJA zחqEL!/`UqUc^k̉%oDA13ro37x)9쀮ԂL',ЩC݂ KR7Nǔ;+Bx⿣ IbfcO@XR`$UR@ۜ H xZ9Q(lS[ 'Ymލr7ȷk|~t)n?g$!S5F0(W@IqiP}HLM@p'i(ɥ ]BfV_QVڀw~BA%s)D0rΖ#4zQF/dڦo}H~@dMƍUA%(a岚ϐY W{.gqHP ]HV-Sgϣ#"#S^U,ث.ox0ƛtn~?E<s -%R_oi"ܐs(,2!VczV7O~suGOD'UtfV.W<;}6Zýr'-LYT`v̨GCwb0bAG0U1DnQ/{T?m@vAh,W7RyiZFI()E.o[DU{wǖ\?q4 &a0 _@{x@ѱZ7!Yp:ݸ7#Qlbo ةlgH0&p:E1b$.p @۩AzC>$tFFPo:7Lߏei_<Goa޼|u+&o EWBѿ׽B T˻A+*UzZ>jYJVqߒJsM)-ZU65S1<NMe #X gFp(KG@{HKQ4̬a<]H˲B%K?{SU96[W*<-'Uig;"Biޱ~3O91V^'qtH{-* EZ N />J(kS=p$mEC_IϸǦڒ+2&6AҖ1%@e̗Iɒ,B2`*6 LQq^겐]8%y6" ΒsLǨ  W%NkS(}I[9&wGh1l\V\^ցBXOVC;FwjgwF)8[с,PU&H4N !vZ;QS=S\O7]{Z2h-?o Qt}!z ?|,Z 8Ok'q\SN3)fZeڂ`IB1%J-A܎S,JƱbpxŬb`&6>{QFǥB\;=gB;ym4"J3 ~"6VE>-:<Xokl\݆g}_7p_ֻwOΩ'bv>6$0¬HaRb + K{WGhq7Qo+BvLoh.cy&<䔤Y{8yU;bR>%pnZ \HbgW-,Nv_P)U)~jTМ#)cWٙ&76TF"jz!Å]"cBᄡ;AvD|mHt4 PK:A3"ƩuR6zDe)ҡq@Wr#KJTLv(xEU.#_nKBmu Ob YyHK SwH?cAᄫE,h2H/i">tMϔWw#qHUU$DdMhx gCiZF1=^n*T\aOe&}208|Ovǣ||0V 0|Oy-VέH;ыyWk+897hE9.utQY#cl2 H*JQX/jZ0S,÷qĴ*O_=ݻz0x2G`W{YE/'g23Wˇ\~9_cڒ_/_⩬ >b}W6x hoٶ=wg+D$rZ?_@7\!vʓK<'4Y~tXTă ]ȷG| ZP+C.bkTQi;0zoG|)LE)ɜ+=t~/8_@]Pt%w5>JiUzJޗ7O7|_*y?'7xZϗYv0{r&Vv۹b`τj(|‡DZih?@,\մ֗c,N<҆,븬P4_ Òy_SiPJ'1\q:mGZWoޜ\U`eG&Q%@p4Z7c!qC\+d> 7ދ0?P2!kt*bdUbFjؼ'Yj5b2m1G[xQζvˎd? ڟ\v?t9nb(tz/W$eepBdD98HPZAJq~6YL/9 2WW#Mx5Qy w\7-n˽HJ,)e,ʹ"YÒPh{nitڅG&:sDbUZ5ұ7C Rjsm=Lu?܋MefbX/+ NXe=nmPjtDPyVO~~*Ұy1) 4J)JN[! -5hX]U`6Ɇ0#FM.nny~*f_q׌qEpN3C=]@/B)72`]¢V)jhI"=*N׿ biYk+> 8"(`AJeλ{z̚v#u4/tև?{_m8l/л![d)rkQ~|yh'ăMH.+?VTDe"zN[?݃fs5 h8UA24bn KzaqzƿJD SuV/ ]Wՠ͠„'5ieuZ|5ۿ2O֘Ċ0 Ct A[t Ɲ#q*fOuA Z8@9ыG$l\@YrhwrCTwSßw]K e}.㏇/BLƍ^Icy\ss1| Z@"XH˳V-2:+B,~J Wp`ׯr0כi3؏~&iRgje3Kv<~QZHT޿~߾.&L%meĪkhڐcxG<1wd71 5\o "Bz-/^5D @4\ߔ1+:)Lok AmZ̿F?̯RE .GDx9 GK\9KO|5zL?t\nLS=5ӺΐeQo'$uaRbâYjeXے5e6S3MaW6y9QMSP"4gVpw^t(iJPK&ˌ97zQ >2=vR,Eu59 R9,x{2]24 Q ӎdR>,sGufU 1bcҔ+gV8c2dUr`{ÃI&_xŖ4ъ Q:rD8QϑOe8HtYyL1V'xd7ZsQc/B}eyAL'M9y<.[J<+Xɬ!y4Aۉb;0._q*;۰\p1iEE{1PBתQQ霿ָ8e4BN 64U q|5Q)f@x_' d7V8])q"> UQ-J|4FND㗙+·3,)wl 9uoc@35#wT?;l,dT" e\Zf>@`djF@2loob16U/a5(4SS?k;`mz*Wd>Ш4va.ܟNe1۷o v.duxůT a 8L#jЧT4R݂c s\PrE&R:gh,W_ [%Y'>䣗N)fp_'&,O~ފs}֦*^rOD-gPi~@t+wVR@FJNhXDKu7w{Vh8GQXjȩH^YeNmG˴WQQdZvWFXGxzoC\;;FYHxM%$x&BԚ,)o3l#-iv? NEӇ,c]d'D/J%Sg83TA'E2hh̉.Zfg(pdz/a890:m Y!:;\&=ww{ HOj{pb|խ,yz%iNȱ : =#iR(sU mmdcwja#3]ӆsEBWz0tFm8+4D9 6Tr3rŝSk.b9 M\"$s8 {!+ 7Ftl]ɲшQOxg+z]]ybiUQF?v fּPl޴hGVnQnD(W3Lщ5D qݎ^쟱E!\p\E۵w߽}}l}ٴ)7_ S6.n0u 1i6)׻/qL4Σk uhfcU^R{-fHְncymMy$I>4 n t~&FGjTDFU` V .po_ب]9aW|T& yXַ0"QBU_+X{yQ 2pZ^ 1&DѤVS'Vg.ZxԖ [ L!!MM @%r!D:X*zCrFXh*k2t{XUNWWPIQMs{At.68YH/WϊEt̊BTB-S0ȾKM+bx1k.KkV@= l,s=-_maaj?4r\5t)+JX:qW0-m2XZ -1\X,)$XYGi#FZbQqT `_m"̊o$`I3|0:md 8FCPf18}, WN`cVKE=\!oߩDpNfIYiTYT4#KiQ(gjb<-ir?D|Y{Mn`pH.msQL6Z+g6V{i(zF8e䟩qijP>`W|1_"}'-LVәC]3ǷpHeG׵Tyt}]V@;z!Ђh8D?'R#Yyh}VtZ3;Luq" RZrؚӞUXWcT0/?䧃wo|odG=iG5y?[OĈl!pF=9I8{1CyXF)JŝiV^l^w89}Nz&Li\,;ekw[eeKT v}_L6ney~1?9XSTx)@ BE=_!B;d#TL*$q?sR留KĐgGgԪ,/4vzA򫲗JU0:ے )tyX0yYGCpd6Z1򼲐30_-m&뭒~1B{Մ9OL+!kL즣P ב@(ѳߒ-88ilw>|}9ȟ'.S|NƞWB=I^p}eJ(o$ UKgtw 2er_&6"(4L)zSR8_8B6/1FA>o#6)L%Ԙ$\6;&<&Vّ%E.j/ɨݧU~i