tsung-1.4.2/0000755000201100017670000000000011701017143012336 5ustar nniclausdreamtsung-1.4.2/tsung.spec.in0000644000201100017670000000451111701017117014761 0ustar nniclausdream%define name tsung %define version @PACKAGE_VERSION@ %define release 1 Name: %{name} Version: %{version} Release: %{release}%{?dist} Summary: A distributed multi-protocol load testing tool Group: Development/Tools License: GPLv2 URL: http://tsung.erlang-projects.org/ Source0: http://tsung.erlang-projects.org/dist/%{name}-%{version}.tar.gz Vendor: Process-one Packager: Nicolas Niclausse BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: erlang Requires: erlang Requires: perl(Template) %description tsung is a distributed load testing tool. It is protocol-independent and can currently be used to stress and benchmark HTTP, Jabber/XMPP, PostgreSQL, MySQL and LDAP servers. It simulates user behaviour using an XML description file, reports many measurements in real time (statistics can be customized with transactions, and graphics generated using gnuplot). For HTTP, it supports 1.0 and 1.1, has a proxy mode to record sessions, supports GET and POST methods, Cookies, and Basic WWW-authentication. It also has support for SSL. More information is available at http://tsung.erlang-projects.org/ . %prep %setup -q %build %configure --docdir=%{_docdir}/%{name}-%{version} make %{?_smp_mflags} %install rm -rf $RPM_BUILD_ROOT make install DESTDIR=$RPM_BUILD_ROOT install -p -m 644 CHANGES CONTRIBUTORS COPYING README TODO \ $RPM_BUILD_ROOT%{_docdir}/%{name}-%{version}/ %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %doc %{_docdir}/%{name}-%{version}/* %{_bindir}/tsung %{_bindir}/tsung-recorder %{_bindir}/tsplot %{_libdir}/erlang/lib %{_libdir}/tsung %{_datadir}/tsung %{_mandir}/man1/tsung.1* %{_mandir}/man1/tsplot.1* %{_mandir}/man1/tsung-recorder.1* %changelog * Wed Sep 20 2006 Nicolas Niclausse 1.2.1-1 - update 'requires': erlang (as in fedora extra) instead of erlang-otp * Wed Apr 27 2005 Nicolas Niclausse 1.0.2-1 - new release * Thu Nov 18 2004 Nicolas Niclausse 1.0.1-1 - new release * Mon Aug 9 2004 Nicolas Niclausse 1.0-1 - new release * Mon Aug 9 2004 Nicolas Niclausse 1.0.beta7-2 - fix doc * Mon Aug 9 2004 Nicolas Niclausse 1.0.beta7-1 - initial rpm # end of file tsung-1.4.2/vsn.mk0000644000201100017670000000000611701017117013472 0ustar nniclausdream1.4.2 tsung-1.4.2/LISEZMOI0000644000201100017670000000540611701017117013522 0ustar nniclausdream# $Id$ Tsung LISEZMOI 1. Introduction 1.1. Gnralits Ce document donne un rapide descriptifs de Tsung, qui est distribu sous les termes de la GNU General Public License version 2 (voir le fichier COPYING). 1.2. Qu'est-ce que ce logiciel fait? Le propos de Tsung est de simuler des utilisateurs afin de tester la monte en charge et les performances d'applications client/serveur (bases sur IP). Actuellement, les protocoles HTTP, Jabber, PostgreSQL, WEBDAV et LDAP sont implments, et Tsung est trs facilement extensible (voir le fichier doc/Design.txt pour une description de l'implmentation et des possibilits d'extensions). Tsung utilise le langage Erlang. Ce logiciel est capable de simuler plusieurs milliers d'utilisateurs simultanment, et ceux-ci peuvent tre rpartis sur plusieurs machines. Plus de 10000 utilisateurs peuvent tre simuls sur une seule machine; la limite suprieure dpend du type de hardware et galement de l'activit des clients simuls. L'ide est de simuler le comportement d'un client rel en utilisant un modle de type stochastique, ceci afin de reproduire le trafic plus fidlement que peuvent le faire de simple modles dterministes. Un utilisateur est caractris par une une suite d'actions (requetes, thinktime) faites au cours d'une session. Plusieurs sessions peuvent tre dfinies, chacune avec une popularit donne. De cette faon, lors de l'injection, chaque nouvel utilisateur utilisera un type de session en tirant alatoirement une session (en fonction de la popularit de chaque session). Un paramtre important est le l'inter-arrive des clients qui dtermine le taux d'arrive des clients sur le systme (ie. le nombre de clients arrivant sur le systme -- dmarrant leur session -- par unit de temps). Plusieurs phases peuvent tre dfinies pour un tests, chaque phase injectant des utilisateurs un taux donn. Dans l'implmentation actuelle, la taux d'arrive des clients et le temps entre message d'un mme client ("think time") sont modliss par une distribution exponentielle (par consquent, le processus d'arrive est un processus de Poisson). Voir galement le site http://tsung.erlang-projects.org/ Un manuel utilisateur est disponible en anglais: http://tsung.erlang-projects.org/user_manual.html 2. Installation & Configuration cf. http://tsung.erlang-projects.org/user_manual.html 2.3. Problmes/Bugs Envoyez vos questions/rapports la liste de diffusion https://lists.process-one.net/mailman/listinfo/tsung-users ou directement l'auteur, 2.4. Portabilit Ce logiciel a t test sous Linux, Solaris, FreeBSD. Il devrait fonctionner sous toute plate-forme support par Erlang. tsung-1.4.2/doc/0000755000201100017670000000000011701017143013103 5ustar nniclausdreamtsung-1.4.2/doc/Design_fr.txt0000644000201100017670000000602211701017117015545 0ustar nniclausdream Arbre de supervision OTP: ======================== tsung est divis en deux applications aux sens OTP: ** un controlleur unique (tsung_controller) * ts_config_server (gen_server). Serveur de configuration server. La dfinition des sessions est gard part le serveur de configuration. * ts_mon (gen_server) * ts_os_mon (gen_server) * ts_timer (utilis par ts_client en global) (gen_fsm) serveurs utiliss pour construire les messages: * ts_msg_server (gen_server) * ts_user_server (gen_server) utilis par ts_launcher et jabber_* pour l'unicit des utilisateurs ** plusieurs injecteurs (tsung). Plusieus noeuds peuvent tre actifs simultanment * ts_launcher (gen_fsm) lance les clients simuls (selon un processus de Poisson). * ts_session_cache (gen_server) cache les sessions (interroge le config_server si la session n'est pas encore dans le cache) * 1 processus erlang par client simul (ts_client), sous la supervision de ts_client_sup (simple_one_for_one) Le principaux modules sont: ========================== 1/ ts_launcher. Le processus matre qui va lancer les autres processus (un par beam) 1.1/ processus clients initiant les connexions TCP (module ts_client). Ces processus sont lancs par le processus matre avec un intervalle de temps suivant une distribution de probabilit exponentielle d'intensit paramtrable au dmarrage (unit = sec). Le nombre de clients total est galement paramtrable au dmarrage du processus matre (il s'agit bien du nombre de client total et non du nombre de clients simultans) 1.2/ Le processus de monitoring (module ts_mon) 2/ Un module pour grer les chantillons alatoires (module ts_stats) 4/ Les modules spcifiques pour grer les diffrents protocoles (module ts_jabber ou ts_http par ex.). Comment rajouter un nouveau protocole ou tendre un existant: ============================================================ Tout protocole doit exporter les fonctions suivantes: -export([init_dynparams/0, add_dynparams/4, get_message/1, session_defaults/0, parse/2, parse_config/2, new_session/0]). cf. fichier template doc/ts_template.erl Rfrences: ========== - Erlang http://www.erlang.org/ Design principles: http://www.erlang.org/doc/r7b/doc/design_principles/part_frame.html - Jabber http://docs.jabber.org/general/html/protocol.html - modlisation stochastiques : Plus de dtails sur ce type de modlisation sont disponibles dans les documents suivants (dans le contexte du protocole HTTP) Nicolas Niclausse. Modlisation, analyse de performance et dimensionnement du World Wide Web. Thse de Doctorat, Universit de Nice - Sophia Antipolis, Juin 1999. http://www-sop.inria.fr/mistral/personnel/Nicolas.Niclausse/these.html Z. Liu, N. Niclausse, C. Jalpa-Villanueva & S. Barbier. Traffic Model and Performance Evaluation of Web Servers Rapport de recherche INRIA, RR-3840 (http://www.inria.fr/rrrt/rr-3840.html) tsung-1.4.2/doc/IDXDOC.css0000644000201100017670000000276511701017117014602 0ustar nniclausdreambody {font-size: 90%; margin-right:40px; margin-left:40px; font-family:Sans-Serif; color:#000000; background-color:#ffffff;} p, .n {font-size: 90%; font-family:Sans-Serif; color:#000000; background-color:#ffffff;} h1 {font-size: 200%; color:#800000; background-color:#ffffff;} h2 {font-size:150%; color:#000080; background-color:#ffffff;} h3 {font-family:Sans-Serif; font-size: 120%; font-weight:bold; color:#000080; background-color:#ffffff;} h4 {font-family:Sans-Serif; font-size:100%; font-weight:bold; color:#000080; background-color:#ffffff;} h5 {font-family:Sans-Serif; font-size:100%; font-weight:bold; color:#000000; background-color:#ffffff; margin-bottom: 0;} form {margin-bottom:-5px;} textarea {color:#333333; background-color:#ffffff; width:90%;} table {font-size:90%;} dl,ul,ol {margin-top: 2pt; margin-bottom: 2pt;} tt, pre {font-family:monospace; color:#666666; background-color:#ffffff;} pre { background-color: #ffffee; white-space:pre; border-style:solid; border-width:thin; border-color:#999999; color:#333333; padding:3px; width:100%; } a:link {color:#800000; background-color: #ffffff;} a:visited {color:#006600; background-color: #ffffff;} a:active {color:green; background-color: #ffffff;} a:hover {background:#ffffaa;} .piedpage, .entete { font-family:Sans-Serif; font-size:90%; font-style:italic; color:#0000ff; background-color: #ffffff; text-align: center; } tsung-1.4.2/doc/tsung-inside.png0000644000201100017670000010401411701017117016223 0ustar nniclausdreamPNG  IHDR|BsBITO IDATxy\TUaDA\5T EpCS~.fbkj&$fjfQ`EbJ*(.:w~s=Lwy0D"  F[@}!*2JC@UIH'Q DEFgv(!))&~GL;bdG#'G%)Im ]*ʂTѭCEemX3ƨ?J6}$Vn}}|ofCc\zڦm ߯0~!ܧ,/N^O*b~lAF7޷oDy2_0f%{fi&~GnϞK啔@Ee廒w&knkk¢βyuK<68<`%A =yG_ =_~7ow^}_~W?vh?XWyTlȳm,u,cqmpts9%2Pkօzǯ`AnD"k+ u~~=G dv2_ӝ.btgG箩җ e ' ^!ED0EWnP Dv}HEvg` n%& 55ZN OtvK<6UP)FiYB]i ~ꈙlKN_<?cq, 6{,s?Ϲ`FKَoy9ۃ(aU!V2uB+F\[fU-csc^w.b&FD%FM*89DSfl@e>f.Nvo'Cx}ul;Qs$ |L/(|1Le#:Ze!Q"U^MU^MP3&YQcu2c[uEj /!y9o=}ٞJ^bĘ֧B?XU .VSYowZIq[m[xm" EAw V'aѤd1|XLZ8sLV,kk]|feK+fXwGϦB?28߳/٬&9|_hcEΟ׼F(Ĺ_3C C `46Q_"  cb(~dAN>+>XY;,潁B%Ew?W{DlVCOߊۑ60;_g@V?aeO_^ ^i1-Q~GL={Mmݚ//;fxC(nJĠiecuL]1x* q;J#ʁ~G}Ar<(w dɝZp6 w=-ʝfpvSK ~Q D"TcRyu]A+8xx~~GHѽ_J|"xjaUw1dVr9GwAZ7,c\O7 ; A:8Atͨ܀(4Tje/D$A#^ТeD_puJr=G/%mFW`A cVeY/D XW  ᕔ`X |87/ꚺA>!=oA b`~Ao wAh+8xx0p>~GD1H=mZ *w1f.Xr]|: ƌZr9W(ch ; JJfF2+}!" AP݀@#B 47!Q{JsT A8A#PcFE?3?>~G1N{s; -8AtW q?A#.QZ9ܟO'`~At C!tAt\&h 5 hjܜZnMA1*-Th:VZ,wA Z@#_(FbQ3(h= bA @ 6AY@\$$hD&OQ&UBMN!N+0a-Tk^;av \\՟? з/NNZv f7{ B'-/V_-F+^a<x, zPQ5G8 Te5Pq A' qN?O`VCu*:kZ+*`x@ M(*0/0ϝhpqrD"رN33xE)ͮN :f:{i8 PX }| n#4`\K`vT!P| :·SЉ |}ij=~ ΝHwN?ƍ>_#B"k;Pl.A Zkj ˠ  /@$4.oz / * ?h:A'KpI5t^`%<֮+?oop _'BAsIvjoZ rNo'X‡X p/Oq0|6}w ɀp1롞*&0 ;z \ӧaf8y-@6mP`~ aH =b a솈^EN Vx\á UP TA>@?_0Oh P :QqQ89Al,xzŠ`auu: 022smlpѓ5](wB~ WrpX"180浆?0 #aXCs0!`U?gVj-@'*z/X, (x- ={tSD(4DAbDEF%%%)tIANh?))WZmG63q;|\SrY7+#8b8ށ^=5ef7RtwaD,Jnt(!P!NTY;0880x0( s+գ0FCLjo8Љoߴ41Bf-ٞZ,x+)χ(߿}qEeM97}4or =ݝl,F Y~f'f>`HNt(c?&|PjN=58Sn-puQb01zh' Kv-'=Vlu/g_3DhOn]a(!'9W#ނB̚=mRO>p8֭m-1?#;޸^N܏G=E=ݟgX5KVT,G6n=i.&D٬l3,/[=}6SNvϼ=ؽ9A}ڶ6~D&)z {X4)~#0Y+rؾ~6Yls6+[B?XU /=G~}-\@ِRW6NlO (-w4;w]S|-&w8>$'`N2bdDȉfG cJԳ(wi˝N-F(͎DSȹ*?`'Ɛhv AhvxS"AԏTW4rG~G5#&v0;-dS4 A Ut&ϴhvjy"*wQeɝZPXZG~G@G {cr'EK^ DU2^F#4A#PQeb Ĉ~Ge;>M;(+̢NQ}GwayPrG#zA*`o6;1\ AAC %͎'wy9;IpTs]]W'.)48y Acv& -bjj5{sza:!<}`N&=~Z⪦1P{l1KrG"E"($9]=ݚO:H>01!^foO<]+w99I@r@#ƌZWf'N^~G'w ~G SH5;IhvDW(]A =#]˕0;0ǝT_vuu/F;m[p8q#'LKnGN^qփ,S ^anb0b J,\~^;ncz8p1yo1D^=*2*)I,14QQJ+^1S7QQxe\:{[yy]3DԾt{B:z{ȼZ~ ?(,|cG] ;UXT"vmC#ITIK QAၧznAWPl- ;5@(Nb?⸡?fxد.bYp8e}$(.C (wD h`А;[*]j¼ݩ |ڹݾ[l%0FwğA=}s}lzVZiב/^ROx"555''oذaPZZrzਨ(6|A~_͛K,5kԾ̙ӳgOB`2[ !mS={6Y%4i yʝ`I `$LI1k&yߊʚ {Įrӷg9l֙fev"T̰Pu喓)vCGJJJ6n8aǏϛ7mذM6ӧOrǏ[`UpRSSr433[z5q|̙3g/.] z@S;v,UV-wͮ9fLϚM||:Y-oh k--̷jY$8 EKs&(/(O_4&$$~뭷jkk-"Nyxxmv͚5YYYdޣG;wnd m۶ı-!_XXX@6m^ZXXHT!,--%{3P|2& :)֭[b /^ܾ}{S]tr Yp~ĉ%% fjDl"%x'ʾ}*K==5j&'wpp,semjd&I͎=3NNN 'O;'O? :̘1ŋgΜQSwkkk9@x]lx#@,frG~ҥ˙3g()v֭[{k`5̙3UUU||>_NuQ(ovx%!trtst=~P: IDATz)}r="6w8qbyyM|x *66/sNqq nKѣBCC_xqURU(Y^VV&^` -ʫK">#3jժƦK.cƌ77+Vٳx(,,lСLOѣGoݺUjw ;w#0**JGsΕ3%Əh @D>+]:;" O qqq2'`0"NU)_V^Q@ݫ(]DD=b8VVe)G~BƫۨcL #; 1(rvӒr'0V) F{ޔ-(w wD6E厘wD_*4;b|#=Y7iq;ԒSh#Xo%e[#^ieANQ*^eȧ"eyDJtr7&G6~GoUw) ݘ0M3Fh:y5wcS՗GT탕w %my(ˬܤ{"6eϷ*7v|}<Y7;^5%Zhk-v@hcUx,/d=+8rkDVM17"B'IGXU*,/&{<-578}yN6VC#&tþOY!y6OYK|M@_?'n$#^I9QZH4p#'_ٺk|y%Cѐo W̰jQs.╔G]׼+*k"}ң[ڰ> gQFCɦʭO_pظ^lhluUO]ԾyWOx`vOS''_1?z 5@(K?j]e;ڡli;QW' U|I-uYs}^ԽdYv ?bQ|@S?#7gg%Jf];ڵqk75laQI[gYռۺ%LHnCOĀI|ܓw$~qlf~'' @`w ,+g<*yٶE :|ŶNW~8q5M&Vn(_r2J"Bj1_CC~9%t' ;5fّk%F}}E`ߺz]?P ʫ^Dzw-8\:=2!9|wq7?aψNU^Q=eywȸ{K7^i]|$qr˽~Ͳ,|>f]kORؾqpB/; /5yL$AhmYHlYH@$bWSϬqVRaUT [ې/V;"8q^N$r|ٮbI6b4m bKJ n҂UH?{eSٹ)'=24`Orj1׽k9J6u Vo۩)3?&/Wgž:x7Nۣ͘\&^6ul+μX==R /SʝF/^Ӆ6V|w'9|dvٮBkF8F6ܬnP@/w Z08xӒuLlǎjkLH( 'E7b._0i1[YX-+gPuu9-a!R#l_?z*.VϠ8߳/٬&9|_hcEΟ׼F(Ĺ_3C C `46nvܰgDfFc0"цo*IQc44:l>`u2X5as'HFXEw23]Fe*}vXH{BK8HXy{$bzVďm؎'|w:'3b>y^@-y\q.J+iUo|Uu(eeF#z"  w{i_䮟֭ 򝞾cp<)u^?Ytl l]H@]H@;[p2C4L E6iK?'7v*F@+fz3ڙ1_ԓ_;<*fHBrW7OWALjaj嬱 4'JDV 1D4U1;ɐ(teHٍ~D9T2Q֖{ L{DIvA䦛Ǽ8[PzZGr<(Ώ|"zG.{݈3 ΕeɝZp6 ]U8BnҞo5{D]u]m -ʝfpvSK ~Q5b;%o?d9_tuXx>bM߶,VmthФF"ZXr Q4x}fR1M&q#˶ʕ˱L,}^ _[W~95vY7:"ngJEe |fZJH0xs^;r7h0~Wv ѓeD!*u:WݥdX t=ʞW9@k1C,Lzoɳ/ p=d hQU2DQbĠwX7njl6f]Q١ّ[VQ L&cpx`Չ+UcwMw.e[x~:#Y!$^K{-UFv^;cj $ͩ?(ͨ()Db$mé>{wj΍xpğD*g MX[vy;_[8@I#6} 3[(~&^%wr2azOw';qkɼJg-ٞyK7{}FNɿ~uTԍp6Jʃٽ!{@yY;M@_3,xpԜx%cy5>ѭCEemX3hBi#UfRn 9_OuztУ[ea! 9HIϽپjm dڒf];ڵqk7ɺ1=yG_ =_'~GtgL գ!W'͉=0n~~=G djΎ\.>]#K2_ӝ;h `,w):`(xEUཅzv5e':;%Lh95㧎ɶAawK*\duı@(+ٲ '<΃3cvhɡ#R_\=ѺB]yo}+-y;f ^l.Nvo'Cx'GؾqpB/; /5yL$hܟOM//J2J;2sܔY_P\W`ei~QϚ=mqdhb^#s+Y<{7jz+t QR^^Ou68]hRF`|>}4DQQIIIr*GC^_^iWQn"hҢ3irv-KtPz?C5CӅs"AQ?NB<{0J6{_H~7u[lUU 3Q0K,%J5KmUjT)@D-Z!Nn萓m O`~ ӿ_HDE G.zJ+O3B;{I$j'zF _:ߑP:-8#voWzxHbitSh"^d" ŷ |`nI9E'K]*dD  ܟOMVk1~hÀע7Z,4 QT w43 ޥBFƛ7\p>w.4u`fzn|:wC9!n*dF jw%!_v^.\V.t7k hzm^lسyV%l$D9!wPPjD-ߕؿ8z2ౌЀc"BQcSYdռGu#+Kg/ppxE9yV.Ԍ '=ٳIy\n\F7E?EϨ++oJʙLK|/]Uc:K ZmGuoAw9%1)%g) ^ďBWɚ ./DH0~W#3ֹ8}#{s)l6f]ׅkŒc9kҾ۠+R.R׳<^zp؋g[<{ܞC?h:WޚgI $l{v~`45zu``\ĄNսbU)ϧsꡤɡ5ps+(yUxWnoBB&2GXH<F2w}R (x9!Ϊ+BQ; NtB{}`iFRSBD )h/'x'Zo7WՅ^o<&g>.DMϣEK+9B!b>?Z%7>*M9Wʡ@hϧbBN*G '3Up04e*K= [S4J~G`wD2NB{5nG-G˷i!5·K, /e %׎ګ"hIuC]>dl Iy pP{hq '~GZ@,x'^hs"tFjͨȨ$-y{7͢ۥR=GAwDao(X õo!wr&%P_rpRwD1EYx04TTn?!(h=CL%:-]]'dho(.M;Bк| ӿV}D-Z$Nr7$H?zW!@#-܉'3ɀ#w\SjnL ]:8ۭ\8yBa鴎d͌s gMa43Z~oS ro=wn/_6ټޖl 'm>^g~ӋO'YhLT/t2̏=|\S'*-ԅO z2ౌЀc"BQcSk=7r+M&ɋ rNf\``. ܅ M=f/!wZYT]XQf'f5DkQ(h=D.wx!fj\S[Z Cwyk ޙ~/tcvp:kFP(M4ͯ\`-`Ġw>z:~:'7}do'/!V2_T g!^>KnqK(wf<4O_J{Z(##kʣUNNT]и8'9Og}8?*6xNëG F֓MN\.U.2Gњ>H- \.m:o3|0~!VS\bCyxWhuĪKeTb <D+*kٽ!{@P^Wdce94"(fXȒ{<-578m]5Swq5FzkW'.Vߞk[V^:߹W q/1&fmAkIbRWT_+ygB6.p=9;x{Qn2 6Es~;sœs웕Zl|))trh`0\ %+~F.yTeᰅ"ġ_&A6l%ƈ MWYx"'h IDATCqmmt8zs[SOunGf1>7"|&vEeۥe\L<݆ٹ?72eC㚭Y,@ ?2ܿϥwVm>p覆&¢69d;߹Ďۊ:oD t';J_kV;xsIYkrݒ8Y]cG#q5.1{Th{VJ҇.43-6ʿe\Ȁw$O3Z|BeMo& "4E;X=Nd^rznw?f|Nd^;"rvnNC¢Q# K*\iVkkg0~ꈙl#2=xż C41ͤlNas@R֡G R ,ʮN88g9qp]+GKG~LɩS~Ϻr˯S [ϳ_*6֖W KFSTL:٬u/c}DSV-6}T'GhVؾb.Nvo?}FM*.ɡwͅ{gՎ` Sc;6>n-puQj~̐C?gPO_ vn9]ȼbӮ#9ROπ]3d"C$][y8b֔號lzy%ֆanmcmM 翫w03p"w;ëxɁ?z-ٜ{fkY?l0{Ө]LX۳Y6+fXY7}18w箒$GYvV/^d2EBQP/a!y ,d9l_?,wu9-~A!o,ͪkGpwΫR!Hj~KKW+7H'z2['G5#)wUgAhvތBА|8)ϋ\4FN^Zbr:”q"Bku*9t-7JDhvތBАɓu*:_F DY\2>t,+)g2rP5ufa2_ןPYn"KG*4'^s#um*ԅbZ6 Z\F1bhYb ]B1Rq3bg }4qŗkfɺ<2W TKmVfdhvǛ\&s }fڗ;1lG))Dbh80()-?CT6hiwVȝ=NV9y1+&x|X ʟ0svq[}fU3%G~5bv Tj`ߏZ{ZůeyGWmz]QY;;nDăiimq#F^64zb #_6|M CTaQ 2Kbmu,\[3&Et^O z2ౌЀc"BQcSGOᯗY5_L˸XYU+2u:Wݥ<ݝ==>Zz F  HtGb? g  }Q?gt8yɡX;PX [ǎ'vDQ xG' )?cee|^I9ɰ07<-fx(I ($^~ͽSD0$M_[^QM=١9-Slͺ-/Y-~FW MNKvn,=]-'!qɬgvι%/Q7,(x9񽁚xbH~p؋g;-OCo \ͱp*!&Gq<6:ǴzKƅnVL:n0q<)&"U[,ݰwq80O=jRLDƹ ؔk/xif9yz߷u]J' >z*"4`I#aX>;d@o9 tr#};7Y-6֖=wg4\><2<558|t[bZZoyuݒ8 29LBߥ9W'/b sr$~wŒ _r2azOwϺ!2%!n|FߚѠ^qOkG^S!b~O=8Я~=eU{oX{Bȗr*֡G R[ pο PIaHwĀF`厘wDa厘 wQS{~D<^r|vnӓ(T1`hD^FR@.Ŀ#ojdU}Չ'QԗDeD2^FAQp)}7ixC[w|u;!Ssڦ_בFz{lj;|4#ۿ~S'M%OM`zX0O}3{Œq@ ,dY3O?(xP՟kN~Wo|r'̼s&EШ )zS/ veX0~VlUΎΓ'Lvuq}-BWN$_jՊuԽ{f#^:99{ߎ:?_7~G4Z{6:3^ܻwsLdwKgE# **3e┞{666ʽE3Y8woy~]`_򾧼?]mnf~rkZٴgQ9/\-mﯪW'.kӬf|yj M ]sTUW E]]\Z-w[~v׷]]Ծ|Sz(yg"GU}ظXϿX.,$T)UP nqk]~chinfӅݻu{/wUW8VuuuBwR<\{tҮ]D%+9˿;88Pϲ16}~[岐Lxei@Gk&lܻ9wrNfן6ڶ5DHHoBn7L;:86551'EO{Wzrnjzj4, b0@$an0L [~FlgMMMK K"*ʪoz2kVVU>/nďu.N9-xs3;j3جܪICY@[;I9UTV8uIѓʊW܋[~._hhhGoF&(0(pGgqws#:q6}w|8ouu ?[I)-p@OZQq>}QUb`~j4Mՠ_r(C jWs? [7lֿk}}>о6iE[۶9ёNӦLw`ߪOf]]\7s'WUUtm˜KZz)Yy|hX4Wnܺaɽ/{vi3%uʇ!"/\b9eEZ̓WZI}h3y%I3 ΡiPB]}݌y3,[J;(-" Qh(6bTPkc M1vihШ55!A}_klD* muvw3ޙٳgko"uQZ$#!Mr7h |6m&?<*psv+q9ߪ6E{oʂԧ mmKqM%ZUSXpIG{wcR<#Y9I,1fW20ʝAv֎[~\vZzއb%e?.!TznB #VV;//O}5B^_E}KAͫ?GtBݸ]V۽5 '8NN&Cdi99"8z;#27#?߭ʂ$8kiŨ w^]_@_ã* s37_~ q.#Ďן:xXlŬQcwn[S[=n^kj"tR+YZMqkp= |ˢ [_dmDzOϞ7FLe ;qTJ̨!-^0;}1):ǿթjhM ڵq+qe[/WY|zzz8}uza9N+G=r^oF3 %,6IeD; yݕf{k /D6֖Rۜ>}P9Ydѧ+!\A αhjjFX~8_?D"NuNdn&:Nʖ|ҴEEVg搷kq ;u3!.fmU,{go2tG5vP+5d֟28~a~}?ƩzRy`{2E~9ROW?PlԹ-ť>[IWlR?&]So}2Vdos"MN`V=":?vҌD#k/Ro Xʻzk$NIAɸj_[%qqvIr^\.l%ȏ +/_b%7xٴt͓0~w?||>Zg(۹uv ,a,oa mT6-B{Ii_b.b⬩cVZ[Yp8?uwu ?uc#ƕөbvQ2 6Խ{4#)U9E%G-K;8L EV([àV[_&4-o$w<;iĬ,Z&wՑд^k|qMq= A#039pU@Ru@6 4EUzݒ1Vp~$fVr&.Nr)k $hhZ 1>0~G@MKaV 0_w P<,1((!tB/GkIs|B|BQ8p/h;&wDbʝ~IL40x;"CʖQ$9iD,;|)a~G"+^UX,4QR2 RTG3&ԷUWNfD*+iX %dF'o(b D@(rڋY[^'4U(SDBj]2rG&Rv-ΖQ.3C\TKy'wBeyUmXl0mv`Hh?k]PzQfe"wR7٩g_hnnͬi 68XsβmSbyrڷv_`L!cVJdf +KC@PX}/{dGx?>J(ཱ.WQIϿܿmYQIYGW/β{2/z{z{אȐų'`vڶEf 5s:gO }pcPPq`@yr8~~ڀqSAv O37"*)o3dmmϭj6eiaNݕ}fBέa\&;0os$DZ07;[^4OJ}CytDgt(D{/_ o`%JNB*v'BUO.ƈ=( 7ǭk>w.0ԧOjf z|;={<ݝ]$1S;?,:(Y'g>(Y!ݹ_WߘwǕ|٫#z#i_g8 1KC"5' n4TRb|fffܹǝI٧۾NRwvR|`_Og^~q0,,f`_R"Nt$I=pÞ::_"f6V^rd`YB P?8#{nڙѿ -o:ްPJQ*.L7 Q"#JGMݹW(~lu_; LHZieiacea4r΍/>yݲe#a6V0;i(>WνG?<dziu]@Ǖ?AN{?ڄfڝ\U>׷I7qEX > }lZ~mMyeu-k> q.#roټx|AxXp|\a6WVGOlܹqnMm}y:mLdFF+=c~. ݸ'vܾ[˵ۍ"`Xtذ0;wW G6Q߶xpԽ  rgRz+z'SJ%✴FO}zzjjg.ڼ{ӂvmJ=YhEL*#ܝ/>S'~Gg=!4vNE$zCm0usA$$+W#t%=;({覦^p)k6;2ooe2_r{Ύ{2g|4ZAqNZyei+@NzR$8  B~ť>[wS5iIM"FP~r[ަj_[\.G xe>r{N @.Ύw ԯaɒ })]:_}gMʂ])[c+f$@Nچ.Oe"(gPY[IT8/7Z5{0Gf+Oc;4PO*LMZ^;.r@I/z:u|G"J##1Qv0{XlSgoO;* :M%qM|']/+ f OU|q}>-~G5A[g$#O^q}>~Gh!+v.С Xv:WОƁFFA#\LZ}'QYlK^d /,˫H\91̬֡h(ulFtЂ*tϛFˋ!)zoUXHD: w2דbD"gRJBέTnh%Pcy0p^%,lM( k1iCAOm2>wD993\3K@ˆչ wЮR'HNR[嵀dh!wr #wz?Z7<#!<Vѫ|RS#~7Bq: ـhOZ^a] \'Uq}>-c~v>olyw}{\N&/!D"PHVV{;fCًl-,}b<>C^̝t_}kgk=勵;7/ ڿU6Q3Wqp=˫| jGeUwZ8wpDX3r۩]o=lOO@MPzw>|lK(8|Ư_}7y g`jr';lŇv͈OL1|LE *+)ͩQ-WJa~#a 7g?y+ln-4Yx<WP;\^]a2[ݡ]noCVooÒǕoph NBhv1(ЛTP&q?_4v>kyHg4X~OsˋS E%z^o[wmsV!ƭm3>w_;IaeM1Jbb=,v'~t}F@S[LQ~Hȹ-VBM` rpjz۟K];κy”C(db|ԄQj`|f׋ hW\}ڸ=V2K.#HԨ~Z@++LݫMDtyW QIDѱvӨ8/zԺ,op.s1#~/{1wӎmj랧L>ssӎ`~kk ji_V02/|0cDw!}~8ĒP }@n\@X2V䰘a D/k]'WM+I}Qjv7e 4@މb>Ϧ)+SҾn.mv< Pma"Bљ rvHu r*+'VP$p.(~uraq)Z8Eً~'O h0)=pU[ YN .J& Kݢ΁~!bf| x3~ƭ.ApVaO‚hCm/t sk~0yOJ[~U]j%$ cFs0>Fֵ(,!)toZ}1̏ݼ3=.:g|4z܈}+ Vx͏O}P#پw~#9Œ]aEo\AM~G2cvOeUXjcjV b:q>ߤr I}wíL.nX`}`Crǫ~H\Got {܍ Ca 1ERZ[4ڟfOGY^R>7 HHru)F!AEOEu"w4aax~W~d_MVL<~~VjoO@jϵl-,L--iogMD%!k>Ԡ^{67Ro ߕ9}wU5O/;9'80z ߤC%!ڄt}bbbHt{8SfϽUf~aqoLYye5񴼲:j b΍skjÆ&oYỳ^;)(tUY8[2Uͳ5 bȯ*}[WǛwg'h:d̰pؾwGC--)6KB*Q&b75mk^S[?sݛkWrikߖPHݓ9$2bqb's6eQϓ,-l_ <[c~$=u{[^d͍oq?rwnIHe&54]%$N>`o۩偌3m\z~:iuWPvf\;ok236LO.3b̽._aŸ![^,[a ##;_z{7?eM uWJ.3>rtlB&Ո>] @:;tl}̕!Rtlmcm w#?vucc)?r㾹˶X/Ғn.buU>UDI1%!6?df_L5 f`o]);~6}N#*G)?_#'.nE=ރ'" I>_GN\;";yw$PRG_GzRDqG?]~Al6yyG%❮ťWPZ$qBLጜ`~oz'!!R?ÂÂ;CR_CQ]?8/_mtsqwY$NI";A]*Tsϒ\m8S?3I3,/Sw 6v-vwuzᰉ.,`Бv$#N#'.UTլټZ^.xktLtxm3J˟x;I*Eç uWZ{[33nV6VyjPQ]'ps_߫kGx/vwĚev?5s#xݙCLZS箙q9q9\.'̕gr]G=vMOtt۰|l6[$ _Px⴯a'dcr;l\95Kʿ򫥅y]}ҿ&AC't|M.Zwݯ(׷= Nl >%p у~GL 95XAwF ?}"ʼr}&SnRS'6*̿waK~GHwfG&w# ,}<'_qh2DJ䜤ͷGQIFWKN"41ܨPAE,w#fv' =x|:u%@uiffƥUrQLB]lt1ash -'P8C}Eh>DTU*h%'-HP/kmb;iy̹#R1#D" -,K.84OLhF "شt$ At\E'hnU ޵CqN@3x'P1bRߍo5# @1xGt "q9[woS[?0z;E V!HʙhP$p 7/co𼉬E݂nd!ن((&y*D=g㇅Н eS(w=ArDwYdQʼn3W[^,_E֩Dj/6ShLb #OL!f(ey=ї~/Aψ!Tf~5Rβnll0ef/ꘅť?M-[&И GFhH׀ Z7]L|3 Hыl}1Fx|Wj/wJ/' ap:#D2D9}A A8A#'wA b2/n)#+KWm۸-]az∂={W]vÊq##ɾ-/M-K/kX,خǕ\;f`KWo+|`w L[/׶^N×2~~GLdgCMMMFFF^^޳glll<=={>}X{իԽ 8q"?ׯùOJJz&pIDAT999=233 0aB֭%[DxݸYPP\ :9_ ϛ?ȉK.[u{DD`/Xn $KNQ¨1{k'_ ]vС39(IPPرcɧvvvc330.͗.]J<>ydAAԩS...Ǐ 󫯯OOO_nݪUl| AȐc崼 E_ !{yҸb #x1"/%-GuYQU? 4C?8_T_TyڼrbNnџyR]oamz(Xov'z-YA]zuܞ<[Xfu+h}dcm70o x[<,X@*jhh*ڶm+uWnϜ93h"ۛQ/\|ܪU+V{zz~Gbeiv-vwuzC:Ԏ<:GUTլټZ^f.0}ؾ5L]}% ߂r:_e{8;*t9ПWj5Jmllyfbb"5[[[&^ 6?ׯ[ZZX,ԓZQ\T*VLl^~qE%Nm3zMZS箙q9'^_w\.'̕#'.QwDžK]Z;u "[vڞN]|:@P]Aq7|QLpo}+++E"zjbb"tʔ)}!رc'NSjX>P{{)Z~<8/(!wC,d&'^ |~%b풗_":'^(B{;Ý:zvsLa/֯[k+fT> EڼY9JW~qffWX]r(9r} @LP*KЍI?6aY3D-(;}ΑS m-Y,Xoq]bu<b=~GӖ'ݻwVVѣGŲ@ ۶m;w6J N`YT#XϽ{_[P֫/xVߤpXWg;>PWD͹d3ssN:ero4pmm~n9E/^am,wnw 3QYuӺ/q9R[{k֬vڣG޽F.TBNr6O3=zݺu O[j9s5{[Ol9!6щy&D45:0g s߄ &ŃGO7x{:=ojuvm=[W<1$x N6\|#9Oj8v_IF~G5C %FO ; bLA*&8y 1&yh- @d;#Fu$A8 qU3IENDB`tsung-1.4.2/doc/images/0000755000201100017670000000000011701017143014350 5ustar nniclausdreamtsung-1.4.2/doc/images/tsung-graph.png0000644000201100017670000010400011701017117017311 0ustar nniclausdreamPNG  IHDRCbKGD pHYs:duhtIME 9tEXtCommentCreated with The GIMPd%n IDATx{eOt&p  9슮n Zey%,]Fgga]И_zi:M&ELԯÞGo~9 m; =-ԫLQ7I#Z?NwGMjmмZuNMh2z{y9CL?kL$5ҁcf^^5Tw7Ըz2)5]tiT2i~vD>Ck{< ׯָ#aH:r~1oЯk^*e #P2EZus0U&g荭:飺JMt}z;]TwSNz~h)o):f ًuFlTiwPZm.lѲvkF&qGFZd:7O1ggc۷^-:st-ت^m{QY^6>{2iz&}}ll1g蟶LܗROkΘҫ^Sޤt\/O>cX-o qϵ}fLA:_[g/N&Gz yB:OGخoW!78AwCO숼Lg{[סKZѵOppZ~1S֭Bɦ -j1N;[_S4ePzzvZw7&۲~gsN8r$}aQ۽St^xnΞ;%}}:ww7;u~=T;Ӕd>A֣ƠLSdtkүܮ}ǘaۤQ;ϼ:EL=>oۥ37&oHTѴfS-2u_X1 0xNwgތm;T!k}/A Ϸ6Oבu;˾9NԧIΝ֣3]15Ic3=ڸ;O6I2.[vpOk!33@73o']uk|N1^>F11o(ln k܄~}ۥjZPI(خ1ʵOӓkz$fbӠ-2ތ=.X47yPvlgؽA7 'Bn,I:pROs}q2 yuqZ=N}2<ߤnڮ>S1 3c}~]w1mz{Im~H D#P'LaZ4OޭצȔQl9e/DU[om#kJt4飿yu!;z'5oifSJ]cW{G$]F}lݒ!n[pb~sd]O߮MGopǺpnG[}MobG)4cWv$פ_)&$SZ[S751Ry hvˠxmkwM2*" 04Iruq cAI&4IC;ki{Iwuy{=d1_ǎ-&Coڝ /a1L].ݳ}9T_8c~vFtWAssq>^Ccm2 h~}cؖ^5gE @}qХMl64bdISM}eN5Ijk/nfC:kjVS>5BڡO/t$) [wk̀'o̠Ξګ>-GЅ3zɗ٭&OC*/Cg8C }p}~]ew-tlwڗI =I:>:_Cvmu 4՞?dZlc]}}ad-|UѵQoDf_ _pkIn&ZuTwғ{iNKX'6N{[ ZT>A e I@µ<2,<$ y@-q5G<$ y#HG<$ y#0J&ɍ˜a̙3GI>}:YuuuU\@GTQS N]o%IM6Nau}i\/t_nҽG:ONф?/HaHO.?Y^EqDƷd F$ҹ;'?]:V4f@։-:V=-WԱk9`n葚1q$鶟7l۽wR~rBI҆Wwo?՟8Jmт+SIѣ6CfCK刺bnKߣU?moI-&}C#-sϭZJ://.bˣ_?qnYS.=yϚkkwf{hP yӔڷV-35Yiv{_W?:Okcu$I[oܱzO.?'9YM/ܽQZ'j֝o^gO=Xd]@]e?<9N۷}0O^ }﯏?un+?@fV>? 9snw}7:jd5ҹ O^$M?F-MojOn05%iڄ1?YuLﶫo`Pܥ[wc5m]YmiOqH8E;{?YG0EMƈ}9\[uq4MYkA?xד[?oTsP^=JCS=}wru]%oЗ:mng9Sg4`ڽg@o$;ilq[o@ohn[wQLxM}>{5nL|4>6MR$ܲSg ^җΙ+I:px-l3u'&]Ly˺/h5'+7{cGIc3иǎ]{ÉӬc]bk>T?H3[lxCw^Yv_A z魷50h=CIҴq2MSF{'o@]'cgMԂ[x]{jnj hyon:jMyywhy0tB4}úStTzY7K7Ys'oP+xIu֔q}#HMIW?՟/ܿY W>L]rh*o[2KsS@lL/kee1M8U3םc@WW̙Sr@hTaz_۴woiY }cP_G<$ y#@G<$ yH@G<$#H@j2MStV\N;MZf.\uUunVOy{d&0V^Vx≺;usO?]vwڥe˖5q*i``@]vZ[[G,7MS[v<ֹq +W_8@tRY믗$iɒ%Xl$_+Wӊ+d&MW:.kz릦&uQ,XPo_+q$(!X.]}I''PkkoF7߬-[(j:Skk.R-ZH]vLԪUzjz:C\_+K/UPizKwen_Xӽk|E^x~߆.@ _.M{^z{{vZe2s9ھ}.B}[g> h``@W]uZ}___4b;3mϲ_e2{RX۫ /PGO[w;$Sй3gΈ2s˖-mZJz.R(Wg\3gNq~?Ni*ͪUvϟ/\VQGU\m~ yD _SN|N$͞=[7|.\zJ?-[gyF>N:$Iܹs}L{$iʕE]g5{l]|Ś9sh"IҕW^y7M-[LfW\lٺuN;4-[L7x>OiժUڹs.?c=k;?yM4I>$[Ə?mϲ=>}ΣĪ^Z}{]-VEI^ϻsRq.v;so~S?`D9}]zZt $K??tR]r%uGT~:Kr,YkF7tSk;v2%I7p֬Y>Z/]_a5770 }ݚ;w~lڴI4aM88L3k,^ZZrW_wܡ˗kƍ%IӦMauۆgoYXyfI>f}bUSSn~bnǜT\,\fI=k'NԘ1cFl:1cƸa$Q؁ąBAW]u>;sL=sҗ[nsպug G;wU;3FT6Y_ƦicՕW^XBmmmy睧 .@cǎլY$I۶mEnu Mmx}{zz4qDyXe*vV࠶o)&yq;bP~o3gZJcǎ-.[hQCXu'뗿~autth„ 7o8?Qjc믿駟>O8D[<>:ҹ?zٳoG?>(^|E-YD֭s=ú袋FlƍztgЦM}H?kɒ%zi&}Ժui&Ϻiӊ}HL qmiŊھ};'H!`v] rӦM̷-$űfƍĉC]@)D5H@G/g$uvB#B iN,hHE|Z@G<$ yH@G<$#a2 (s{2*4eƈVpv{o;@|$>u\.eG4h+0:k@|$> A%C@@|$> ZsvY- >Qq5H@G<$#HPaÌml~a@]&ΠgOnNhc)5mTFS=\Z02aHL*c$qIy ZLy/)>cN&- USX]$Fc$(-rQ@=g@50I8H%SFJʨ1Tu2@| #e<IT?s ? k^ˬsxՎgAʹ/ݶCAd+=ljpA8J]Im|Hf\ۭ:#)7(Pq-ʺǨJW;@ƸV5[7*G e5t7n-^qGoj*T P8xo[X񚍏j$yRx<=;q EicQbט0T>ЙV+Z>%PW5']@@Mkͨ*K4IPH)f|( r\sC<Hw$1.+qLS%P($XPFz4l6;!^]tO @rźH:Rv`h_z/?!Y3>#lrvUܭ/Z\$]6ŀhU+yg#lh݉r+.} 3mK,Fl[@s+(\F񩉏%=b-1S+oB?ڭAJ.+rl)VNfըKYsq [Snx|tq@7Smm|n.@*1`l\b4/yxm3ldlǰM$H:yс}Mh݂o=Ot[5P4j+6n%c$}zә2ھ}( <.+qw&{gOs@Jĸc[YJ ,uS3IC_+6N4 . '= .Fo?"T[%[gz^I@G3BODyRvl$`5p>uU5m]J@Z\C8LvjοI<rLJ#q: 2#HXf]ڍʬ<\6Sy~]ֱIOeFu@%AIˑiQ˵qTf5=UCd @2-PIw #G@F>-nkC74\l$y@ht[NuVe QGu)hoq4At[H5R =2$1ҡt=Hԟ*yokHT%XX#2>麜Z5Pg5qq ͒[[PH)v!˩=Wleʹ֓GG `5qAFxBAl2RPFj!zk8f^rJt_#Ɩ'ajsqFw5d}Mڴ $2U)eDq[7n2*h1L"5(-8epUƨ/(S.e!$j/#u_ʹګnj>V$@դu+$@uj0"mI@5)x} zhHT>6.6eF Ee IDATߛ8uH> BpbܡHX115G7wpW5]Zy yx^#ZI >4FpIx*$ BMu \nhu>r#bHVR~OQe6b*@9y"yPӜwPv<ړIǡOP((RF H1r#s>ieGĐYN6;:N,m[q}@[rh&qL.qF-J$p5#}I 2fzTs2+KfZ/$y0=0h5no1Zē G4$${]~Ic %Y9RPWsNf yDZ5h%ήIX$@Ԙk8qtQAc=Y tKP&yPU~1>L ^v<{:oǕT;/9 wbyh3αq9Զɭe29ɣWhw+ $P_3a$q%=- K(!8'a&%qcz HZJ\^<{b=ygcGXek͆xpwkKrʷeP=B{Ay},%$*؇?q V{?Mx_o{_5On:;;Mi' IK%iԏsY2/?|2RRF ^^YfJU֏9eĢw{(1>F8w.c2wn;s+e-m <&hG>z\L-81:[C-lH_k'q CKY~:7BQzWz*~5RUx]ݗ]hdkbqUdJ$폫9Y&s6raAmGu+79kLR^ՊnV)e2&H1xv>ﶞs,^P!?Y\6ni=]L4V51tTk_ύРc8jMeT=>&46>JzZ]@ٕօ1O0fA fԘr2n)T쌥98I2$wጳe GZı9[ϊ?+a? q\ JhYZm9b@ 16)Y+q.ʝm" VԪ(<iJuH8X_AI!!S\#+N\θu\q5b>Zq}_K*11 ^WTX uXگ'Qq%2qJr$s8̷畫<JX+ȆiyJ$<J%r?ʻ~Ԇj+m \?8F@B980X5+^jͬbR<گJk %ܱ^2b@mUggK$M cbVյdDJ"!>u5o`T=nZC5PIhZ"djUҭF##P3ݥqZR61F5-U2qs-()a2Psw^.~^E]dw!6#a:ݳG+P }ݠ0D*k}Z q'r:"##jo I0Mp"sanz1ɪHIZ|  ď2y Y2lڢ[!Wv1%yrm9*2o/s޶1ɣ3Uo- V6Ė;InؖIJ.2ՊI'I|gYHAU9\γ#{]p1%}rIؔKYe˚8m셈m$8/kP3H,n$ij/x"cѐ4TkҜ^0A@:0\1QZ>c' O*>F)}Lx{ea71u['`VFԉwXeT!!6Fm3 Peym; . q'ek@KG|M)5^QߞЯNM){J)RI$օ8mb[ >H,LYCf{uGȽ ֮={WeWuJx_\v1'$Go*&(VAm %6ի\lXb\jL2y%yPvpK6Mu7Pn-Q<%}n]~2<hp J"^@iXnЕm<(#SʠQҴMCBHoIZQÅVCJx+PI*qVP,QPz֠谍U$@c'V ,e@8a"EoOo{8ʠ29T>DfxWHU=/-.?1FbCV3:kyb9fWH8Sg6Մa'Zz{VGTI6ίEro?'KIxsm9q&AIjTG ^)zr ~OymߏFXj/T[ܮl¾(ɨ\~sC]Sb0#h#Ccj^GMܺZ-SR8s: j0~-w^ WR% @mt~5}'1G vȠWpbkv@H#hp^1*(xUcyt?;(-%^3d2Ah 5Y5ǯ qT.VƳ(Q'̱<1%_`p.+uLO + ~@_b%rl xcKɣQ ײ]a7u=̷}sкNk-8H&>ƕdZc:ˉkoVq3d(|mbD@o@b\L:ڇ1􂖻BAl2RPFŎdsr*+-~l%fsfC3Tz :W>fYLϫc?s粿BRb%PKj!-jjk!RF, S@u'D1F VTMu[ۃ`*Q%ծE{M*XtrƏ3~Hrxڒ)>tmO#֓1(0"eoX)Zo6Ջa8e$~ ɋ kMa ٛӈ龜0I8P459xb@x:mN A$@$)S/kXC/ @4p$4Fs4{6vi#Po]Ϋ(.S~ ď2Fړ>PVRmYTpG̜Tʫ^Q2rc5r[# $zMKi}LŲP((wJc;VBlV֖؎myn2ܶ𕆼ښ_xKM"L=4Q (c[cNqu?[xL ?^f:{fY$lI#7GI_ gυ}>~#yLsXTe8z}Gzޯ:ז0und15RoxZa@cx%x^IΤ+ J(ܖy0[ Z&˝ESW[[-aH0djI T2rmsmڞS2? OEB:0nlz&aΤ08r=g y1|3@W'b^=v>۾ n};wK8 U{ix}x%k|K]$ՉYkUmDN$yhɚW2ls&beX˜I9^I>:Żm<{DqkNgB/ ۢQmߋo H{c8!ʷ=[w=;('^ s{m .I l+#@$zdo*9+2- vʎRM읭wn%eV\ne%rIkn v_nNAu,IҼ;NSf={Rf#Ԡd0zI&ʰzd.6ewug}j2- nrzL4ΨS@s|=1u]_go8ˍX߫ańMJ fe0g"h2|n]Ky*V# 2i:S%os8[W-&׷aw{蹡`om=+Ip28{*m6ˆ[e58%^W䖻0Sx]U0 \V<ƍr= rʊXu&A!ÕDnR0ʷGl?տ^\n^k󩣓yk9}{~]b}u9)@lRTüja (w5sz. Is>v{Di끲xgEܫa« _ h׈)cZS5_2-0cQ ,2jAQZ Z+'!ԣyg+[>/ wï-~s.۱o/ysb/˹?d{u3#3dqXQ_3j=rɥNP>Wh>>s$8)#(tvvn?O=zr'&|~t2[?}r~F9,sǍ28X1ܥ}1z]׈˼Uv#S]3 _նZPv6~e]mܞ]i3] 3BرNn{;QpE%P?񾜖ϭeu^X¼z[/\=c,nT^i^+1uS am=uk*z^zSQs~I| |I~|=1Dx`!JPAS92l/hp{4n1l ~w ;ܭ:42 B>?ڲW,# rIx ZxQv{!Q[}`qNCǸhy\jR*o8 nkf -a3k[9X(~Qsep(uI:;A| ʰjẓ &}ɣ_`J4@nkwC#VSX52S28OAâ@%GqnkTVͶ<:b@|@HRI@GTQ:΁^aR2We͍弻F\`yq{*GceF qݶkBO2_vV/2c/S|渍H|$>F_&VJQji. 0?Im?w*wI H|$>[ְc%Z>|J=$(wכ9'L ERr $v乪#>G 1݁nR1H X/`Z(&q{]eT*(u;i:cJ8}G#Qcõrїe5@VRJvJ^TOHb<GceLH@G<$ yH~~ G4GԆ qՌ~A&6z+׹o_ GG tvgqT-ݯ0?vH|$y XRwisZGE-:yRɯk H|$yBJYWPtW >$@@ԯF8r)u9($_`atqhy@sgg'#Y - y#H@G<$ y#H@G$ y@d/HȜ9s+yӧYWWWUʥ$ y~ޭgz_ifLoЈ2t?L^aH^?G;{d{>CM @cz鋧~[=dM0$;6MT1љ¶]Nфf4RnkG{#:_hso46?I-2MSoѧz??v܎\Ǯ^]W ߻v  IDATSoѲ>;b,6:'KxZvڏ$_4y3 y o8b:t^Ͼ}x}#$I{&4OC/ƭ;??z;I^uzc4oޮ\zd5pB÷.^ IzE͵WsH 7S:Vxwvp:+:\'QS߽6nݩOZ2M:> 7K>M!.ysK=`Mhi֤:ؙߥYyuK4鯎vӊ1g {xH^IҾwrv ށtHfO˾MрijH 2NԱk-ܣc5qlIRzPkbWKtk… _wy^}U{G>g}V ,пۿnӘ1cgZJ>o'|>{Gwen=ܣ/}Ku]}GO=/_;w*W^ 8jڵkdt9稳S_~N?tmذA_$I˗/WKK&M'xB?яl2}kۿսޫm|k_-ZTL؇po&M:N8A4{l|Zpz)=Zly=:餓$Is>1{r+W[]t.~ٳuk̙֢E$IW^y͛Wܯ_7l2͚5KW\qE I[nie˖oԧ>)ZJ;w%\zǴvZq?I&G$uQkm1G~:Kr,YkF7t$i``رcd488(If}/ 6Yaܹs=cӦM &hĉa$͚5KWVssV\.=zWuwhqFIҴiFZzqۆ_|#vuPUW]}sΜ9S=/[nq}ܹsn:ٳG(.Ν;VȞ;#Z%$ci:cuWC+VP[[uy .رc5k,IҶmۊ]iǪT ӣ'P˗a=3;٩C=TsΕaڰa.͘1CzZZZkiݺud2joo5sL};n=3:O~RLFZpC=?_۶m߮[ꬳґG'|R֭SWW6oެZSLڵkauwwk}ќ9stg?ms9#{b\jƎKCdFgg'Rio׊+ HXOO~iICjӎ;$IƍӤI8) y@m$ y#jAkN#Xhy@hLs:g @C2.#8G<$ y#@G<$@=0 J< NN6lfVlgmپk^Jb%#P'A[{:@$V<5+:kQ,+ yDֲ_-Jyt n-zK㊏Na:l{MǠYj|$vR-Hw HCMKNXɣ__,%/*ѽT5;kȾkₙRt4J ۗN Gg= !$vh3'$ yL|>_ۯcHzITE4l}NVK4,"yPH9:@zj]Kz4.^YPr.qGmP<5@: n{mqb58Aj>ŵ8^6!><5/vKy JQ6J}kQ-7>FݗTǔ!yR\c tHc\F9#E8T|c2eM"Ų8bR幍OM|$yPN rkm }|y5HP˯5AH,@|5ߚ53/$& ;KG uanL &(h[c36U~*'ھD~Grk zIֺ戫ߗ%#gr05Ƨ">zJ dKG /JvGmo+p`)xE{ȴ=IzΫ&m|> D_o}z?D!X]h_k31oϫ^P.#yVNCc Hox!#|>BAl6)7%.a/Q[N^퉣3IJ x9_yBր *^r[luW;tĺNH(Kr-H*1sa]z[s9hXYJ@g -l1L "M!.T_m]OMz^W RV6~HD4(眏vQcdkuچz9_2l3ѳ\N2MESQ@G˒ fJM ۽ ?N+(I}.Djj c%s[cR3ht^s B/{$mm;˥?yR ħ.RU: e w=R%C|lvN t[@ "KJ]RW[ZkDwg$@ɣ3$$\*0$ ycdtU/Zn%YIa> m3Jmqm?g??L2N.Jwt)Il]ۅBAlf_Pu9;*)d=?ˍ^0WpqܶCO`T" rV4=j%hymbr`9ɷNHQ@@BBKҢ~yۻez%EE:Hwh$;=2!@n &Pүe.:~ ) %#P,]/0۶'Ae*gjAd3Ty%xn 9Mc=FN:kbp#d*Vjva(gdcVk2nV9畗\[NvVrf^9Z4{7w^BaԾx^yk:;;MiQ ~=CUov>7ac{_aJ؊iQվQ,FG۾+Z1{}} tهZt[ju ےe0WRGm snpMZ$zJY$~ut;uCۻ{aw.S鷯Cmċ}$>arZl3]h l1#$-:Ӝ[Rf.6G%WEϙ$oOHrZ8̠q~ s&yt^GbH:07]9wn˾~&I ㌹n7Q(e*38*>v$MkDo;VRk˅~_.7}utk|m% J` J٧eЙH%^^]Q[ϷG8ǭ-1m]aݞ/+="&0-xn̙(u1iߎWtMpv 7J!vqmĐVG%&aZtULݏ-^Ot+VcMlXJ:_g[MRI+H`0/{:Gf5:{uy7L( S(!zlڟs趽VC9\d~c.k $-&-#2>G>^-^]~~ Aqp}蜪 U5FGc%]}>@pVɨ]n-a:yP -[_QD1a&5'yи &$d8RڠQ.M@($qN=8Zﲎ-y$wo1T,$fWIL 1Q_K 2 ȃ Fh$BQ. Y"!$$l Y0R9uSϩtfTuuׯR(QRvo\-sL&yDn^"څ9kK `a*307m;eㅪ.l-!QH5yTKIpMf.:Q1t|$vlPԾ,'#5>F#cڶ- P[gAT#-+(!42ߝTI_yQDDFfG[sHD@|OZGQjݚd}oYSv w=R~ k7>}gq^Ǧ6vwfE퉏s!PטP͠ȾwLG\؉%. #T=!Ͼv]2;qbPX9 ؉F׳CǓG5` 8zU]DhuD_%ƙb_(yT , CG@q%;KW0 gXu`J'ʐ<GD;H=3_<><X\ Gf0a+Ao Gkp$Y" )<syd~ U #zY JT=F5-Tɣ%C 5ɣ8S#״&ywUu^sh/ Csғ Z_]$Eq(Tﻖ@&x5኷>'0V]'mo\|p,//GW1g-D\nhWceYZZC|ݑ#m*'doBn벵UCƲ&{TZ$ꭅz%>24~Gנ 4XL~ r1ֱL7GG#tEIfe(cߴe vOЕ%>ˏ= r'^!Z2DW~(Y)d &1Z4ncʣɣ:Q-ɣ"@?lbW@iyTO!i yH/ֱ{bk괪 XhJ$GR_udIl 4[ЩzD$MEhW3' 7Ar*flAGGhfrM,KKKQc϶[kI =n٦rB&m J.H$ mM#cޒ_92<ѺHPKH<Qfn04C< HУ1I2ٍ1i%|<Q֓ ]>ZwB'fqrlc4NN}Ǣ<G["eKLղ1DWiZ-)TWOM'$ǙqYu|0 IDATP /$`K߉3z>l-H t7rk( k9v$]񉮤wc]5+^u$H=2L"- %s+ڔl@NnKf]$H'QT`ď$=҉DXӧ9kW2}񲡬MǓMˣvU2\]/cac}C)eg7Hnk]ۙg=KֶBHь|[1&>|Ր?KKKQc=80(qk^%Y,dܽJka%Yʕ9~Y\9&6ga*'docQ嗧d(VM8ɰAuNRg<$Npɵ=45վD*F5-+j,H+3?lhd>-')wK%MSS1=F|9Y;+ZfU+))|XlW&ޣ*<S[NJ~Lϳ%VASY2dӧ[4C<*{|K&JV5eeXktjrX(Z]E˜G;iI6ܘG8Aeeۦ'}LO5_@diɿ̐˲ecE|Ǯb\Y&w%}Ӳϯ㧯hz4:fEc|CŞe/3uyeuwu %yljW2)46J6[41x+ Wfpl.1|c FM `U-g=>I4]k]vjL6hd\ӶmvlIHǞkyB]1GVsȲǽ>'QAx0t!N>9Iû}se\ӝܑs:g>ٶZPtQ&3c]2^vTi]H/(ۚ."R.1r(eYsC8tOQKmjuP>- U[r,*coj~>\Wm˹u.{s呲gVyi>L<;׏%C7s+Lq*w:|Nikhp`̣L$DEg&l1ޯUH1;;9kS<'!`$NAtۚNqyB<34. 5Xv|uW-{c4my3HCo8V-NiNlklklsc+><:vzK&ueyouöB9@-u3U~c+1O~?5n=^5VƎm.| , U˯k^§|i7(ֹ\)eq rq2fM$S|c$[f-:|c4|j|1>i)?/>cX}Φ6.X|مHu Ǫ%M ^G>DE]MN}9>"q$vv9nv1^6=Vf]R JXID RU[Ucn{@$V9Ǯ HZq"b%ںV-d21h4ݻw‚۷O~˕W^)'x|+_:KDD7,ws=WZ9s5du/\.RY^^$Ieea׮]""7I#9rDv%wy='|RDD;8ٲeȎ;ϗ~Ǯ/)ǎ-[͛'c Cstg}wqO͛ljk]7o۷?ϗ @eg%I"ѣGennNDDow-gq<#8iucO.Ow}Xַx=:^s='<|c˓O>)ww^ټyqN:$-=>|Xo.x;|{ߓ}}'o~~zb];v|;eٷotIo|Cnj,{ǎ ݹ9ٹsD=묳`v-=7z-4Mȑ#+"~O(@G1@#L־u hEcU6@mH,M]]/weuXjUv?C:v]G9`?ufI"p!c϶v}1@ZNr qޟF'YwKvSa @3d&"F˜H$یcUֳ^]}`o$i*iG& h,an6UR@cZ;{]x}XMׁ3xt]< uv, D|lh<rbM}z]g[LA^c{/ w􍁾kpc:<R<e&g.s9}nn{n2HrqP]GgGG xSe MѶ Z[WE0wLNBŵVyYTy<͆ Vm 0跲=)1bYx5/GGRպmcL몵n׶L[^_K`$ cLcBƝ]]fjD| \]s-c[|55jXgJBĝ27VYfzIR5I\9P&Rt[=$kZ&Msc(ty!I*~\M^5}qVv;Vƺ-'- .]?7e|jU_?m9$hR,[y]ǩuEo0>KZ?κ dM$gr8K/JjgelMX~׺.O* Y:pyGI(Ol(:٭l׋k2ڪi%уxE \jo@jIwUW!CM{y&\\Q/*ɦ[C#Hfyi[*hW@VhJ({ŏX13FN %!-G͹ .*oďp@xP?ʔ#L'lə9F"fnj~~je'zNۧZ5[ghV|4Pf1RHQɂiLEeeʞ6ۺ]kkfu OUg<Ԟ^ `$䌇4I[ՆWdolh42Fm۲FJ}^&1 0Xl7C, d:t>gآ! غ$/h4]|T:>ˀf[ rgUW4I5fp"fz4RMd$k{~e>p8l]mfʞ6ˊLW/ןx41&,bY+~j:dEisqqĈi5gwWʼncN m(4.G +1!:{E'2F'^`6> @, "IbPWiBUg[zH=qlJͻl'*?2[&/KdrhE zU9m$Yչ r6 r-j2$ڒ@j$|r 2#fRհ`0xUkAQ'ޛ2"(A[6*#лHrfWM\@Dq:tEݵ}/m1.Pk)nih'6@m.k}VPLgRLԪ9@m V..2fID+|Z <H t#@#41IIV, Zu!#b$eLXAP`J տ$qȸGAiy$y3q/@^b8pC9x"\{[W;T2@ƌ+e#L-Yn@#Р/F%ژ ŕMCb dk>326kB|f4e;&SI21/"cFOk۶Z:ߣc[7ֲy8  `~008MEDd4 c$e# 4ȲZRZ=-ŰuVhn"k7kc_LeuXC+L++u>82>|U,bYh{h[Leeee1-V^>?'kKDZGSMq111Sʠc,*q,F40NEndemztq-&9^>+'&tʺoڽ^Ӧ+„)wb-8f:k1@x64!qb8v Z7'k'[%k$ɸdJNia׼֦ɵ$*I>ԍ=VJ0Yvc*Mr1YdZ/8kP<}[⨮הZ%@ƤG g,Z˥`Жܩq.70͟cjQTy =&Ī4IDAT3o@ASOL>aQX05;0k=`V3qW\,[T*d@|q2nIM6>l.O+ζ6^_뭃MzDQpjr8ۢwQZ0hG`FRmzZil$VDeJ1}q#̝MG]17&ǃ߁.1_dgU4:u,ԋĘi9좤 6m/rVЮ%G˿1w.2c;#[vbG%OnllbѸɢ[tlh/gdN@Ez3tTuK:&ͤ1R=;@AbEz'(4^ulwJ5.lή sֶgohj"2q?2m m86ns]eˊ9F.;ic]YGC߃=lQ :BA' =ŵ1FSG+svG`:D}D2#Iq L"xD#ޙc@O$zj]G&x,C|DO<:jnFB0 yc5$@7E9ۚ1=ƄoeepH€1־ Jϋ""2u'>` b`Gh4JM7Xo!ףzYmp8l]meˊ9Xv1U+N2>"ٳ]ECoT=Xm25}, VeeЅN-ڔyB! 6XpiŽbp8'#H$HHZۻf]ļа/ڟ,\O=<@ dvz_OIJjIENDB`tsung-1.4.2/doc/images/connected.png0000644000201100017670000027642711701017117017043 0ustar nniclausdreamPNG  IHDRmsBIT|d pHYs&? IDATxwX?,,Db/XP@c=FELbLxsYn)4xSD*~5 X "T)*Jwŭe]߯هٝs|f.9#(B;"""""""""c"""""""""Y4&Ȣ1EDDDDDDDD ,""""""""hL`Ec,XDDDDDDDDdј"""""""""Y4&Ȣ1EDDDDDDDD ,""""""""hL`Ec,XDDDDDDDDdј"""""""""Y4&Ȣ1EDDDDDDDD ,""""""""hL`Ec,XDDDDDDDDdј"""""""""Y4&Ȣ1EDDDDDDDD ,""""""""hL`Ec,XDDDDDDDDdј"""""""""Y4&Ȣ1EDDDDDDDD ,""""""""hL`Ec,XDDDDDDDDdј"""""""""Y4&Ȣ1EDDDDDDDD ,""""""""hL`Ec,XDDDDDDDDdј"""""""""Y4&Ȣ1EDDDDDDDD ,""""""""hL`Ec1vyd2d2w8DDDDz1EDDp/_ ivvvAFн{w̘1[nEyyy}ػu-ZEa֭I ԸEDŋͮ'۷o_ODDDM^y1o}}%EǏF(cǎ; n޼ ȑ#z+**!Cp!&NN:AP̙3X~=N>X3qqq/Eؿ?0m4@R!==[JwTTxѧO4mArr2bccV9;FDDD;vLaoo/VTTI7nmժxi2'O[h!۸qNQS NNNCteff7Arxʕ۷VZI߿_u8{4B^)K1Ǐۖ(bjjhGr!iTxEo.ڊ O?*>c2w\[ÇK&Md :Ϟ=[j9*++CBBr~N,ͣuz?;ÇAJawaFDDD5,TVVݦMdug{ŊwȋSO=Scǎg&ꫯлwo۵k_PQQݻw?жV^ףz.caa!ë/((eJ۷oG.] ܣG<&͘>pttѺuk>*m&Z … uPTشik󃤽FӍСv%Y0vX,]TH.cݺuҚe_|~vgϞѣr6y<""XDDDJ{ƍIYYYHKKN,;tPt p `KFcm[6loÇ7zAE -m6ܹs0ydɫ8?/eȔ)Sc^6؆Kv,}ĉjQQQAirL8,ۮ];mOQTTTH"""zh@q1@rr2<<<6O˖-|ǬM[RR[n5y ʕ+8uT׵G59hu!11QUs7;C R>|`cc???BNN_cĉh֬t}CJDDcBr 6Lz?ֺMOO6Y̙3Ҷ;Y5.\Pڶm @=& WW]ުU+իWM&-_sEQ'!NNN5j.]$dggK25TV}xױi&\z[l `Ν?->""GXDDDl֬YvDDDO?k.5eAw0Hk:3b4li&?~mhO45UuSi=Z^jryyyذaCCС?:|󍴭su d5yd)lٲ2:Zh!m#""z1EDDd~i_bb"TuYf,=o]z`Z[oIkŊXlO7oĪU{*;88`R1h+%%Egt7Is0[tɓc峲0gUy}ȑH8,]Tnaa!&O\Qj\/  ]f 4;CSfƍ2zʃ"%%ڋҷm* 33>,^jJVUS3gɓ' ///GxxG>&5wDDDd\-{n 6 HIICRN®]t=m&M_/_'BCCѧO"""B5n8L4類sm8::~C`` ܹ `ڵ;v,:w ܾ}ΝáCw^4o<$$$`ǎFϞ=s͚5Caa!Nbcc1~x4mgΜ#((d\!!!ų> 4oeeeBttt+W;̙3Xd x whݺ5T*nܸl޼N+V@ZZ~cر񁳳3JKKqUI `…֭úuеkWK.prrBEEΜ9͛7#==zڸqju<"""'#!??_|饗D\. `ѢE /u*.._xe2"'++K*j4vSe-4[nf]+;;;qΝz)))gΜi L&8p@~jjR ֩yf٬؛5k&^~]oׯJAAAbFFeeeٳE7nX2ŋͺJR\|vė_~Ydfuڈ,^uдexzDDDTG`="7nk'6mڄ={ɓEyy9ooocĈW*ذa^z%[^_~x饗0`qUӲuVݑ-[_Err2._24lmڴA=0x`9Ru?[[[|w5k֭[}!;;%%%hذ!ѷo_?^Z4\[=+W"!!/^4yg & ((ׯqqB&QF7 !CSwX|4HRӧOqyr9+눌޽{qYܺu Yfҥ 899lk…ݻq!>}׮]𩧞 AK/]AǛo)Ŕ4hԩg}]tiCs}̽N/_FLL 8cǎ!++ o߆-\]]ѳgO7'NkODDdQ4qK""""""""zE܉Ȣ1e.] L&=ᅬΝ;CT^^^Xl;h\|gφ; 6l|w3+N!PN'JJJ[0H:s q%=***Unݰ{n8;;A͛(..n>x`]]&Ie*++1c hR=.]B˖-k.ݻѰaC;v /7nѣqMt ƭ[p|7P(سg|qDDDDDDDD&1ek$%%aʔ)2dѲ8y$AoPؚ0a;@\\tXPT?P(xװxbSNi , _|L 4}?i$m۶JY Q)kӦNٳg("**&DDDDDDDDT+L`Y_~Xr%6mjlAA>  2Xnذa*ڵkFۣݻwwDDDDDDDDu , vZٳ2e'O(]v5XN/77yyy'N맧uDDDDDDDDu , qePTҺUhժr-[\R}Yy}@j n߾˗ͬ:wܑU*rԴѸA0A14=8lذ'zwy!"""""""(L`ճk׮᭷ނ\.ڵk!%vaarԶ>)lq^{5tСʴ>(--޽ QT*P(кukiߥK .Į֕zXر*q棯 (--BpO Ӻ? O Ӻp)X,++ ߢaÆpttXt)u"H޼y;BǏs""""""""XΜ;)xxx 55GNPQQA> xy=z:ڵk(J<3Xf bccѠA<-zd*?>.O.O>.OG FTMI7nd=اօi]؟օi}اօi==1Eu8aPDZDDDDDDDDdј"""""""""Y4y}@DDDؾ};WaYg00EDDD۷׿; """ݏ ,"2>S|x{{ϯ ""({]ab!Ê+; """2Eqw""""""""hL`Id}اօIDDDD֎ ,""""""""hL`Ec,XDDDDDDDDdј"""""""""$B}@u}j]؟DDDDd"""""""""Q:(TTT`͚50`6m ^ZZڃ C,(1uaޅ ?#>>Eii)ЦMx{{c*W IDATԨQ>|8r>ݺu _|gϞ5jT=Gdy?믿bǎ￑"8;;M6ѵk4MsޣTVVbԨQ??%%"""V~~>͛TVV/((qqDFF?0k,&I~~>,Y =z6n܈ ҥK:rrr$|xյ"SfN+::ZJ^=Sx7OH&X"0EDDDD 6 .\ xxx@Rصk0t 3 $"}]\RzcƌAǎ#&&駟_aǎرc=F_BBBb&y%G#4zL1EDDDD ''uSoO?Gł I=ⵧeɒ%RJP`Ŋ={{7ٳg$dgg# pvvŋݻc$8"DDDDؙ1cEBBF`͚5P(#LC{_ISSe26oތ0 S:)8._RR"m3LXDDDDXٵkbcc駟РAϜ9SҪO||%3"Q =:/Ժ?ssέPkcǎAAz:kH| =BBBĒmdeeIBCCŤ$`[zx[b6mW_mD|L^+A}U9LVeL&: U:!ׯJ6Č*׾BBBSuʞ={V0뚧=ƭ[qƙ?tP֭[zۈ\hfFOn*͊{ո8qիժ^4i~M2Tْ?CPW6zJ'""B%݇>ŋ=>;֏k`c111SYSNoGhh(HJJBDD~zܽ{Ο?`ܽ{SLA'N`ո~:RRR;~xm%$$`Ȑ!(..L&СC1dj EEEHLLď?BpttԻtyy9F-\.ѣ1h 鈍EjjTѸv^ysf͚UyrJ̝;oƍ}iӦCLL mۆ\СCܹN XO_e)DQJ={رcB7o"##8rީ`EEE2d:@=nĉԩ Μ9ӈŘ1cgpZ(aggiӦ* ZR[nT*<ӧ6mb 995'mTӦMÚ5kw6XNgd̷a_Zua>:8;vL-`oo/VTTI7nmժxi2'O[h!۸qNQS NNNCteff7Arxʕ۷VZI߿_u8{4B^)K1Ǐۖ(bjjhGr!ixEo.ڊ O?*#>c2w\[Ç!-##C~gϖx~vʤ8A[2;G֭~^4. *J#DQ=2F͐ &H|Gծ=*J&N_{# wi'NHkJ%BXi8XDdRԺ?̗%mi2YsxŊԋGFF⩧)ӱcGDDDH?3~Wݻڵ믿0:.Zz5rrr ֯_=t˖-(Jl߾]t1s=O<7c>TVV۷oG֭>|8ϟ8|0߶m4) .iCRaӦMpttUuM{]ӧ-ۡC899UyҥKcbҥz?;r֭)/Lr={D= s%iCծokkmPM Ad2d2;w~dee鼗UL`cC{ƍIYYYHKKN,;tPt p `KFcm[6loÇ7zAE -m6ܹs0ydɫ+M{祸 2e}ڋpqqҎ%5ӽFEEAb"1qD@ff&Ο?olvd=MϟGQQQ7Ӎ7^hԨ}ZT[< uBMD"EDDDs6P\ w$G6hP&,cǎᡷ5}Zl)m=fmϗZ...غuɑ 6ĕ+Wpԩ*k9r6Bbbvbc?|0~~~F;x`|nvvv(**‚ pk׮fOHHN\xW\1Z^;!t)-k؁8r]___̟?ppp0+`.u󈊊޽{7oDo˗/?x& `X`$Q7oެ6 /E Q*vqqѲiK{pmi$Jvv6uSNfSSΝ׬Y#M3ky氳3ZݽQ>xM4_|W_}eee矣YfEdpm8a+bd̟?۷oGzz:RSS1i$d24h Lv=3ªU0* W MZ;Q{)DDDT3/ 7fm@LM64S‪S .clJT]U۶޽[ f=Z 1 3F*qy’%K سgPPP>Ž;ݾ/@.ҟwWO> ~<Y2\aÆIZ= &˟9sF6tGN˖-'|zDEQuK `ԨQO4 WGǎ㣏>2nVVN&MH;j'ڷooK'Uѣݻqor/mD4r'O6Ñ$=wss9rh"66Gڵ]+.M:zͮO?1 6lڴ ǏvS6MMkݺtDTxGWZe\^^6шΝ;KW7o,}AF2U7yd)qlٲ2:Zh!mtŋKflݺdBL6MPeF.]mc#GܯV.]T_"Kd7>.ҟ ,ܕj Q ԿDF뤥!88fY,z޼yiӐSԩS>}|#tηzK^+Veˌy&VZݻwWyJ5bIrNNN]ތ}Xt)lll1Hœ9sW#GJ#tR|8`APƍ@BBs̩rL]ᅬX?M6ř3g bk޼9L(ܸq{kN:Ν;ػw/6mGGG 83g`ɒ%x7޽{uPTqyfTVVB:mXiiiHHHc(--իW8\v Xpaun:t~~~ҥ PQQ3g`HOOIøqj|>nʕ+QVV0|3ftk׮!>>;vֳjٲ%bbbugkk0,^eeeìYЫW/ )) ׯGee{FP`֬Y׿b_7***HܼyǏ/RkAdD:@zZ^QtwZf5[[Q͝;W Ν;C_z%Q. |hB/r^xh}L&zʒʆTٺlK#--M֭YNܹsvJJJę3glC&Щ*T*ul޼Ytvv6+f͚ׯ_ERinPPa7%??_իXĐxb[T˗/7KQQ/2̬>}NRŋ<M[驷kbÆ O}:Zj\GGG 0?Osչ4n XlаaCڢ}3g?gyF:c.\{|Cm۶PTP(prrB>}0|w5K GbΜ9ի7n ۣ]v>|8>3=zT:LXnO^z r* m۶Ř1cO?!%%Eg^M 8s ~GL4 ۷Gƍu_CŦMlMB۷7FAP 3gıc0rHckk!Ѽys9)e: sIT(r![ڿ""2_`3),lݡe}]_ܹsbŊ)++ `oV\YQYPUy4_j G+:C ah۶-v܉ƍPߑ>稈L`Il}اg\.=}P#8 FDD֭o;;;)֭稈BHDDD手S1'DR 9gQQK/=0}bǎػw/ 77w܁CFdŘ""""hXw&Щ;3EDDVi0`@}AB"2 Zuy(Qec LFPNoPR#""""}3r*g׮+PXx΄DDDDD55E ז_}@CDDDD&ի-@EEݵYQޭ67m۪],DDDDXbȚ;ܼ wݵy4lm~&M?oܨX59wNS3.h4P(̯I`],DDDDXbLC:>.U33SA$3}` XDDDD3+) sޮnI ,""""%&AEpĽXee}oiTj߾zu9XDd(1uE|7,4ׁJ״Tw"|Q-0EDDd Νzkv/wiQZI{#ڵ~LVYPXXDDDDD5$N=Z 'GҶkq#s5 2Zn`V{cwBN#$"""Z`>|oKb=~^XxoQ?U_(/NL ޮ,A:XDDDDT'"""7o?PoZ￯~zQMGG`Ju) "B]% #^5I`L`Q`h/Vj=Z`"(ϪB ( IDATBDDDDD5$TcdاE{ _TU%40֭jc99_V-вe͂,""""L`YeˀRO~T*HJRS5^M~yzm[Ʀfq99r=FΟ?LLH)7;jaZ4kNBu~͛3g+gk o֞jt^B]߁>}jSꟗ/׼ … X|9ЦM5B1c lݺ7:zq-,Z-V͝R-G=?-Z$%vڢYfׯ,XL;>A{9L`I(wTاE{Te^(J̝;#%z0EDDT-\0:9rvvQW倏O}_WquBDWMiEuD ''xyyaݺu[O?ѣG`0qRxAر#FܹsիWwxSeIEQ^Pʒ)! yUD|UPQ@T@^DA_eS {JK7=?'M6INuN<;<LC\r"[B"""GyW/JڡC'm?(i+Ca@~Z5icF>`U6mox%kԨ̛7:GxH>q&A_jժt_^ᨈ XDDD2u*pг'jU<ѣҶQ#m __$xzJ#N#$elڴ vK/UF0x`ԬY^^^B͚5 /`ǎV6wD[.f͚O?EVVVe,33GnN___4lp9cǎ7DTTj(kضm)dnB y&LVZL2kժM>u*U@!88:tŋZT:tTYYY *˰@y1rH^o;vQN@բRJӧuɶonsʔ)#G`ᆟFc@+C`` Z-QV- ӧO/=<<Э[7cǎ]pGFݺuF/Ҥ0qF<([,Z-ЧOBAD"`ޖ-QjEQ('(XQ_x[n# |Lu(ŸlY?;v@;vҡ} o{9Cߖ.C qECaÆw˕+gM+dvUfyzzg϶Ovvꫯ|A|G_.˗/(x,8Çaa}?Xh -[vލE!33K.Ezz:~]t =z@zz:;Ǐܹsq]>]tAVV4 v.]RJĮ]l2ddd?1d~>|gyƐVgy;vDhh(222p lڴ 6իW֭[xxM|gŶЯ_?ne˖۷a]ѣ݋aÆ!77СC<(WΜ9 {9 QF(5i}EDDt:q)СCfD.]w^@Z0`ԫW:gϞҥKqlڴ }͛-Y&"c^@V'N֕Zf >C/ -[lٲBBB8M6ho6msIOs=;"88ϟG {cԩHbccѩS't:۷ ,)S#r9Hʷ0Ü:ȱcȢpi+x()]Rd.Qt߷|"/t$,?~~~bNNC]|J*gΜ1isIB v˗/7ic2i.FGG{ݝa־} 8j?N޽krѣ }fvz!Aįڤ<KT\ϋgϞ őa(۷of#zXjU vؗ~o>}}90G`XN0awA"$$-[ĤIhaɓ'_r1߿?ájQlY<䓆5"?7oJ矗֗GC]*[ݚK ߷""-G`xaZjh̙3H#\/^_&m֭EO6fgFMnY&F֭[Ks"!! `ҥh׮Lj0<nj ̟??1c ֭C ,ܸqcÂE#77Xn*Wl]Ϟ=1n8}k׮|]0sxLO?dqTR%:tնuAHHHۮ]yӧjX`a?fl7R0&MqyzzYf68rssoի|biӦY]/Q7̙^8q"MDDD`…c!r,`9Eaظq#.]/// 55ԩS 7n؇NCxxŋVky_֭[n߾@?Dn0a„x"DN5S洘mo) ξS\To x-i%ng-c6ye_EŋqRsv=*r?~硡xN 'NcqkFO'}ر#*T&_]iiiX+GHLLGg4h!.Kl/o>BCC Ǐ/+V@NNA0LŴDbϟ%+_ԬYݻwڟKYɓXf V^իW_~̙3ѴiS4=x S-/n#oM///C1R? $Zj e˖_L8YYYXnx $$$8{,BCCMhӦM3kǎ=z4DQD޽W_bŊHJJ{goӧOGÆ CD6f^{ 민|3hH#._ؾ}z Ss矁z6m/ +(-r #U6)5_JeEtyM iELL yT>pjժe]fͬSbE==׽{ EPY!qN>vQMO=>a׮]X3Y]vLL :[nM67nzb/+Wbʕ1~xmQ7nݺk׮"##dho"gx~B h׮RSSvZ1aBE4j?3<<<!!!7o.]7b0`~"" LTc/]FKlT f&OF- ܿg]Z 8HN.^Jx5ҧykS̵~vFvȼ YYYV/i;wΝ;eݻ_|T,W3LG<i'QL|9r$z=f͚Yf|hӦ ڵkݻnݺf7~o㊢UqԒqaݺu8q>BѠI&hӦ :v숮]Z-qW"((jB1|pԮ]񶞛~ocnޛD).x>w?q)cǚ-N,\vͮT] - x]Cy}[mh ^]ZO18%~A2 uHachhš0Ļ(>JQ X@۶Ҿ<ʚ}ieQQy7j$m]ŅIh|_-[}O:cGٳ}Kgs&]rX}ըQ44td*Uܼyfa\3^˜;woHH~iL>w˗ &^oP:k`)EDFFbԨQ駟pMo7n܈?C Q71ncޛDJ` y{{C@ӡrʕ+7Ɯ$\r8{,,XM7{<Zl1!!ϊ\E_51`\[S>=,MNtGg2)`%<&BraѢE.l߲erA-BCC ٳ/r_[6]H}OY;=7o@X6'''/TX-BժUDvv~yuQ:: xnwv*U>L8IP 'O⯿2Uݺu1w\;wHLLDzz:6n܈MBE|Lw.|azzɉ\7ܶ$ZC.`xΔƷN#ȉt ]v M=UA7,HS"M=t6mG [˙=w>"ӫW/~'3 SlM\ጁv†  xgyư_Xlwm_:F W+W,,sAws5bcc Kf̘Q*? B }W{V~i&?aNNϘ~,`9/"!!wARRϟ???,^۷7C=p@90$ >>mɓYQxzzL:dOs؆m؆mS'_}e&M RS!xzZxi^= 8BJ 6郏>ytuZ:nC #wڅvY]v9=zW^1;_x73:t|Wo^3gbƌVG?%''/0Y?ƍ }ի"MFȅ&ifӧO7E3 ySOFm޼ӧO796##;&&ܹs͎ܿ?^{5,_K,z={TF 9py<䓸yžrssiӦ|#꥗^ɓ'-C̟?pq~FNL:l1… x]@SNOtd]`` FƍUV8y$f͚>㽼' &&غu+zm_<~ž-\}Fɓ'<ְp)6Q shy$lm /&Y~k}@Z_? Ç J\A|A)^Km>Z ; ې*T[[n|2<(m:uBDD|}}ӧOc˖-)Z į~:"##1l0l(bϞ=XhaW~0pR}UV!&&iii?~<;ׇ?߿ .`޽ؾ};zّH~z\|M4A޽ѡC/_8y$6mڄÇc톩mho8< >}Z '7i_|ѣG#99={D֭ѣGT^:III8uq1c=;t0a6mڄg}e˖ٳgpB\t ݻwY(Gxx8%K )) ͛7ǫz!-- ۷oO?@<6={~!^{5Ġy\2|}}x\&L0c̙8rcDDDo߾hժʕ+͛8raΝ;+k`,X 6DРA ''gϞʕ+ ԩ~J~oԮ]'NĤI.] 66:uV`[nX =}-Xr"'xBA| u\ZZ((8k֬|ꫢ bxx>7o. 0\T?_jߺ6GKm^~۷KbF(b˖m#G9(%"BE #QرcEرcw8bQ_vP_>4+++K|h!C<0ŋ m f5v[mٗȑ#c=fk#nܸl?K/dF#ܹÇ|1+W˕+gW˗޽k6K^^^޽x)_{[ݻ'6mjw bO2Ů%~cƮ :Ԥ8C)S|r_/22l5iҤBeNa~d!5j>L".^pے%KX@ڷ];Juc-; IDAT~'rpL>]tA*Uqxj*\raZ1///ضmbccQzuԖիcŋ },17ʫmWFpa/0`jժhZ)S2d,Y7nK.fķ~`ȑ_> 4k :v؁]4n#Pn]ٜۿ\p_}vJ*A hݺ5^u[  zqa :*UVE`` ڷo쁅y- F||}MFFa㲧/{8Ɯ9sa:gA\\\Yz.]ct `Æ ,1eȷNO>i1pf4U-֯>L*Ni4RqL}L!i`D`T<0r$7@ƣ~1vX̜9SpJqNsq5Z֒[b޽`,s sѩS|שS?:YmD&/PYJ㉈NaG`y#Vt6[M͚Y.^şJş`ʷNI˗++WŋW^OHG6_k׮XbE3zlݺڵ޽{!><3hp 8 $ذaAi %"r[E)`u,m O?݊)KNlټ}ׂI=z @!00ؠAo("6oތ͏>)))T7nƎk۵k GƯ_AAAHII |w1hРyA0\aN(;HK1DGKSO^ tVKV.^fr|FE?wfB"""""8 TT ˗/Lj# 3#ya~FZcO>SO=ZjCdd$FÇc5RF]v_~ CZZBBBУG_|I=""q.Ye@VA/*޿ۧ\,DDDDRt4hPF8-[ƍsH-Z?쐾TI>X40ڴx4uX性> @"0Pzy>p};""""r{,`M.15 9-"Va#6oRSg<㢕,gf?0},B"""{S L.>?,\bE\͜9ҔJ o=1"""""8^E9ܚU Gy!!DDH83Z5p6ib1( @rى,"""{g EڻwքWP4U8 ,"""{g 95k=Vc""OGt%۷V: """}vC '$klbN8S```l`䬅.O~ """r,`٫8SU@t4PR裢=3qXO<61vRJ>̩0ɞ~ѥ%W -,̷:9-5Kݻ/? \а\.<м9w8'Y|s.̧zsisZDŝBX&xq|,{\>*S}Sua>\G`ñMD x{KII@2##zSDZW!H[""""P,"""{_yxAAEח+ o,]*'Ms(9-a<}P?b\7^"""""< !=XE]_̝+_l=L\DDDDX"""<\9eP??/_?9#J7.""""rZAD6/H\.k_Z*`q|U̧0|)%o_n]ێW.""""r:,`ÉG`֭;"""""#,`M(*9sZN\r|6oowKL0Ü I:X"""\BhV.mkڴɻߍ XDDDDd,"""{k`9,hWXׯDDDDX"""O!tI@zgm> (Q&6""""r:,`كS+4[ٲ 4j$]4B""""zDtDDDN/;HK911TZ,""""2,"IC cN I^ R63TnZR]>>̩0D,"""[!!:K<1 'GX)p"""[xUdf'N( 9&Q9-$'?4o.(@utş0|lK_˖ XDDDDd,"""[8EDDDDFX"""ɧ\:~HKS6""""R XDDDp aX\W:""""R XDDDp 28alABr)ͧTO7|s.'`'/`\ڵ x>`fͤ-`ɒ=|L;LlDDDDTX""DQT:r0F8,x=i$ ##[ޏU&|)S}Sua>\ XDDD {xAA^}V ~꫼ۯ]*,`),u^^ԩ̙Ҿ>̩0D,"""k89sEyg!w'""""UbN!t?tΝyO=+(&gR\` J`] Jg'|-er0ɧ`>Շ9UuEDDd :@X<Ыg J XDDDp s)_xmiR%i[uKıEDDd L!t;o +K¤- XDDDD%o~hPF`;w""""%[Hƍ ecocGi\9@rs|ED6 t`̩6o;+ nO_ț⩂inOc>Շ9UuEDDd(bb3!OTALEDDdk'Юѐ-\ȝHX""DQT:r0諶m__ec X̧0Ü I:X"""2] XDDDDd,""z`vipGX""""R5 o 5 4Q: XDDDDQANec!EDDDj,`eW,""""UclASm*O\ec)&S]OaNՅ$r,`KO.PBCmNl,DDDDp,`y{BEN#$"""R!&Q9B.~.2|D%`1|s.'`Șq\J XDDDDd,"""c,`.T,"""cr#<\8 Μ[\ XDDDƮ^*(q+7hظQ٘!X"""2v䈴mP8 X.DDDDD$ș~̩#GF*K!0]| >̩0D,"""@VDD( J XDDDDd,'qL0ݻwGDDj-[bҤIHLL1zhDDD@! Z·~1:u Æ C*UhXQOȹ>,m74rVb"0eCDDDD#(*k0֠ hZܻwP| ?]gʂ^DGG_z5 l@;66˖-kqȥ ̙̚t4TXYY@suN)~U?~$ZjsbHOOGRR+VDjj*o2+)) < Q^=۷)))HKKÜ9sm6?1gϞEll,Ѷm[>}Crr2>?iӦ@D(yV&AEmvYjBDDDDX.Ю];1b}Ǐnj3ǏZ֧O &@8quwArJTP'OD```_y̟?~~~r ʔ)c5VV%"PX4\S@0xpuHXrb999HHH… ѿAcǎ6Ǐ 6lh/'N]>޸ʡCҶJz^~9o,""""U*ƃ3gbȐ!n~aRJX" ##7L/S lPgBj!瑫 sj /|и4+5% X̧0Ü I:8 UP F#ɓ믿iii}___}g|oX^7{)T?DD%N.`q:L(m]EDDDXIx"p$%%xboJh7OOOSBaaRi \(7n|faaafԩ?c1b_'OĬY 322,c|1c7><0{y!b 8G!aNi[|o<_|k6y?>>>qzsFn}oǏGpp_""%8<|)Tt4HyF1V|CDDDDK-OҥKYNC`` 233APV->ك={޽{RYYYx! ::֭?F֬YA!++ tô4Bbٲev=Ӡ 0*Ü{m۔HO BBG1lkx 1XXЬa>ՅTT]OP,'PR%,_#F@TTQ~6[GŨQPfMdgg ?8͛M6Y,^O?CaȐ!\2222:xEDrz+HJ?zXr""""r.Eʷ)aNo)4^h`(믥6mxi_;<6LOua>Շ9US=9T8laNU,Eޕov<Ы4"ˉ0|s.'`ŋ@j*ԭt4hy7oJ66['/ο;9}ɣ6t:ec! O?mջ7p$0` Rפb9}:$m]x Y1d0gN>6o5/` kW@Εƚ8Q*lS`ܗ ֿ"+ka5yTamв%|1^E,`G`5ilT<=_z4mױ#{7JדK>6"""" XŠqA9rg U3>%-s 1HHAN˜O}lk $l\0|s.'`ˊ'N`ʔ)LCժUѼysDEEz?$""9rD֪lun-[-nIpUEDDDDXo)Sp|߻wí[ "DQիWO&\DDxyijv4y9 ؽ{7[nn_p!ݻի㯿޽{ѴiS/T"T)ÜBU 36+h4@t4jSOua>Շ9UueŅ :u}͚5?ڵCf76mTqQpwQ,мy@[.`)>O i{BӡW^ۣsΕvDDTXz=p挴 ʕbbo/8iht:dffСCh֬ ???Z""痜 JaaB%/66/,""""XVT\FU֭[k|E-""rRra +Jك,"""":t(;v,q ZW=z̙3x!W@DDT(ra0E rBHDDDtX⭷ނ'֮]e۷ѸqcXKcÆ Fuyr9nS0| >̩0D,+֭5kք(E[իM~}h%B%",S!&&ΝCbb"t:ʔ)cFc͛7W J""*0!O#2&8iePt:tС!*e(*9TeS>&6| >̩0DSp !Y)DDDDN#씓g޽{V۶o߾"""BF~_.<0X!!!ǏǪUi(999ʦ/t l|t=N!"!!-Zeːa8 9DD.Sɖӥ?r*!`ˊɓ'#!!AAA={6._ 77ꅈ-Mk99@AA7 IDAT\=?˗/k*U@Kr? (9TeS>%%&Fn\| >̩0D,+nݺ???t]Pȑ8ѹ-bˊxxx*OD6BH/\㒈HQ,`Y̙3JB(@}:XnϒT}q&bGb>ՅTT]O"'N}]C!""Gzi_%,*!ѿJiDDDD[.~7l߾;wƶmېtXDDT+A D(""""ROgF E۶mC)ȈȌj=,bHQ,``<'󣉈T@ d}Xb۶m>g,$"rr*[J>XDDDDbˊ:(SҒzuNVpq8[糤R| >̩0D{B* N!$"""r UH999HJJCለP8 SG`!==fBfp-Z $*1V>nSN!t|4)C2|s.'`ˆSN!22o6<^Q!"z=ߏ1c 22OV:\""S0G`͞l,DDDDnSHMME׮]qUhZ111Tڵkزe VZsΡk׮8v,B* }|9)/pUTP"##Mڌ1GA=p|8q]T8J] """rsBhood7w}""rREa>9zT8 XV;wٳͶݺuΝ;W Qq ,*.KDDD 6j4xzzBחBdD˞r-nSr|SMK!Oua>Շ9UueEʕǏl{1rʥ(XsG8 XVt(bȑʲ.33*SNVn*G`Q 7*VEDDD;v,<==ƍctz=z=.\7F||<<==1vX&r8k4--o #63|yz)TS}Sua>\VYDD.]ٳx饗LH˿t:.]%B%""{ȣt:GX5 +'],""""Ep Ǟ={еkWR"w={ GKDDVRaEDDD(CTT֯_dՅTT]O"$''/QSvGRhe\7?QXDDDDT*JLL)S߼y6Oٷoi;ȬpFdQ,#rJEþukN_|ƍS*|c@v9 X)G q|*EFa%6TS}Sua>\G`6l:tA "UVYŦhڵkǧ#&""ܽ+m9}+"8~\߲EX XFUjժWZx'M#qK""""EeťK,rg T&""""75H8%**kהͰe2e 66fA!$$ׯ/ȈP8AKh!w""""ʏ,+VX 8f 99+V(ȈJ J@v9UyեjP#Oua>Շ9UueŞ={hбcGm BrooV-`: v`m`` q"=ݻwh" <ׇPreWxŋhl^nj5SNaذaR t:QeDT>Jٌ@D)DDDDg!<^/c#''4q֬Y5k֠{}h4Z| ooo^ Bvv6A@`` Ұ}vl߾֭òe8Mt`nS.J}[/X̧0Ü I:8ˊ+"==ΝܹsHOOGXXX+''-[ļypyΝ;~:F X~=^~e}TZ /mڴ1{ٳglmOƽ{>?bڴiEznDD}N!$G)_^&%)`ˊvAE̘1f[M۶mXqqqؽ{7^~eT^p{xx8KC;ZciZYed)2sIm15%{*{JD4325qTPQ6eX3sg|ޯ׼;g/s8kN4 ٨Y&֬Yn ɓ'gL6 iiiV6ͩ_AABQfd`Ks=Xd &L`v8aNN^}U,^WZݺu||ذaݻwW\rV c ,f„ LXj&" u`Ho,dL`СC1sŌ3xbƢ^zD!__|Eu]6onݺP}5ۦ^zhҤ >,""jt} """rL`aΜ9Ype,_Dwww;o_ -Zm;GԬY:u3;Dn<_*""""[3f`ذaXloߎ .@QSNxꩧP]N222 ر#nv_GJ*z*/C'|bgZ;88˜Zj(Z ƥX.՟BM`ri,Oa y0e;SNux'ooo̟?Dڵkc̙߿?7n\;w7ƍtR?,k׮4PW[*77q774""BOOʒ"""":9N^z%PGTTT61113fLA Iر#~g 80|?vަLEQ naat3X38G,yD3۰ ۰ ۰2eJ1P^^~wX]9f̛7СC}EQ0k,@~~>~"B}\mo7nM8B[i1~aiژI`9|lsx\#3 \}}lcqs1vژk1r'C-FJJ PC )x<55]vEnn.~WUƍÜ9s P/RհaCTV )))8ydj׮ HKKCNNN`sesaYӳY+I`s* 2 xqeͭ,$$[Ʊc7TcǎŬY( fΜQFUi֬Y~BBB8@{B""Nԉ0aUV!==Kc+u1c`ɫѣGW|pqܪ@_~ǺtOOO!~z?u9իW!"+&V+l , 1`Μ94'ر#*3ȰAk$;v,Ynٿ"WR> `rJs̘1tLDDv&8C , ;w2J*pByȄ%KsNW >~{s[qTz 7aY'"rgYٍ"wq^^^ Brrrp\t yyy%#;;ׯ_/uN>H[Ucǎ-JLLD \J\z`%EQ0tP|'f=v( •j2EQؗ2}zP&ܸtOOG3u*aEV;-X؟>5q{qB jG]yPKjj*222Q0}x^tI}fff~xx8̙;v8w燆 ⮻O?]0ı4޽{1}t… @֭1|p<#~]DDS6yE:RYEDDDdsL`ihѢlقݻw]vm/_hӦMY*/x+5jKVH6I} """rK:aon߾}xV#$2@2hAMi,Oa y0gAӦM~z7o.Ɔ 0bu]@tt4W#"r$\l)2rrc􎆈8P֮]>}`ݺuXn]cM4`طh~m8l hرؿ"""">f[P^=޽o&֭[p\!"""0ydl߾5k1R""*Ea8q"&Ns!)) yyyY&իwxDDT!$[kRn]8 r@96!$[]nn_|}u Ȩ8ʤ(!LBOGԸ1!'r߶*d xا$rP^^;4jڵ"""MBH(@l,gڵ@L"eȬ$L0~-_VEQgSbEa_iP94jw462~0*u:?}j,OPcKCRRڷor=/??F9~pCˬ"ص 71C\0yd$%%jժq)ܸq7""r"+lEQW^k AKCڵ5k{;79e:5jjsZ􎈈ȥ{Kŋ} @HZ5MqXBBBΥUW ${Cnw7""""bKCll,\[P9XE!JDDDduL`ix Xh<.ѧm:a.џ@MV24OI<иqc|w_M6!33Sﰈ c0|=++d5LpٔY k'=[Ӱ?i5'`KæM&"rjKӦAEM`YYXwwDDTiirW@// Ypw""25oZj+QQL`-+ޘ"""" !йs琐4j2d"""8Rܦ0UcĈJ(L`pya1|XLP4OIU+9l˙?UXL`U`,?}j,O"!DDd,BHY+)11.]^z:GDDDEp!959XHJJˆ#PF 4lшF #GzIDDK!$(WA0Um۶!** CJJJ/_s"** ۶m;\""BrroDDDD!1`!00=bcc8{,6n܈ "%% ÇsDD.LB ,S@^oDDDDٳg#-- 5ƍ Wƍ#&&/bbbp̞=3f)b"P++ ݟΪXOcaX؟D΃C5Y駟H^VV-|k%6""* &ȪҐt̶ܹ:uN:eȈTBHs`YXVS2"^+ ݟΪXOcaX؟D΃ , vvQf;v ++ JV`P+ , sgErrr.^g}зo_FDDʒ[!$= ,΁EDDDd`d.\&M ##!!!xS03g "55UVÇsR`^Ddw2?@ÆzGC!Y3Z5e!""2<~5>&ʰew}PMAAAXz5vj ?8H7׮UWM۩S@d$mJDDD6!e֭ߏÇ#88B"c @>˰̲M U;ph@&Z` xا4~5>V`(hРQ+qq=۸ذy{rJ&p^, IDAT"""rbIXB$Ui 8y$EV(W"ܺXHI'.""""'[UVxlϣuؾ}"#/aIX.M`7w)u?0jT3l:;u,K`xQ4OI<Ұl2_ܹsm;w}aٲevrZ]uv֮s_/sg`6}b$+(^rCʅ , EA޽l{w~$qi^ݿAzUNfNp͛r+;\<:u UVEjl[zuԩSvrVNe=@dL`|NBн"$)\g#XEDDDT. @~~~0""" ;,8` `: 7Yt ̶oaٝ(z@Vf>~]N8EKcL&2d<{VnLjՒs`?i`6/u9RxcJS35 Xpwg*g}EQb $$$ !!lG}/stD!;2Cu&+WsVZʕ~Pa!{xժ\ŋ' ٟF1a?@߾򾺲B?}j,O"!e?Cz DFFǦMW_Wp\Wr:Pi`bY=գ\00x)xrO/:y;`z9j~LH ׋.GIDDDDXe^zW^zADDq 'Cj֯WлkCv=GXp)?GN˴ ߸!K/.""""' ,""2M`Ur;s&кq('6hu+zV`X4bXV^װia2 NNDDDTnL`18`_M@ƥ}UǙ2 &ʍ ,"*R$!TG!W KXO#+c!X؟>5'`*Ξ5y%Iܘr)))Xt)x 4mFڵqce#-- дiSx{{[ƌ3]ϝ;#FaÆD*UбcG,\yyyxDDq&$ \bk;+&''Q)Boӳ I( |||담4]Էo_\%1 goyyy-Z >>4sNs=HOO(@vv6rss={ď?h.ٳ@:?ڷ7_G}i2q\;OOLvQ#_w4DDDƧ :`8~8233q傪(Xn^7n}݇gϢVZظq#^L|רR ㏛vjj*>I&?k׮ᣏ>'6mڄ#Gg@DTar?.;*fMY39֌KX'V3ÍEDDDD%9 7oƎ;0|pDFF|P/ TK.Ç( [@4 .!..ĵ}]$''?Zn @V x7/ѣGډ*'A` "bqwMӧ?}r1N8ܘdZ =+unݺi>>l02){"-]УGtСs}Qԯ_H[}YAzx#!-[f ""'L`X SYS''%7%ȉ2EDDDd1&4ܹC_ⱯxqnݺXeuo.@~~~W? *M>}J:|t_W@DdCڶ}5԰!PJέ(r W39Q7SXKCʍ , /Ʋe_xRR ]?[/@V`hѢÇ!(h޼yWKNNƥK QtL`!pイQz׺pDKO)]lHQL`q!JIk׮! ȱׯc޽A޽ 7n~~~HLLj /~(#mʔ)PVaaJ`; Z_jƍ 77ģ^t`TҺc6l6l6lmLRwL2>&4x{{6?6mLANNծ?f̛7СCK)`ҘGcSƍfo'NVq6wx؆}ZM=YS3x xÆ{UF<)X֦zu⫯J)|ߡbf 1׿3ha+8qb1l_~@y&QjU\{ܸq3gEYK/mWv+f|X}~xzz[|""M7oayr8ߣc _/Ykwsr2MONCݽd|L`iݻ7xo>OO{o@~~>Zaޱcb֬YP3gĨQJmۤI(!8Pj;PTWW?ЬY3j{""q钜p͔%KLP\_l{}5umGV[lfw""""* , GFPPoߎ6mڠvzQKؽm۶1c0{ѣ5}כm#? ٳgǚ6mpgffboEzك:|F 9\ !~[65e>'N(1RW0ga :M6> 6mT׊+3f BCC~aϞ= ,ĉÆ wQGDd38e0:sqPX"z`O>Yf0m4L6cǎ-RիWW^8s bbbU_=_HHV^~СCh۶-߳gO|NB.GgP}.@a:VK ~}""i !4؟>5'``t-339n6۷*4i<(;ӦMΝ;L^\tt4ߏ_| 4@NN `Æ Qׅ'"r$:T`= >,/6]CC+: !$"""*V`iزe E)vZI;#wyBϏܹs+]Pqܶm UnWuy;WV`B"""raKC=,J`.9Uyyy XVm¸89բEc);ƶo 9 $O#b y B2o>>>x'k.DDD2C ^{ S΁€(iB""""KæM4@HHxxGIDdwX6B l*ϜBOnKY]l|Lr BHDDDd1f]4t]H +mI~hꗲX`l`RyA(e!!DDlRԩVMtxyZ_,AM`]de cV9%&&ҥKPԫWO爈\  5oelON~˗:t7>% rs0ºuaIII1bjԨ "::hРBCC1rH?^0lƒ8ɹOmTu2w܏ƌ&M%\bl'''(fI؟F>5'` ۶mCTT͛+^|sETTmۦwDDF xiKV5l&_u QX<&4$''cHMME`` ƍ8:t† 0n8TZ)))0`5 ›7V%cc+uZ]j.~\Gr_? /Oب 6!A8XfϞ44jѫW/4n7FLL OqHKKٳ!TdQ% f̉| ,s<5lP5kP!ޟTiSڵkSXG$^}Gi_׮VSgmI/rw>P1XEDDDT*&4$&&" ;w.mNSN!2""*X _@j/^ij> ۷]b#"ry/m%+6nj}g9Zd|A~}];1&XDDDDeܬ7zh,ZDӦMs!&&`B3g >> .Djj*VѣG5)c>RK-]WhpAV+9 K36V}&HPޚO*4?Xñzjw}HMM;#iӦi~a#T""S!BdgGcmjSej%#2x]98XDDDDe2t  !D[HHy$$$['"r' 78|BB5M1ӫ\Y14T˓}ol. ,"""2u 0|0ԯ__lec>DoogQQ"YYҟ 54WZ)E:(6O*4?X( 4h   r!E/jUTcspaa2'Kg""""*sR*gܹe%nI'X{_q-B55~!Anϝ3.!o,C̟n,JJ2w侹׭ƚaDD䲘a_rssqd*dxy8}V+-4(61"Yx,V`,04vdS<)#^l)՜;Wiض x-N`ٵKoUskd̙@f@vy IzKW.YX!+š6~HOL)ueYd y{/]61tl;VK' =|h={6=.-?(=5k kyD!&M3?_>}??E) xy|S,'O ѵ))Bw)ĵkI6!>DٳuBm+c5V_s7Qom/kPcj9rPEL2֍7DttPEL>hPψ C=ɓ[˗ !J&v-t"E"99Yp?8N_ )&6ˉܼ)֭gw4$~hR Bt$Ě5֭Bh!_\$ܹBi#ĩSۿ_ѣx%!ZbcNɊ>Ӕ[d|NS\i#ȭȪ!CLo2UiM2gի}MZI7sɲ ZUq?,SO3w#"=!Eg_bȐ!u")) ƩSp!+V`:GK׋lb`t=ӧww9a9+Ӂ`- '$'{n~CK7?_ΟUYii_SKr.?+#o/'O` `Ӧ X {NTX@pE9}yT_}U3G96Eؼ\V>LNIsmEEsYA7N_6]E{q2yUS={WUʄƍr޽xt9R~nT*']AUI󡡦J-RDWtz^0ݯV xU9',K^Mu?.Wk7ONߩ|Ç^fG=OݺݻW$""28V`Y(??+mۆ$f͚ԩzwwwCt|(Kt>m`㩧ed¸q6۾]ں5v\t7T?CBd!I+FtcW7ؤII[VVU.WɩPY&Y\et`cGȕ?\B%? Nt-JVݸ!z۶}!nB&`0Y,ii\;w;}7;.>^9thc q(ib8 777tE?^ԥTl?pL^"2 ߝ/^d*X囬 -eRl,( дܯ^V*KN2 ֺXN^.pIQdu_/ykiӢ\}Pu{~Ӧ2Q"'YӺ90&4!0tP|zBD8.]2)S>J%XDV-`(9phSR2!%ō*x/>>wg\Ν[jӦ  5kC?EC*ӔN`AiXv;89qj?T45}z-w'עL ( , K.˘3gL]"peưt\˂ GiSY@c^ժ֧V􍫼|}+(o%""I5=zx뭷HscN`esr} T C\NY39Pr2?zGCa0F %q]M܉\XEAժUP2DFǡS>[`Ci~9qJ9-Mnm{_rA4&\czi#[dGI}O|(L`ɣ`8!OKr֠fs&*F-lc{!Z};0pqX Rp "2$5y4z4+}Kțq߸J$|"" ,"6yy_3f0>;""p6i"r$' /]2-߼&@ӦEEV`Z!Dp}L􋇈8;iySYuu[>mذ/hr{i4eXDND]P&""2 &Lkaq, ?K,mpYdffBq ;FGDdcjVzmBpi^,[(+WuСr?& kwDD顇LOC 2Z O>$233H7Z [rN}z=)zF U+`^`F'll~>pH`%u ,kGdaL`遟b|!`d 0r{w^=j,O"!:ׯ͛ ĢE0et 9s`ҥzLDT>ݦ`Fqu*,3:A}5R,2#57"r`Mb{LO}!""&4{СC?駟k͛7cڵA5Q9llڤF+Nn˕^[77$fk~UkT>L`9EƎ5}ڥo , Ν?_n)'VXa*gnܞ=e {mJ`U&YYEW+&Kk.M`@nry*&MT9M989ĺfM /Ȑ O/l͛7GPP;f*..Nnl)XF1V`))SREYVzM5!"(*,#$""bKCxx8222_p,22믿r "](ö9)R43Q|ʪTSST./_MT`ݼY{T1e5'Xv?XZl\9rXΝnSLA~~>HDTa;v'BnϞծLms5ժ%梊SX6ID ,""2 &4ZedžEQ7ߠy׿mLOWZׯ_Ǻuo@z777曚ϝ1Y X~a 2 MLܲeK|pssáC_bϞ=G}#Fеvڅ~aҤIXz5Μ9Se/Scq]waʕt~ })s%F26e G)wZKr&K*!p?zYPU|AW>=}.hf_5g@Ь/\e j8 Qca93p|g%zꅕ+W̙3 B>}гg _KQM6hݺ5ZjQFB9ԩ6d_#F?ΝZj!55.\ӧy0u1U@K(\23,֥ڴ xyl2az:o|DDDV5i'Nt邔b+x?j2~x!o  11?3&LGyq"rR EϞ@p/e2MOWO[:Pwイz˭žeT`m'~}Q >ޮ l(m!!2DzJMy{wQU7!!@BE ꆈV.E* ""Z.QۈU@, d k [ Ⱦɽ3L|?ϓܹL2=Ǐ7~ \WU%ҡ <Ѧ 5$n-Z.!$1c,@捋c̘1Q^`ԩ3f# >RW]u>#F@LL \.XDN h=GEiijQwY+)54uoXo1*xQf`H|}`֬Yعs޽M0'"{^UYĹ@2d ,%@pvj KWdXD6%wX |)!"" }eFw)Ç9,O"`+H9SN!!!ݷo_K8x JJJ|g袋rtRwsZˏ}׋ŝPUUFAEDƪ5Q5Hwo .NO}e`|} Xed4}3L$50Ed3^6Oa` `FӆFD555>cqB?~!u9`AAZyrPPPPW}ĈԩSkѢ&LQFaԨQضm.\_W3ll^?XhQm_/Ode}"h2/>N׮P[p`)\5;dd@⊦Y孏g%py]Bh1|`h(}$vXF>;&vrk}؇}'>K,q{I,;v7p;W^^^/wZl'|&L@II ֭[nz̹w| 8cF0,\RW}h}7+}|9Mh].pۧr9227fM>Xw6_=*?h[a<Ӽ> pkP[+^$ˁmۀj>)'96r<1o<}}%Os01x`~uz-DEE[n C\\)SM6F 7`Æ  GPGXXB"j`dqt$v!,(.?Dd37{d4z\uz-`loQ 77N555w. c\*DDԘX⫲R}jJ@(*>Xo]lwo: 68za5~z=fۼysqO?Tƍ@dg}^_'"m 4 PӟmǎVll8@fպ5#T|"r[{sADDD `1zh >a4KEEEᘘz>}-1|Q =Snݺ1Edwrgv7f`jvQ,Y8Pn2Byy!2+E YM`$ `YH~~>rssq梶PRRRw.77%7n܈+V©SWUUaݺu9r$n EQ`;> Sĉ[w;rt@VкukeScΜ9x衇~ߑ#G_}݇4!>>֥<M֤OQw"$ s2#e`sK\g|zf`y{]>u HL4f<6f9|Xlܸ!@Qh>ēO>͛7cϞ=8u ={bȑ5kV]v_fϞ.=6mڄ\kÆ ߏ+?":rWV|#رq`~A<X{mgҸ;!YX,X (Ӝ,#rgFh߾=̙u% cW @NNDfU^e:2\2իr_|<0dn3s aUy30j]Z ^-XS?>""0u… e1!d.]:}:=k统g `u a@nnKS**s2/sXkX|e IDATf>}#]'2p+)>X(T @ Kr`C<L2i 4h˗ǎ;[e:Df`96unj|zN.?tx5EDD BŹFѹs=Z^迟 ,fz&NwQ( 'Eul^h+ `9v [ ?_=^EÆEDD(B\֘v]'ڻWWVa!gߋ>* ~^KDJI_srY :DDDc ,?z٤e ""+;l}tm~ib11YS l|S?`wֿҗKVV d8":k }VYYNjcπS||@Ń|XkXj>SoNj^yD<yy,5lO"`+H"""C J W Dڻ{ 2?f`9ܽ'ְajXDDd1 `IVVњ{UjXuc-}۴ 2?:za^"#Ek\0gXDDdY `Ann.NE]dh|3վ=Э:պu\^PTX99eK?ډ\/DDժg,1c (~j ;;7oFYYEC=dAV Ə~XuMZNɓ}2bbgCDaYX~ /J5 `EF'L^xA.LI ` ,#h!2DCGDDbw""(^oѢڶmcҤI3hdDR;8&˔9r "8v 8xPJIJJD},) |J'$0^{>cc鏯bdVl7>70{DDSY)ZրEG?q’dN `\>ئ -6Vd1Ed ,f`Ű;Q0 "]Bص{꧟D{yAy+Z_5AE{ ""5i `62~=p8NNtq0\Ѳ&Qb1,dў< ;ݜ_O"F(++CAAdwnЈ X]$<))b'~sqb8rE'XD!];68r`AgϞ3m+%ܾ=>B0EbksH}w5\ch 7W2X@Ϟ_6m>Di),1Ebk掃HKCll,W/ /Od+<7Neg֐!baX `'-s Ѯ[=jPȖsJ>q>,?:wv""Q]2;J,  hg_|QgO=f ,1Eb.7,0{4DDDCII njP/3S@I8JkXQ3K#;x/_\hD`~R掇 `c!11gF~~!2 w״)΃'O\9mN|ŸTP{?Of`'msP?O>ih,ŶsJ^q>EHHH_|oǬYp#V%èQ !㢵J,_DF|XGssZ;,5s&{]f!)) {ŋs\P555ȇ\X 7x˧Oֱcy\TXD!J.>|¸xKQPPPwSehX'NJX ,0>nXɼBT"hU^:tl(c?Νt*QYYZ_DD-ˆ,P3RRѣY(2U\,0(DDD  L֭(HKKÌ3йsgh5"3gcQPXݻ&?P-(7$!:d8(1Gqq10|pBd*d`aN>¤$ફ̜Y٪Zg',Zf`ձ'}0G=P]hX2d H?Q3޽9s_(Q .N3Ebd޽掃BX~|(++úu Qi &lrԩ}M5&'KqXȝ(DɕW3 LsEjj*f͚M!kڔh E `Y}N q߈V~ icθʈ"ݿUȳ9jp@U0٣)|+3gb4hLK.j^~ȇ /O*p82Ҽ15̾ڶŹŒ623BSO\ <0d#""3f̀(uQ~o(@]6x57iiYB(X;`UJ \Ry e `!CEQbˏM(],~I@Dt_b 6b\ `YD+=U&%""2X~=z!5ޏ?:tm߾̙ `Mp7 XD!np :v.QG'DDN?pejsŤl[ ,sʼn, \~8^ܱQHbĥ6};0r;v}7ϩ ,3b>e??q5&6=qQsJO"an֬*+eeG$m$$:` `^XD`x~%P^nX(9-(Сs``Hj.O>Ν', `u&jѻ\G$1ED 8ycYho߾nR,reHa|CHn xiv 47{`8q3TWcrg|VQ*YITP5$5j IS\Ek &":$"9OZMFpE/Zw_IbXӦ7df="""  `ى6%^edv jV6zu|(&&;.Rʼn5 \| .!l |8pZl /#F0{XD R;V۶j?V +>^"%BXV]w۶tFQQ>(爊g۶m2e )K.|@( wh m֡C#b>@мka4GNcg|VMQ iVz~R9v> `9vNC>PO>3 /x ^`ĉn+@leL4ɨQf`Ogaa@~GD3fо9sFdab YGt4.  Y@") 'Q`K/L6~۷oΝ;; Br5J,!?DEQ  Y@L "w2w,DDرcફz}ŊI&a}A}{ u$PQ!vt̛'udب>.!$"72{7P]mXHHH@II ]o_|!^dV׮u lPDJf_unߋ r""7={-JDDD:aK /«v>33r/ǎCUU6ƒdKf` ?!ʍ$YD uX\BHDDMƁ DDDzaKce˖gW_՝ƤI/ 0HQ@0r̩w-52UK/#N;8~NC >Ҹ馛0j(TWWcΜ9HMM_^WD\\.̰Q t 6 0ED,^ YL t`ոk. . ஻X_56ٶlȳhQ=qq5pfUM6Lܹ/FRRDFF?Ddd$R<r=ʜ_4Q)k`1K_Vy~Rp| p5Lt٣MHi|X>{o=УG}DDZkgXT `OW]<(t)0w.0y,Qp !Ę7 ?V掃%D# 5f,"";(.mx8iXX%DWl,0q8޹ܱ0EDd2U)&pvu,P[kXȢ mF ""adRe/2rEE掅,j@jX|cXQ")-mVXaNkj qV5QQ V.#ԏX:< 1Qgd6GM""f`"O<nF$%%!,, aaaXhQ@qqw}HIIADDbcc1|pEMMM߷of̘nݺ!""mڴرcjժxD\A|PQDfw"$gC =]cԛ a˖-(wmHZ͛q5נ &&زe lق}ZjWƴiPQQEQblذ6l~)!OM'XAœj yO{QXI9}:Xpis!9$f`Y(h۶-Ə~eupףضm Q\\/3331}tTTT`Ĉؿ?QPP V\e˖%FKU!KA'hɓ޽掉l,9r$Μ9?O=nDJ xg֭[O>^=SW_(//GNW^h,\f,[ qeBQov?Ǩ75Xִp\x7SNERRR>wbbbro]+**BZZ`وwsJJJ7iDL.!$B" ȭ>~ܜqc0es{ӧW_}>9r$`ݺun6mڄ*(IIIHMMz"2ӕО" t6o,DD `ݻZ O^۳gk?Xd_aa/~ޖX指l,;Iҥ~;w۶m-[6x'N4kdO}e9eV9ҟb45}䱿jk*_555zR[ 8~΁XgΈ,{aֿ? Q`(`&Q|I*22ג%K(Jݗ/>!'<J>4Xs#<#>\BgXϴi-*gJ۶3Xϒ%K|$kayb4nJZcuTVVz= 6xW)@8[oM4;2˒sa>Kf`޽~P=oOx3{̟?ڗA,cvZw}z'k]nqqq ue-@EȺD-[͛gXqa:j%rsAD6(@׮ţD,/,""n0$\Bhs "?~yصkWS!iIf`90)d%Xj6u*a = dd9"""!l_~HLL|^`ӦMqƹ]9r$"""r|رcطoQ'OwQ ,q_N=>uʼqP1EDhXJرzDDD0/V±c]饗PRR0qnbcc1eQTTTO?4Qo͎v'Be`ig8dy %|KHPOP!, Gnn.Μ9\T\nn.Jd&9=PZZ'bD˗c3gϯ}/^VZɓ4ilQkkтC2K5eEDMEDD݊tףGdee5;믿vn͘8q"lrTWWƎ?QQQ^?ĴiP~*o\\Q[[ EQ0}tmPt IDATE\[Dq[FP% 9m8re!iذaދ ˗?ɓ'cǎ;еkW"&&Gʕ+^Q3i3Yngcjj<Ǭ 2})(Q<3XȝXt|;?2@bT/\y%ЫZXd~0tx!s={20r>Ϝ:tUU@|ې\g|j:$~IC۶7&: 9>g'5 "RS-eB3TֿJIqKMg| ,,5Y8X?挧8$.!$" Rm[q]B萵v69qPxn DErADDD a*N,la;;jHҼqMu\p0h:XDD "3Sb>Xd9L ,"j""Fc cqr ,O.#d-)~{;.""<^%",QȶeKsg?*%v)KV a@Zi"""c5H%-D% ^ˬ9jkVDSHF'3\g|pC~N>ؼQs,O"` <+;4Kj%ڶc3""0,^R"`Wţqq@|"""/"\.|;vVf`ǫ)J٢ bYs>HѲ>,O?ڷukׯ7{T : >""27Wsaa'2 \vh8yEDA\y%0anxȲ""2<81HMUe[5 Eۦa&2Rd/n^~$""` .p'M@Tp{o; @2K(许B{4 f,,""3̞ ,_.n7@׮}~{5 pTX ," H?iXȒ""2Cz(Tf_\{zX\B `. HDD5HQqAɢ@P3̚Sf`B}5Y8Хh"sY8D*`.v>bAodQ[w\:sX)ED EVYPS\r##"" `䮂̙&&ʀjq%Fփ<~\ڸe0ED r\fYB'60K͘S|0,,dK_|ug#e٢^ Ωp>,""+/.w濿 `<#,7aoEDX۶w[2EDD`H> |8NIƍk>FA3XD|ho7o,DDd) `iNq}}l^ĝ;:XD%c8r""2Νq,HJ X ,B"Mf,,""#O۷ŠB ,cfXE s3""A +nٳq~y9s%1z>_sH-Z-U̝s,O"`(r VB}""]L\vzw CDDQ2o&?~掃Of`BQv?Qcr3hkx4̘S ֎g|r ,&2`XSg|XDDFf`7TTA 5XDd2oDDd `EbcM2޽@M Ю:%Dی4"VEqʏADi7f`!ڵ3""2] @D~kB, p"B"2gȏ>v 3&""2MhQ(\|U9eK_F'3\g|6,.֯8N$ X! J>-j_oh(""Ct(Nu?nxT `Ap`.DG; ,"Uǎ% (ΛgxT `Q\.C/K|Y,9,p׏QXkp>eK;x )ZI p@v: >""Sn.!uh{2w<\BHD,b)aV9!""1EDlGmd`I~8޵a ⋢1~,DDd t8@aݳ1Hy9{hrYK0DvƏLdm2U̷n mS3t1wL<"" ;""2 XDDz7o+)mf&p8fs0ED8bc9!""1ED R!ؗ6huÃs*X|b7t҇Q,}5Y8AҢп?0fy˥԰pNId `IB|嗢Idk` u?'_~6~LDDd$ @slX &DW%Ddo\Nw/|K "s\fd}"z֪gK_F?G_sDG6>^iGZ #OapNId""ٳ@Q85@y}3҇%DdTի ^dU|<cX &3tACom""2r>9XDڎK sA1,%dMX~8df\*FDdk `Qh۹SCJJo9 }% RRTT_|s{ 7F"" :(edk7®]癁Ղ[8 3T =zN}TCq-""b(COZhGesf]6 ь;Oxf[Ωp>," m,=fNhC8KRAP5 ,"2vdoC9sg}&")(tGZ6ljj^hy'vmH.!dwg,$"t( ''ljkŒ4`Ӧ9}Zdc0ED r9uמݻݗ fd۷ܸQԚ3G,j `ό(3Qǎ X˦&q_տߡCSg|XDvm۪Ӂ~Nl\rsqg`=ƒ,"2^-y ֭6"Д<86M`? \. :Ydpig6"3~ sA-i:u-Z11VFRSff`9"s0bhu~;woqѾz`uoH9vkkEp, hsAaYNmCE;aEDdK `QyMQwoE;d{ccrcG`"q^=7|8p ~\@v6P]~>&F,]js*2QXgk.@r'ЭZ_OJǫes,O"`oZ嗁#Gx2+.O"rߚ5@fX@X(iɴn.8cO)9ə,DeK`z];`QC("",ΞFK^ 8- ^|Q,3w>cSOy&wA CKȒ{NdZ͚%6 ֎.&XD W qΝL_X_mۊ*eMyDn_T7wABKA4/~!Za7=&,i: >""kp3wnW$XR_A5_p_~ L$mǣfB"YE:ȈHW `}h.>_XrMV0zk ,cl-:[/^,e/N @.⸉iLPaEwHVVw8 p8y~Kvd""ۓ._dd,,"<ѶkxgΨE{ڴާ];۞ K/8NO S%\ED'X؝0=wK""2XDd]eejvXq|loD~hWϓgΝ@Nz;D2JKE\=G_N] ۶rey]%N%Dd{޿,"" c(9X􄇋`SS3JK3ıt=Xq8v@V sZR"ݻ={2_ ,.{HNiTQ.!ԏi Χ]pAsZΩp>,".AEizSlzk`9$K_=2 +;[LJN:wnd#\BHD= 5,x7׺u|>ƾ}0c t hӦ ƎUVihX@3E+ѣQkkch!c2qh~u:~="Z.!t6.!$" n Xڵ ca+**V^!C7ĉ'lذӧOǭ ܂Bis+ZjNrݻĉ>.zQQQ#F`GAA,XXr%-[fCnўhe`僞:tkքD, Qo?޹S2KZF}uDDd `,Xrt 1z… 1k,eːoP)x֫X.!ܱClױ.9SqcH#oワPMnbVhB"rŇUgXUU_=*""V+**BZZ`وf3w\@II Vs&SN3~hu,-mcNJvheex``u@Rmۊr`& qjy1):8(YDDd*&''FVЪU+$''nƍ߽{w|>O+@36-k'R.E Ob[Z l3վz/ZoX\>:Xe ̸qI #8px5GHDr2߿2U=+V`̘19s&jjj?>Ѷm[lvp FNVRry`56k~r \ Ɔ{'];DEm9\Be` Kd|~86ӡcڵ"r: >΋ڵ+y޽eeeAii)k? p_@@jŁΩH#rsbȑf`ɀ4o^)BgW#" X-6mD[P P%DX:X2u$?7&"|IC?=[wNQ >}&O xqAkɒ%P >}BTVDd`8gǑ'z^FY`˖^ƹP,ܿ>jxCS?e}7sYc<>>A#XkBco08fad1 ⣏>sxz!z{*++~͟?.FuCsupp=MV++{yE8oΘY]QQbmD =}\.֮uaLJK]xA 7W$i3ĿeVAHCݿg~v'k9jح>m~>sewɓo}/?s>Z}̡g>c1"RRR\#Gԝڵ+ ??>/keZX]H'UU_czUX>ܽ(-g| XTz}{୷2Eطx}`n_$J|Ht$%?n.!$"NJKݻsc "<<{Lr>B\M]v?S!QPl*j\kQd_ei.]3>mV˖Vt8m ,\kֈ`ԗ_ycYa>ݺA r>q'"G~6pz{vH `C̹_={;?rHDDDrO?zcǎa߾}q?X mk׊vܸ[VkX=^[KUx.۰<3owiL(?A'?Ebhg`i7[ֱb"X'"GF:u ׭3o\DD!rHkbʔ)˗H~u70b i\>(ʹS/Wh7nT mͳ Ѭ}}wq^^XTv L""G8HH+_j'NDD,G8z( ^{ YYYukkkyf\}Xz5F޽xbj 'OĤIv),))ŋʹܹsF"CQy8@nm 55[ `{/p.A"dkS|3K5v,f`""G O_~FDDia(8n݊["##gƠ( f̘?۫W/\ӦMæMp#..Ũ(>}:rE;Hl QzhJN)B9jJ=KhV~A{XVM m_&冏7^ϰnj$p>ml${p=|s>oBAAZlT̜9_} /ɓ'cǎ;еkW"&&Gʕ+oSQHfﻏɽ{{dVM^k`5>fd`ڬ} a6@ ;е:BML~GEs'jc"" ar(w~f=N>}2J IDATiTDM]VUzN.زEd0{oNm!"{!/O]B8dHid/\BHD׵k_r:Ǐ=t%"23AU#H;w?Jv99pu `s a2Μ}]jO>.-u+.XXd.3>p ~Y86'w?p#O_|Y`lG(}0EDְvh/XYųRxFgW_Xze`@ZZ^- 8zx53׵; XO43BQȊ^/Y|zoO>1g\DDYC Q@U+#=H "rǎ=:""a̗ p\xɻ ,3Qo{֗j'Ce_۾]/Js}k`|^ZZ\>u}<6QPDDBII"yJ^`,"2LC=:|)SDv8p{]Dk.?+V*kH&kJ-|DĽՊڡCvEg݁u뀄`Fu_*9C `UW7?;wdÇEV_񉈂Cű61ED R^#shG ȑ@\鿿^m\B(wޖz+^YY?RPUI p"JfW~bN۶UkkjDs`ƏO{ÇKE!7df'KEjdwm/RS';Kdݫ? .p!1Q](i}A:zT@Ɍ+@e&8Ѥ!ږI|:9E9;;I|XDd<5}s3ձrTV#k`#ڟ~;i&1o}^{M,o~,\X?lXOX¾}mli桞YDDwx$B C R) `A@ ~A@,f* B!q-e|?ϳNٜλym+9`C diW+=SzD@77yQv_k.~jX iVp옚ZWZhU=7n_79С@{ȑ@˖3reaء˗m~RӣGz/[ED.n]~}㲜!">B֩.T23Տꮧe;R_mڨ3""gOa@""r,"*Ye`_]\}tbW]Wb\ƒ ,@eniu:ie`9^g[6̑VS۞Dk2 #GT@^unq萊o\rDDT0ED9/Zp5 `k׀Dի40PMOS(Ih񇱨CƚZ6!cPAYkSdq/== -/mMF fv}y `T{R`{=N40hNu!_W^)<{`J?ijy7X -MeQ%%VMS+i}^t40u%%pJMU˴,͍9b?z>j\vGe@9lm>Le`իx"RW/COLT}j""r,"*YZjռB,zII'˴i)e{ʶS%$p/^Կ^^tDMA@%fͺf۶lQt颯RE[uk}9Q0l?TlQ1ED9<Xť9og̀/UF&6VTY\MXZ`Ԧw΍\CGajX~9~ym6͞=*1!$hʸN4kg jO*lϲ'6m@e`N=JTz0ED%'+K/d.T+.N?~\M۵S]L&-6Է;y8Ϗ}oeK 뫬I#2~TWQ1ED%϶T];y矫Tky-Z5Ujp=J}tv[Ϻ;aRGв6iLqʲVOq^Sz/m?XDT̝ ,X"Nj_r,"*9[}"V׮jz];7'._23TX.9C{>#Vd?aPPQܞ=*86mRuҀ7$$M/Vk,"*u6&NfPgL)s""*"jo-%Pq]Jj흿c; ԬM:;nXjZ&0~GuʎeNog݅0,L.X R՛0};uR5^ջ&&P5jr|ʕU`X,ND+  /P7ogϖy2 `QHIY׈ VJ5-ֱclW\z8Bh]@ݺ+_HJ:sz `_?੧2g,. ,Zd.(H mi…кSlZp}Vp2Jgk| н;0aBɞQ)HQݸQ]ۘn*@(RZ.yZ VcU -]EmP=S_V~ m6*X@ `Q6{>rrϞjnADDbW}HLHk@5rQ.4Zw8Ү4ZաCj-{Z˺69'ӧU}p=e+3Eх0G!tw/9mR<{Vb !QMK@VVɞ Q)/-{wo(ㅵ#;݅P˼-3Je9d`;)U_230 be,"*^>x7og-.U6T9e`1EDYd`iܘEDe b/ݛar$(޽jwo\ ܼArҗyy-Z޺r{Rޱ=˞BmSCQs/!| `QV vm̙\5R5ߋ]6;drS* e)<x==+ YTN|NEܵ?mv,dva}(,"*G?""XDT|(Q:/Ξ>RQׯ/)͙~j\~0m2[*TPU,BBwO77; We9u!TIMύ8i+͍y>0EDeƍ@jjɝ cr$d@isԩ*A+*J V=Űmo]/')Æy-Hv.f_|xv2W^xpηbE 8ʾTpV]JBN,""MaS `Ef&jz ˺uji௿|6X]|l]SЂQII@B|vয়Th}yJ 杍bݢW/s G={/ s\qp <:ŞDTh_W(""8sxac'N QWZe4 (})}Iu>cǪC<|uΧiNֽAMU՚_sSGeCըa{ aa ׭Ήyyzm7+#HN. -5y΅1ET%'ZمUڵ|o_K`Ro_mXR:efKȀ/\`߲e*qY?II.}];}w8>?"eJ`{XmS]7ul8C>H-4IϪH˯`Q]#muﮦ*c&uH}xq *mJACUUۓYzNJR)Tl%*="*碣U&>^=uf`mܨHNi4Q}<mJegx>_>_VyUۓYI 2U%*="*4nGGOe fe`ժ"DqMZܫF Iuk-]tzRWϜQt`S;~\=3or@.7WZLbB:e.ڀ "2KFC `s%u:+|3Q9}QGX*WsZ׿ݻ€#ZާC߸/ׂa{!jVїkf̀6mСzwpE-!ԅui"*ٓ6/؅<-XAD"*l3}VM+dF 3SD70ϲ];@XnUX[$iwϝ3_f Y#^^j@4lYM\B8SED-ʱի#EkaBkq~:Y=Q_vn  ] ^~v.}3f5< `eK}ʕڨ5j,Z( Rv _ޏ ,"*ED-ʱNF G]ݏg"Td(8+zKo ο6cǀq#^*PfX8^=sm~w(Md$p^ +<\qc}=Tx@e]ռyQV ܞZVgXдzHy*Iy,{MYD=JTz0ETNI oLվRQcPÄU^ݬX~Dܐo:ѣgy> an=cUCPW>_?ha .љ1C}PՍY[Z7\HQi]ykV5㬻3cQ"*磣ɓ|cu*±{]t#_ l$t*jԳz% 00A Hi?LM{GSPת_*P~_̚ űnCe`V?h֠zbpƍzV*VDW%U `ժ9hח|~_ `5jOn-.,SB9wn [`-0WoOgSdm"%Q҃,rjF}^+ @UCP;v Zb7:^;*mBYa2ՁO>~C}v#$2bE5>@|`NU%K^+s%"*9oBDeͮ]*qJ eF_YB ,0Ѱ~8 mV.q~ !AkǮZU !d*<]ׇnn<nAA*1!>]φ~KOחU ?Hj}oou+;/u?Q9uS i_& TEu+U1b@ppN&}\ ϝ3U 3߂kw&4GM"*XׯgۗY:-[|.6V_f嗅nn #HӼHDdCHVB&>prM;>¾}T;;>?Զʚ1c.[Wռ}a@v!s]ӧ˖;)Q 4nj#飖] X ~;u gmj*Vϩ$TL*TxZ1E8\[|<de:U5jtIǑ8z:Xr2Y27 Hص ݻ/Pj4U 걚7)'KN !,CK{>0n~?4rE]I֗= gSmoкue!ef@VAa Yw,f(#ʉŋq TiS+V]fT6ֺua5WV5o1[hf/Q! 2ޏQ=x9kӦx]ݬq.nYY*UeyrEKM5v'PͭдyOOv$Qip0ТE?;vT)Sn_0a;YYzkN:ۤ . /V3XnTM>!T yDS7Ձ_W3EDDkPb>:,ʅ2ҿʥqr*R#11N9v~{I[ӧ&CPzbnUz<2 ,f`QQBn]_Ti_*x (zgߓSRTO?U5O/CDDT.i7J>o&~={& M6?gyykobm:,FBDhy7|!0E)<ύ8LEzD@0xۅcPT޽G+VTWYO[/oC#1l};1Ǹ3Ϩ*_~5Qq2TZ@|J T4118߅HN~ GwݥbwVw`Sg@j:w6+URSp'"WҤüARR |mݪzkid9T8U>ж-&""S{$$(EeXdqiQQ*f|rl޼!dLupb2R 4d'g:FԎ??/lW? 4^dϧ>c0va_7u*5Jemެ'$N~%`V@>dXv4tJhK6mC1Ax~!!ڴQA`T֧h\܋N*ծ-[ڜԒ%juk ٌ3fl6O[}ʩMta{&!wp__[fgz{QQo//#mڨU\,{ئe L:Z>#reHJ&5k&FrRJ)ʕ+e@@B޽{x,~jI9c]JpǎI Ȕwɧ2:Z->a)UIH t8-)p)y JR23l S.3,PSww)oޔrW^?D)wȐiS): 㼋 7~Sߗ+e|m%̔oo ;(Ϲ*32tL-WoOyejjOO5'.R|sHOOdzJypTTӦ؞eK~33S}#5R='Rv"S}$eVV= a-lOQڵ@1\RĖ%)BH$wm~ŊR!rÆ "eBfkI ]X!|=)wWy.O6n۰|s4 Z1`\Vik7Y:d}:˗lNʥK7{Yʕ+6kHhFEImÿ֭U*䒠 -Ȓ]6 H٪;Osޕ_͍umH:<UHߢZi2-þ!W=%)u3^4j"l9{6-[؞eKQgl=)8XB}rϲmZ=]D˖ t>,I)СB=z8ݦ^zR!)'M;TSV+w&y?;r)M&)oyRmøE2+Kʹ ?,[^3tkO2)IJ??nv)-SGʧ24TebiFV/ӥ|濙r?ZʉxG` ˆ@l2T=8v ~I HDD功js]w# s.R>h&$ycpqH)!@-nz*Yz<4m/ts""~˰mPI Z h:{SR͏^<ի[ 7l9^؄H7?ZhȄ9sTܵk76x)L #>[OMO =]?anYO'zYzlӫ*t)so4vm૯Wmiл7qj>PW&k@+x_gƌ6U ~xER T9~>]PTmP%{8 !!::2_#Wn|rF# t؆E;tVt٠kJ[8j0__< -놡BLU 0lZAEy֮Uiデ7*[vn, FP`nk_.]Q ؊ :cշg!M"a}<hu^ <yDhq. `kW8U:*ԫz*Cۧ@S1سG 05DHDDdM z9nƍӧؾ};:wp;oDDDDDDTB(ؗ\X *XSRRngz"""""""X0^(ܭkeYòtM"""""""*L"4mBH)qiTDDDDDDDD,B о}{op)%֯_޽{X1b ""իWBX%"""""""* `j֬߿?~w@VVV^1cz쉞={Q9#$+nmgΜA=p%ǦMX3ȢA8pMMl6CVZa޼yؽ{U||<^|E4k ^^^A6m#55 e'.._~%f͚^^^ ã>~!c]a2,=]1{lopwwGʕѵkWHHHp]C͚5-mR^= :۶mv_gu~W̙3Cڵ-Cg͚cDGGc„ _><<2,yy)zK.W\;{};}t(+{Vrr_ڵ16-|m˗[>Sur_v=K/@>c?ݻwۭ_bC~Æ %pdk˖-ٮꩧ,mf}%%ۻ4Xp%9slXlOדlɸyg/tA !d-ʒM6B9x`:gsMSv\]LM6M ![?o!жm[>|DJ鉰0 4ٞXoі5j8ݮz˗/9Q$$$_t 6c{hZ6l._ ///,ZȰƍ7ob̙SNa{x9soIIICdd$ϰ`lOו$CD$&&B4ƍ}؞KAKn_l뒓{ v5effbȑ0x%F5IDAT}٦E$M6!)) 8ru)%^y|ٞXDx駱vZ!h")Q.,[ ZnmD0zjnnn5kow ;w.fs+ξ}Ю];|ر#lقd$''#"":tw}va%}DL <<==|rTVOri8x ~a 0O ;~7tݲiӦ_P~};? `QYL8zm5g}~!X`Fa `ĉpwwǧ~ )ٞۢE tn!~i-5؞iHJJB6mqFtF׮]qFn=ze?gRz=ۺf 2?#<<<|r|cǎaalSbӧƍ>|W^u?۳a-,,2t;~:5R!===U8}=PM3|p)uu ۴x=K))))~R!;wlYz1E.7o.2,,Ln޼YJZJH!ݻw )i{9… /ۻt1cF,k۷BȐf)… %K =]BSNY֝8qB9{lþlϒqu+]&ccce͚5B>e2))ɰ_\\ Bټysw^)iiirѢES !رc>ӧBȮ]ӧOK)LJJfͲNΝ[2&?홙) d8o}=s}fgذa9ئE#홙)NKm޼#бc+]FDD=.۳b ӲVZ/Wre˖266OMf{{뭷.=r `ItE ,+!dhIkn֞C q,~گ9݆n]dppeaߣGy-?H+VnnnСCIiϭ[Z{zzf=jժrʕr QgrmZ Ҟ.]M41|FVX>sl҉54h`ڴihڴ)f3hժ͛ݻwKqlov`{¦#lO-[`ѢEԩ0 <;w+p_kqss7|kעU222ZjaXn-[,~"Z9Iر#:ǣ^zHKK:v숏>6l׿cذa CJJ *TnݺaXtiQ>2)?)웙W"55c@=1smZ Ҟaaaػw/֭͛[C4ԭ[F¾}0j(,K f`Kc\XDDDDDDDD"""""""""4ȥ1EDDDDDDDD.,""""""""ri `Kcdd֭[KTa20bĈ>R_"""Q97sLźdʕ+s4sE ! (S.`̙xwKTJ+s/Qy|ٮ|2֯_oqhܸ1|}} xWp’>""""%>"""r !!!u6mڄhԨQvK,AVVԩH;~xih7f"""" ,|WN2^<'VȤ%} DDDDG `vڱcN>k׮YHCh99n6k,c_F֭QbEx{{^z=z4;0i$4o~~~pwwG`` Zj1c`?Q{1222|x|xаaCkƐ!Cgj5^ҥKѩS'wy'>Cdee9=ѯ_?^^^^:^̜9KLLѩS' 5ksCJ*o߾?s#""b'\1cBȺuJ)_Bm۶m;rH)s̑[lBi2 mݺհu? BYBYbE)BYbEyA~嗆u1ϭjժ200P !VoqF&IJ*I$K.Y8 ,*UH___>|w&%%E0r YrecY֩S'oaäB1nЎ"-d2{9ivM>ӖmJwLKK֬YmL& !!!}m_CRJ~fOH777˱͛ϟ?/k׮mrN_DDDT2EDDDZ_|aXUV Ç/pW<)%MFbb"nܸ;w",, 7oĄ  \b)^V-\|p.\Y 8"%%wƴi`ʕ+ӧ1tP>|IIIϟ777 f 8/^Dpp0V^dѣԩF M\J Јf`]tɒmd/B٧O)Bf͚T}'QQQuRJٽ{w)p#<BȾ}H$ߟcŶmۤB֨QC9&**JVPA ! _)G[g5l0O>ݒte+WJ!lٲeG7nm,X UVcL&헒"4hm?f`AXXzꅤ$Zʲ\+ޞL2^^^v~xxxPP-22ĉnO6ol/_.sGFppmjԨnݺPu <عsmxW{LYƲp \RSS|r!,ahms!\zղou]]|||[>"""r=#F _`Ĉ8s v؁`۷PC:8\ʕ+N x%-P:u8l6p5T\c4hƎGyڵOi…㏝nwM{^^;w-BDDΞ=D.tQQQNY&իp?Nر{?.^vaܸqٳ'5j4xw^KSN9>/)%.\*Uo@ݝ:"""* `G}ob8>A$D4El#ư Ür6ci(Nm k_:7/tL[M4c 9Ε1%O@NOg#pM|l\x>^|cWEEEaСQ 2=(gTww^1F@={ҢZڵKvSnnϟedd hMjkkr!) SRR(cnܸ۷oGc}^ùߞ~8b5665RRR4sL-\P-r v8!Xg\ƺqŋZZ2Ƹ9;Oww4fw?zmܸQyyyJHHɓ'UZZ &GwMy5uuu*׵|rA͞=[uuu͛7ҢK.%}Ν&UVVjٲez֦-]Ta?"??_[?x4@DNXU^^/jɚ2ec^vf9AF$nzk5Jt5utt x_ϟ{;'I{~ǁ.5ÒN=zg̘!Iz͒'*111b~_\p!b[[[[^9WFF6liySEkjjbuSJjkk_{^"QiZ~r$I999B#i/O?:~ذaBo&++}͛,4g8r*..y{*))$566Y]n߾aÇ$߿?ߊWaaSs+Y9ڭdIEsK-Z$I:zzڹs{O@Tvܩ;v(--q/GR3dժUtqɓ3g._uɓ%~wQٳG:^|E>|}:fw^͞=)$v"eee?TCCCX(t97LNN̙3}w}W֭SSS٩Ǐkƍ;vlKRAA|>/X ߪ@1:t***f]~]RhնmTZZ9 )I۷o׼yo߾c{wQuu$Iֶm۔k׮饗^Ҿ}_zUUUUZ`Ga uAuwwKΞ=yNXekƍqc1?Յ}8Gff5؊^m vذa<)))';b[]]5̙㶧LivԨQn߸8f ~f%%%g}hb>{Arʰ>K.]l5+VwӦMasxkcǎr?fͲWv~YPP`;;;nݺ5C O>4h٤I˗{]ٳ6;;;SSSÞs{]p33x``~,14msڣщ'okȑ *))o36Zvܬ^_~e?^󕚚VgҥK~cz)UUUiժU6m~Z[[eѤI{况QofI||K;vL˗/WVVPzzf͚-[ac=Ϗ>H6mޮשSٯRӧOW0Tbbrss{n9rD aW\ݻw뭷Rvvt- >\tԩߟy>}Z_| 4b &h…ꫯ"Aqܸqjhhкu4~xIw7>c-00[nUii^}U ;iX4,xBQ;<X4,x< FO#`iX4,x< FO#`iX4,x< FO#`iX4,x< FOg*KfQ%IENDB`tsung-1.4.2/doc/images/ldap-results.png0000644000201100017670000002505211701017117017502 0ustar nniclausdreamPNG  IHDReU IDATxyXWQV ֪"JqAAQѠVXuyKja]@Q ګ"O[" ՠA6EDT*`%qfL%kr2s9wfLzvtBH+`4D!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!!DhǏۢ!Ա40`V/Bu,RF!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!!D`4D!,SUU@գˀBB!B B!B B!B B!B B!B B!B B!B B!B B!B BeD222jr Pzh Bu,RF!V97$zkeJ=`vv)2B +ehBFChŻ(>|8`ޭ܂EPiRuhHU_|*kU3k-UYy99 edd4z޾-Ȱ$4ʹTWiiiׯS H3R,!!aڵGSetsCY!!!ޑyٖГ'O#G۹ٵxn8pƪ_Ç>b++%K;wv;bҤI+<{lʕ]޼ysС3رMQD<~xݺucƌ0aΝ;!onee_xyy~Jm5|ߟ嚛s8.뛞N􌌌ӧOt{5.իϝ;wsٿ]]oZTnORZ"v9a„1cƬYFPz㹡CCCg͚գG222DsssMMX,uuu+V_v ^~~***;f罹fٲe=}'^zO>6VK}7 H~rU3{zz^tL9sF__Y5%&&R30eQe7:44|ż?d;\N>,::zĈT:H_ʕ+_Nttte~!C޼y3zh?tPT:jԨ={ԩSKJJ -- 0)׽5y䲲2HHH G™3g*]Tc}}9hT_~eNN|駣G&fff2ѳgO'''>}E[KД֖@Qٳ3f/MmX,i <[ˢQ?I>Jrrr\cS.I.?޽{S)=X,[2AR^;zF)kbbB&H)EW^F'PLMK-EOtEQd[yUwhbx<ǣν)IxxxiӚs~*:$G'xoқsU3bRwmmǏE"QPPPssÇ'755%''㏯^rʠAm--M}ܰY\\L}El7Dٳ۷oRiSSF;_EqQҥR)000?Ǽy֭[RD":v옓D```uuuUUծ]4̈́I5466ZXXXZZĚ*r YYYW3eʔ|<~{ޢtmKp\2{ݻw}}f^2*ׯ__v-XYYQs ԁ!jOVJHHkhhD~~~$}ѢEZ.v(//$Uu8'iAoHFBCDDD@ :t(O˖I"26@JI9CRl Dc+++]]]++۷_~ի-Ρ!uv*;W|Aj򥟟+eH}9NF ̜97UD2),,trr"4-REEE|>ё $666Fnymyz׿bbyyyrr@ FFUs#Bi1'MD .8VT翣j΃[N$?~<,,Llnnn˖-bx֭rogFTzѢEWFFƩS d_=ʤ.\sΌC]~.\wX'''U#gƘ]۫W///OJBOO^zQȊJӦMׯ}4-mT57(mU1?x9t6&L5j>ڊUH$~ZZZ:thDJCCC n:{l6p|}}kkke!3f|'3gdR~E!!!\.f: RQQ5yd6mkkQ^^.vڢƴМ9sNU:,^x͚5ΝnY>QҘ5k5'۲ˎdeMRHI4ٸ61-w lPhhhX]%%%ݻbˉNVvU͍Ub|||ҚvRgCEC2 Jg;|%K =kMш99r#ᥪJvq2$+ş9R)d2?smQcZ߿>((p8={y_n+W477Ϙ1C.]eFֱ455uqq  <9hQrK$ϖF򱦦J@$}gH -mT57uqƬgϞy{{99% "vРA7o&j!J.\8{۷;J#""Ba~~~}}.]J @ϟ߿?11lÆ y46mn;uͷ։ ӳ' $qȑ";0mki2B;fROտ_QW5!l^ˋP5-/B"FC" sC"0":aͷ?!!!ѐ~׭MCEA]R~~~LLLLLLzzzss]B?7oަMhϚ\WWgjja6M_TooV$6 '" >kٞ\]]͛'Ν۷o׮]:uԤM6eddWiHFm>3xRFHֵk^nLxbrr+\. mʕɗ.]R:PPD"ٱcۯ_NT=V6{ʕ cbb\.Yr%LVii?/8889rDv|CWZZb r5BՂrW|eA 4jjj̙39θq\]]^a=|B'''j\$666F,;˗/+>>>88833s޽NNN%%%j~>>J!$]۷o?xf-4nnn˖-bx֭gϞuW\\p¸;wfdd:tHv 0Ө}Ri\盘N2E[SSӫWN8b 43MEYSL111111RSji Fi&,~hlݺul66cƌO>d̙CBB***&Ofmmm=<<˙?eǜׯ j|*Ls S-Ʀ_~G|!!!\.f:t޽o|4j_rai!9F PRixx-,,NFGz\&d4Z:96l<}&ЈYfQ)d۷oΦ4CDJ䙔6͍b555(-X+++U#[XXŋ׬Ys9jk#B?FRoIIɽ{zi!+: ;'krrrrrr@٩/_dvf&ý7440lȑ#F =!ﯸl"9*R)]QQ6GiRHtE666...[lZ ^daBBbo>R",REm1o+W477Ϙ1(UUU| E/Lvtt4޽{o޼1u/O8~6jxAY@cΜ9 bdsNz2mgg9k ]Ԍ3j* ӧgddPXX[B<Ǥ}ɉY&;.kׂJJJ&M$lmmoo޼@<]du-w~Gͤ +G6^}}}DDP(ϯ<$$$))Ύ㙘ZD8Wn^7p@GGG777ٳ9 ӳ&;wܰ0.Ѱ) H-MG)**o5bͷNsP&w2B˨kѐyY%@;.PULђX%@;~ChBDwy!!B`4D .!0"!v4{}Fv2&OU QPg4^X,2E,=z4%%E"3fϏIOOonnfXjjwtt4Eߚ!!!uuuNNN6l` ϟ?ṣG97|_oΜ9C^]oiiٕ _~~>>P qe˖}ϟ%CDZ__/g>[3++kѢE'矮TS۷?ÇΝӧ߿?ٳg!!!0A}}}{STZXX٫W/___ &$$thH|\իW'Nb DF&)S|TJ)4Zp„ FߴiȌF 4BBB***&Ofmmm=<<˃eg+..^rT*=q(]kQC>&ݠ O._؄j~:!!\)D`ccӯ_?]]]KKKr -((غuٳl6%RYYrlСCZԶ&Ν;"d3gɓ'>|HF9sfvvɓ'T]]]!9P1dtT* ?ǎ /^˄ZL0?hAذaS&@#11rc.))Iv+W՝8ql*))w^޽\l:9Yյھ}{SSkbccTO!/^f͹s窫I:Tf(i;rCZGn[tsscX>>>iiiMMMiii$MJJBavvP(444tqq᧟~*++.Ѱf@ ׮]!m(-KkhAjf]]]hhh` 4АJ!犊 يoTGrJss3:HgbGRFFFk~Ç/Y0!!Ύ^r2r#F󚪪*x "l5mll"##MMM]\\,--l&3q8gϞ7o %JbK4$L#5A;۶m355eXnP-޿ko7ٱSx;={\~͛7[Pv2͕/S>b1IRRR._<|D rrrrssCByyyH ֜4iRTTTffN>MlllȷO<#FPB*۷qqq\.DCry=Q/͛7 Sԭ@9dbܸq eo 2ƹl<6,,G ߿'{OIP(>}̪޽tTQrM=5tttVZE>2\*xݻI_pwׯgX{-)))**ud<|p}}}JJ yIylx߾},k o 7`@H(y<ܜSNHN g͚էO)Sv$yxxP/`III.** 6olnn^ZZ*JʄB644+aҚ7njjjz왷w\\ȑ#o+''G*>zãG?3!00ԩS?IJ7zڵkAAA%%%&MԷUUU7oupp .Ku떇ݻw?#7AՂr%T|&{yHHHRRRee311Q7oxַo_zxܹaaaJX߼ysK.O}Çϟ۷z7$IENDB`tsung-1.4.2/doc/images/tsung-report.png0000644000201100017670000024314611701017117017542 0ustar nniclausdreamPNG  IHDRb IDATxe\MgFADT 뵻^[TLnEPPB}?qw"q<fggfgݽYHTvB|F!BFR @!+0A!q(!B B'rB!d0A!q(!B B'rB!d0A!q(!B B'rB!d0A!q(!BƉV @EK֭BwX rB!d0A!q(!B B'rB!d3VDIW[BbD9m5Jٽ!BAsgN;ͯRQs!TtO|!JHҊ`2ȿQkuy{y!PфrBEkubXvIf}8&\ɚeL[*i:m92FPR, ž0kQ)_2 P6S6tUeeOW8|tՕ4>I\az]2>e(.w9CԄ*f&l8-bW_Hת uK/*!ʉmq[ŜplR|E6fWflf^R<,bcRۑu:wTJNgR {|蕡O4̧BDswY)@S:,T7U.)̥ɬ2š)dd3HdMg?S4EL9k2MUi^U64V,}&Q0ıdc +$c2IkimM VIw: Pd QΌ$$hw+[uITx.MK 9`@M) `[c7|NPȨ39Pd QQvtf7RùiSocS{g*dIQujڭSw#S>9irRL02+C0"+W2Kx5oLpńu|eۍW2*t9]Q'b%lQ4.]8Ǔ jO'l* @䲄+_LIdYWd9Pd X[>^6A󫩵ȤeY/F*h8]!-IƦ,Hdɤ5|*hbZKB('q> ӉÐTH=^.c6e #-Iyca|,'1 * Gõ|^uoƕ[xcQׄKJT әrlX?!d$47Ι] A8 mf6 )Co/՟!H/ԋ'XL ͻc>7&s*WaA! NQ<?*Q$]P3(ǻ]˛aϾRKT*Ss,9}YpN@FqJP.(Sd0Vs'"* F텐Bua{UIzX&\ {fՊ#R-`!Org$dϦpZn'!] >餞K9ki9${8PHg5'" oX2o;㩐~(brYBS' Atk;$~3 ,7#Tz=9_ cLGy/@( sUY?5+LxGHW:|F`U.1ė\`W΄,M*s  JKYrB!]X i#-~^ѹi.cxnғ+~ki1?,AH_o _47#T$Yc\{ZTi1YΩӗ;eqZ{1kpyJ <4 }lxd?SdnMvtf"qJǩYn-ܖ T$0eB*6,J1k95҉o)_ 6eL#=nHRux(=u4`@;^Lݡ+onB!0N.Gv+B!>c4B!>F!q(!B B'rB!d0A!q(!B B'rB!d0A!q(!B B'rB!d0A!q(!B B'Za7nB!Q\|)?,,, !Ajjj~wB!d0A!q(!BƩD^n9/i[Mا2:Oc!|穤Fj5ƍ[}/L(:ۘF3?Q‹$$L˷RI;##*R8n&9*Ԋ᭖0*j-*tag<[z] J.rQG+XJB2ƾz8Ib $o5oGS{/NDSMڭ}״- ruoܷK@hgf Rqg6FyB䱫y%+\wDP'ܗ:0zS-mmU_}-:Vw+v;bMIA@Mf!9 9ɰvg$+UCJnͩKNŒ rFЯf4By]B2]K,#2Ҍe}KjLp[ƺ3xɏIilߗKS%yUaR@!ϤPs*t޿vg`*Vv=鱚$c|e:vn~ԚqT[&.}tP+AY:VFژhH{j?uf=jL_y٨Nj\q+%DJb[yÅ#T([1EJ/yץ?:)| \7R);!fD$ yֽKRvJjI;}2ҹ;TΈ )JjaFMoNrifG6UiՍ om+P}L1u;]hzG_&!/K_O2UƖCOމApsZ5*g[XLPيfɾ'eۜMƧ*X8::gU5}ki]PP_sZE,ʑJ1U+2TY*v9PS<jO^J(S{_T$ҞY7b? 8Z(1\sJ^`8 Di#%/Zd^ڳj}G+pn`9ӫ,>~yaP/4;[)^!M6ZqKPۛ \{tTFf%~y/=QmyGB8<שqRp̭C7tsFD\ƭqj4xs')К3~.fy4ץ YbXGυ#Tt(g eoJ(l3=QnV8r8 ´91 ׺&Pׯ ˜}]Mi]eu*VgLeOqZ0#).Ɲ)_N@6<]Y:^d[3nSߙ(gMʊE.yH$p9ܗҞMuY06?$q_=AxxwǣfNlymNp/}:Ӯ+2 SvJyyx,JF:j?W_#Tt(ƹrϯ*{̈́k42SƷgbIsI]6n諹l&~iiwEyE@PO8[c,m;ʴ%vuE.pL̦Ud n8[nWit;'<=p31#%) T8BEmp{Fn;\*TDiT~^}&2QNg ár '\KNjNAY"~bJYFj(*^ǵǮ zud\ؽ-#c²zsU%wy7w7+kFWJ_n6i]U߄rͧQeT`\KKM]Jq51}mتL<д[Eϻ ЬLeKz&_|י9PQBȘx;󑛁laW[ReG;(St[ ]}Fz|s|?|(& *Y\]]'jG9_ R&ZďF1wߖr8n?ɷf}a*tkuf|Je*gU{\-s+\I{*(GeoRv3Z6gnݭ_wu2]j\^f{У;:wլ[c݀!KNҷE!*:s΅h^m) O~;G?3)eT’.JL߭%M7:X^-LbQI\[Əvٟrd_5:wwﻶ2lv7iTeHrfG)+;k7ڟxWϔXum]]`H]Hx*G ]Xt[4κ^MȭAX5ظ}j!`wljCtҎrnC9Xy.h@Z>xn7]Bƣ?~muOTڨ ;(c`&튦b`#P``ĝcě٣V9wG~q'žf~o$ e/)Wf%&@-Zc;wn>y1 fGHY T1{i^9TU㈈.=Ȋr̽ 輀*W"sIog=B!T&;NG=s+rxУ= \|.B޻ :r[7k7Irm;^|.p^fW*22P|XLSSnl<7R5Xĝ.,8FYpuuuuuv@ [xz-<ݳw_~5xTLEy cB7u$F@@dfǮeiqr41K;4s,pҭv2.떓"[n_on?8=Z(eZ488B /ˋZjϦ60EQ"ͯR ;,wR w~ؽ?DH~LIdjS9AUZ[~Pc78̣jvzVʶؼd}gdv/$O XBBB)zB?.I>?и?G"C"XAEņʠs-5Ni=jne&&vܹ);?|Tf)Sk_WI{~+U BgVtϥ:"Tby雋kvCtߜ"S,Uu&F ][E^ď dĴޛq/;8ЮĦ ~ފCx58㘫.[=FZ[v̻'Cn=nS'&+D1; T)5k]Jv>qU7VS°M~W7n7FZTgG=kJY}7W0[JSóo=<oخ_;-/VC8rT^2mM:wo߬շ2?;4kK*}jg[Ilscws{^ǞT#J3B eOlRjݶ݇ =jҼgqu[wo^^ya)}ݫ Cw76R IDATI)Ilo(2bwN#^JΣt5=tyɓ7P4=~ sΜs.uVXka8rHJJ[TYlCOjݺïkO%nvAC==U2:Qf-"?(U?GZ71˾7x)SsPa7Ĥ6=\:lQ;3z 6/?Bw(?hV\q1M773`3s䩣*SnV1`ն}B~eE *Ԭe@%KnU=iӳӨbA+gow 薥GGrMw ^ PH{ J|qXGدOZ֒_o~Ǥ@F@;iO&7cID*j Z:8uuRXC A@q-=cQZŮe5d+Yu>[r}6沜-ʷ!p,xy- R|:xw {A>lUg #]{կ;\ Z^k)GҼwъ_7V9gNp7gm87 =G5@/QFIs/4 sTHTgw"90@g2H?? (f ^x?h}Bk8hip[8G|RR!T%r71PFeaa~c>z mTԦׯ֜:3p㔍]楼\)(U6Q\ d~rw_ J(~R8gu!jR>ī>ܻ SG5=vl7a/@PhM TgU|8Q-֣x4C ~DL\@4#V}aVZ);ʆ^0q@IyBp$ q02 GY+eI+D3&@7+(HH :hsբ)uoaT*ݞAYL)Oj?Oll"JIs2SUth~ Y%LcA] ^z OmUopJl`$ZnqQq9.~,Z? }>Ok2A)ԮMw^-f b6 RSSsΔ;y'{D+5ԬhOӫaE/̝`5]CgG|@z-{YJǜZ5h:JY\|lYnrN\ ?0PξM@BzQGnj1|,mzC)jf.cO6MRԮviikCzO?[1lʂqN_#a޲ϒM Hٱ537,tsq\ެvGUW^vye|\)Xdy6eQC.=j&TJVollUv{In Ç`ը$,Wԭ;mƆ"~\%~U} 師a0A#XMC*fl,C{TReܜrꚒQ((rH$ݺ*XD9y"xΡRVW:U2Z%ht[gIv2v *sy((.օsK;[׬|rJ+TP6{ dsvqǖztҶc E15 ^a/-?_5V]QiE P#]Q}NzhlЍPIPrA bܛr=Y> ZiѬS {@ivTf}*uS{C4a`+w#rP>jЪVphbk"T݇9vvpd v>Ҹf#oejܛt|Vo=ץ:O!>##z^;~&7. ۦ~wn̥Z-{8̍MyMW9.+LaP*g-xYsK SnSGؗK-{[q+C&oe ;ѽ=^l9(UT7!g[m~բVo]O{})Lmk$i۠ձ7<~C7N$LV/ZJ7$W,+IRe00FA2u"uV#_\KOq?7{'+çLٕ/XE LKOReoDTrʞ*`. 4} LRc{+0Ag77=::rToxj sXؕo> uä ߤfz)qWOZS'=حZa.MVQC30IBH9 A_rP3aO <ɐ(?Lk@$_HHsA+={~eˏ/nzU/c3~ΥNVx/]6m,N4|-IZ!_2t{U8408IUc3I* Gs_c>rQ*he~ HR%lscreqŨvqmzZ,~c T-;`Ffs~KG@`3Q0dجt !T4Jpn_y*+;wnW{,3, @IY t*ԤY:N}AҎc5n) {1sO.hZ^am ݿZM5CY}{pN o,o7g v/Aa̗ PObo\]|ϱKoC&4S]W^2}Y ϯvq@ţXR^+ֵaK.?xbC<ƯmY[` 3(_+w+ zkH`k ۭϲvTᬀH/rOGU<_{LX]M)wLG<1Q߂:\/oܧN7/S꒭t{Xw~ИfxA8u.BNݥ5;nG% P1y|`>C|s ZX_E-]tرUZ4_/yb-;&eœoG-.}mmt)Lx=ݿu'l&T T|q̩|?~}~Uɂ9&I@UZnt$i77..#+mT&qIO.?}٩}mեsk/oc<2~[vݎJuM:U+BBbB!P QB!F9!2N$9[\g]/&ևPl>5}Q0;h6M!:w[v-&pw0ZDN'3N[[٫8Doܐ_5#`8C8I)kOzDf@;{ uչ)V+$U1߫q~B?|}}`Æ &&&3kjQ b%Q~y_P@jbA }Sx73]`>&36~ 4$atdVĻ YKYgB=L*rV˖-Gչ,VDa@+kTr!B(˗/B۷۴icx3/H3!Ո^24(ũʏbV°V(ݘ*ɣƢ:1a2i񌊽mA_ "y*VE 37fE%fʇ)!PIA//<+άm~??<)H#GY@zcu4U,=]̡l˗"YZgӿ;7MTJ% _30ho>VqJ(s=Rv沢&)*W{=HC9hM6-ZP}ٳgzr֢US}7j;%K0܏RF~N{yI)fC+*u9%X5 37A!T")=k8BrhjViw# Q}vk[I.zPME-:~…ׯ_8p t? \w%I33T"no#X1$GH5P)@(mG"ly-Xg JtVrBdoev_@*ǖqi5/=O|UB<_N Տ#J 2} ҢDL3y-3ZtQRO 2UHrE?+bA_oo;J9)gژ:[f+Rv_ ;9itwm_#~p,@Bd̗2K7SSJu\>f5k\X` =Y%yOv+P܀Aܪ 7~֓C/s}Cǟ¤"}]VwV__f7D+ȷRCB\ߨ4K;]!T~nh7Z8BLSF@!G!a7sc. [ۅ8^#K1v {]ה@JE_A*Lͩ@$Gi]?aŐ-l^/M<Ɩf8 [1=E^|΍]-ݮ}۽|,E0s͒:6lQQAś'5JWHb%Nb"[gM͙m A[:ʰ9GGH ٲ4V/aN%+L5xY< M!L]?UO^f9ŶJY>5I{F1|a$y(XEȈ޵1륺*U5㏞~4Ҭ^Dݺ!Α2PljnzOcE*D릕h! eMtr kw^0?u DCєu*KҌӧyN::k"[Q|)kxء!;mY}K`Y>rMپ-btZ%d )=o=zs}@rRB4ũq5[vj]A+ Y~!oY+*Wgv*U1y[o^-[vT:dp%(rhxkITdiґn=W }oi*}SC]kJ]^Qy|\_rS($}ᗁMiPi=tvۮáؗv~GN&@PM)\|rbcc/^rdȠqѹ݉) "bؿz4 W1<\%l؈ߤ 8L(*}ĖqC~Y׌b՛Tn|ZMu W NϏ?]tH}-g*uWǯ_#DhL"J>q2N&f{<1$iWI6AdM@:B_$ʶשC'6j5>cޒA1SǏc3gEpČ3w?`yNCh"mm7}rSfk~*nRu_`ݨY}Lfr4vrΕosab}& mW!z77T~R}LEs@%NVwq:+db GD'+v4L)˗/B۷۴iۥMN*r9Ո^24(W. IDATʏbpq{kX+ nLbpbQaxF^Զ*`.37k'YTb4[le 5˔18s٘aR`bkeǸr. ^Ux#]Oe9g|`–= ̲*|OSzͧ*L9NAʿA+BR ¿kvjUerB&43{<5L)O_SO:~RVQ;KU{~QG99V|)}ٸC0ʶlmN.Irc߲;*rv89׺O;z+Q>7\){ =t\U6]O𤋮TA=ŋWsqS:2F2KkTe흶l @+z!dU!gӦM-Z\t zY^|)D49 @Ri0//hrN}E栫 3Iu ӄ/"1)eΠ1%c;]= 4GOUM3KnYퟣ3@淭g vhHP 4?QG9xʬ]֚ ΃ ˱ Y;tڨ.jc6xi0GNdHk,)~dmv //܄8ХwsL{XX!K1hg1hQGIQ-}>+g)_^+GՃƏЏvrSSދ`MR|F~;٬gژ:[f})'W<4^S_FƳZ{McB-O_|5Sح@YpwV^vbkUd4 ·#QџA((B?I |!*!<)u"B(<@1UQλA;ew*;r?`0QڲCeV{y}b\靇#B[zСꯇBnϜlR9n37^&4yUp'4z}(G5pH0T){.qңEf;ko!23.g(B!B%DE9/^rerrr|O̾HϯZ_zFA9 NmϏ? R+N@Riߓp,TʯV_FH )jP; R/; Kp%RHk3;B!T gV///kɝ#""ߩ~tf1dW/ouv:vmՅj[TئiL=MUE &UM" sj [KS|ɥi UoP|W+ѰcF3HPYB˗/B۷s\\wn.fly89eaEW{Ae`ʝ"ҢŌtUQ+#ȩShrK\#8Tr9ygo3 BF)+79Cc3?K~6w]G(m NQ̤g;7?S73)Zd+[DIvW\Dv YGڐ}v}Mv\TT6cQ4]ssw| ĆAtG/Kth0\9]$B'm4|2}07GT=D Ǖ]Psds`ԘH$wwwݻKE̚S7wZå0|ٻ(Hly--Pso4V7vj'%P+om?SFu۪ Fs`[@ 13C"SRR^~aNITf–m+yW\L c/^cR``B}%Wp}(*U 9DJ&Љe6,;QMF#6TXT@ Zj[Fe¬M {ᜐ;̹cy; 瓔֔I=Dqʜ)[/& bEb`? NmϜ9,:Sl!;]ڮzQXZDV"w1AT9D/VZ4Hڸo5XӮ]H6'KFMVըl#aN^̑2rN>fײL]xs9&tT?Q}&gXs7Sr/ɠE9fj߁FP~ۮ Ӏ.}><;@k}5vy)ߩOz5b_qVwMWS$ 9'ʲrX1Qg |u?4\+o"D9I=F|cᛏj؟՗e!"i@IL<줺*f={T݉XW@T+J 2qg Y5]:7}[#:> =3}MoٌcpnN^RTRͪ&,1/V֗FoMv{|&ױ7浵0`+ 0dn:pؖ^k;(niI*e na" @Y@2LAGS~?vX, ^}[AQi'2ԣcQ$4* U4'sq Lq P.u~7op2ӆXJ3{4Ke$F+q L1Z_6e 'Yn9~xTR?˥v=r@ Y`RBug=}T#G|ZR@W}5v:Pq!^RCbtww˻);:p? ٔ} Z6*SFGtcбܴl!W!y4sۊqǯf {mTӽBAáM^ͳ sΝڌx݌w;ʅEޯ^&g: ܼ/ZNGy|yiustnOxgtLp~SqJRl.E$ĢWSksb߯}[8%ό>3=}}eJm0 -ԣoi fvQːELhHjk{\"@E!u N<oO8w6?">I,s⏝:kZm K,2ER 7Zp}>J\F ONv87qڭg>fL)⸤݇ZFA7:fNSd#bE;hqL ]`m3ccr؜TF24 1'+TT[շyZGg7|C7t׳::>RfX``VwxS DG"iz.V$(49jch}*SU /Q :  jykPP9kϠ#|gnM$ GZ+=94DΛ4'xIvO g|L" ]:xпC\XMMkˤbvQ#PddX483m_KY5ZࢧY[]' c:F_e T.nu 3|J98ɲ9W(Xre$Q x;Љ d40+"2d\3Ru$5 r@qnP@.Ԣͽ$82QѬb+,2ɤlN@&2~kXLgVΣG-:LQH =8+KqC^=fv@dhd[jn?ٍjfO#^1@fb+̈m>u8ɼֺ?f,/:$! D2VwM)ifLKUڄ9_p5gƦT 3f={F=e~/R+oT| ÆZv"Q=O1 \p ;9H!F5uVg}g* MMTkAV"z~eXMjxO ;ҭH1aѸCh7?2`E"x#y'|.,(kx޽?t> f'w<|r:*r]NiY[3q7docR= We>|tem__ry=oOUϳؑ`x_5%`v@.]\jFu\|<&&&&F9aaSZ(Rt,H@ScǥWoJ=0v㹯Ƌnݗ%E|LbԼT+3 tvWʡw/W\S;gZ1"&{!lja#Dלfh:)s}Y\9ٷORbcRt,ݖ+#NPcNv;T˽^m+mE &/hQG'n  EV3/Yߑ 9/6J@9,kڅ\-a'ΨXUXp 33ԊҦVG^EP.}9àK޾ѽcNNN:56wj>_ZG75jݭ&!+.>[*3{ p*rY]L>[/"?G_Q48ڲ^]C@%؂5w|e^{p7/ֈr@1)zqGz lQY<~jÃllS5=V KFR:vߞpډrDf:gnFcejFjÃllS5{ .*짴0'UfF?Urt;@ Ǡ^Nؿ]bl1) 5S**`'fhO2@ ~%rIt)Fnav鑰]'/Hd9'kYȀf&TkվTAתҨ{n/oцv^9^5i8{\[ޘ֢@ֈocOraGz F2MRqxkuʩX%Q]X!QӐKzm@k??AMdg =Ko2m2z{>-tc U:p>Kfp`pekS?o@  {3RNߺ{EcK߲σbΓD=O˩1~kmHOƼvh0ږ| {ͫp@P\LBbՑJr%.֡qov_≠E/-^κ^\| dl~ZӋ@ Y~]#;/{MWAz9k׮ &#J_Ǎ.)٘JU\KҙCm]]z*O` v*P ̈ $k}WA`y_RdU\]sȕʽح; ȕp1Ai(NCxN } 5x@T$Dz3H)Cٙ8ѬYY霼o:O&/A)!lLS*r3U IDATrr 3gNlF1AX.Lc7 Qfzjwl Y!@6&M IƍBd GKQG%2_gn@ ;фryoTqq oN0bkCe -.¸ļQSj@jfO#^P.L`Naё&2zk3 Uq._͛75Ma-+šW [kܸ-D=&v\—|=U )3Hۉ*SS Ȣ v5$;0V|~QCÐ>.GPc 8ue\-!-QR费6]9lN ZݫtiBe0qdɷ1;pCQqR=vo~db?D h|PM/'XkS[h SgM70%X,b4s/|4x.BӕhuJ,$` Ě(aDnm "[40dS |dlëߑ俐>!(^!=_*K5 N;YUȯ<*zLn4[ǝ 6?i[qm!ȿt Q]h3~Po/aҮ%-@*y*gezަ1,%0-R;NtU-ݾ]m: gM73c޻woŵF1" _;aWLk.PDxU*gʈBʏb*HvB<uX5 O[$h] GD g,W6H6Wx@8bKR5P#uiVXx?K$m!O$ڊvсY8ͦ[>N@]xsn{s)Mƅz&lUO\ƿ"js iWlxs;mq3Iǃ pJ;Wg$Z}z%:ݔbX 'I8w%ːyg՜ Cda]1]E5&/r@ _ l(1՜4lXq7MYvpW áJU-{]RΩ'rmxxy{ 3nAXT s g?wcgg3v[=_*f?P]+ҦJ|}z\#Oy]2Qp6 JGڠO,8ڜX(U;8cQ]Wļz{t8^!V࠽uD/WN>wϟUq~e\ V=mOxr;m$-ӫ͋UM>wxꯉ8s~Eb+* husV)D578TT9j˥4#޻OWKWPuurvmJt @gM# ðVZ5mT՜x5#mSWkwv+(>;3ۥ}ᓎ;۔.*Ŕ )̹s$`e_v@jGĖUÚ2>qwy<!w Zwd,;DaL_"i7i++cle,U'7*y{xNa{_<ײ%l;w,ةݿx/_>ϴ.' ]Xdړz젺VQ?؀C1qZ9%S$[Suiw%.j6W+Ll$!vyk@frm,/\&,2TZGg7|CmvS-5O&-pfw_Aѧۗ۵)vh*>kڨ4+#{7o0cà 1;ۏX'S|;rODӧ7M$1LYz\nz$*$1#`^&\ԃD#|Qf}k̛LIȢd&q{tVuf3ft2a#w_|7ivO-l2]8=9Y6"5?q7ɍB'B۩K-/|г&QAuM\o7 HJ5\[sNL?ZOOW:%m(]:xпC\"afmڬ.'%l[Kp6GLY2Uj*0F3:4ƐI/yQE=++zIq v-I Dʴ5>|qfp<3Wn5͋6t6 t_t"hיֶ޼Tb5 of6Tȥأ͏ZEUXzvm/;̷xae~4bP/9  3`VDTWcf$/'c r9HejIr3 遡gY=.sȫ &RcBM1ٱF1~ ~Ym/q{(3 #Le$װάG2[ujՁFkk@dhd[m(PچrwPY#dRG&}Y!ʃlؓXfLKUڄ9_pQ-?Լ4fF ul -Dd}IK}=bVDR}b:$nwoho5v`\ݵCs1CC1qdAT^}X=ƞғ랕x dej~ ݻwg6ngP"@L~r9׍oysY @FQgxPFu\|<&&&&&FI-|X 8to6䜏{˿QٸV۔V-mQA &>)tn08hira0.*`v@.]\jŒ(?l1H8W?aw V</m7Uu䢂g1>E}\w EkhPޮE6ymY&WafffknS=lվð5Ca3k-EjLťAWf@ɋH[ilm]{.]+<֪}9\Xdܔ5 7(NCKnߌ iro(Gƶ/-§v|^k2G^xFyjes??o^}vvl;!KiMК$]l,^-Pv =aQe VZ*)hOBY.7ifs-:mekכg,)|n nU:*|:/g Uj10tSMސV9{o =\[2d톭?4zK-O i󈏝Zɵ}jNiTU:/ޮM h{;J}֖WU.77Jbh/y !Qdr1NQƓ8:N ϭv(=`jW:Tk;5v/h`_?ԣ;sqJZ;}Hg6%>{ˆνaAZ&ߩG8&_3w,9Mty_&8!}pԨ׈yj^A= O4r#M۬M;W@X?Ř4o.U|=(M㗿1 euawi_5K8kBك|+"r 'C 9ʞ@^5mWј`o.-:=8סJ~EjalA|q.C -eFhuɫK~#N>)$wM~|x9YYYNNNAAAsi߾=(F1!X@µ]8z??M()ww NY32%;މp#U 0Հ6X_ ڔJ࠘t4*:9Մ  85>ͼ<()l r$F!2#'b_eF]B *y:scLU?jWј`:r>x<A(rg#gQm\I^ )ȹ'NZdf'xoZ啽/ي?޼yS5Sz8LxC|j9r#ݡ+YfO!N%\N HXBd">Ӡ`pKq @]L>[/"bv\—}z:(j[reW>_1M~@ Ã6ɗ]d2K b 2kbO8})Дq8;9HK֖!ՙ6%זu f> ɲ][L)rΗSCP(6)#R Ӣ Tdϊ5p@>՞0IlZVYnxGli8feϫT_$)111%%ױyL,OM<\9hiCK9iv+lva]Ή)ܳtFAl "%D6K-%UY76Wj;Sԡ;2oU)'xwV%*_@]]ZxR45@ cu96g[MQL:@.1J)ȴK;bS]urֿ+S>eSy5U'ЍL}`MкC=kOSF3lXwY.P,ْ DO?k$xNJ-U)PZ3VFg0luURT7ؑ$%w:2E&7OHdUFoMn~X:a*TvМnA"ߟdDМniB JG>srdn`W;]P(NPjՕ=ou_wvsGd91v@5e&*e@ $-^_-h3q7tځ)\:oדn+G\_ 5]E5rrrRKcǎgϞߩƘlz_"ӗd"IbB񲄃 O 44/YGuK9#+><v*Ӕi?zrG2Ĩgc)31l Y51#B ` Pz(}bQ)S͉8"Nߺ=2\՘_X@b˗/+\͛7uw&Ԗ< _(oԅpID*nCE#<@Ѱ-6{!aB OlKϋiG(Mr3{$ 8P&H%_ERc-uU>j<ϩܰX Q߈Jd^μZԱdc⛏6-sߑk4+mɷ bܺ<C9@ ez$*}ezS1{V\T_L,+1Or\q ?G/)( _ p@ 5~BOlE$ D]1;'!UzTC"SRR^~Usi]r0K-%p b_q519 @  3+ѣGjfO#^PmL`KЖLOUðVZ5mT<%UT@Uʮ?LT&0nqBjy=VƲX8Ú2>qwyԧ NP.S)0Bϑσq|!~-;fK wlA&޷bb{^*ܻ&{h_wrI)_umx2ήx^!ލR(I4ZLAPa>#tu_-v;;;OElT-SW[aN#8LmcLa-]y3<@(*P!h?=;pyO>*_)htQEzxy{ 7kI=W:*́%Rjwݼ!jջ:h[ZFL8Rr>K.Q^G!&cw2T^O%k+ 3;i3aq~]5ܸ}d39='{JIZwO:XƼ:ޝv"0Uz6JUt'f1zm ʱX54 "iC-D}Igԩ[^ :Y/!ρmfpM;Lڵt8}jX.DנtzֈocOraGz 56{n<'Lu٠rC9{WhRY rCAgl^RreȢ{_d&4\$55ba_N sRlh|.㩿^^gR#+=bB~Oqjھr)[}"_ K7 JV>?'N [M-ko Qso 8w`ř[2E20'wXS^z[p,)cs*üL9s4ȉSl|LɃ7E['.ģzBnh-٦QCk˥\- [011{WElNW?os}ټNLl$!vyk<w(8k ?E~7\D@g.֡qov_(nt2 pE ^gLrHihME}M`;`wrrr#.̿:oZeڄ5& y?#F]6vnE#Z4a_B[Q!7\]Wh;I@ZsTg)5n,a`aN$j$7 lEVf=v!-Xl:2AԊǶBH%_gvFxŤdqjZw|G&Hm=\GGy=g@y2,mS`40(T3oLlh͗zTbx jLťVhJC`8)GxW8U\`v.=q97?>r"W9*ÄPlN秊v|܏]-jY=& Zh,KTT0'U;F@g{EREcV*nY9-Cƭp|~E\hJQ5S5PK_3|_ٸmPu4zm;j1(O X4!~*,YjjjEi3?vrrR 0I-_ =0WwQl(xϤ*Z@ Dm/h,@ jbi}Ĕ%]YM'%MF&y Qm@B ڀ6ɗ 4@ Dm/A @2jl[ԲR8~8f3hЌ@  r@L@ fNx@'< 4@ fX!*;;.@ h :c@ DX!hfeaR HJ`XluҲG/(92ty Q0jl԰m^3@ /'7176uw0!=92|lJÀ!Uc'`z}|tjZU@ \{9vI;:T.~o>}G+%bXNu'[Z4n[m]'ʾ[En,C@ u/rN?\",,^wc)beŧ{`aGY|FknK*&?rŋo3K-oFvxBFu/?_"ag%ݽ%7WL4jdiwu +Pd|>_|j^>\z͞URTߩ%nέhl>K|H}@ Qr.]dm6=w @&4=wkWRHϜ}MIʕ<9WU\@>l?"7(= lF 0ƅWV]:4`S0qx|:ge?2ߤzVONZl c譃-{FnLvYN73#i>i+Kλmý?4dD͠!0(^NJղx\*6B#-4߰וBz@8a~-I]n67r DM| f]6}x?]O6W>gbBQcb β*5pͻE&JdrNƷ1"u' Oz1p ޸]h]f9M#qQS:l&3h@ #ɍJ =!C.֤}tR.K:W"S&f@cҦYѸFboHyr\۩Qj9$z7?>jk{GZb敶7f^5NfMՒc}I{7\*QnX.F.=h]f9O8Q%#6^?z5ңi-Q9dҌow~r`jέUw?G4(t3S S.Ώxzj6A Q ۞H3~Ӧ-9hq|yATN'Ij夊43}3, Z̼7-C6yr-3ֵαn}șr#ZwisYji{\G9kS w̃r2, XRZ΃R9F-=溚@Ӥ6nzy]9Ɩ"&: jEI-PܝB K sٶѩ[riŲmSo~?Z'zzE2bⲳ'԰SYtZ\­鳸YZM>O\7]Ϗ=Lڦ-|\F-- P~:Xޘrm\ڛq&'8wB9f΢5fy had V /@T&U]jsAꂶ6Y6flԥܞm66!sIϰe*/y?_c"B7;" ñWKQC.b_2&Hd_˵qRשv6`M@y9p-ָs[Jƶ9<黕=3>bbF{c Ͳ|W(`MlBcX=Y<׳`ؔ ] 2|߰P4vw6/?'qx7o6֤,orK&E-QR[Q4>QHM[I@ Jƛۭ kE @"b&3[ճ\ԭwpn,hk+ORS[coB?jD`MԢԯwHn"~疧4Y0#q x7d)<6'[6ܙ^zV}@p0 b1҄xͫ lbZ>guRO :y9 HFg}J,YtKd<pE(r C¥_\z;at *.ۢ$'dG; U.^9z*YQ-Sl4c}]].IeIZt5~np7?kL՘9žWN>`bltH>)zVPG!Ԡ[I@ Mr$?E?Ccո>Hΰx!T {:F}W[ "ڰOB-~} ,5*p‰ȓ>bA7 ,O2 e\Miɝ2MR:=A^0(t3/-!h Ôc X"1A1* #\GK>@XظPbyC+_ܶV柳%.leo FL={A*17]PX$sqKVqj  OXJὕfk&& JU!*9>]x?Irsv3__~^L O ُ}zaXn[#Eo$ r7ߥwW\jd&Է T@0 wwϻ2(r_ :'56o>峃^|:y* 4nA)ݱڰaCDDL^bEHHd8>MA ;qvao/*"5ɶf:&h(Wmʧ:% `S߼=s4ߑ{qٓ-T `eW^G)G#w̺P?6w G ͹7|%-aŤ9cE=%,Qוqk%!Q24ã%0uQW0Zvd}-x%,M;NUx9GgAAU)'A}*a觴,ª@Aԧu:x4yX?f{Z+ hwBrAƂ  HTt1vW=[_z^*3(ʢM6 Gޱ ~BJfڢvXJGwAڧR{Ҙ ]+ںC@AG999Zk*Ca !@i u'+Gg &$Oݸ'%0(>Qa4B?Nt-X2¶h8IG]ӻg/%]`0mebGVeQod&c#(:i?g\_˦ HemF=.h7aR\@3#ڗ rc:,kFsmga-wN5jg*lLIII,RVN=*F@o\RڲԍfB*'{d%82՛cp IDAT8:)n_4݆+PiF= .#ž-{T݊%/4 ˷]uY)U75(Ղ1jQ5;cJbtbl s(\ &)tRO> @(- O7 5J.Qo%4RSV028݁ˉw\X}5b>g42dALqrr:uꔇsM ,'lNA,*~"ւ_rfڤXf&1 :cjaf%$\%d^c*mtK*x~X;t89T6;  Hmg^^^IIInnn%&izѬФ ^_0UYrG]NTsO~v/&ѭSw#z?`]vbn|a, `S5tmw9 bѧ#O=0 )݂vԃ7h '}ȦQBq4{0aLod[' jӡwrSQk{iYO7cu;g5ਖVm)j@CirkBVՊ({DkM5 5Cϗan]Q!1ȍ rf#'sUkb0\{/7g=]сUgªW*S忩œvV0 >1^3^>O=MәaTZ`Hiq_Z.u[m?dWBo;̋MgծKѫӬ_F_>'E(nf;yM0g$0~^e;oCd88F[j?c7a,ˎdz# pox;Z,p@G&\iն>ޠîuLsJ1H,*22 Zfq{?qIdأw2ԫ=p7%;񲦡ZP+hZLb^4>}NwJ(CJ+i{{)x}/:捥;N[X;*">EP|3H;vFNǧ >>MV0m63&m+mIb. -gB9YdfYxZgq#|dcN) >U5qkmVOzǶ{79Mi.2 [_pbãmF鈗5#yT &jI__s2HP$GZ XqN4?ݠ`||6/<޷]'KܹcAP_q"\w$ ^v ",5@A]t4HrnNΥ[f$eϼ2,pgMt]LEPxl>)kW *'^>|^qdH8rOߒXܧG Hy;|;fMTwCfY#c@sX+fC<cNVw>,>,ʺf^𠞝E%_:q PݖHzgpD2Y@}S %os|~d*_M 5Br"e;94_Z+uE6k.XG_7aj֞n7 Qr@@@-[^R_ڶl,*nGzy~6.Yvz+ ߌ_$w^dd Xٕ{vQ-HlEg-` ZʋW)Զ`MߵXPZz۾eOhaCfctL1{`֝4 qIҘI.ʀ%7k1WP̦JRr9X.]L q'lљ e4Gl GJi)>lWIP::kp5K υDG3 ę8NjB7.'qaQ}9#xĖZˤ[Ro.ѝn?>RRY49P'cbdVGf|L[C)G/ehO0R`J5$R=Z˼YP̩*Aj+"heQ٩?׸xNFLrQlGrp%;kC{}hTP̦ԩSΝי ii*8\ŰOj-W ,X9ݒJ ?.a2Æ:rzemj,'lNA, f(0ooAe:oN)sZcΗn~rbbeӟrNPlCOw s3dGl׳s/u1JA V 촲 ]?NewlL6H Pm$UݵJJJJLLtssӗUlZ+OOG3wЌ:DYu.?݊zh:uwO1#ȪXIv-mGڏإ蘺T7x|U޹!{ ٭حs\{ |Suj/bحwrIqO \{yĶO\l]Z7dY^fۂ Hzjf"Yͅ@˧9p%)[޽ fz\gu'2'INO<4u7|PMCyso ;+T 9~IPj~OeCSF΃YlE̫;Fϋwi_fAX3#f=뫬At󮋷i 2c*a&)ELBJI p{Whzzlp##8jKg+R"bIf4j1T)P29:ݔ1wU?G4V :-j,jUߞNZ"q0:N9d^%Ȑ4,a^Χ**@U2UJj99Ȁu>tZՌYHժVB(o3Tȿ3p%Ov=2'Xyjot!9R*K~(m\Z[ȪkPά#8eP7(b *-?}ت'U*g^֒VA0᫥w >$CA;Th HII1ʱt\Iut:o\2 aW2dyg|p?Eο@ZOHֲ Wgޖl@RHPo[PzOM2VA2h֏AA4WWW**buEg2s?icl Χ(#9 P`Π_rv?{#9*BLB'5)TYM[XadikA ;jH[ՂaZCwDNi899:ujΝd2y…:r"- _<3 Tpi6y;VP ]0ik ߎdx ,'lŘ-T @)Ȍb0 DY2yXѲgUmRSQfp*PQƌ*]uSBکP-Z#`1V^^^IIInnnҰӖM]+pv[|掺!3I~ksט(ьQrȅ 1,@dCd2ApI\#N8{؝qk2BTgx{A-6Eƻz*퓡U5qѫBUJ|r⠈ ½M Q-hD\%*xXD'qb`ۛY3 ѬzU™_ʹO&Gldl& i! H o4s]o:/>ڜэٚ5Ҽ/E T/ zM)kRf?`7f^i]mS=d+4dA2WLЭIJ@΀boӢ÷)J.|h*TJEg2b իCa t4nqJ)g>Qٙ߯//`AT3՞;=F: <"t$hne}! uZҚ4 IDATtCO? Z%U&}aÆ2bŊÉ%JI\*eDT]y/ɶf:&h(^dO3tD'q4 39V$hp\#XA4 ʟ4ղKpA1#[_sאY$hySXmLцE9?Ln@#C-eV͛7,YZ ߿g@D^X20 UJ#f~&AnL]e-hNr@_ˣ 6ڍ'`o$Ь}1/AAJa}'^4`񕄦#Ίh,har82̑I!d@fRu[ǤLk)))e |w`4(Xr[s kcn{7a-B! .#ōo\KNF蔛ϻkqD>`B(ϐ!?Gљg\5qֿMb̨a>~ރf !=_:g؏s#N1FWWGo=wϕq骕:Qd>iwn{+5^T}Z˿A$YRLrdlllllۼ`ˡF4q\5<6mz_?\94v؀>~#c1 38[Utw/PJ FTN٥EjX,3{ :f{Z y4G@U&Hq̃?b|F},$1!>ׄzykˡ|WnUb~K3;jQ'Y0Z$ܿn`}Ծ_Cꌚ;>ʀ'R:-M^W kf|5t汧S.^xe8?A!$~œ _4ЪҦ/iZ9NNNN^sV( P /?L:tO~c|DI->W? +3+B2ΆRplH}RsgP?8_tTǮS*69MAcwתdհ"G9bgB&O:lκC #'w)>+Lq"_{NuZY Q5Qd՟4%ߪ2Cٹxŕ6};R=/XׇYcۛy0]ma \'8{g eaf+H &aOأ^%W.ª?m;Gm9~~ ,u{TTz}!J釉ajߝ>[!6Z?V?MSdҬ@(ƺktrKGugi#EN;G_ݤMaoHl ߂E>H`IsfB82[F_kC[º RM&* dY @(tTNNذŧFO_>\UN0 ym_丙$ Z3keJ)>SqԥFaolʮM}mS, ǡRz=ClLQ?Յex52 e 2kT)QlB'<>f/vLke$}W%Ƌ U/пljšaͤ\ff׌rݸ޻έr7N[NGqKN[3wP} y˴eT)>ᴥFmTDznp=jJe0(8 _T*sgyz1E\)14si3f(D)6lf㶬a`[}h0y=2i=!W1gA ߰pO-d%Tg%(,mN#Ǣ+Ts8ÅV[:aÆm`G<;ܖ:Ǟ ^ ظж笓i\{t|_;Wϊ1YC|0d\vOQ y4O2r~6lfsC 5|=P V$S'߼S֣+z_W.xŕ6J>暬guZZr+4 ;N3 ߔMΝ uXFjTDp綾+q_o^tǙ%qSHz{ŇV:}v$dLq~. e1|!r.i/BTFY deJкք.clwg<:PXl#8|'"WqMfPubGc[5>u,vmxYGO8V#2e8l<01!>9ϱ~+njHMs'cFKmX!U/ @kMe1jD^#w&sD>њ:rm Qhրf 8Ql?$$ǡo<\0$~JXwY5$>ȐڧʀZ9HՋ-M} AFqT  5oꗃ  5o  T5:@AlƟ(TN;Wۖr\+xdFG  j  R;V  zP   wBLC@At ޱBAjݱBAvBAAj'AAvBAAj'AAvnDϣ2nY-f{"Tİ\ m9N +1,}vJ}ęʍam,ٹTޑ72H|nBDDY}.Td+CAAО/'(S&C%'-:$c6&YXk%r\n >qhǿ$ԹHL ޿T9.ǭʖgav˧6B4w84I v+"'bF2~#\ipH4~M>E qHr]w a2\H02͚Py=3qeo8:*(=̯?ϒKer \7RCܱ)Jm؆q/n%SӺԣs-ڞ7m fE^ͷRܖ]f  }<$ہ{Gq<Ὓ?MXV8] Z1[SE>Ȝ\Hɑ8Nڋc\?4tޟ'c_>GS.SosI۷u_nὑ(zΡ>9P^9bLZ.Nߓ6G]̻2?=  Bsf)(Z1ۅف% ۫OLgeZ,f_]dգ:O_bc2Ԓ(|C"aֈǝ^|lt#s@Ax[0X^IPgHخMJDZ,fKߍiL'mN~Tkރʏ> K3>k3^7$*i}7SҺkm/f2(PB ~r_HMX  /'}G>[pU q|AoIJU%\1yXBT1.2|ɦ lŨW= ( b0oU$˖-L/\_xA1!w[/v*/LhG}bh!f]F_KG-W`6$]BokN &^ŒLPj]ޫ"s_LKV{ AAnJbR1ݞvq%͖v"}ĺ@= .5#w=I_?exN-Zc^{R]q HAA?$vp"q A5NY~};y3I5ߏjX5 rm2_|~[xU;΍,4ˤgd5W. :V U̢:oK/?н{#9rʜ>m(Q-;^k .wYuqh߻@I}ܪ-쨚[3v(R.r{_=#ڝכnfu nԂa\qj]2ZEEJc<7.zO@$-]]3V7O1꬚2%)Z2Y 50V4deweb{?6`+ǿNԨ}넩.?`mص"Uk|*%ޭ 202l]@Vawyϕx|g&moQ)UzMڭlMO/VC֕$iZ^bXowDSujث"#F G)bvG[w# wޙn,o6[UFJٟA-* !L ]}/"R1t[b=]B1< 2>׬Vr[>[ko_QA* R܆Ŝ?k[i&@JPМ ]zTڶ+t -6!c?;~L/jHNOM8o>bcf27q~FYz!ӼUnq)i`߹DoXS *D-d7v'z@rJޞfo%۲[)rB"+1YuDoʐ܏4@&ֈ:O;Wv FA4[hY|S\bvo>Ed$YFЍ rKӀ i"P ]jd$lrRDƔ j RŜhݥ]fTLԬ~`HZ۷}ع1Ky|U0/KILS5ը]餴'3.:**ik8N ' lJEz0r 2eLR};VT12E딄[Cr3r8iHBI|쩙>͔s}\q̿z1Ŗ%W6Y7hb8aw8|ʇ L'}{+$.AoӾ20Zr[2dֶ)W T2Aٮbd'!Eo>|Е3NipH^,,Ѯ[$L4q/ j.x9rRΥrf->xy]{7ɇq͌8rܲ ao.=LerCW%'5aOr^$/~2b>2{ok71.Jƾ?XgӺW#WmV=YI%Ժ)~yVot'"i IDAT]uHU [ .5\W.|#G==]]^j;\s}+vTx.!dֶ?ӻvPUW^{l)o YJU\. ȔNmU=7/kF+VX!j1jq~NB!AX ExB&rB!$A!l*!B B&rB!$A!l*!B B&rB!$A!l*!B B&rB!$A!l*!B*Аv !rPKKKv5/CCCij^B!ls9v?$w8h|/ P@ҎI_aaaS W344u9|x !B B&rp˓LQno| L&Ӵ)L.J7B1n2LCCCCCC69e[O]GQWok3Չld;9?aad2LHs"ZY˦ŲYN?3 $5T ҦyOvE<>imX,V[Ŷ,˄rW˩YX,g&4^[YUe*h$i#]'qv~EU?2rӢдY,kLiJdM(BE*IYN&fٯ P^ĭ˱nm-@j-y|v,ѨvOPh:{r_gw~q[TV~e+u&DP="8`tb+f^|^%2AhE,y=qc[PڞݦU^4D)~4DP U>K"\z%z>7~Bɑ 8;cb}7V;!d\\#vĔl [wo<7'@(/J:43˕9&um6W~ܻkJW3^{5M@15%|}q|+/nqz/(8o|Z6Y|>ݴi9!#g9Jtާ5oEhE\Py[nk;kGqzpQA =f!$]x $ʊa& fYN:I Z=yh;GfhϺj@[ceᬾC64-$6msY߈p)F3MU hs9lz^467Wv7nW/y:~BtC;9@e}d7j!dV9E2L/HYqk~i9`mXLgD;>tJdYg-O8w߇dNҮ];-%}Mt SCWBI"ě@)twA枡ɍ<3g)onP.>uH%ϏK@W'N4.տQGx ]x@Bj;M9^@=} RVoo־DKj>,m$O>9.S>[эgR [^OBzLş&^ЦJ*W |V߉]WQju\jAG6Qo@4-?okf@<<;4wgRSXnVzs!GH`2mP{ =J( FDbjm0YpP)OYt"*BGJ>^} %O&ꢂBաlܸQ1 髬lo߾l2Izr˓f-KHwЉ6(&|t@ųnfw7s3;%Oo|! >We\x$q%[o}e?Nx=Zwon=vwSUwNWh}_Uts5P٫#{f͘yj78}{g:6Ob̩;):͘flqD a9W"S!Wo:=`.z3q-W/&:;N'^M0@h،:r3pm/<|pԩDVfe QG_e#; 4Q/TY/VeH޽{===Bu,wT. *yE],2^ 5~['B^B)x}Ѧϟ—!#AGA{H7ۆ+CijFAюV6Rh B7x + X!*\ K;;ZQXi[inBUs9 Hx.VW#$DBBCe77r?aB!لUB!dV9E( 5?Wdn144qpd~Q ޞopWeh͌{œ/'BH8<>H ӴW1 =OӮ_k<̻ﭽr$aTg_ i?֥[5V)ku0{W?c`GDW M2:B6N0EuHتDڹǔ_8"ZwL6w3|2g~ dT2U:424+Xxs.?=)PM[}t^[ս;||5HL&RW>oyrvR /?J~ŷ}]cgP2ΟCW^;@C;:ޢ堟"ΨnZv$%啕gX80e[ ;9Vq=wqG{BW38B->}%D5yOhV$+wd$ #)i`Pj@OyqV?eZa3;O?}D_^Ǵ.NdqgVȑw55r=ɓ KwT|n)8{NBY+:ʡ?LiCͨv:q9Z/.8PKUh>ewvXǥgV~e2F.v$]%sO9-ZVGّ^K 4 䝯ygy97֚xOi0~eNoXxUNGv lC_W.=dy:T dCNI.+BhwW.~ݷ3C <Χ,g'} jjFf 1{?ee<E#2UةNr(69$q)h$Ю_P$ X"?堟-.f wamvsvnAdOR R/p>s-_ 4htA)?]vg}Pԛm}5Kg- <#vKD%QBӬ.>U{Xg T$Go yaw X줪hZvh5yȽ~#$\g%zd؅dJ[-z8SDXOMQDM [tk_&i$*+/ 5ƪQl[C.[Ep&%=f[*bCso".5(TSjm\>5*C 7zR }g6=<*4,'6r^71CfKSKzj^ۑ{>XWB?|IOO8c &m#[oWZ>>Jܹ%NciK0ibjV= Hsl8s?l؉?.}+<|+/" @;%r'ӴϯrvB$;PE_"NԈ9AʹtJ ::42cB(c7H Pdn<|!a6PL֨=Vj-qIj9\5gZsI<0_}gU)T.C7,nyDoXg=%PUSt&Pwm[v$ڗFrxzL.E:lq}EeК>c em-=9>tFŋ63wnO.(-"-ڰm_nrߐTDU`wtw\ >-kG܌{ڨWge=7Pgw'b;Ǩ/wO/g~#3X“+/|7tڍ#%v1X:u&5Odn1 ٷ· Jo\?򜇻gSWRPcDu{?Iٷʋ>ڹlOY!A?]|UoEyֱ. a3<@#3G_\;' ^bMV}<^[8%$'wݛöÃ[*S=k.;Î)P@ehk_,z44'87FPHW/ߧ`a_r&Wy'YYg|n=Yߜ7rtsК=a\gZk!$b~6*ǒyE(qڔ[ǯ,;Qp`8TgC M{3iӡ>2M{ 󔨽{ukњyW$K!Ǻ4v#f+5& x5j˼Xz6d܃v5">?`]hS~'rx !ԼyKQR c|Wto77:k.:9_?M%S|C#S~'P)w{"5G{$t$/r'cV8v- K2տh6;؆J.)O#Y '*kAAet(7N)圥cڕ惫пEL}2i: ؚsz2oNA*?g2.4p18u\Ql[ @a=\'KRNYb\Am?kR@ϒ!zؚ:aA:.lpn}i_n !>PЎވ|fTug҈H։p7 {xJFϒGX,v-FFn8FǞ8#~= eEmfqx5OZ{ϕq&wgX-q/ٯi*L+26ly˖4%ӫ+$.[QĶt~:o(ύ;ګtϻq"//cߚ y_|375V9;1V-$v>yGB- V9Q:)1+ &JLL\kR;$yJfZ vt! ]Vʤ}ߟ n8T Ϋ̬>A f5P5x9O}^2Yt6-*޽M6"9Ynyf7OEW]Jf+ȍKxEs٥ F jl\ -gω4P:K}{e_[;xe5iii!P;{mxql%!S]}h?LiCͨzv:q9Z/.0rPUjF4~n2ͻj;|ҳO*ܲ_jhy.M֒9L&d.ZVGK8~kR)@"QDQ3(|=eWll oKekR%Fe@&Kj D@ IDATKxDUfQ۰fK$S^mZvh5yȽ~ظI+rg|Yl.A!va.ҖFaNh>HU^)TpWW`wٝ ꭹFAcu}ͶU9^,7x.]eG MSNȍRr^UrvRU}Ъm UE$*s*B AͨcШQ"?Pz'uR7~g3+#K+yrj#uS ^f91jFKy 2M J/g箐t쎨OԌ9پ(4ggo)}0miSG$9fw6Yi6[o}|}%QE$x$^u\h>K \(ȄU]S3hozx_'oIsK!x=''4ҿis ~M H0ibjV.qfʄo@&R;.B?AH^z0G˝ˊczfYBUIKEg'ͩ}ӥZӻ^|4UN0V4Ǿ whqiG@H\BHrS?X< Is煇{'숐|uL7kh[ v#j+s}n5# k!$MUqȨƤ_Ȑfdy$r#NxhwlC<>Om~) c@KOOO^x׼ut+ۙ@骯`Lk=TK*[Wk|~/m1WKKe5=(w8Cעm]I/.dQ];le<\Y強>9%|ZWΧ( M6o&0ESUQw!Fal`UJGQ;bp(KoU 4*,, dAXjV%N-!h"I֫{NID_Ivr$um{詾Y7aWM L*^;լrЬv=J8,+}ŸnmNǒOLWMIޟebWu}񋊇.h-^A)+-?w7}޾[^)⎢1AJ8fORXүwR!)*!$M+:]Ľi%T9t($gr}J n. $:;^!U@AIwkc_euץo'wN~rXGtU*4b|"pbQ}`Hrj} ԧ0Z,xU;nQ32PMYlU %z̫ttG:L@gGV;C Uγ=ࣤvt# %m(fd6 x|O3k:huȨuIV%E\qjZ,_2My~)R>ˆ&~[:L1Ѹ?#IF=gRWϵSO6d; AI]B.q0A'Z3 Ps*!$MFr4ǀJ*̭BU ~߸3H#t?\ITR銿7})h+k<ڳM/] >~\^`SϪjGU}eoc2}]S/Q4*HIFa?ig#РTyּQ@g*!$ez]:p8r;ڣ"v%*oYиDUX+]ktc}rc'ǧH];ٸӎےmV=YI%|OC^d|rOه*Kda5Z_hO?{r>s4^4&4Lw)/%Ť־uQV R jgP\*Egf !Z&+ A!l*!B B&\}A!>FX Ex !B B&rB!$A!l*!B B&rB!$A!l*!B B&rB!$A!l*!B B&rB!$A!lJ;$k444Bg(((v5ҤByJ;WB!$\j.4o41 /0Z@ v H j( b344u9+V!MX BH6ad}-\1_h=o6& }f?>I)7ˌSs:`M?Zr$M^aW ]'Xq΂As#2C\1>_GуK.Zr#QV3ax<7;ϢxuLJF92CӾf~T+gUŷi1 )(ُ?!ހ {\Q@ }tGuSU(#d#1[1{;b9ƭv2g/}@wT_P}EkQ&4 jZՈR>2~2?/jed&BTJ׌ԑv'5X/qh[Vjg$2! 4&Zq  ڵ#[XTpI2>t5:($$!y[YyO"S=tkX2?|CgsaNDq)DfԉƜ`)bMhzު+D x3#'/a(X ]x@Bj;M^}ߋp~dОz{'ZRӚC?'@AszuQWWUrnYN쏌+HKF|%#7J;$}M5ۗ-[TgdY2ݿ7t"$ʠ}/;]$P}lg.yFx[g#|O8s^T3B$/[gnZ$|cfw,&3C޽{===UPoDmNq;Q".`~/tp[iZN*?:B-D6]'~h/PV !Z2\}ov WԌ^mW P W@\2\TcFIFJP XrBUjp]B!dV9ɔi`$B V9!MX BH6a- ZNp6~Y%< Bt(Ja?uajقEzmBs>4Q Jeot6r܆Odn144qpd~Q,j7@Z .4kM8=,[gQAuUYЫ_h iLtuDuP38knl>%[-C|g.Ln햪ثr}lc&W( 0evC,z44XMo'\Tlo<#qŵ#_}%t.ɬ!Լ8e/L@Zdx1;2V[(4=_V*, zFZ8 .*ۜd0>y_/]YC*5#5#{`gcw~>9 8Д,fn8FǞ8#~q[:|Jj{M`px: 6;^9q9G{Yh Xտh6jv+~c$=.\ ;y*DZj!J4 9F ]gokk6[8~BR2[!oFͦiD%\hl˩Y4T |#>~L%jopj-AҤI,Z'Dh>ewvXǥgۊ,t+I]%s,ZVGWkh=:E*B7'b_xlJQi"u!{I E#2Wc'SU6īʪ'qSQ)jFZ2Q .&kDYRĿ#퍝5Z,j܂bۮpwfg?W)ɏD8K Um;BG+c҉n_ 42<6 Nw񯌟.>]xs޶QEw X줪U6QF]_ UvX5*C 7zR 59p=?yë|N U]S3hojx_' 6jڡn_ѴD$*f14-(+W(.vbX%AbJ QTwL~f 8-%===[+mJ0}RK \6j6EHK&*Ee9پ(4gg@;bAus9pPw5iǫa 8Њs+ fZsI<0FB*=D*gY IDATGwGWu_ڼwЄp2eCiX4Q q0QT<ʾĘ]vLi#,mJiI&~65#⻇ IŊ>uyu:T}4ey!=+% lw@m7x!'XQ'cL(#ECU*!ԌhJ65dq\w'l(4=wg_ i?֥y[=b&PHn.* NB!/=$)7韗>I2DZ+S~'P)w{"5 پW"(5%}zt{U' /1cˋ.{$4*ieO yqWQFӤ~ժTFwm :.¨Nc϶~rj 6OuqSeK)K+ܢyy_y&TT8Z<@HãVsiUbc]e>pt&xE(5WKW^۹cnE ^<*44̥x‹̅k}r@p8Cf_ǏGF] *OZ:u!|O./*.qs#*xU!_4S&0i8]<͊d }U` .<p-fu!$]X ǡf`w^effzȫB#2iug7=;>2뷜@q~wknO7 Q1ϽS)z5/hga4GgU_ogVr蔈ϕ%&&vV*ϽyckR2[!oF%"~#U5zsPkD (t2ίIv8 X!<,>Kd2 ܯ:<_FgWt#nɺ%ԣW秫s9.ZiRɵ-=qqm_24~nڻu?]a ʹTSY:s ?Fd>u4@Ci(Q};8ECHAHR/p>*h)RA#C۱ﶤ{ ӵWtQ840[O‡**r(4t+q< pF> +/fJ#1QD=s\,?yۣ<?^Y1a+Jtg;4Y(hU9 U]OKڏOBR-"2ȩM-01sbՌg[]p_f,zZ?YgÑIN_R1bUSHo'DcǠQFE~(2FO$ڻ۹KIQc~\o1waaB-&$?#?+J,%Df1cc3S½n{VK>ys^>'zu{ jh/u{mVj{@;ݞeG4ֶҍjaӁ="Gw*;ucw~#rY""2~ez_8Ϧ򅫃TjrwNJDS|}7 ܟ&pqMָ +?Rq܎MȦ|RI yvvM Pvv/^ߴo?qh/o[ Us_ёU"17SU>Tj4#"???y ۉaUr3ξ+7Y@8i/ T90jXk.0|u^tW+:Q֩ bӷBV;u,}Z`Ohк?qC|Q=8^UվU]7-us ^>=>SGa&v:BU؅UdR̹㸵U^GU@#V/- _ ȈH%}oЭ~܁N|tiMU/&DqyGW:V]11<_|`}Ӂ}s¢*0h 30233۴'CDkZ9rF& ?z9#fv3^؝{`1(+aXo97aЋߚMTj3zZWi_;$.H~4i=\CDwY-=$1=HQ*V[{""xuW|)e5D3BPѣ>v"Ruw;DDCD<#"RVy1F툨IiL۵g 7 LGD<~$dyqžsVhvEeE\oi+Xjڳ{/˛I!mױFKr@ODhUz*?ɜ*_)6MDs#c$D"OJM9beC*o&4GD׋eݺED&=3–_QZrnD-[^+gZ 1=3/Pruƫ=*Ujj"ŐNbM|j@$ˍtrr*/~rUJ9?{ss٢X r@?#yr"vYNСC}7i5l̛Őo[ǖNbx4!V[*#T&l:пUq{BI,vF'"ҨWwr_t嬃9=SLWLxLoDn;KDQƯLo3|GF-[ wn`b㪭YvDAim +vE?-KPU7H2]B݈H||֭MGnF]"Ø<+~ӵZ#@u3ykRfgz88~3Ƒ+tѦ.f۶ 95K~65bXAW4rwNx9cRvw70j4C7q_Uɮ o|bms ӑs/.|=џKivBEDNKKs\qx`ɻ*Bn7hQOJ URCѼ aCږ~KO%qNZN [֯BKv۞)ЮZTy5IߓZW^t퉱ؠPADeDv9ƺCe_Lh8 8๓g:+T9@Uޫk!<@y{sq߿CD}VU+_Ƽx^40B^ǢJa͖"e?q-^raa{(#ycY7J]3f(/m1{PDD&ys?5ûo}䫉s|&yuF.9Cw!7bnАD˒,[3iiZhk\o<83ΩWV_|XI4rrwwc'aIHLќc⸵Cf[ò 1ޛ6s{y?JvOIoA,Z`&TOÖde5wlHSbv}[Ahxx̼h;[7mAo zĢ#ywg>fŖ_;%l-Nr&"nMOp-!}"2mFD#kGDc|ډԠsfVD[צloy(mv&x'KA ߤ>'t?qG_+ /@`Qp-dJ"eX,d´֭[7oRG0<%oT@[&#u#SMzU4PVgч2.aڿ#oEDu0Q>D"9sfKZ';߲ "Ye hZUʔUL\7T9pSfDT "3h=:'e5De田{V놀 &Y3ʵںvzwHV0/ZAd ءʁ{^,W,?TxQ3 e#<3hJDޓf͚ͭKOfs'606'$s6.Ne]&._||+S,],sT*&{w7+jΩ_,<"=Y;B 7T@ jHOkr@?*/ET}P2C>G.Cff)>F---Sx!@@ U'>x?N_rɸOu*:hB+N͈s].<}cc Xeu(ooYXGm@gY"(}5[kW;J(Ж3Hܟ7gUъ;L=x CwHuTۉog:5DGP*Ei)ؖ[+xz>-OMtƌ >];HФ h4;1Wj { @ʁUJ<ğ]=?<|kg!3=<ǎ[X!ŒD? ;~:Kytp3gBV詳ShX,^s屎 NCzLyW뾉d;//F]|,\>~BF):۶-ztXMFFL(Z1legjT9P g-L bǯo8?!οmu[+Yt ƆnoW=Ϙ{H[^Q],]Qx"ꯂi>"""f"R8ne"|lڌDDl7yߊ rD{;;VPL"zXPDNDJۍ[%'K7 KG[  HYn>mv.!iTOlyJV-涯vn&#"{p γdK~7ex qq=ںUeI2u%{P\ ZZzU"Y?E.&O $?[aD472f`S!ג8R`P/شQ*كѨAi Ԭ\֘2<[A[p7[u/G2ym.^^[&ڃZz#VPMZ:rTw"2:ΨDDUN^WŐo[^G9Mq\"24'gju)h`l[&qWH}x+0{K!qxvWxLID*imY 6^&:?=<<)ڂu|\Xt!"zKF'}60|>FWkk݌vױU{,}Az_qy%,1-& th_;$.H4ZrqH$(b~+̭=u<,=5?gNlacp?0Gw]%qg-)a OPo%dFxXygzzzGcFQx"ꯂi>"""f"Rk'1lݹVIghM@H;G2kD|ӎ Ww/i1o~>R2!+r@ ?5^ýR.=5 *X,3޴R)CJPcԎEvY{nڳ{%Ab9=5,Y7#Y.÷V^[r H/kV򳟚{1NuaD472F"H$ԔC)6U͆6Ɩ-"%xt7k%ddNNNW%Hq^#2c"2zr_fR*G.FC:ϖsf7i)I#yr"vYNСC}7i5l̛Őo[^y4!V[*#Tcw~#rY""2~ez+lvD3.>Ԉam^\(9/]ֆoGDJٕ*#Z8<0]QIyCf4c آێ᮷MHVђ͟C1-9cˣ vn3 h@CS$8--s-,wPׯZ5"Kp pҝ5&M/n,=y]َ2p'M=)ݖKdRų9{ aҸ$HJWRNDILlvGkcdqؘ(Sx14 u&@Dv$b,55qvnw5]F?9kj8:v1}X1wDu\l`3w]3LJGzv%Rp{tk%ϛ#è9OnoRy;bbV5w*o{_8.u/}~ꖃE7^%9{H7(-ɫno^q-wQ_ⓟrߡ2TNsa6Ԟ8?^>e{k}92|̭+f GwXJnuy# Ac|hM,)8ퟯ7l[9Szڌߟ2i|?=Yv <}łEO܋4t@whk>)l`@'~rCx=b<h*5%6?luڈ_?[v}ao7%(4zDohb9ؽgmXk+!qn̹?htL'/6MDq|sp÷^^ZZQ;*-lTua% B-#I`,7%_fs8X27TII H4sf^]8Ua_`ZaWo~9{]z<1p~pWLjur+џuݓ?U?ƺ`=7̉w]: 5e{+k 1GZ. b6<"m3y{O1S2ZN2>QG炇jg⇣/:ݖ9w^Kk57YǍIG;N[=45tßngZbv:()Ku(D k~+?rX|$a|ګk?whYZܵW]|JԸKx+s2]׏L1 c/ᬳu\?&qÒkvrl>:lGGV+f̻o~v̻WzZ]RZ睠@݌g%LȈhi7" vפ(iomiqz$~٨@"FLTC2,ΐ z%,*'8((bUo=ޢU6|[.dNKMi@ 3Jr %>.s,piw8bH䮽ݫ-&ȏtr;kD Crko-db)EYOCXBͿ1ٳO0!>C!aHJ }3=˯ `ٯL_.1'D枑0Ќάyn.FSmnq0,(5z|?o_|.Nn3&Ƙ#:[n֓y!aPW'@י_y{bLaUya4tr[^LRbUlvբݸǿߜ8cV;afK\bTCꙸѩPcv鈦;6V)*X 0lIU"PIYcMA&wLo8O[CBb#zr[Y [—-tnW'޶_}9|$e8r* 5) M-  mVS7ΙS[Lޫյ/*l5Z [:lUܞprn31~3*-+F^|Q9o+ɲ0~U>)Os]67޸bEJάiߴE|c-3߬޺sc3p@Tgt7*UN4XC6tYSdNpV?qՆscǥ)`KIy¬)]T쎟<[ܑ1D@-M$H5izݍ6£vI5<|7ڶ K^-/ F>\K/y"m^~kÊU)%;˩9]]]x9{P%8֒eݪ]kՋ%% SUg#H$2gUuư3Ɵ:Vf4hᰏ_W]~t:ݟ:׍v݊Qd߂XTuogI'7<1^ 4uFs(fhoa &JDpJ;{쵝C'ϹkcUq]AaxrnK_i}ks5 cOsǴsM}IYli:QG*Jc(Y 6:YĉN k8Kq. w7t ;vYN4ũ?S^OY}Tp l6gdWV]iCkXV\_oSC؊M[w9A>?\S3&ݴ|?^yzh<-7dYwS'}!gZ9&䗜}~$)8XtqA;+%/_+ {ńY{ ,x ]ZĎP;&V۸cZV`Ld/9G_^R~80sRc S&$N#O7Z~䶼Y5{jtXDӧ* U" !ű+?:͝3&[֩I1'sy=cs ,UyhҴI c~wW䚄YϽ-Y|}EG_P>k+)~ KВ5C%Gk̙z諯}ݵf2NWeeއ$Q1s oz+˟mu̹}VY+iĻ-@dRsFcg/sW9%0Gׯ~7DOXqe0IƤIč8G,zכו4dܰ酪Jx{/ &]>?$3co0:z=UFGLX=|7c8g_S" dXRcYd7U:fI^t QKw&(äϿ&_d[ecEyvx=LdXe-gRҗZw9J1L%3"\RnCo0_\7k\ ([0,)bqW1v;@#Q}꨹SrDqcG@kXiڝ7tZ^LX_ȮO}YQEYU{ lݩ ˺v,y⾍G7Ȱīι-S2 q˯VƠ$ {78ye#nyb3Ιv<_77]I}l~n嚙w^m̸;:O0yr{&&[Ѐ29w#EuP18⛩W]騬ZmP[V>_x'joٳ ~F=uŃoE(QŠlu/yD.]|џoz{kFFuGj/!k^x~ߟj!u¨]8kY cމxoǏF Gף>ܯ %,^8f*qB[}4Q>VG;gf,\8͚rAy"u Pygh>&'FE\Ȥ.{dܙsJ*.6Pz+̿?Q=3?>>Pm * >Vޔ(50kϲ)Tz֠AnY9a]p>eniqo^}ӵ_r 2 9Jy)9_sD*T8ugʢHZZ (Jyy_;u[ۍU2#GCb1}Z zW}?OSYQѤK*PD.*[3Mȭ(wX̕ pl'JAuu ox`,^鑑AXV_t_ M* `ʔ{8%11qgAy=,I6m)~rP<|/GY-MTO>&?1=8@Z5b1諾|UU-7޼Gf-ߖshŢZBIѣGYZ=8e2FXMM}oUqtusUAd7U wbc̗-~ʑx0a[X'I/dw)$I[UQqNp~ąSFy9Kpz63sUV6ug>/޿z!TqaA@||)3g&RqrKܳGwoܩi9IGǟH3 T83yRLll`Ȑ(k9V*, &B1ׯ;V}w>_q$I5pYow^uV?Ɲc M?G`,V 7${Nzq} 85OKX HA7;֠JR,1A ʋJ7xޑi=p]RՉN'Pf330aBy((hk7onݿ!Oy":PF#Ųtxnp݈aT"8/wgܑ_ڙۏ~|]GCIYq}dX'LRnxeכc4;gs7'Ӏ5l-vpqn?PrϐL{m{c,򕕭>j*|j:"b(sֽ3/OP.9p-۶dŖ+4QQSWWwRTyǹkqGGK5o.HkJHd$WܾHPKDC${h6LqY#/AN6lݻ駛EᅬYƼ`QTGU/_xwxU+kEK7Tx}_&I'X|ɟ}1Z}}5'ˆJ5OxUk_!PO5,(tgI@k|rJh4xƯXr oe^o=pZX#y$4 R_R@+yƹNu. MA=0"Zo6UUr/&u:sFSFqBSwhˎ |l͛;sb GNYMTO nHhصqݺO?NURyǫ־uZNt5 6Ȋ| tXzIֱuU$S&kn¯mAA͔.&))[2Ϙ,K&24{v}7ȱc0TzʤCNw9ff>XO}! IDAT=Rw:=.WK[ 7̕}y5(piq^(Y^N"&&orH83_ݛ_V0`HX6}"`?_;?u6ǺțY_eoL!&Ѱ[}4(z!r/"P=72''xr)DhWTOW}/pS3'15+,^ů[]G X}'pJ Uin~'ЧVt V?YZQ3_\EE D?,Zf"(grn9j{t.I&?:pw.,qޭ X>漛C}>"+Q<~T{64]ޓQp3iDi7`"_F92c֬ J9ݻO%ԼFl>h={k;OJ~F5)|ӡR)= 7o\Jth/uuk(øqawOjI&/#}O{. P|*([X;;w1>UW42~YmLzY]j*rą;mߛ>:ݧ}y - 3\goryLd IL$ǹoP;MLJJ764֮yܼhbqG_PܻW1;i0F-?D9HeWeّ|ySE+2֡@4=L.R{,((0˥;>cEsw |/,ded%NK˪- "p;0IF׆|/&w?53GKlTc?y;*4:9uWNtиW7of~Bќž tHIce |DGEZ2myW#Qtޭ,5q}V|a۴!+[H}6&Tqw Qtyk 1mF^Q՗b6*aj;:rDhV44ݧD]gsc\kFuhǪ:d.#J4ڨM8 8OV SAӀ)0A< Ç,G>WcA!ag0;UɺxǛ=E7-\f;uW`a0[ 6L ERvJ4'W6ddȯтC\_U°kCx;>\y~eŭ;X5谏 &bP9XlFCdQTg0m\m5ȑ`ٷOtwO2d]k/"qBo>$㾊kR{K`2Y>DK0;J2LÅ"O |p6[[I.,*  gA`_cG`+cQb 1 wY_݅G 俻9PQd&*!FUbRRP`ߡtz?O?M|dy^,_=}2]PL Ҹ?6_ܗ j"OS'r=r*mALWBp v{bu:}_a*. aKT:UjL :G)S=;Zh^SU p|07P[ w&N%eryaxUjPxl',ﴴxnaHk(Q /ӄk>utyӦlWrW W2 +4~)تBw[.?E|[bDt:}F_Rȟ͓łh$I:;U tÞ]YeEhMR Sڇ$*Yz`jQPkV-EyIIAAA~5 81(r>;gill=&uޔ)F  ˲zCh4c}ݛP:|؟Zy**.xABҦ6T /ƖtԵ$#y#x[h7A˖e5>2 [vx;(,n޶٪qǗD=s={ܥ~V)kf.ڜTxG^ :xroi1)S*yFA^:UsGSz b}έ&687'ayOÞ P\Q32ؐe_I:Jz!:421J8lC,GAAO?=矍S ޙ6M w)YXO֛C<7}cDMt#ڸO .REn߫/_)ĜADuQb~}ID>Xڞ=!+WʶBi-(P3T:A1HcݾëGG:]:ۑ="܄f!}qR#5T Lȫ~WJyɐ8c/堚soQ5f %َ Mz=Տk20~|7Ӹ#/g"w}z&2Rwʊy嵼ޯ9?7ZgxE@ib#wF<6jc.oNw'fHjg!C;ae (Ä й{4/\r!2/X~u_z쉟g2dwy,_Z,: 0% AyI[GC4Śh1dӹSQ|At$Gh)S5m)DWO}/L۳Gwƌ{y*[zhsoqt!X$ۚסى?q9Ν=4͚Y[zk $y^|tnAAY6ԝ:w<*)y'(H($qn!Id%I={A]ZZܹj㨼3pt61TG{ M# ݧ;~NxbĨ]*NQ H4M@ c $I (I J$I2ANWڟtЩl=iq4* /fN?3J zc b]b3V̋ n!p.0eO$ J27Q$HQE+euY*IR{F*3kiRIK\tSGx0pzh@@ $IcDD$  $_|{MEXD2Hg;7h8e͖ osDmC(! βS}r ]6쮻6my*G|LB>guU !E%##(VLxpkY4AD@"m۬wΌ2IaR$IpA;^1*Hఇq[U,@X`A>X:Z\~kg8rs]^}ꡇL3g\rItOXuo%4+[nQ;^8~(bX !8H B"[5fǢS+`0Uꑰx,`1HEEP` D! o,ؽ^_X8&y 82BXF4A^d<! H K=Ŋ38DbN:o\LAF+..ØXP?J9!5 DqnaӬYY@x]sǎyN櫪w_LD^GD!FV.0pm.3F"E atzhZ-"(VPV`b Z!@:تmQ cщ$h JF4ᖢNKaqwA͆  @kS4ej׏w Hd  lX$9XxHg1nBX -BH&,-0:-MެgS*r\sÁ-tԢNNGhv6DEQҀ $,5r] ܀cUQqANDy)86$Ai$ĈA6C#F3 La$R@Af͑u5WgdG;bh18΍y$q4 z0vcsn Np8jZyIxp6.#­.,"@"0ql,bhg+$u թ{դ!AD``Yc>j0.8u(2Ó4N=HFGEܷ/>)<0ƀ! FD! )DiiSgy@$ ,`D"( q.su}008 y(F .F\qz;"թ^2EcO&%aDHc,cٛӆ-w/<6<*)^vfY!1lǜxTp;r #$[pkn*ڗ_RVYRT4"mdL\tPjT\>Hd]օq=@:T\мg6 CH9ÖDHc0LzVڲㅇ"Cag!!5!;\!ZVK`4q<v`c]$8(/9_7[nZzW7Fa^ΌǵnF|RqYkm O!0h6((;Af,+A1=+9H5'Py _2l #z<lhjm ) %\6`H321LyQq)&5):.eartc cc=_QeBMw 1,t5Uaff VVWr6Y%1  c&1615%*.f8 )s`+Y=όnT*Ti#;U[ yE0P[daڂbp{ `,HOKHKuj撢b58>,e@E P1ޖDi9 acA,Ihq8R9,D0iwoq9,kh $DhD1!IDAT2J0ZmE{׵` /` weEQY*Tj'-}ۃRZS -GzBJX 2A56*/~z}Wj n&@gb1-DF',/D-Y@YslxF! ɘ2ktf#P۲ c?IHӑZ@ET *.x`7V,?Xt88hMV3..Z_h8b4QIm,!YcRxdk0ZUI7178â.XaL0 `ZMB坞0+9WmoƎ)85}yѩ B!kHJJ8]oo79@KPb;TT=H #zLpcmse ,kఛ#?"0NM⸓Gj 02ZX8f6!Dw- Qg@T3i40pWvB;=A3&gŬ3AC[}m$ #!y}H)K4 ĺn8 (3bIU 6f(;gt FE$[BZ^4V er3V$Q:90nۢ,VkX5ZX.dognؙ\ =#˲KNN6p*T#Bvɧvopnܖ`Y&{Xh7Z,̚-i&Ĺ812b/篿,.k7HK )S$D mzXBYV_\x1Z55p15.!*. lLҔ5YcnhjZւ`&44$999%%l6e*?;uM߭/ zfsD>:*FB pTTTjYdfMY3 eeUeIͬ paEOX 0 8 aqxڢyeEǎ-];<);6#rgɷ#QZ+Rd[md[EHR4A #򣗴ARȱAEР? v[PV,)mڢ,J\KqwJ,A6nھA$}_|ߛCg<6Y,-7ΏkRTi,y^߿%=R,H8<ϞIinm$ K '#f CԆYX (s۶:[?`);YtIJ؛ߙOk?9>z٨QB|xfΡQn,y ۪W!#rc΄Βԕ8ȐymDCP졙csDz/\/`Qc4ԇfjT(RH?sn}#)=[&fُ(RJ'^-/{iWwREq_~s"ҝDB;:7vtnG?|u{Օ;oz#~M^7 ƒsfQ.Bz}{<ב݁QHԞ#UVSq`9Yf\pr~sr!AD!q33E6;ёS'f*D?h8v7o(:ۖj#Jwa s}^ O~vxB)gz'zym3aM"h$%n }酗S荵ϞJVX`HD=q[Tn$y.o[یAR:WDGVuc}q٭nE6X@ADa\by^>]0IQ |^m`)< (J;s$M}ys^]jQaB `4Qqw ^kШo;;DGtmRX5KJZ4q敗W; BMiQBLt1Љgu ITY\ިvlP*:h*6R)+nX:z<O?:*u@Ͷff٬9;m.@MfmtOaVu}0O8Nd2tZ ; Mrޕ+Wܦ{_c̣<:SsqS.D}zfzu7*-Ј!B]wgoE#X\^Gp).41pַ`PLOMXbHN@y 1!E#i=zTQ(yxtF~R-speBR,K[C^ {!hz,%5Q7E"wb-Dk]Sc +',!qPq@D"Bƒ6:MU*ұkw|){뫕?@^'szؓ!'bX|獤5W(yiszz/̤fRH{l6-50HGSAWnG~<F2<5מyᄋQ;8ڥH#:c%&9;Sg'ӥpuiyqu%=)ُ43"`xMC4 O>966Jw":O8199y7nH){Q-?:I 'w8B׃`VTJL<={ OF"D33]4]zᕗV++2 ž)u!@1f2mͳfP(yxDbbbW_ ޓRa?a6Ο?>m^IPH`D'M>ό+()mJ<8Pw i򴷶^oNJd*'O?Vިm.^jn\VX)IAzy[hADY,x9/E*Q(OO|^n=9~kn: k._l6@# YۧRZx82t ηI|oem#3vƿW{=iꆞ#ܶ83q "= EU Rf沾g2uVPt狋BP(͝9s}"jZ.]j6Rʀ[چzy^ J9fi! ;H]Ji FWrB4i6G򚦩xGt翘D"ai`Ç_|NaE`0ZWƑQ,$$.dmRJYV^RT8!3n!D6Lm 4]LSBe;rș3gb= ð|'}Q}?ڷXitODCxO{xPIs#i$ɩZ ҝ/qyhtttzz}?82Rnmm% ۶yaA`YVܤ" L}ڵk@=T,UN[\^XUBTmJwx`0OiT*(fYVPj`0}yghr{{͛ۦiV*]׋c=8Na%V(J %~`|?0&'''''"*wk AyOQ(w:|?p)U`Pyaal$dHB em&JlIENDB`tsung-1.4.2/doc/images/ldap-hierarchy.png0000644000201100017670000013363311701017117017764 0ustar nniclausdreamPNG  IHDRsī IDATxw\Vzo" Q,g콀grSφz (ͮtv-3?e,EG3yKfd,?Q0 iI"P?z7+ U)++%bc^|1''U;iJX/N!(oTWthD"u&9jt%I͛]ݺժ N {EZ^\ҢkoDc2226mҤi3sqvU57{-[\IUxe,QI-Sѣ/KOO7334h4(m[buy, gϽzrRRvԔ'kkkjл= <\NNgzz]t)331>}Qdzn<ʪ4cJl??/baCAwةFۻwwMM>[0 #Ir=zzy%]x.>fffCwuB>o?455ono_J2/22 zyݲhђׯEGGa޼y)SiLQTb|2xF|gL[rՙ珟8ǽKN?*`"]SK,+99 LMaG9sǢB_VXF̊rvn|8vj"/\8Ot$/^@IO<),,6}]3inn1g|eTW 2L(f47`2YHe,,阩PVՍ#JJVSm5bU[HKKhPWנN599ׯ]CNNAQR MQQRy-,bc/[fMlm~KWWO0ǥc uJ$ItkP?55u۷0Rd2GwE d21jR˓_bUǔׯ]~0೧A`eea9RmHe| -Wܤ x"]IMM1OCCS< N Bi4jԨc$Cn+Pb =ڪD bcc H$iS':wvui%Ig$))L^ ܄O8IZtz&q||1)ès-p8 \o|YOOO>/,%.((峌m w$33?}553Սn/ML茊qqYH-..yFq*8066F^dSQئE$33333Ħcl( %}]::]moX%Q`ӄl3]Z]Ri)L^S33iԜ:u>jbӄ*Hm/,%nAA2Җ0q2aaѰwow$&&]2j_& ?odae$&2+nRWxaPXX i||<}eQ#ټe^te(Q(Yb=OfPw bX, j[X@xx(ggg޼q]ZJD"D"znn7™'&[XPR i+p' bQ@?tu&- ǝ`=f >;~t{_C##$ɕ+MOˆ=1A&^]]Mq&'} -Z)S ={a zIUw>L&Ν)))L&ߺy)nRW$D;a%~P< EWnggDϦ {􌊊Mz߃566ջS'h)Evw(##CGG?Gn^ݟ> vJ~~s:Ul _=MM-JK֍۷m۶eTTnsԘ ZW޽ѳT߾}аi*3Lдl0etnss)S5MΙ3S'󍌌{]&Up˼7V-Ber iCj1F"\-\ȪQ#3Yں15=hj@a*B TDj1G߯^|̙|m,1hUSZIrU7^2b?\U~ `iYeB #jc˗6mbūB_,bȐa zs8_0M_OOJgȤ̣ڼ=L;eoX%Lmgҡ? >)d2\pGUfB9}P(V-\8ѹo QNCYbdQa$  gۆeQ *?5BuJR?C233 H&E׎6tԹrU}B4ggǸ>ۡtdA^6WFB<ϴ#qbX8 P q )a=qL) DgU__mё_ Eotw޸qϟMLLz/3fڵ#88T{ZCCqy..m*e2QR;vt '7 =+ bޤx` `őKgs$P$)Pà9"zEF}~kK;zdt*5s=o&7pw;`7lp鬬sgL<Ҝ^$}u} ܱBMFЫ@DQ(.fPhi BHaa MMB\ I4qˎ @AA!Kg͊2UXh֓?QKKϦO$I.~tuuqq|F__ ugׯ?+44dΜYOG=h$;ul7n\>|>>#Ck {MgK;%iEFKg5l'6'嫵Mqwg`L"KìɦD"WS;Pkc&\p{zRiH 0N8SqA=755vvOY3@oW#Hbcc֮]f={O;uLԩS" akAA! #Y_[aL+Ar[,J\sFֽ LbND,[B5HQ:'6}&?~tӦ- 0A1 􈪴c->CN4$a)trry]NxGgq-۶u#I&] ?; Xb Hn{wN50TGN><;FlmL$\L r׶S*IVnSOfRdjڴgA,K"wСx[tI^MÇ|?~}eǏڵ+UdBl֬gAݻϞۗ)NVK' lᄺ KXaaꍸ-撚-\\ssyR844ƾ/02dc,kQ{aWWFPUk֟#G;vb;AFJ.ZU+׭ K޼yu`gSi<<lذnE8 I`0Gݰa*6wEDll̿kWqKEN6}"Zr_{ee(p&X)@jj|pL'"SI1S$V,sl1^201&q1 d"U:Ƚ^UZKNv6.auuM751?tA8;xz{/Jֵېp###*&55SǶae 8trZGۛoaa91"SC]KHz6q1`I~/D-^A\6%䀄V@9<#\8za/e(xFS5ut a", 6@J  gX7,<\"YCLl&Rw+V- IRSp B[ ɬl2O \ha:ژ:f .`8$e@  fI~Q^$r L hoƒ7 ;#H p 88K&'M\`fu%$d@ {-2:2,٘|k  E!HO#]qLnG_j2E zE]*mfEPNP_S1hʕ|c~s@ˆFڒf>_1=3Ç"B w)@+nvCl&!%YZDHLjv6ðZҚ]^pA<4Ux_{Kldߠ}XSTϣVWM. R{\,蚚@߉Yp6@054jچ$I @ *yŚF#IB0̙T6~#H$"u瑚F{0{E Us5mC "PgP+Pݽ+Pݽ8s@_O 0L cΝ;`fq}̬sgq'k׾3xeS U5k9WĝvߘX, ;wX$^diۦB/NJyfp(/8B. (^KJ-`0;0/nTə3>,klcfҤ)ʬ+F&f.@7)٥Pa}K.޳a>ǎmİK'K=~SV&m8;v@_GT5Y@ H'ͼA?~O/ sSK qƤ/${[fF[:u:J#i˻603nde1aXi;Kk/_>>Qn]^R)C@_رm88;=sm@w @#gZm׭MM$I;wl`l\۶ntʕ˻w<ظD_Cw5!I[g6Q>YE'ޱ}n {ڣ5kYȘаw -&uӦߵϟkB11N۵s#I2=#3];ouּlqc1 [lvJVE/v2޽Pl\Ûܺu\f EÎjnn#F0LL3gtO0 wjv{*{Hx֟=L^jd bݻgɒ)NNt=ܖ.]֣ԩ}M?޽z,{[7Jaa)zdm߮ͻO^4k֌egg7m( ;1[&}ɓ=lffgׯ?244dΜYOGc u> ttq,M_,F(P+ݺy;w(&p;t1"g6qt4Rwݽ_PPŋS&O2.ZU+_zUXXq?GP"33AAAҪc\A1ѣGyyy6*Wʼ j'mo߼/_2`8&լϲƠA\WWVƅaay/<덽ұ%7sƴx s8&&4ޔ8}5O$K2k6lӧ hSAE>0@Qn'ԥK1GǛ IDAT31  I_]kK2% 4|iZKu`˖-.]ٴi)1bϞ=}vҤIOT2ƍ̥K5nja5i$) ٻƈ "== ]@AԴ r=C|Ycp 7XL͘T"sڽ-t4wvn+/e| ~/* #pa~sn^[{՚8w(2;V3k:v1bDζmhytNK  CCxA=0M48p,&=({}uNN~vL榣S FV 6T6%[fVƺ}jeI2Y|qTO"]M.nc̲T*Xi=IfT{1IPWjYWٻW H$DNq1do~ׯX,hf|Μ9t_,^f/=#EƍmgKԴQ Y֭[zĉ߼yty H2*im+[+=CZƧPWj,0- /KXL΂3挾;YTtRɕe?85$qwP9`2fzTz^:1>}E/_dKs/\Ƚp۵j=zPC6ff |6o|ҥK|6ois?۷o?xR1[>~iSVV6++f+{!(e cPG~N,KJϓ ,\*I`1Yl k&#nqlv{_s4h?e*Sa8<(IKՏі 33# * \Qii %m664Хum{^~^1g'}=Aۧ_>T8,,L_O٩Q ..nҤ v6FzvvMN3Zϋ вQn]_JJ 9x𠷷7͖3g-pB׮]MMM6l8nܸT:͑#G;&4ѧO ְaC]^|ݾ}K.&&&'N(`===Zӧi]Y===\tѣmGEV@e.Ӈwjb![ő4oĨAsk}/O' $,XuoI$J$*';KzH$n'DF\q ǰIN?tA=urܮsI.lqL Y3ekK5 2ضh~}ŋc! M&''-]zq?zS~  `0ކEXa[ /_| /^<ĉ]m΄ǏzqwJdG?H˯ߢ[r8qsgXU?Ͼ~} vwڵaÆׯ_kii͛7?~Νݷo߶m.]Thܸq={ ٲe˵kרx// Ĝ:uj׮]o.ͤݻwn޼֭[H& \re݇޽[>Z+H,!y8lÖDVN.Wlgme2f1f^$;O'-455W^ [nV`UePrrrmۖb^ѣGŅbieb=Jo۶mǎ}eXذaCi&899X,gggݻwG\T`tv'''\ j _|ɷ$.W +<ȹ,3 4; $;>U'K%^|<4:E]ܫZokOU#k[qG9 GD)c,=pNSs mEڥb!I$A@"岹Sx])Ο01ODgW:MfVvak׮>qتU+N9G'k&>,, [N@hh~]te2'wNFHwСÂ_`2]tRj*$${o55&Mvs@H[ܩt1ҧ2VUoõJK`llLk0,??:S˖-iQ˖-?|PhԨQݺuӧcǎMMM4aaa&L.,E޽O2͛͛Z+4%HM00FjL+"@H@j6\T_7[M. iyLfL.XUO~'3La0"RL;ж pS\㼀o}qOc1 bOq-B5L%#xeH22*P/kO?qt|gkkk;;|?f8$O<mvE{;5)/~@' '+nիW{VZ!aaeD˗/4hгgׯ_?u3fAJHAJ]fUAY,Qvf&I@Pin)c` kثn49NE5kBBBBh|@eVx<ϠTeAQZZ\ɘҾ ??6"kח# d.䀢# uKN9|+8ur ՃnhE͛ʼngϖ'33sժзo?-Z?V^ qot{ɓ40wvqqqicfSb[GK[)Ѳ+l{!Ξ=ާ * Vnnnݺu>|Çsss%IDD͛鞝4tĉW\&HV\9y2E&L O>͛~H$={6iҤ +WʧM&c볲$۷o̙CNbŊ+WL@֊5b3150%O-:G;@3%KL iȤ $ 2nVQ? wAG:@ZH?5S޷$IhGBe3>IVެ za28U^_<}Z:G-aff6ceW*H߭{l޼888\pY[B}A<<<qvv-X}ysggee :E3ڵkm[71ЃuyUU^w2oŋmرc ,ٳg^[ ZPP0gΜDsss//AQ &Nw޷o߲m̙)))'O^f ]|@f Z #B/406`Z>I-2!GYY8 40]"i  %(~ 2H|P(6Ɔe(ߚ?y0 &?g+ؔVyt]ڟ`0cc6nڼqfd;vZ''Δ˪ L{Kr||cǎ;DzѣGxСCJ)WZDw3f̘1cpDDDtvŵV 3Lvws[Yml8[Gy"Cb dJL`2cڵ':OsJM@A h_. KJ|+q> B Dݮ5={y,--?dɒ#GxkK̀L$KO-*#0+amR0 H Xe|B'6L-@@)+sAQ1͌XR$Xv>#wM-1  4TFcƌIHH0559r3jCRܜcZ#=l܀1HL}K73g?3,dž5ͮU7^D?5t QxJō򅌈pWG>-G1Ƚ"AwͪJj9rȑ#Ę*!t2⒲ZYkYLJ*N.0s,NƦ ⸤:¾NLMM-U֗$  x@@Bh0] @jDӡԑa>GVM̿tOo/,'~7 ~-^mUe^y#J^)K#,C \:\kpq .58X6_Kp|]C-55"  Wt {C2 @-EZ7b$H=*[^G[/{0ˆ;'9!Qى&LC=u !IʏJg5:Zjijj)s}gϙ_%_W,ACBHLtm4ssK?k H 2䩼Z$A]Sr@A,` A!#*ID#(kqk39~ z=>@QaY-ZZح`]]q|x :jCӁp~m_ȩ ܂|Q_٬[ʱJ$ H&$N$HL\;WRrpܻwWMR_ػwo+G _afcؙ3Ss1b2LLWip5556]1[S/ag-?<;߻D t}(K=x/ !<<4U۠+BYZ<23W%Dq{ pu"02fm͐{] q۠acnkhW^I4Zi_tѭUCǦÏ}=mNܳg7 sSC](>p]:::8uÆX54751zj!Q$A 2Ғ#<;wpokڨתg|6%y5ol-QxoOȖV9{ظ3gܵQcFޫoݺڵX9lnL_ރ +#u9Y:*YD啨P48PAnN#Gmmp[h@Ͼ}nݼk=E7/}@\\\SUQ!*aٍm[,X 'ee^?;o{=3D$L,vom '2eЧbZEM_UDGvxẹ|HgR{0 I6ú 88:2nh/zԘ4y[WW.mtڵAr؍@T/Fxr|rr2l{zHp }uұLwx qV]Csʼ5h?' 7L/QaJUHP%:_`0%D&dfуu6\x;ر F TCCq%&x l;~ڋCۢuђ1NVgsXtyNݤgm|k}N{w޻5`C-qbT0 #Ie}{֭CCCJ:::5{Α#>쳩 D T@YyC*e~75jbൊ6sVifF*x!<==sX4eZ^^mi_of (艒vҿWXG#DÆ~a~~~^^^ࣇVVTh4Q9b}& a=>*Ƚ< IDATg~졊gjnl6:% 3EQbUt(" (^jTg*!*m=“)mFeX̵RU̘>@W]Yߵkl[5޺e)S)Ѵ3nlʾ;yte+@S]zIXq^rQ[π -~/1/AH$b>ELX  Hy$G 0hT QiKP3*\+W^t#Lbԩ'ܸ^uظs/4iREB Pe3=N+1 /D"fwy9tԗǹ8_>/ߎdS'oTLˋfx8Hp%~P3E>"y.X@`z QHϦ=CN?Iث'2Np3^>c~Tԅ/d=ypɃ4y0lY ]zRLq08  4k{qQx D|2> Q`fs L[t4s(-#z1ԢbV6\v/`ߛ]:vWS6zrԵu9uڂ)y߿o9+v(YL8   @ (L=S]BE%r`3ifE $\.YT$kbEAz W} Dhs+OҧQ-ߪd)-2 nr\Qª<@ ]TcH$DnQkȩ۲XO}нp+ vt.(`!rJTX|H j@JdEѩG>jSq& 3ZIjz23OTX^ȒҖX BefEhp.}"Ҷm٣3rh~ceJ{`Se*TPEe~KeDu 6tђ.J`Τ6V|Ík"D]WP Ƚ"J@VfսoE ~ {ET1"½֞DA)S0׷iӦl6رcҔOOOuuu///NӤI6ݤICC|Q6ݴiS___iэ78NF?:Ǐ}p8...ϟ ?~ݿ?u~Pԋk-yXݼyӧsssϜ9vګWnҮ]̷ox%K(ɓ7n .Ȩ 9s&e,,D c<>ubz7 xP 0CN- J8P~߾}m۶eڵۻw-JHH?~<577߷oߥKEs}oߞfwa߾};v}E֭ pN>rJJ3h mmm6ݶm˗/KcE_M+P "uRtW"r%7 -w  Y}! `u}TX޲i.29RSS3 77$IPH<A|>_:DR>ڶmܹcmm}mGGǍѪU>|KƎ{9:R~qYAR^+YJ:r Jw+i,  pJX{T!J[^|).\H;88lذ!33S ܼys䢙3gWpp0fܒ,X ӓ^z…ϟ?/,,ׯh1wwY[[Kѣǃ͛GC077t( "uV"p9`@:jY`Qbcc---/_>d*ܹs>>>$I6klƍ\4cǎ=ztllҥKGUC1bDbbŋg(z%٣G-[,[˗Ak׎vs׺u^xp\]]\"ɓ'}^x ƎwzPBeV`cUidbb\+,`>4fWo.д %/^z?R7ރί(98tp_+KSDYj1}'&>ZZՈUu zvi  jPL(Ĝ/+M@@ȪP⫭z.,_^B% @ *WP Ƚ"J@@ TR^*12Jw ՃoЦ>zz Q @ * @jDMx6mp\+++=vCsy5ud'dA- Eq n-(uu*UZ |@GAu!NT, "8쐝ū=sW QkT?+J^nUΜ9coo=Tz]$% b￿G"̞=m=gffٝ9sv%!!A,}ouB;?tPnݐ wܱ=rH}ʕ+&&&gΜH$?ѣǹsӮ]x~Wiii* WznOEVmWZnݺ{ӧ]\\D"rt^bccx|ݵ3L&H'&&𪫕ch{{GAR)oŋ^^^$`L0!??j:))K.H Z[_&Z_ KkkgxնomA H+4 0vh#p$ @  , @ ^Y@1Ronm/h i4L*@4UWmTRPf"LLR,?Œjl<@2,n 0 TB!N'MF+Օt)/:ZxJ%-w!SHDN%01R1|K9Ϧ3pC#A@/0T_*c 03K PcTo*^I$R&/'˖:W+J=i3|f?i87rs2J$=JrqA66,T*'{Ӧ rūWxI ˗%q|75bxr*J( #&F,WU"BxdIT|PO%+a5RB}ZVRITU $''d&9uԊGK|2F3f̓'O.__STz.4H `LFPYXV'Obd\yݦ[0~h$b^hK]tnG2ᯝc:;U\3ܬ,1lӞS/ՈqSGL#dq@Q\LR"4p 5EEQFYX,dRFP!&c̐T*y pCd0nxFCtECR"4ghyhFEY`XHP aT,y4ݣؓ! 9!!/Ӈv߯څ fvi_9soi\ ='R1c0F$/<0IN^w]A?>y%\w:čXK'58j.\ JHHo^~adfB8p )JJJpԩ#G 01cFqq1b2d\Ej=\)/RQjJgʁfj}$?}~{jE/Ӽݿx%Ǿg_?̦{i`onK9ۚ ER ǙY^*Csf%ј4i\._|y^^^Ν׬9vX```TTZvrr駟ԩSy<޴i޽{gkkozҞ1y"!4.yjN $t<ͮ,O.|%r߻a5 ieW\Y?ߺ:VNJRjˮ: ynVP2Ȧ{O WXXwi/Ŷ|||RiqqšSN]&%ɣG̙ e2YAA?j2dܚ-xZ>OոýUFnֽ^+ܿ5(tiN%L='0Z",{t KX@ M څj)s_j_8:ak_w0)Ɍ[`j~1A3:}mì]{_ZMp @#_y_-)P(oK 6:ks~N-+fM =V*e__>ȷjf"mM*7/CmM-%В^ѽXRPȹWUsh~J/lY9_7s\vD^k5W4,Zu&)gϥ1{xlҠ7_ȢWa=fo}~{vY|AWlF]mlb>VEjg ;a@Q)vgU/nPշRX.KŁ/+i bX{~+ķ/Mgᵽz_H "U}|YPv hZ fY.g,d]@ D'D"$JI}A HsS^S.IeLs1S"a0i4P$VzLz @^y1e5ՓC\\HtppطoZް%Ϟ=?~<$HnnnǏGXHnݺ_@#Ip8N /F\@^>tr駟Y"8J$a$%%EDD>| ONN6r[?~7oAll'WΝ;wڵ|>bEEE?m}vi46*+Aja2z$ +رcϞ= "޳gOttڨucHoooA$=<P-Z:eOOQF}WHÇ}||kjs &B IDATZDr5Y6[LS^~юM5k-?RSS/]믿XVTFL:@ӥKwz 3fU[gluvvtp}5JFFFj<{L!Q[lO>Hdtss{𡗗Wi|mC"`pppPPн{r{.]Ǝ\~Ν3g==7DB0%%{HCBB޽+JE"QjjE]@ y]㧮-saE5qF]l>P?=<_>x<>>>-/++2e N'...Gzwj/zyyH$1a„|GAR)oz:47b5"|Ѽy.\ЮH$̙*ҥK fј[[[ @B[޽ENNv=z,""͛ Eի?~ԮӫW/R+/_^hݿQ 1\՚2p$яg]GN81cƌw޽~έvOW\ b@cC48@z8///Я_:k7A1 Ǐصkq8_5r&[4˖-b6m*//͛X,z V~͛?|P^^qFmkIIIٳgk[p![__4p333@AAA=yX5E9">XleɓV'Na5g$(3g,fc5̘KXk}ks'@ m//Ç+{zzJҤ$=z׮]%%%nnnSN~llرcz0rT*9sf- bŊÇG1kK ?%%[[ec$o#&x˗X>{6䄇#M֬;'T__\͛f۷KׯB,NhvX EG˲q;\)8tHz:y2y0#qz43S-ּ& qC / &hС7nh[޽/R)/*Q@TxDgFYnZ&SD£GI.U xIV*ǓܽVu4ab}\xO4 1X!!YGNZtD6og:06kjXd[[[T ruu%L&sԩtiE /*:{x*SLVXvQQ=,,;Q!Yt3f۷cMMǏEg,:z"!hFÒ%gUN-AC"ŋ"""x<^jjjFFjܵk/,,d0 .E /Ym2P`Ȑ!ĉk6C_p8666B^k# % 1aÆ-[[s%++kk6 1?)]:4CϣGD^j[LoM"| b;4 yos˗/wҥK@.턤ptB%dhF "]ǚ"]+V8~xVV:"U_g V^;([CCC-Z& MBstA _0vD \4iRttt1cj&j0qqq<ΎF\5-^8<<̬K.ϟ?yWeԨQ[n]zŦMH~niBD>>>6@mѹkN.A'Ǐ?~|jhI;v숉)..ܹ˧LV-ɜC)H͛7?y͛7wp8_9s<==&ѹjo߾˖-CSU!=yù4{fOHرcǏA.],Y߽{WP:;;W'6))駟~z7o.\,,,vѣ###~~~8Ν;x}:gIUgPP22xTo*k2jϣ M/BU1[#A|z*XQc 1՛\O/lM4"yuA }G 4 33lllvڕ 5@6U M;^(HΏ_ȁx*A؏89\ZVT£B I!^%QU@T<䮄xH& jZJS'Qi6P]pzx~#ׯS(޽;y?iӦ]rǏd2FM Mltt!ϗ~!~ZU(k@T-rcXF™U4m.ϗ+RP!{&xHsõfNիΝ;رcرHw? 33!CZJOWK.믿V\rMMMW^ Slbͦ;`(ǽut`b(U2%P cuJ㡠2DY}9tqxh qo$[ҫͻwj<+[LlejDmR^gcWy!sƍ4>_܂0X a0nxFCt(VP}X*CĐ -((8 ,KAQ)4 !L%"@.^^^l6 'N8ui1nT"!`0/y;wj 28>>JUҬ,mV, 8: F9;Y*c9NYJnHl2 /弝<SFt,d+YRN8G }H .n/UVm޼ȑ#k׮mm3%(86h) eO4k"a~)bqfyR]%2%gVLDC;]_!(,kÆ O}V\9m4ԙuui͚53?;vxX,V*[lH$ŵA |)vm۶ٳg7[@ <IxxxllѣBMǎC^:ضm'F*))f0Ӛ Z[;rȂ {ĚKk+99ŋwiWHhZs׭[]L&۷f͚5b Iھ}{dddzzm}oi`xjm!ׯ_ڵkS ԗk{ hܴ] .>4vk˚e w1mTk l-/^nfsHdͲfkU@VL5˺|v9c!JJ :!D@$VzL 4-RlZZΝ;wİ۷7I6Bׯ_;v5*66T^K' @&gb䦤bnŇpN$JI}A &K.6r}ɓ'9::Μ9|ӧٳ{ƍ￵_~9`n"׬Yӯ_?@ޙ3g bkk;bĈSNi\97o_= 0D&@!dJRJϪK@:M%_k;r#VdggkVNIIYxqΝ5k?TC :" L4I./_w#qz43S-W@ MA+^˗@h ^L Tx~,:yRr.\{m-Ƅnm kvSP ߭# @t\-T,!I2QYTe(bL&eEQb; !K 2լPFܼY>og:0B 6MUtU8sseYiQQ _iMH?K*liVz@1P #\ERA HP^))└JhaC&ٓ'2tՐsr1 KxltD`xgL?OHs@ .<ڃvA)]:"u v6=ƦG(?|e~ ]l(r 3/yBjgВVItc Rщw}@ۮVF#T9=s7k 6a_0[^{HskŐdc| c16hD6EoT ݖM#9bҳiq?~uerC_!u`O/ccAY3'O$J}诶,#T(nj~_ʮE%i`xagy\ȣ)p21'e]z#_I2%~R<-\ufO>mI~G4XA!6F;^O FWkuc?wfpk5)BL]\Sf ^k=1I>{o *UD!z] iy]S\e>e0EL^)*kZ$bN[R+/%Rgl{ IDAT}^~ aRF cWK#mk;a;!ڔ$:~Eyk5w *6Yּhm: kU)E'EODg"͏Fp 5x1<GDJ;Օj@%Tpf8,х3jYU)}(j DD+k2o=6x}Ա'|K16˯!+?DEOOI)7_7?-`0ՑǪīo'v9WTNbzҧ@^4UiuCBFr%r-%xKgәLN8G㡑 I ϗc/K%JұD7 $Ws)͓epq+lc@fLnke';>r݈x쫲Bg@Ln7sWlr:nmepVgԕ`4 g\.#Iљ&ocuDM 骹 ܱZ&=jTeFu@t$w"&Tw|} |K9@^$shx $ ,[89£B$KxIDW!$w%TaPz_ PR4_ V[|Ю7W(pvIZvrq美刜-9:\hR>.\&MxuiABoiWQ@URebi~4 goJ|H(U( g2noyoך!ʙC[Xr̡& -3lrt Ek8XQ`eJ<ꌕCAej%щs1?s  {C?->'w^vr!䠶Z SD{#W\8~RYʙCF+)l3W+Gd:>=\f׳_o#"ޖFyhlb:dߢC NtCIj&k35?P)~/x31G«@UTԗgwoTճIXokZ =;0x=->qp ǒͦ3t55bv~c?Oi  1l[w>sk~|`KAoủ3b$K  ѹXAbX CHb3<4,p L,eEQx(d0 vX*K*Ji=l {,j@_@'8ȟE=9Y޿.ܪ3º7cݸu.us譿S?¾g_@<7 w ߽~\8vy;L1k] Z\w@"k|#-Q)98dOd'29z@ŕ3I{WͪI z@uR_Mpitf{Nu;]QZ Qn iC F"QSDOJlDMxB.k/s/ 8JI^ԝW?wY~1@: +iR/ܡc'S>L}3zǹ7Rblj()|ݶBCسe%<67,HyOgWñruQ~Tj kp&kgJEחO'.y]A$4Wwl sbAύ7MfaaA"{禁43#%aXal7B0nOL=oö%~3j=smaos>f_Qa:=gٓ3k+6#ֹ];woH'|?s:Y[j7ԝy0T6-0ѣ7mZPP8rȵk׶_ D'|U.@Vv횧'@ݻ#GbbbZ):0Dþ} 3==<{lL&D"?yb 0D"um===i4ɖ-[&bq-gQ jj !)))""B0)))<<<99ِgo޼'N@ʯ^:wܵk|uyUxx9RI5m6{lC@Z^;rj-vرgϞAٳ'::ڐQԺ1yII7 58pۛD"ȑ#k C !;&99ŋ5MHYKVWzBӧO~>|xnnnh"WW)Sxzz5ꫯB>|]S!$''[.--N7}@jnA[*jȚeeת 6~kurBѕ@tB$bnItW[G;6ՙjQ?KMMtүbŊUVT*@ h, ¾}"""lll ?H^ DZu)MjF.Cd1o'cMc%Ij 윑w!322j2q8Ϟ}vېWWWd6Z\\ܧO$2=|˫tؾ}{LLLzzz׮] ia^{/@Y @G *$j: $SUoUzVF,WU"B2U!0888((޽{r޽{AAAK.ELcǎ \.s̙3뀞ƍ?D"Pҽ{w|!!!wޕJ"(55u„ Ixxxllal|ԝ_ [=I= 1udfϞ-f͚+[[P4I޽{\r֬Y\O/$HC=I>jԨ[^*3$$DW: T[p8iV Gi^25\$S' fSK;Ǐ_k+//5':B֞R=&_@=P.hLIis=$HҍC4L&@$Ah}A H#GDr52G@O+=&I!;vdvvv?K.'Op8wwp4Ht@ MãG&Lѣ}ǧhWٶmϗ/_/- iزeD"ٲe ݻ+DEE9::9sZVW4<|0h ]z\ Q`x@ -D^!@}vCHv{#_ 0B !44D">|PP .:&uWket([\xo߾Ϸ_`vvD 6gX$ɶ."J&LԩS+**@YYY``UPPPee!살.z{WӑCw޽{Nf͒ C"ŋ"""x<^jjjFFjܵk/,,d0 .^"]nnnl6 u_ B@^eiӧc͕ee'Z 4??Pe99*mTRPf"LLR,?Œ>|Ռ]U\ԩ˗r;ii HRg6n$ѣBMǎCXXXl۶M;7`}߾}\teϞ=={ܹsQP."ص`^B MU,)HyI ֖lZ*ܾ/_bi4@Nx8b2[ARPT<]vXbN&LL޿^㩓&!&1$" @GF BZ-T*vWvi׶ꐶHU_ڢ%%J P(5ɤpCwEEREaLDj&ȸx{{R'^^ʨQ'7ɭBQgluqqIOO#Ұnu֥Kd2놴`*H=kb(-e)ʀR4ZTʊط/}qJ ?.Z>YGԉ2ly0qzzn C`hhEׯ_oذA{Iׯߖ-[)JJJ!;w*?ŋMD?;wnmt+gO&Qu- Μղ*+FAXӋ2A«i:7yPcɓwk3asҤIr|yyy;w^fM{rر(ZO?٪^]f qIUxeS^RaOSyZ.'CMfQQWrr?btʈ"$-qΖܨ(EB`/_v4@LC&NϟRl4+ =l { ?.:}1}Xnőճgiv«8%Ey[JhaC&ٓ''Ot )a4cHv-Nc sWHƆ@ f j[@ H#Wi`x@ MÛ7oߵkW TK.]txɓgΜY\\ܺ770B ! … ׮]=ztddv(jX[p}kWB$(3g,fci4gf\Ag3Y6&yxmnuŁF25˚2boau<֜2m%:MZ:aef5I$(33V-L, \´8ha}ꘕzVͬN[Y0gԠ%hm%''h1c>>}h8nСΝÇl6^Aͮ]cccO>mmZ O_14)JJ>.]Z:eJ46; >> eڴiW\#LhMI-c2)VN}XDY[1HIZZիGb4__T@,kj,Wư>1#bAQ@LX:bEٴmmնg2d>qĚu4ccc{TSW7 ʣ:"";V*T|^5Vf)TGV :*h;;Rb+">rIB8'9#g>gǺn~ gٳgR?=zZBBB Cñ{D"ё#G#FGGoذ!**²>w߽͟ۦ3^'Θ2-_o޼:K.}g!:u"8o ۰j4)e_`]= C]bX5E譐KꕋkՁ)eK,[Yۯtt iŽ$@>[v%KZ*77W_"[ 0Ѯ1ڶ'%h.tKc+ت*m;TUP 5?ȟK$H۾Bs?CO&؆Qk9kxZS'Nwސ///ar[Ӓ7ov133s.r GJ(;L(Ⱥk !dt-gf(wߴof(݁W}s={m5%.\=v+W4|8##&uJ\V;w>#{PI@M}daaD"+^; \P'e2"!P:Imf/xX)Eh:Q4C%"&b{7{QɈb^Gﵵw^vEaGߏl6nwy뭷v޽bŊDa[GOJ(;kߟ$$v.]N "”3L |||b{z̪]pws1zvT*>}?G6I;`.4 +FW4bmצiښyyi7|3((fв5?ի9i[WhSD"Qǎ7oܥK֞KzڰЂZ{ #]@PxxmTTK.|m*x4,{Eړj}׶m}6@*k۷ooR A@8ѱ .k~*G+jlKף\t5pmlԩ=\hh sv=lذ~9GٵkΝ;7]nܹ#{%K_|l6uk֬6mpMaÆS- \r}#3s^{ѣeee+Vo~i ,pBNNΚ5k~{B?!$33>__eee8:|𨨨sFqoh{\\g t;w/oܸQYYosά/~g_u>[\`8qz1{PwyrY9Qt팍qØgTʍr<{m_ߓ9<<<''ǹZh]ׯ_1bL&իηgff~#F?|VVG}$ZjUtttǞ,{BǏP(/_w߹u!!!ݺu !^Z{Xoƍ/_%z׿{צvpA.{VNX#V'638 8(?HU^ G5 j>m۶Y Povll%Koݺ%̜9SJR7gO/%%% >||{ee…`@ &IDAT {ҩSݻWVVuSqqq׿! 9rd!w>|8ZiiQ n'aޫGd5P{ԿQç\BR*VT.[.f?( lٲk׾ """/o>h \ٳG|5kW./;;;##Cy//N:YF ,lݺ5$$|׳vq…N 7ϹvZqq {ァT*nܸqUMݻl6oڴ)!!i&pܹA53gpGgޒhhŽbbb_~띵q5ڏgʜ{###5ݵk^{ٳgBzYRR]wȑ[*CuSaaa6m:|ll,!$66>͍{C _;o{ؿv^6l[%jNq)'NTT{5l6aRJ^y啴*e;,tM2555&($$oOIIIOO?~f9xԩS=u=>3NgX3of+Wj4GΚ5|׃N2[koz#&8cʴ~xޫ[j=֧~pB__7xpI&[K.峕}N:k׎_C֯_T*Urrŋ[p>-+"$%%%%%5lwV```o?a !^@PxVi+xqf0h>@PRlOU';t߾m7+!Ք)q}vC}ƍFDp55֣G 7:FB}{^$qVzQo% qt;koCfy^\m)ǎխ\\"E`gZ~6z\RN5KlkeJKE2rxܹ !~} 99Q+C9f (O?<)ob{U_!B_MH:e޷z0g @K-T[ǔtzzӳ ̅ nw-[={ ]tۙ30yy^viΖcpTK9aq#ðWVoؠ52_Jݤ$9_|7vO+pi.Ka0cF2EѻߟDΚo鐕Ŕ.ŋuu_p ;^yE=th-V&h&VTTo&vH* +ː[瞓##}~\XX~=u}g~e_X2h]*vLq׮knG^xAjw߿+Ms$AAxet~h1cbbdaaӧMb^UC:)IN$ih:)^+"\NLgdx*  bX2t2n߮IIDTz_W+2RTtg4D]Ot`}۸k#uĀ"1LGs'.wtRi;&|v483d]9޴kU9w.7:j\Xh.,lc%~6-.ٳg<[[T ^*z @ +W*T ^@Px @ +W*T ^@Px @ +W*T ^@Px @ +W*T ^@Px @ +W*T ^@Px @ +W*T ^@Px @ +W*T ^@PxⱋW88k[sBi.[:3}q*iG{k=/ 7Pso[*iԡsvj]"᨝BWirFNhi.XC)eD2rR=W[j?_嬜=5(fFv&([g[&*)88a ]rjkl=s!v; [ r mv8a3*z).Ys!JlL)# Jc8*[Z9Jw.RzeByX#VMQ)z+$q6@$% ӘRFyR]5_[VVoۉs@fQ){AM Ty<}s },eX}){eV+"HP'sQv&E#  +^l=QRR HZktrzeԉꀜ""L9p62""yƴBϹ7̙vZ諠y= ]jA +|b} 3§˷RZ^^>m4QFVݻ7on׮q;sEz~ ڪ>Q@mr w&ީLtjጚ~U+M&mjcTw&Ѧi}||B)Ӊ'.\p„ *j)))νnjj_׮]ܹi&koJ8qEƍBWmv8a3*z)3lgm9b+1$P·+'([#Vثlj(%%jdz\parrS9qDrrsQg6u:| ]'N`ԩSgϮ(xFJkĪ)*Eo_Ұ86%e22pSHi}Pȑ#_~E8Xlو#yDRoÇGEFFR!@ʀg?J߶;IEA_5gp^ԧ/`]| //^|2˲rj6uXBd/ȴZa*7g$S>9L]S`Yb879sO>EEE[3j(̿|W~{Y'+X>(1!*bX$)Dav&E#  +^?JJJ'deeS*FEEeee ]O^hȑ#!QQQ2gϞYYY|ioxFJeԉꀜ""L9p62""yƴ3gNnnڵk*/g_wM0!++ƍ"ĉӧOo;w~wʕ+W= CJ߶kӴέ+Bl%СCo޼y-[/_9rΥz(xqXxl!X=Oh_+L[צI@q3ʀgz n8 <+WpϠ8n8^@(xp` @@q3((xW*T ^@Px5<ژ{hA(P!%NVbh CI;Vss'N/?=29NՒ%)Sdl%%lVK鐝_p%M3wnKE߾q\MQƍQcpAS+"viSA}{^$qVz8?.撒0 .(pNUWLs,4T-t;6VW- ӦUoU*ߩS5fi-#X+&yOl!7)-ds/wfsa>3S㣚0◚jٷϐ0ž=c ^չ)(nj^tLyyuBy~yd];B:>;pްegOrD$Gy d)*Fimgp6d2)z7Ç9աvl9vQ]M^o).JM}?ҐLxɔ ǜb9xg0_ʟ3ׅ^a0k3eݻ3˟{37.F2Eѻߟ}[bYP7R%xn6mіxvt%kjABWLiꗒBJM;b :Lz-ٳ{8Fydd4ubC'W3Y׮KYν̥K^]2k " e />~& !f^~y^c^nٳxwoQ'&JCCD" W'%_wu-[vN5~~^+"\Eh C%D,k4C.5s&?.fk)ԹIENDB`tsung-1.4.2/doc/tsplot.1.sgml0000644000201100017670000002553611701017117015467 0ustar nniclausdream
dim@tapoueh.org
Dimitri Fontaine February 2007 2006-2007 Dimitri Fontaine
tsplot 1 tsplot Plot several tsung logs on the same charts, for comparison purpose. tsplot configuration file images output directory verbose legend logfile DESCRIPTION Tsung comes with a plotting tool using gnuplot, producing some graphs from the tsung.log file data. tsplot is able to plot data from several tsung.log files onto the same charts serie, for further comparison and analyze. OPTIONS specifies the configuration file to use. Default is http.en.plots.conf. directory where tsplot saves the images it produces, defaults to /tmp/tsung. makes tsplot very verbose about what it does. CONFIGURATION The configuration file of tsplot allows to define the plots you want to obtain, from their label to the data they will show. The configuration file adopts a .ini file syntax, each section defining a chart. tsplot comes with two sample configuration files, namely http.plots.en.conf and pgsql.plots.en.conf. They respectively define charts to be plotted for a tsung HTTP test and a tsung PGSQL test. A DEFAULT section may be provided, any element configured here may then be overriden into a specific plot section. Another configuration file is used by tsplot: the tsung/stats.conf one. It's used to define by type the statistics to be read into tsung log files, and you shouldn't need to edit it, short of adding support for new tsung statistics. Common settings, to be found into DEFAULT section or any specific chart section. set here the encoding used thereafter in the file, for labels and titles. dpi setting of produced charts images dpi setting of produced charts thumbnail images type of chart image to produce, as in png or ps A complete list might be obtained on the python-matplotlib website, http://matplotlib.sourceforge.net/ default label for horizontal axe, often you want seconds or minutes, depending on xfactor. Please note you can also set some defaults for ylabel, but this seems not to be a good idea in practise. tsung logs statistics in its logfile every 10 seconds. By default, charts will not scale this and have seconds as horizontal axis units. By setting an xfactor of 60, you have a minute precision on horizontal axis. same as xfactor, but for vertical axis. Depending on the data you obtain with your tests, you may want to adapt the vertical scale of your plotting. For example, the page.mean statistic is logged in milliseconds by tsung. You may want to display seconds if this unit better fits your measures. Then simply set yfactor = 1000. set here any number of matplotlib styles you want to use, separated by spaces, as available here: http://matplotlib.sourceforge.net/matplotlib.pylab.html#-plot. For exemple, set styles = b- g+ r- cx for plotting first dataset (see stats below) with a blue solid line, second with green plus symbols, third with a red line and last with cyan cross symbols. This could fit a stats = 200.count 400.count stats setting when plotting two tsung logs. You then can define any number of plot, one by section, and give them an arbitrary name. The name must be unique, and will be used for naming output images. Any option available in DEFAULT section is also available in any specific chart section, with the same meaning and effect. The specific setting will systematically override the DEFAULT one. Title of the chart, as printed into the resulting image. The statistics properties to use for this plotting, as named in the tsung/stats configuration file. Please see this bundled file for a list of what is available. Tsung provide several types of statistics, as documented here: http://tsung.erlang-projects.org/user_manual.html#htoc53. The two main types of statistics used are sample and counter. A third one is gauge but is only use for a single statistic (users). sample provides count, mean, stdvar, max, min and gmean (global mean) properties, and counter provides only count and totalcount. gauge provide count and max. The stats setting can accept several stat.property elements, separated by spaces. Examples: stats = users.count to plot the number of simultaneously connected users, and stats = 200.count 400.count to plot given HTTP return codes count, both on the same chart. Please notice tsplot is currently limited to use only one horizontal and only one vertical scales. matplotlib is able to define some more complex drawings, but tsplot is not yet able to benefit from this. Legend prefix, which will be followed by the legend given on command line. Each plot on a chart has a legend entry, you configure here the meaning of the plot (say 'concurrent users') and tsplot will add it the name of the data serie being plotted (say 'scenario x'). You'd obtain this legend: 'concurrent users scenario x'. label for vertical axe CONFIGURATION EXAMPLE Please see the given configuration examples which should be distributed in /usr/share/doc/tsung/tsung-plotter/http.plots.en.conf and /usr/share/doc/tsung/tsung-plotter/pgsql.plots.en.conf. BUGS Please reports bugs to the mailing list tsung-users@process-one.net or in the bug tracker , see also for archives. AUTHORS tsplot is written by Dimitri Fontaine dim@tapoueh.org.
tsung-1.4.2/doc/user_manual.tex0000644000201100017670000033110311701017117016142 0ustar nniclausdream%%% user_manual.tex --- %% Author: Nicolas Niclausse %% Version: $Id$ \documentclass{TSUNG-en} \usepackage{shortcuts} % -------------------------------------------- % Title % -------------------------------------------- \doctitle{Tsung User's manual} %\addauthor{Nicolas}{Niclausse}{nicolas.niclausse@niclux.org} \doccopyright{Nicolas Niclausse.} \docversion{1.4.2} \docreldate{\date{}} \docref{tsung-user-manual} \begin{document} \maketitle \newpage \tableofcontents \section{Introduction} \subsection{What is Tsung ?} \program{Tsung} (formerly IDX-Tsunami) is a distributed load testing tool. It is protocol-independent and can currently be used to stress HTTP, WebDAV, SOAP, PostgreSQL, MySQL, LDAP, and Jabber/XMPP servers. It is distributed under the GNU General Public License version 2. \subsection{What is Erlang and why is it important for Tsung ?} \program{Tsung's} main strength is its ability to simulate a huge number of simultaneous user from a single machine. When used on cluster, you can generate a really impressive load on a server with a modest cluster, easy to set-up and to maintain. You can also use Tsung on a cloud like EC2. \program{Tsung} is developed in Erlang and this is where the power of \program{Tsung} resides. \par Erlang is a \emph{concurrency-oriented} programming language. Tsung is based on the Erlang OTP (Open Transaction Platform) and inherits several characteristics from Erlang: \begin{itemize} \item \emph{Performance}: Erlang has been made to support hundred thousands of lightweight processes in a single virtual machine. \item \emph{Scalability}: Erlang runtime environment is naturally distributed, promoting the idea of process's location transparency. \item \emph{Fault-tolerance}:Erlang has been built to develop robust, fault-tolerant systems. As such, wrong answer sent from the server to \program{Tsung} does not make the whole running benchmark crash. \end{itemize} More information on Erlang on \url{http://www.erlang.org} and \url{http://www.erlang-projects.org/} \subsection{Tsung background} History: \begin{itemize} \item \program{Tsung} development was started by Nicolas Niclausse in 2001 as a distributed jabber load stress tool for internal use at \url{http://IDEALX.com/} (now OpenTrust). It has evolved as an open-source multi-protocol load testing tool several months later. The HTTP support was added in 2003, and this tool has been used for several industrial projects. It is now hosted by Erlang-projects, and supported by \url{http://process-one.net/}. The list of contributors is available in the source archive (\url{https://git.process-one.net/tsung/mainline/blobs/master/CONTRIBUTORS}). \item It is an industrial strength implementation of a \emph{stochastic model} for real users simulation. User events distribution is based on a Poisson Process. More information on this topic in: Z. Liu, N. Niclausse, and C. Jalpa-Villanueva. \strong{Traffic Model and Performance Evaluation of Web Servers}. \emph{Performance Evaluation, Volume 46, Issue 2-3, October 2001}. \item This model has already been tested in the INRIA \emph{WAGON} research prototype (Web trAffic GeneratOr and beNchmark). WAGON was used in the \url{http://www.vthd.org/} project (Very High Broadband IP/WDM test platform for new generation Internet applications, 2000-2004). \end{itemize} \program{Tsung} has been used for very high load tests: \begin{itemize} \item \emph{Jabber/XMPP} protocol: \begin{itemize} \item 90 000 simultaneous jabber users on a 4-node Tsung cluster (3xSun V240 + 1 Sun V440) \item 10 000 simultaneous users. \program{Tsung} was running on a 3-computers cluster (CPU 800MHz) \end{itemize} \item \emph{HTTP and HTTPS} protocol: \begin{itemize} \item 12 000 simultaneous users. \program{Tsung} were running on a 4-computers cluster (in 2003). The tested platform reached 3 000 requests per second. \item 10 million simultaneous users running on a 75-computers cluster, generating more than one million requests per second. \end{itemize} \end{itemize} \program{Tsung} has been used at: \begin{itemize} \item \emph{DGI} (Direction Générale des impôts): French finance ministry \item \emph{Cap Gemini Ernst \& Young} \item \emph{IFP} (Institut Français du Pétrole): French Research Organization for Petroleum \item \emph{LibertySurf} \item Sun\texttrademark for their Mooddlerooms platform on Niagara processors: \url{http://blogs.sun.com/kevinr/resource/Moodle-Sun-RA.pdf} \end{itemize} \section{Features} \subsection{Tsung main features} \begin{itemize} \item \emph{High Performance}: \program{Tsung} can simulate a huge number of simultaneous users per physical computer: It can simulates thousands of users on a single CPU (Note: a simulated user is not always active: it can be idle during a \varname{thinktime} period). Traditional injection tools can hardly go further than a few hundreds (Hint: if all you want to do is requesting a single URL in a loop, use \program{ab}; but if you want to build complex scenarios with extended reports, \program{Tsung} is for you). \item \emph{Distributed}: the load can be distributed on a cluster of client machines \item \emph{Multi-Protocols} using a plug-in system: HTTP (both standard web traffic and SOAP), WebDAV, Jabber/XMPP and PostgreSQL are currently supported. LDAP and MySQL plugins were first included in the 1.3.0 release. \item \emph{SSL} support \item \emph{Several IP addresses} can be used on a single machine using the underlying OS IP Aliasing \item \emph{OS monitoring} (CPU, memory and network traffic) using Erlang agents on remote servers or \emph{SNMP} \item \emph{XML configuration system}: complex user's scenarios are written in XML. Scenarios can be written with a simple browser using the Tsung recorder (HTTP and PostgreSQL only). \item \emph{Dynamic scenarios}: You can get dynamic data from the server under load (without writing any code) and re-inject it in subsequent requests. You can also loop, restart or stop a session when a string (or regexp) matches the server response. \item \emph{Mixed behaviours}: several sessions can be used to simulate different type of users during the same benchmark. You can define the proportion of the various behaviours in the benchmark scenario. \item \emph{Stochastic processes}: in order to generate a realistic traffic, user thinktimes and the arrival rate can be randomized using a probability distribution (currently exponential) \end{itemize} \subsection{HTTP related features} \begin{itemize} \item HTTP/1.0 and HTTP/1.1 support \item GET, POST, PUT, DELETE and HEAD requests \item Cookies: Automatic cookies management( but you can also manually add more cookies) \item \verb|'|GET If-modified since\verb|'| type of request \item WWW-authentication Basic \item User Agent support \item Any HTTP Headers can be added \item Proxy mode to record sessions using a Web browser \item SOAP support using the HTTP mode (the SOAPAction HTTP header is handled). \item HTTP server or proxy server load testing. \end{itemize} \subsection{WEBDAV related features} The WebDAV (RFC 4918) plugin is a superset of the HTTP plugin. It adds the following features (some versionning extensions to WebDAV (RFC 3253) are also supported): \begin{itemize} \item Methods implemented: DELETE, CONNECT, PROPFIND, PROPPATCH, COPY, MOVE, LOCK, UNLOCK, MKCOL, REPORT, OPTIONS, MKACTIVITY, CHECKOUT, MERGE \item Recording of DEPTH, IF, TIMEOUT OVERWRITE, DESTINATION, URL and LOCK-TOKEN Headers. \end{itemize} \subsection{Jabber/XMPP related features} \begin{itemize} \item Authentication (plain-text, digest and sip-digest) \item presence and register messages \item Chat messages to online or offline users \item MUC: join room, send message in room, change nickname \item Roster set and get requests \item Global users\verb|'| synchronization can be set on specific actions \item raw XML messages \item PubSub \item Multiple vhost instances supported \item privacy lists: get all privacy list names, set list as active \end{itemize} \subsection{PostgreSQL related features} \begin{itemize} \item Basic and MD5 Authentication \item Simple Protocol \item Extended Protocol (new in version \strong{1.4.0} ) \item Proxy mode to record sessions \end{itemize} \subsection{MySQL related features} This plugin is experimental. It works only with MySQL version 4.1 and higher. \begin{itemize} \item Secured Authentication method only (MySQL >= 4.1) \item Basic Queries \end{itemize} \subsection{LDAP related features} \begin{itemize} \item bind \item add, modify and search queries \item starttls \end{itemize} \subsection{Complete reports set} Measures and statistics produced by Tsung are extremely feature-full. They are all represented as a graphic. \program{Tsung} produces statistics regarding: \begin{itemize} \item \emph{Performance}: response time, connection time, decomposition of the user scenario based on request grouping instruction (called \textit{transactions}), requests per second \item \emph{Errors}: Statistics on page return code to trace errors \item \emph{Target server behaviour}: An Erlang agent can gather information from the target server(s). Tsung produces graphs for CPU and memory consumption and network traffic. SNMP and munin is also supported to monitor remote servers. \end{itemize} \par Note that \program{Tsung} takes care of the synchronization process by itself. Gathered statistics are «synchronized». It is possible to generate graphs during the benchmark as statistics are gathered in real-time. \subsection{Highlights} \program{Tsung} has several advantages over other injection tools: \begin{itemize} \item \emph{High performance} and \emph{distributed benchmark}: You can use Tsung to simulate tens of thousands of virtual users. \item \emph{Ease of use}: The hard work is already done for all supported protocol. No need to write complex scripts. Dynamic scenarios only requires small trivial piece of code. \item \emph{Multi-protocol support}: \program{Tsung} is for example one of the only tool to benchmark SOAP applications \item \emph{Monitoring} of the target server(s) to analyze the behaviour and find bottlenecks. For example, it has been used to analyze cluster symmetry (is the load properly balanced ?) and to determine the best combination of machines on the three cluster tiers (Web engine, EJB engine and database) \end{itemize} \section{Installation} This package has been tested on Linux, FreeBSD and Solaris. A port is available on MacOS X. It should work on Erlang supported platforms (Linux, Solaris, *BSD, Win32 and MacOS-X). \subsection{Dependencies} \begin{itemize} \item Erlang/OTP R12B-5 and up (\url{http://www.erlang.org/download.html}). Erlang is now part of fedora and debian/ubuntu repositories. \item pgsql module made by Christian Sunesson (for the PostgreSQL plugin): sources available at \url{http://jungerl.sourceforge.net/} . The module is included in the source and binary distribution of \program{Tsung}. It is released under the EPL License. \item mysql module made by Magnus Ahltorp \& Fredrik Thulin (for the mysql plugin): sources available at \url{http://www.stacken.kth.se/projekt/yxa/} . The modified module is included in the source and binary distribution of \program{Tsung}. It is released under the three-clause BSD License. \item eldap module (for the LDAP plugin): sources available at \url{http://jungerl.sourceforge.net/} . The module is included in the source and binary distribution of \program{Tsung}. It is released under the GPL License. \item mochiweb libs (for xpath parsing, optionally used for dynamic variables in the HTTP plugin): sources available at \url{http://code.google.com/p/mochiweb/} . The module is included in the source and binary distribution of \program{Tsung}. It is released under the MIT License. \item gnuplot and perl5 (optional; for graphical output with \command{tsung\_stats.pl} script). The Template Toolkit is used for HTML reports (see \url{http://template-toolkit.org/}) \item python and mathplotlib (optional; for graphical output with \command{tsung-plotter}). \item for distributed tests, you need an ssh access to remote machines without password (use a RSA/DSA key without pass-phrase or ssh-agent) (rsh is also supported) \item bash \end{itemize} \subsection{Compilation} \begin{Verbatim} ./configure make make install \end{Verbatim} If you want to download the development version, use git: \command{git clone git://git.process-one.net/tsung/mainline.git} (see also \url{https://git.process-one.net/tsung} and the github mirror: \url{https://github.com/processone/tsung}). You can also build packages with \command{make deb} (on debian and ubuntu) and \command{make rpm} (on fedora, rhel, and other rpm based distribution) \subsection{Configuration} The default configuration file is \file{~/.tsung/tsung.xml} ( there are several sample files in \file{/usr/share/doc/tsung/examples}). Log files are saved in \file{~/.tsung/log/} . A new sub-directory is created for each test using the current date as name (\file{~/.tsung/log/20040217-0940} for ex.) \subsection{Running} Two commands are installed in the directory \varname{\$PREFIX/bin}: \command{tsung} and \command{tsung-recorder}. A man page is available for both commands. \begin{Verbatim} >tsung -h Usage: tsung start|stop|debug|status Options: -f set configuration file (default is ~/.tsung/tsung.xml) (use - for standard input) -l set log directory (default is ~/.tsung/log/YYYYMMDD-HHMM/) -i set controller id (default is empty) -r set remote connector (default is ssh) -s enable erlang smp on client nodes -m write monitoring output on this file (default is tsung.log) (use - for standard output) -F use long names (FQDN) for erlang nodes -w warm-up delay (default is 10 sec) -v print version information and exit -6 use IPv6 for tsung internal communications -h display this help and exit \end{Verbatim} A typical way of using tsung is to run: \command{tsung -f myconfigfile.xml start}. The command will print the current log directory created for the test, and wait until the test is over. \subsection{Feedback} Use the Tsung mailing list (see \url{https://lists.process-one.net/mailman/listinfo/tsung-users}) if you have suggestions or questions about \program{Tsung}. You can also use the bug-tracker available at \url{https://support.process-one.net/browse/TSUN}. You can also try the \#tsung IRC channel on Freenode. \section{Benchmark approach} \subsection{HTTP/WebDAV benchmark approach} \subsubsection{Benchmarking a Web server} \begin{enumerate} \item Record one or more sessions: start the recorder with: \command{tsung-recorder start}, and then configure your browser to use Tsung proxy recorder (the listen port is 8090). A session file will be created. For HTTPS recording, use \userinput{http://-} instead of \userinput{https://} in your browser. \item Edit / organize scenario, by adding recorded sessions in the configuration file. \item Write small code for dynamic parts if needed and place dynamic mark-up in the scenario. \item Test and adjust scenario to have a nice progression of the load. This is highly dependent of the application and of the size of the target server(s). Calculate the normal duration of the scenario and use the interarrival time between users and the duration of the phase to estimate the number of simultaneous users for each given phase. \item Launch benchmark with your first application parameters set-up: \command{tsung start} (run \command{man tsung} for more options) \item Wait for the end of the test or stop by hand with \command{tsung stop} (reports can also be generated during the test (see § \ref{sec:statistics-reports}) : the statistics are updated every 10 seconds). For a brief summary of the current activity, use \command{tsung status} \item Analyze results, change parameters and relaunch another benchmark \end{enumerate} \subsubsection{WEBDAV } It's the same approach as HTTP: first you start to record one or more sessions with the recorder: \command{tsung-recorder -p webdav start} \subsubsection{Benchmarking a proxy server} By default, the HTTP plugin is used to benchmark HTTP servers. But you can also benchmark HTTP Proxy servers. To do that, you must add in the \varname{options} section: \begin{Verbatim} \end{Verbatim} \subsection{LDAP benchmark approach} An LDAP plugin for the recorder is not yet implemented, so you have to write the session by yourself; see section \ref{sec:session:ldap} for more information. \subsection{PostgreSQL benchmark approach} It's the same approach as HTTP: first you start to record one or more sessions with the recorder: \command{tsung-recorder -p pgsql start} This will start a proxy listening to port 8090 and will proxy requests to 127.0.0.0:5432. To choose another port and/or address: \command{tsung-recorder -L 5432 -I 10.6.1.1 -P 5433 -p pgsql start} This will start a proxy listening to port 5432 and will proxy requests to 10.6.1.1:5433. \subsection{MySQL benchmark approach} A MySQL plugin for the recorder is not yet implemented, so you have to write the session by yourself; see section \ref{sec:session:mysql} for more information. \subsection{Jabber/XMPP benchmark approach} \subsubsection{Overview} This paragraph explains how to write a session for Jabber/XMPP. There are two differences between HTTP and Jabber testing: \begin{enumerate} \item There is no recorder for Jabber, so you have to write your sessions by hand (an example is provided in \ref{sec:sessions:jabber}). \item the jabber plugin does not parse XML; instead it uses packet acknowledgments. \end{enumerate} \subsubsection{Acknowledgments of messages} Since the jabber plugin does not parse XML (historically, it was for performance reasons), you must have a way to tell when a request is finished. There are 3 possibilities: \begin{description} \item[ack=local] as soon as a packet is received from the server, the request is considered as completed. Hence if you use a local ack with a request that do not require a response from the server (presence for ex.), it will wait forever (or until a timeout is reached). \item[ack=no\_ack] as soon as the request is send, it is considered as completed (do not wait for incoming data) \item[ack=global] synchronized users. its main use is for waiting for all users to connect before sending messages. To do that, set a request with global ack (it can be the first presence msg: \begin{Verbatim} \end{Verbatim} You also have to specify the number of users to be connected: \begin{Verbatim} \end{Verbatim} To be sure that exactly \varname{global\_number} users are started, add the \userinput{'maxnumber'} attribute to \varname{'users'} \begin{Verbatim} \end{Verbatim} If you do not specify \varname{maxnumber}, the global ack will be reset every \varname{global\_number} users \end{description} \label{bidi:presence} \strong{New in 1.2.2:} This version adds an new option for a session. if you set the attribute \varname{bidi} (for bidirectional) in the \varname{session} tag: \userinput{}, then incoming messages from the server will be analyzed. Currently, only roster subscription requests are handled: if a user received a subscription request (\userinput{}), it will respond with a \userinput{} message. \subsubsection{Status: Offline, Connected and Online} You can send messages to offline or online users. A user is considered online when he has send a \userinput{presence:initial} message (before this message , the state of the user is \varname{connected}). If you want to switch back to \varname{connected} before going \varname{offline}, you can use a \userinput{presence:final} message: \userinput{presence:final} does two things: \begin{enumerate} \item It removes the client from the list of Online users, and moves them into the list of Connected users. \item It sends a broadcast presence update of type='unavailable'. \end{enumerate} \userinput{presence:final} is optional. \emph{warn:} this is new in \strong{1.2.0}, in earlier version, only 2 status were available: online and offline; a user was considered online as soon as it was connected. \subsubsection{Authentication} Below are configuration examples for the possible authentication methods. Note: the regular expressions used here are only examples - they may need to be altered depending on how a particular server implementation composes messages (see also ~\ref{sec:jabber-options} for password settings). \begin{itemize} \item \strong{plain authentication} - sends clear-text passwords: \begin{Verbatim} ... \end{Verbatim} \item \strong{digest authentication} as described in XMPP JEP-0078: Non-SASL Authentication \url{http://www.jabber.org/jeps/jep-0078.html} \begin{Verbatim} ... \end{Verbatim} \item \strong{sip-digest authentication} \begin{Verbatim} ... \end{Verbatim} \end{itemize} \subsubsection{Privacy list testing} There are two actions available to allow for rudimentary privacy lists load testing: \begin{itemize} \item \userinput{privacy:get\_names} - gets the list of all names of privacy lists stored by the server for a given user \item \userinput{privacy:set\_active} - sets a list with a predefined name as active. The list name is determined from the JID, e.g. if the user's JID is "john@average.com" then the list name is "john@average.com\_list". One should take care of properly seeding the server database in order to ensure that such a list exists. \end{itemize} \section{Using the proxy recorder} The recorder has three plugins: for HTTP, WebDAV and for PostgreSQL. To start it, run \command{tsung-recorder -p start}, where \varname{PLUGIN} can be \userinput{http}, \userinput{webdav} or \userinput{pgsql} for PostgreSQL. The default plugin is \userinput{http}. The proxy is listening to port 8090. You can change the port with \userinput{-L portnumber}. To stop it, use \command{tsung-recorder stop}. The recorded session is created as \file{~/.tsung/tsung_recorderYYYMMDD-HH:MM.xml}; if it doesn't work, take a look at \file{~/.tsung/log/tsung.log-tsung_recorder@hostname} During the recording, you can add custom tag in the XML file, this can be useful to set transactions or comments: \command{tsung-recorder record\_tag "''} Once a session has been created, you can insert it in your main configuration file, either by editing by hand the file, or by using an ENTITY declaration, like: \begin{Verbatim} ]> ... &mysession1; \end{Verbatim} \subsection{PostgreSQL} For PostgreSQL, the proxy will connect to the server at IP 127.0.0.1 and port 5432. Use \userinput{-I serverIP} to change the IP and \userinput{-P portnumber} to change the port. \subsection{HTTP and WEBDAV} For HTTPS recording, use \userinput{http://-} instead of \userinput{https://} in your browser \strong{New in 1.2.2}: For HTTP, you can configure the recorder to use a parent proxy (but this will not work for https). Add the -u option to enable parent proxy, and use \userinput{-I serverIP} to set the IP and \userinput{-P portnumber} to set the port of the parent. \section{Understanding tsung.xml configuration file} The default encoding is utf-8. You can use a different encoding, like in: \begin{Verbatim} \end{Verbatim} \subsection{File structure} Scenarios are enclosed into Tsung tags: \begin{Verbatim} ... \end{Verbatim} If you add the attribute \userinput{dumptraffic="true"}, all the traffic will be logged to a file. \emph{Warn:} this will considerably slow down Tsung, so use with care. It is useful for debugging purpose. You can use the attribute \userinput{dumptraffic="light"} to dump only the first 44 bytes. Since version \strong{1.4.0}, you have also a specific logging per protocol, using \userinput{dumptraffic="protocol"}. It's currently only implemented for HTTP: this will log all requests in a CSV file, with the following data: \begin{Verbatim} #date;pid;id;http method;host;URL;HTTP status;size;match;error \end{Verbatim} The \varname{loglevel} can also have a great impact on performance: For high load, \userinput{warning} is recommended. Possible values are: \begin{itemize} \item emergency \item critical \item error \item warning \item notice (default) \item info \item debug \end{itemize} For REALLY verbose logging, recompile tsung with \command{make debug} and set \varname{loglevel} to \userinput{debug}. \subsection{Clients and server} Scenarios start with clients (Tsung cluster) and server definitions: \subsubsection{Basic setup} For non distributed load, you can use a basic setup like: \begin{Verbatim} \end{Verbatim} This will start the load on the same host and on the same Erlang virtual machine as the controller. The server is the entry point into the cluster (\strong{New in 1.2.0:} if several servers are defined, a round robin algorithm is used to choose the server). \varname{Type} can be \userinput{tcp}, \userinput{ssl} or \userinput{udp} (for IPv6, use \userinput{tcp6}, \userinput{ssl6} or \userinput{udp6} ; only available in version \strong{1.4.2} and newer) \subsubsection{Advanced setup} The next example is more complex, and use several features for advanced distributed testing: \begin{Verbatim} \end{Verbatim} Several virtual IP can be used to simulate more machines. This is very useful when a load-balancer use the client\verb|'|s IP to distribute the traffic among a cluster of servers. \strong{New in 1.1.1:} IP is no longer mandatory. If not specified, the default IP will be used. \strong{New in 1.4.0:} You can use \userinput{} to scan for all the IP aliases on a given interface (\userinput{eth0} in this example). In this example, a second machine is used in the Tsung cluster, with a higher weight, and 2 cpus. Two Erlang virtual machines will be used to take advantage of the number of CPU. \begin{quote} \strong{Note:} Even if an Erlang VM is now able to handle several CPUs (erlang SMP), benchmarks shows that it's more efficient to use one VM per CPU (with SMP disabled) for tsung clients. Only the controller node is using SMP erlang. Therefore, \varname{cpu} should be equal to the number of cores of your nodes. If you prefer to use erlang SMP, add the \userinput{-s} option when starting tsung (and don't set \varname{cpu} in the config file). \end{quote} By default, the load is distributed uniformly on all CPU (one CPU per client by default). The weight parameter (integer) can be used to take into account the speed of the client machine. For instance, if one real client has a weight of 1 and the other client has a weight of 2, the second one will start twice the number of users as the first (the proportions will be 1/3 and 2/3). In the earlier example where for the second client has 2 CPU and weight=3, the weight is equal to 1.5 for each CPU. The \varname{maxusers} parameter is used to bypass the limit of maximum number of sockets opened by a single process (1024 by default on many OS) and the lack of scalability of the \varname{select} system call. When the number of users is higher than the limit, a new erlang virtual machine will be started to handle new users. The default value of \varname{maxusers} is 800 . Nowadays, with kernel polling enable, you can and should use a very large value for \varname{maxusers} (30000 for example) without performance penalty (but don't forget to raise the limit of the OS with \command{ulimit -n}, see also FAQ \ref{sec:faq:emfile}). \subsubsection{Running Tsung with a job scheduler} Tsung is able to get its client node list from a batch/job scheduler. It currently handle PBS/torque, LSF and OAR. To do this, set the \varname{type} attribute to \userinput{batch}, e.g.: \begin{Verbatim} \end{Verbatim} If you need to scan IP aliases on nodes given by the batch scheduler, use \varname{scan\_intf} like this: \begin{Verbatim} \end{Verbatim} \subsection{Monitoring} Tsung is able to monitor remote servers using several backends that communicates with remote agent; This is configured in the \varname{} section. Available statistics are: CPU activity, load average, memory usage. Note that you can get the nodes to monitor from a job scheduler, like: \begin{Verbatim} \end{Verbatim} Several types of remote agents are supported (\userinput{erlang} is the default) : \subsubsection{Erlang} The remote agent is started by Tsung. It use erlang communications to retrieve statistics of activity on the server. For example, here is a cluster monitoring definition based on Erlang agents, for a cluster of 6 computers: \begin{Verbatim} \end{Verbatim} Note: monitored computers needs to be accessible through the network, and erlang communications must be allowed (no firewall is better ). SSH (or rsh) needs to be configured to allow connection without password on. \strong{You must use the same version of Erlang/OTP on all nodes otherwise it may not work properly !} If you can't have erlang installed on remote servers, you can use one of the other available agents: \subsubsection{SNMP} The type keyword \userinput{snmp} can replace the erlang keyword, if SNMP monitoring is preferred. They can be mixed. Since version 1.2.2, you can customize the SNMP version, community and port number. It uses the MIB provided in \program{net-snmp} (see also \ref{sec:faq:snmp}). \begin{Verbatim} \end{Verbatim} The default version is \userinput{v1}, default community \userinput{public} and default port \userinput{161}. Since version \strong{1.4.2}, you can also customize the OIDs retrieved from the SNMP server, using one or several \userinput{oid} element: \begin{Verbatim} \end{Verbatim} \varname{type} can be \userinput{sample}, \userinput{counter} or \userinput{sum}, and optionally you can define a function (with erlang syntax) to be applied to the value (\varname{eval} attribute). \subsubsection{Munin} Since version \strong{1.3.1}, Tsung is able to retrieve data from a munin-node agent (see \url{http://munin.projects.linpro.no/wiki/munin-node}). The \varname{type} keyword must be set to \userinput{munin}, for example: \begin{Verbatim} \end{Verbatim} \subsection{Defining the load progression} \subsubsection{Randomly generated users} The load progression is set-up by defining several arrival phases: \begin{Verbatim} \end{Verbatim} With this setup, during the first 10 minutes of the test, a new user will be created every 2 seconds, then during the next 10 minutes, a new user will be created every second, and for the last 10 minutes, 10 users will be generated every second. The test will finish when all users have ended their session. You can also use \varname{arrivalrate} instead of \varname{interarrival}. For example, if you want 10 new users per second, use: \begin{Verbatim} \end{Verbatim} You can limit the number of users started for each phase by using the \varname{maxnumber} attribute, just like this: \begin{Verbatim} \end{Verbatim} In this case, only 100 users will be created in the first phases, and 200 more during the second phase. The complete sequence can be executed several times using the \varname{loop} attribute in the \varname{load} tag (\userinput{loop='2'} means the sequence will be looped twice, so the complete load will be executed 3 times) (feature available since version 1.2.2). The load generated in terms of HTTP requests / seconds will also depend on the mean number of requests within a session (if you have a mean value of 100 requests per session and 10 new users per seconds, the theoretical average throughput will be 1000 requests/ sec). \subsubsection{Statically generated users} If you want to start a given session (see ~\ref{sec:sessions}) at a given time during the test, it is possible since version \strong{1.3.1}: \begin{Verbatim} \end{Verbatim} In this example, we have two sessions, one has a "0" probability (and therefore will not be used in the first phase), and the other 100\%. We define 3 users starting respectively 3mn and 5 seconds after the beginning of the test (using the \userinput{http-example} session), one starting after 10 minutes, and a last one starting after 11 minutes (using the \userinput{foo} session this time) \subsubsection{Duration of the load test} By default, tsung will end when all started users have finished their session. So it can be much longer than the duration of arrivalphases. If you want to stop Tsung after a given duration (even if phases are not finished or if some sessions are still actives), you can do this with the \varname{duration} attribute in \varname{load} (\strong{feature added in 1.3.2}): \begin{Verbatim} \end{Verbatim} Currently, the maximum value for duration is a little bit less than 50 days. \varname{unit} can be \userinput{second}, \userinput{minute} or \userinput{hour}. \subsection{Setting options} \label{sec:options} \paragraph{Thinktimes, SSL, Buffers} \par Default values can be set-up globally: \varname{thinktime} between requests in the scenario, SSL cipher algorithms, TCP/UDP buffer sizes (the default value is 32KB). These values overrides those set in session configuration tags if override is true. \begin{Verbatim} \end{Verbatim} \paragraph{Hibernate} A new option is available in version \strong{1.3.1}: \varname{hibernate}. This is used to reduced memory consumption of simulated users during thinktimes. By default, hibernation will be activated for thinktimes higher than 10sec. This value can be changed like this: \begin{Verbatim} \end{Verbatim} To disable hibernation, you must set the value to \userinput{infinity}. \paragraph{Rate\_limit} A new option is available in version \strong{1.4.0}: \varname{rate\_limit}. This will limit the bandwidth of each client (using a token bucket algorithm). The value is in KBytes per second. You can also specify a maximum burst value (eg. \userinput{max='2048'}). By default the burst size is the same as the rate (1024KB in the following example). Currently, only incoming traffic is rate limited. \begin{Verbatim} \end{Verbatim} \paragraph{Ports\_range} If you need to open more than 30000 simultaneous connections on a client machine, you will be limited by the number of TCP client ports, even if you use several IPs (this is true at least on Linux). To bypass this limit, Tsung must not delegate the selection of client ports and together with using several IP for each client, you have to defined a range for available clients ports, for ex: \begin{Verbatim} \end{Verbatim} \subsection{Sessions} \label{sec:sessions} Sessions define the content of the scenario itself. They describe the requests to execute. Each session has a given probability. This is used to decide which session a new user will execute. The sum of all session\verb|'|s probabilities must be 100. A transaction is just a way to have customized statistics. Say if you want to know the response time of the login page of your website, you just have to put all the requests of this page (HTML + embedded pictures) within a transaction. In the example above, the transaction called \varname{index\_request} will gives you in the statistics/reports the mean response time to get \userinput{index.en.html + header.gif}. Be warn that If you have a thinktime inside the transaction, the thinktime will be part of the response time. \subsubsection{Thinktimes} You can set static or random thinktimes to separate requests. By default, a random thinktime will be a exponential distribution with mean equals to \varname{value}. \begin{Verbatim} \end{Verbatim} In this case, the thinktime will be an exponential distribution with a mean equals to 20 seconds. \strong{Since version 1.3.0}, you can also use a range \userinput{[min:max]} instead of a mean for random thinktimes (the distribution will be uniform in the interval): \begin{Verbatim} \end{Verbatim} \strong{Since version 1.4.0}, you can use a dynamic variable to set the thinktime value: \begin{Verbatim} \end{Verbatim} \subsubsection{HTTP} This example shows several features of the HTTP protocol support in Tsung: GET and POST request, basic authentication, transaction for statistics definition, conditional request (IF MODIFIED SINCE), ... \begin{Verbatim} ... \end{Verbatim} If you use an absolute URL, the server used in the URL will override the one specified in the \varname{} section. The following relative requests in the session will also use this new server value (until a new absolute URL is set). \strong{New in 1.2.2:} You can add any HTTP header now, as in: \begin{Verbatim} \end{Verbatim} \strong{New in 1.3.0:} You can also read the content of a POST or PUT request from an external file: \begin{Verbatim} \end{Verbatim} Since \strong{1.3.1}, you can also manually set a cookie, though the cookie is not persistent: you must add it in every \varname{}: \begin{Verbatim} \end{Verbatim} \subsubsection{Jabber/XMPP} \label{sec:sessions:jabber} \par Here is an example of a session definition for the Jabber/XMPP protocol: \begin{Verbatim} \end{Verbatim} \paragraph{Roster} What you can do with rosters using Tsung: You can \begin{enumerate} \item Add a new contact to their roster - The new contact is added to the \userinput{Tsung Group} group, and their name matches their JID \item Send a \userinput{subscribe} presence notification to the new contact's JID - This results in a \emph{pending} subscription \item Rename a roster contact This changes the previously added contact's name from the default JID, to \userinput{Tsung Testuser} \item Delete the previously added contact. \end{enumerate} Note that when you add a new contact, the contact JID is stored and used for the operations that follow. It is recommended that for each session which is configured to perform these operations, only do so once. In other words, you would NOT want to ADD more than one new contact per session. If you want to alter the rate that these roster functions are used during your test, it is best to use the session 'probability' factor to shape this. The nice thing about this is that when you test run is complete, your roster tables should look the same as before you started the test. So, if you set it up properly, you can have pre-loaded roster entries before the test, and then use these methods to dynamically add, modify, and remove roster entries during the test as well. Example roster modification setup: \begin{Verbatim} \end{Verbatim} See also \ref{bidi:presence} for automatic handling of subscribing requests. \paragraph{SASL Anonymous} SASL Anonymous authentication example: \begin{Verbatim} \end{Verbatim} \paragraph{Presence} \begin{itemize} \item \varname{type} can be either \userinput{presence:broadcast} or \userinput{presence:directed}. \item \varname{show} value must be either \userinput{away}, \userinput{chat}, \userinput{dnd}, or \userinput{xa}. \item \varname{status} value can be any text. \end{itemize} For more info, see section 2.2 of RFC 3921. If you omit the \varname{show} or \varname{status} attributes, they default to \userinput{chat} and \userinput{Available} respectively. Example of broadcast presence (broadcast to members of your roster): \begin{Verbatim} \end{Verbatim} Example of directed presence (sent to random \userinput{online} users): \begin{Verbatim} \end{Verbatim} \paragraph{MUC} Tsung supports three MUC operations: \begin{enumerate} \item Join a room (attribute \userinput{type='muc:join'}) \item Send a message to a room (attribute \userinput{type='muc:chat'}) \item Change nickname (attribute \userinput{type='muc:nick'}) \item Exit a room (attribute \userinput{type='muc:exit}) \end{enumerate} Here's an example: \begin{Verbatim} <-- First, choose an random room and random nickname: --> \end{Verbatim} MUC support is available since version 1.3.1 \paragraph{PubSub} Experimental support for PubSub is available in version 1.3.1 You can read the following entry: \url{https://support.process-one.net/browse/TSUN-115} \paragraph{VHost} VHost support is available since version 1.3.2 Tsung is able to bench multiple vhost instances by choosing a vhost XMPP name from a list at connection time in the scenario. The vhost list is read from a file: \begin{Verbatim} ... ... ... \end{Verbatim} When each client starts a session, it chooses randomly a domain (each domain has the same probability). \paragraph{Reading usernames and password from a CSV file} Since version 1.4.0, you can now use a CSV file to store the usernames and password. Configure the CSV file: \begin{Verbatim} \end{Verbatim} And then you have to defined two variables of type \userinput{file}, and the first jabber request (\userinput{connect}) must include a \varname{xmpp\_authenticate} tag: \begin{Verbatim} ... \end{Verbatim} \paragraph{raw XML} You can send raw XML data to the server using the \varname{raw} type: \begin{Verbatim} \end{Verbatim} Beware: you must encode XML characters like \userinput{<} ,\userinput{>}, \userinput{\&}, etc. \subsubsection{PostgreSQL} For PostgreSQL, 4 types of requests are available: \begin{enumerate} \item connect (to a given database with a given username \item authenticate (with password or not) \item sql (basic protocol) \item close \end{enumerate} In addition, the following parts of the extended protocol is supported: \begin{enumerate} \item copy, copydone and copyfail \item parse, bind, execute, describe \item sync, flush \end{enumerate} This example shows most of the features of a PostgreSQL session: \begin{Verbatim} SELECT * from accounts; SELECT * from users; \end{Verbatim} Example with the extended protocol: \begin{Verbatim} \end{Verbatim} % $ \subsubsection{MySQL} \label{sec:session:mysql} For MySQL, 4 types of requests are available (same as PostgreSQL): \begin{enumerate} \item connect (to a given database with a given username \item authenticate (with password or not) \item sql \item close \end{enumerate} This example shows most of the features of a MySQL session: \begin{Verbatim} SHOW TABLES SELECT * FROM mytable \end{Verbatim} \subsubsection{LDAP} \label{sec:session:ldap} \paragraph{Authentication} The recommended mechanism used to authenticate users against a LDAP repository requires two steps to follow. Given an username and password, we: \begin{enumerate} \item Search the user in the repository tree, using the username (so users can reside in different subtrees of the organization) \item Try to bind as the user, with the distinguished name found in the first step and the user's password \end{enumerate} If the bind is successful, the user is authenticated (this is the scheme used, among others, by the LDAP authentication module for Apache \url{http://httpd.apache.org/docs/2.0/mod/mod_auth_ldap.html}) \paragraph{LDAP Setup} For this example we are going to use a simple repository with the following hierarchy: \begin{figure}[htb] \begin{center} \includegraphics[width=0.4\linewidth]{ldap-hierarchy} \end{center} \caption{LDAP Hierarchy} \label{fig:ldap:hierarchy} \end{figure} the repository has users in two organizational units \begin{enumerate} \item users (with four members) \item users2 (with tree members) \end{enumerate} For simplicity we set the password of each user to be the same as its common name (cn). Tsung Setup We will use a CSV file as input, containing the user:password pairs for our test. So we start by writing it, in this case we name the file \file{users.csv} \begin{Verbatim} user1;user1 user2;user2 user3;user3 user4;user4 jane;jane mary;mary paul;pablo paul;paul \end{Verbatim} (the pair paul:pablo should fail to authenticate, we will note that in the Tsung report) Then, in our Tsung scenario, we let Tsung know about this file \begin{Verbatim} We use two dynamic variables to hold the username and password \end{Verbatim} To start the authentication process we instruct Tsung to perform a search, to find the distinguished name of the user we are trying to authenticate \begin{Verbatim} \end{Verbatim} As we need to access the search result, we specify it using the \varname{result\_var} attribute. This attribute tells Tsung in which dynamic variable we want to store the result (if the \varname{result\_var} attribute isn't set, Tsung doesn't store the search result in any place). Finally, we try to bind as that user. \begin{Verbatim} \end{Verbatim} The only thing that remains to do is to implement the \varname{ldap\_auth:user\_dn} function, that extract the distinguished name from the search result. \begin{Verbatim} -module(ldap_auth). -export([user_dn/1]). user_dn({_Pid,DynVars}) -> [SearchResultEntry] = proplists:get_value(search_result,DynVars), {_,DN,_} = SearchResultEntry, DN. \end{Verbatim} We aren't covering errors here. supposing that there is always one (and only one) user found, that we extract from the \varname{search\_result} variable (as defined in the previous search operation). Each entry in the result set is a SearchResultEntry record. The record definition can be found in \file{/include/ELDAPv3.hrl}. As we only need to access the distinguished name of the object, we index into the result tuple directly. But if you need to access other attributes you probably will want to include the appropriate .hrl and use the record syntax instead. One of the eight user:password pairs in our users file was wrong, so we expect 1/8 of the authentication attempts to fail. Indeed, after running the scenario we can confirm this in the Tsung report (see figure \ref{fig:ldap:results}). The bind operation maintains two counters: \varname{ldap\_bind\_ok} and \varname{ldap\_bind\_error}, that counts successful and unsuccessful bind attempts. \begin{figure}[htb] \begin{center} \includegraphics[width=0.4\linewidth]{ldap-results} \end{center} \caption{LDAP Results} \label{fig:ldap:results} \end{figure} \paragraph{Other examples} \begin{Verbatim} organizationalPerson inetOrgPerson person %%_new_user_cn%% fffs SomeSN some@mail.com \end{Verbatim} \subsubsection{Mixing session type} Since version \strong{1.3.2}, a new tag \varname{change\_type} can be used in a session to change it's type. \begin{Verbatim} \end{Verbatim} \userinput{store='true'} can be used to save the current state of the session (socket, cookies for http, \ldots{}) and \userinput{restore='true'} to reuse the previous state when you switch back to the old protocol. A dynamic variable set in the first part of the session will be available after a \varname{change\_type}. There is currently one caveat: you have to use a full URL in the first http request after a \varname{} (a relative URL will fail). \subsection{Advanced features} \subsubsection{Dynamic substitutions} Dynamic substitution are mark-up placed in element of the scenario. For HTTP, this mark-up can be placed in basic authentication (www\_authenticate tag: userid and passwd attributes), URL (to change GET parameter) and POST content. Those mark-up are of the form \userinput{\%\%Module:Function\%\%}. Substitutions are executed on a request-by-request basis, only if the request tag has the attribute \userinput{subst="true"}. When a substitution is requested, the substitution mark-up is replaced by the result of the call to the Erlang function: \userinput{Module:Function(\{Pid, DynData\})} where Pid is the Erlang process id of the current virtual user and DynData the list of all Dynamic variables (\strong{Warn: before version 1.1.0, the argument was just the Pid !}). Here is an example of use of substitution in a Tsung scenario: \begin{Verbatim} \end{Verbatim} Here is the Erlang code of the module used for dynamic substitution: \begin{Verbatim} -module(symbol). -export([new/1]). new({Pid, DynData}) -> case random:uniform(3) of 1 -> "IBM"; 2 -> "MSFT"; 3 -> "RHAT" end. \end{Verbatim} (use \command{erlc} to compiled the code, and put the resulting .beam file in \file{\$PREFIX/lib/erlang/lib/tsung-X.X.X/ebin/} on all client machines) As you can see, writing scenario with dynamic substitution is simple. It can be even simpler using dynamic variables (see later). If you want to set unique id, you can use the built-in function \varname{ts\_user\_server:get\_unique\_id}. \begin{Verbatim} \end{Verbatim} \subsubsection{Reading external file} \strong{New in 1.0.3}: A new module \varname{ts\_file\_server} is available. You can use it to read external files. For example, if you need to read user names and passwd from a CSV file, you can do it with it (currently, you can read only a single file). You have to add this in the XML configuration file: \begin{Verbatim} \end{Verbatim} \strong{New in 1.2.2}: You can read several files, using the \varname{id} attribute to identify each file: \begin{Verbatim} \end{Verbatim} Now you can build you own function to use it, for example, create a file called \file{readcsv.erl}: \begin{Verbatim} -module(readcsv). -export([user/1]). user({Pid,DynVar})-> {ok,Line} = ts_file_server:get_next_line(), [Username, Passwd] = string:tokens(Line,";"), "username=" ++ Username ++"&password=" ++ Passwd. \end{Verbatim} The output of the function will be a string \userinput{username=USER\&password=PASSWORD} Then compile it with \command{erlc readcsv.erl} and put \file{readcsv.beam} in \file{\$prefix/lib/erlang/lib/tsung-VERSION/ebin} directory (if the file has an id set to \userinput{random}, change the call to: \\ \texttt{ts\_file\_server:get\_next\_line(random)}). Then use something like this in your session: \begin{Verbatim} \end{Verbatim} Two functions are available: \varname{ts\_file\_server:get\_next\_line} and \varname{ts\_file\_server:get\_random\_line}. For the \varname{get\_next\_line} function, when the end of file is reached, the first line of the file will be the next line. \strong{New in 1.3.0}: you no longer have to create an external function to parse a simple csv file: you can use \varname{setdynvars} (see next section for detailed documentation): \begin{Verbatim} \end{Verbatim} This defines two dynamic variables \varname{username} and \varname{user\_password} filled with the next entry from the csv file. Using the previous example, the request is now: \begin{Verbatim} \end{Verbatim} Much simpler than the old method ! \subsubsection{Dynamic variables}\label{Dynamic variables} In some cases, you may want to use a value given by the server in a response later in the session, and this value is \strong{dynamically generated} by the server for each user. For this, you can use \userinput{} in the scenario Let's take an example with HTTP. You can easily grab a value in a HTML form like: \begin{Verbatim}
\end{Verbatim} with: \begin{Verbatim} \end{Verbatim} Now \varname{random\_num} will be set to 42 during the user's session. It's value will be replace in all mark-up of the form \userinput{\%\%\_random\_num\%\%} if and only if the \varname{request} tag has the attribute \userinput{subst="true"}, like: \begin{Verbatim} \end{Verbatim} \paragraph{Regexp} If the dynamic value is not a form variable, you can set a regexp by hand, for example to get the title of a HTML page: the regexp engine uses the \varname{re} module, a Perl like regular expressions module for Erlang. \begin{Verbatim} \end{Verbatim} Previously (before 1.4.0), Tsung uses the old \varname{regexp} module from erlang. This is now deprecated. The syntax was: \begin{Verbatim} \end{Verbatim} \paragraph{XPath} A new way to analyze the server response has been introduced in the release \strong{1.3.0}. It is available only for the HTTP and XMPP plugin since it is based on XML/HTML parsing. This feature uses the mochiweb library and \strong{only works with erlang R12B and newer version}. This give us some benefices: \begin{itemize} \item XPath is simple to write and to read, and match very well with HTML/XML pages \item The parser works on binaries(), and doesn't create any string(). \item The cost of parsing the HTML/XML and build the tree is amortized between all the dyn\_variables defined for a given request \end{itemize} To utilize xpath expression, use a \varname{xpath} attribute when defining the dyn\_variable, instead of \varname{re}, like: \begin{Verbatim} \end{Verbatim} There is a bug in the xpath engine, result nodes from "descendant-or-self" aren't returned in document order. This isn't a problem for the most common cases. However, queries like \userinput{//img[1]/@src} are not recommended, as the order of the \userinput{} elements returned from //img is not the expected. The order is respected for paths without "descendant-or-self" axis, so this: \userinput{/html/body/div[2]/img[3]/@src} is interpreted as expected and can be safely used. It is possible to use Xpath to get a list of elements from an html page, allowing dynamic retrieval of objects. You can either create embedded erlang code to parse the list produced, or use foreach that was introduced in release \strong(1.4.0). For XMPP, you can get all the contacts in a dynamic variable: \begin{Verbatim} \end{Verbatim} \paragraph{JSONPath} \label{sec:jsonpath} Another way to analyze the server response has been introduced in the release \strong{1.3.2} when the server is sending JSON data. It is only for the HTTP plugin. This feature uses the mochiweb library and \strong{only works with erlang R13B and newer version}. Tsung implements a (very) limited subset of JSONPath as defined here \url{http://goessner.net/articles/JsonPath/} To utilize jsonpath expression, use a \varname{jsonpath} attribute when defining the dyn\_variable, instead of \varname{re}, like: \begin{Verbatim} \end{Verbatim} You can also use expressions \userinput{Key=Val}, e.g.: \begin{Verbatim} \end{Verbatim} \paragraph{PostgreSQL} \strong{New in 1.3.2!} Since the PostgreSQL protocol is binary, regexp are not useful to parse the output of the server. Instead, a specific parsing can be done to extract content from the server's response; to do this, use the \varname{pgsql\_expr} attribute. Use \userinput{data\_row[L][C]} to extract the column C of the line L of the data output. You can also use the literal name of the column (\ie the field name of the table). This example extract 3 dynamic variables from the server's response: First one, extract the 3rd column of the fourth row, then the \varname{mtime} field from the second row, and then it extract some data of the row\_description. \begin{Verbatim} SELECT * from pgbench_history LIMIT 20; \end{Verbatim} A row description looks like this: \begin{Verbatim} =INFO REPORT==== 14-Apr-2010::11:03:22 === ts_pgsql:(7:<0.102.0>) PGSQL: Pair={row_description, [{"tid",text,1,23,4,-1,16395}, {"bid",text,2,23,4,-1,16395}, {"aid",text,3,23,4,-1,16395}, {"delta",text,4,23,4,-1, 16395}, {"mtime",text,5,1114,8,-1, 16395}, {"filler",text,6,1042,-1,26, 16395}]} \end{Verbatim} So in the example, the \varname{row} variable equals "aid". \paragraph{set\_dynvars} \strong{Since version 1.3.0}, more powerful dynamic variables are implemented: You can set dynamic variables not only while parsing server data, but you can build them using external files or generate them with a function or generate random numbers/strings: Six types of dynamic variables are currently implemented (\varname{sourcetype} tag): \begin{enumerate} \item Dynamic variables defined by calling an erlang function: \begin{Verbatim} \end{Verbatim} \item Dynamic variables defined by parsing an external file: \begin{Verbatim} \end{Verbatim} \varname{delimiter} can be any string, and \varname{order} can be \userinput{iter} or \userinput{random} \item A dynamic variable can be a random number (uniform distribution) \begin{Verbatim} \end{Verbatim} \item A dynamic variable can be a random string \begin{Verbatim} \end{Verbatim} \item A dynamic variable can be a urandom string: this is much faster than the random string, but the string is not really random: the same set of characters is always used. \item A dynamic variable can be generated by dynamic evaluation of erlang code: \begin{Verbatim} \end{Verbatim} In this case, we use tsung function \varname{ts\_dynvars:lookup} to retrieve the dynamic variable named \varname{md5data}. This dyn\_variable \varname{md5data} can be set in any of the ways described in the Dynamic variables section \ref{Dynamic variables}. \item A dynamic variable can be generated by applying a JSONPath specification (see \ref{sec:jsonpath}) to an existing dynamic variable: \begin{Verbatim} \end{Verbatim} \end{enumerate} A \varname{setdynvars} can be defined anywhere in a session. \subsubsection{Checking the server's response} With the tag \varname{match} in a \varname{request} tag, you can check the server's response against a given string, and do some actions depending on the result. In any case, if it matches, this will increment the \varname{match} counter, if it does not match, the \varname{nomatch} counter will be incremented. For example, let's say you want to test a login page. If the login is ok, the server will respond with \computeroutput{Welcome !} in the HTML body, otherwise not. To check that: \begin{Verbatim} Welcome ! \end{Verbatim} You can use a regexp instead of a simple string. The list of available actions to do is: \begin{itemize} \item continue: do nothing, continue (only update match or nomatch counters) \item log: log the request id, userid, sessionid in a file (in \file{match.log}) \item abort : abort the session \item restart: restart the session. The maximum number of restarts is 3 by default. \item loop: repeat the request, after 5 seconds. The maximum number of loops is 20 by default. \item dump: dump the content of the response in a file. The filename is \file{match----.dump} \end{itemize} You can mixed several match tag in a single request: \begin{Verbatim} Retry Error \end{Verbatim} You can also do the action on "nomatch" instead of "match". If you want to skip the HTTP headers, and match only on the body, you can use \userinput{skip\_headers='http'}. Also, you can apply a function to the content before matching; for example the following example use both features to compute the md5sum on the body of a HTTP response, and compares it to a given value: \begin{Verbatim} 01441debe3d7cc65ba843eee1acff89d \end{Verbatim} You can also use dynamic variables, using the \varname{subst} attribute: \begin{Verbatim} %%_myvar%% \end{Verbatim} \subsubsection{Loops, If, Foreach} \strong{Since 1.3.0}, it's now possible to add conditional/unconditional loops in a session. \strong{Since 1.4.0}, it is possible to loop through a list of dynamic variables thanks to foreach. \paragraph{} Repeat the enclosing actions a fixed number of times. A dynamic variable is used as counter, so the current iteration could be used in requests. List of attributes: \begin{description} \item[from] Initial Value \item[to] Last value \item[incr] Amount to increment in each iteration \item[var] Name of the variable to hold the counter \end{description} \begin{Verbatim} [...] [...] \end{Verbatim} \paragraph{} Repeat the enclosing action (while|until) some condition. This is intended to be used together with \userinput{dyn\_variable} declarations. List of attributes: \begin{description} \item[name] Name of the repeat \item[max\_repeat] Max number of loops (default value is 20) \end{description} The last element of repeat must be either \userinput{} or \userinput{} example: \begin{Verbatim} [...] [...] \end{Verbatim} \strong{Since 1.3.1}, it's also possible to add if statements based on dynamic variables: \paragraph{} \begin{Verbatim} \end{Verbatim} You can use \varname{eq} or \varname{neq} to check the variable. If the dynamic variable is a list (output from xpath for example), you can access to the nth element of a list like this: \begin{Verbatim} \end{Verbatim} (here we compare the first element of the list to 3) \paragraph{} Repeat the enclosing actions for all the elements contained in the list specified. The basic syntax is as follows: \begin{Verbatim} \end{Verbatim} It is possible to limit the list of elements you're looping through, thanks to the use of the \userinput{include} or \userinput{exclude} attributes inside the foreach statement. As an example, if you want to include only elements with a local path you can write: \begin{Verbatim} \end{Verbatim} If you want to exclude all the elements from a specific URI, you would write: \begin{Verbatim} \end{Verbatim} You can combine this with a xpath query. For instance the following scenario will retrieve all the images specified on a web page: \begin{Verbatim} \end{Verbatim} \subsubsection{Rate limiting} Since version \strong{1.4.0}, rate limiting can be enabled, either globally (see \ref{sec:options}), or for each session separately. For example, to limit the rate to 64KB/sec for a given session: \begin{Verbatim} ... \end{Verbatim} Only the incoming traffic is rate limited currently. \section{Statistics and reports} \label{sec:statistics-reports} \subsection{File format} By default, tsung use it's own format (see FAQ \ref{sec:what-format-stats}). Since version 1.4.2, you can configure tsung to use a JSON format; however in this case, the tools \command{tsung\_stats.pl} and \command{tsung\_plotter} will not work with the JSON files. To enable JSON output, use: \begin{Verbatim} \end{Verbatim} Example output file with JSON: \begin{Verbatim} { "stats": [ {"timestamp": 1317413841, "samples": []}, {"timestamp": 1317413851, "samples": [ {"name": "users", "value": 0, "max": 0}, {"name": "users_count", "value": 0, "total": 0}, {"name": "finish_users_count", "value": 0, "total": 0}]}, {"timestamp": 1317413861, "samples": [ {"name": "users", "value": 0, "max": 1}, {"name": "load", "hostname": "requiem", "value": 1, "mean": 0.0,"stdvar": 0,"max": 0.0,"min": 0.0 ,"global_mean": 0 ,"global_count": 0}, {"name": "freemem", "hostname": "requiem", "value": 1, "mean": 2249.32421875,"stdvar": 0,"max": 2249.32421875,"min": 2249.32421875 ,"global_mean": 0 ,"global_count": 0}, {"name": "cpu", "hostname": "requiem", "value": 1, "mean": 4.790419161676647,"stdvar": 0,"max": 4.790419161676647,"min": 4.790419161676647 ,"global_mean": 0 ,"global_count": 0}, {"name": "session", "value": 1, "mean": 387.864990234375,"stdvar": 0,"max": 387.864990234375,"min": 387.864990234375 ,"global_mean": 0 ,"global_count": 0}, {"name": "users_count", "value": 1, "total": 1}, {"name": "finish_users_count", "value": 1, "total": 1}, {"name": "request", "value": 5, "mean": 75.331787109375,"stdvar": 46.689242405019954,"max": 168.708984375,"min": 51.744873046875 ,"global_mean": 0 ,"global_count": 0}, {"name": "page", "value": 1, "mean": 380.7548828125,"stdvar": 0.0,"max": 380.7548828125,"min": 380.7548828125 ,"global_mean": 0 ,"global_count": 0}, {"name": "connect", "value": 1, "mean": 116.70703125,"stdvar": 0.0,"max": 116.70703125,"min": 116.70703125 ,"global_mean": 0 ,"global_count": 0}, {"name": "size_rcv", "value": 703, "total": 703}, {"name": "size_sent", "value": 1083, "total": 1083}, {"name": "connected", "value": 0, "max": 0}, {"name": "http_304", "value": 5, "total": 5}]}]} \end{Verbatim} \subsection{Available stats} \begin{itemize} \item \varname{request} Response time for each request. \item \varname{page} Response time for each set of requests (a page is a group of request not separated by a thinktime). \item \varname{connect} Duration of the connection establishment. \item \varname{reconnect} number of reconnection. \item \varname{size\_rcv} Size of responses in bytes. \item \varname{size\_sent} Size of requests in bytes. \item \varname{session} Duration of a user's session. \item \varname{users} Number of simultaneous users. \item \varname{connected} Number of simultaneous connected users. \strong{new in 1.2.2}. \item custom transactions \end{itemize} The mean response time (for requests, page, etc.) is computed every 10 sec (and reset). That's why you have the highest mean and lowest mean values in the Stats report. \strong{Since version 1.3.0}, the mean for the whole test is also computed. HTTP specific stats: \begin{itemize} \item counter for each response status (200, 404, etc.) \end{itemize} Jabber specific stats: \begin{itemize} \item \varname{request\_noack} Counter of \varname{no\_ack} requests. Since response time is meaningless with \varname{no\_ack} requests, we keep a separate stats for this. \strong{new in 1.2.2}. \item \varname{async\_unknown\_data\_rcv} Only if bidi is true for a session. counter the number of messages received from the server without doing anything. \strong{new in 1.2.2}. \item \varname{async\_data\_sent} Only if bidi is true for a session. Count the number of messages sent to the server in response of a message received from the server. \strong{new in 1.2.2}. \end{itemize} \subsection{Design} A bit of explanation on the design and internals of the statistics engine: Tsung was designed to handle thousands of requests/sec, for very long period of times (several hours) so it do not write all data to the disk (for performance reasons). Instead it computes on the fly an estimation of the mean and standard variation for each type of data, and writes these estimations every 10 seconds to the disk (and then starts a new estimation for the next 10 sec). These computations are done for two kinds of data: \begin{itemize} \item \varname{sample}, for things like response time \item \varname{sample\_counter} when the input is a cumulative one (number of packet sent for ex.). \end{itemize} There are also two other types of useful data (no averaging is done for those) : \begin{itemize} \item \varname{counter}: a simple counter, for HTTP status code for ex. \item \varname{sum} for ex. the cumulative HTTP response's size (it gives an estimated bandwidth usage). \end{itemize} \subsection{Generating the report} cd to the log directory of your test (say \file{~/.tsung/log/20040325-16:33/}) and use the script \command{tsung\_stats.pl}: \begin{Verbatim} /usr/lib/tsung/bin/tsung_stats.pl \end{Verbatim} \strong{You can generate the statistics even when the test is running !} use \userinput{--help} to view all available options: \begin{Verbatim} Available options: [--help] (this help text) [--verbose] (print all messages) [--debug] (print receive without send messages) [--dygraph] use dygraphs (http://danvk.org/dygraphs/) to render graphs [--noplot] (don't make graphics) [--gnuplot ] (path to the gnuplot binary) [--nohtml] (don't create HTML reports) [--logy] (logarithmic scale for Y axis) [--tdir ] (Path to the HTML tsung templates) [--noextra (don't generate graphics from extra data (os monitor, etc) [--rotate-xtics (rotate legend of x axes) [--stats ] (stats file to analyse, default=tsung.log) [--img_format ] (output format for images, default=png available format: ps, svg, png, pdf) \end{Verbatim} Version \strong{1.4.0} adds a new graphical output based on \url{http://danvk.org/dygraphs/}. \subsection{Tsung summary} Figure \ref{fig:report} shows an example of a summary report. \begin{figure}[htb] \begin{center} \includegraphics[width=0.6\linewidth]{tsung-report} \end{center} \caption{Report} \label{fig:report} \end{figure} \subsection{Graphical overview} Figure \ref{fig:graph} shows an example of a graphical report. \begin{figure}[htb] \begin{center} \includegraphics[width=0.6\linewidth]{tsung-graph} \end{center} \caption{Graphical output} \label{fig:graph} \end{figure} \subsection{Tsung Plotter} Tsung-Plotter (\command {tsplot} command) is an optional tool recently added in the Tsung distribution (it is written in Python), useful to compare different tests runned by Tsung. \command{tsplot} is able to plot data from several \file{tsung.log} files onto the same charts, for further comparisons and analyzes. You can easily customize the plots you want to generate by editing simple configuration files. You can get more information in the manual page of the tool (\command{man tsplot}). Example of use: \begin{Verbatim} tsplot "First test" firsttest/tsung.log "Second test" secondtest/tsung.log -d outputdir \end{Verbatim} Here's an example of the charts generated by tsplot (figure \ref{fig:graph:tsplot}): \begin{figure}[htb] \begin{center} \includegraphics[width=0.6\linewidth]{connected} \end{center} \caption{Graphical output of tsplot} \label{fig:graph:tsplot} \end{figure} \subsection{RRD} A contributed perl script \command{tsung-rrd.pl} is able to create rrd files from the tsung log files. It's available in \file{/usr/lib/tsung/bin/tsung-rrd.pl} \section{References} \begin{itemize} \item \program{Tsung} home page: \url{http://tsung.erlang-projects.org/} \item \program{Tsung} description (French)\footnote{\url{http://www.erlang-projects.org/Members/mremond/events/dossier_de_presentat/block_10766817551485/file}} \item Erlang web site \url{http://www.erlang.org/} \item Erlang programmation, Mickaël Rémond, Editions Eyrolles, 2003 \footnote{\url{http://www.editions-eyrolles.com/php.accueil/Ouvrages/ouvrage.php3?ouv_ean13=9782212110791}} \item \emph{Making reliable system in presence of software errors}, Doctoral Thesis, Joe Armstrong, Stockholm, 2003 \footnote{\url{http://www.sics.se/~joe/thesis/armstrong_thesis_2003.pdf}} \item \emph{Tutorial on How to write a Tsung plugin}, written by t ty, \url{http://www.process-one.net/en/wiki/Writing_a_Tsung_plugin/} \end{itemize} \section{Acknowledgments} The first version of this document was based on a talk given by Mickael Rémond\footnote{\email{mickael.remond@erlang-fr.org}} during an Object Web benchmarking workshop in April 2004 (more info at \url{http://jmob.objectweb.org/}). \begin{appendix} \section{Frequently Asked Questions} \subsection{Can't start distributed clients: timeout error } Most of the time, when a crash happened at startup without any traffic generated, the problem arise because the main Erlang controller node cannot create a "slave" Erlang virtual machine. The message looks like: \begin{Verbatim} Can't start newbeam on host 'XXXXX (reason: timeout) ! Aborting! \end{Verbatim} %%%$ The problem is that the Erlang slave module cannot start a remote slave node. You can test this using this simple command on the controller node (remotehost is the name of the client node) \begin{Verbatim} >erl -rsh ssh -sname foo -setcookie mycookie Eshell V5.4.3 (abort with ^G) (foo@myhostname)1>slave:start(remotehost,bar,"-setcookie mycookie"). \end{Verbatim} You should see this: \computeroutput{\{ok,bar@remotehost\}} If you got \computeroutput{\{error,timeout\}}, it can be caused by several problems: \begin{enumerate} \item ssh in not working (you must have a key without passphrase, or use an agent) \item Tsung and Erlang are not installed on all clients nodes \item Erlang version or location (install path) is not the same on all clients nodes \item A firewall is dropping erlang packets: indeed erlang virtual machines use several TCP ports (dynamically generated) to communicate. \item SELinux: You should disable SELinux on all clients. \item Bad \file{/etc/hosts:} This one is wrong (real hostname should not refer to localhost/loopback): \begin{Verbatim} 127.0.0.1 localhost myhostname \end{Verbatim} This one is good: \begin{Verbatim} 127.0.0.1 localhost 192.168.3.2 myhostname \end{Verbatim} \item sshd configuration: For example, for SuSE 9.2 sshd is compiled with restricted set of paths (\ie{} when you shell into the account you get the users shell, when you execute a command via ssh you don't) and this makes it impossible to start an erlang node (if erlang is installed in \file{/usr/local} for example). Run: \begin{Verbatim} ssh myhostname erl \end{Verbatim} If the erlang shell doesn't start then check what paths sshd was compiled with (in SuSE see \file{/etc/ssh/sshd_config}) and symlink from one of the approved paths to the erlang executable (thanks to Gordon Guthrie for reporting this). \item old beam processes (erlang virtual machines) running on client nodes: kill all beam processes before starting tsung. \end{enumerate} Note that you do not need to use the 127.0.0.1 address in the configuration file. It will not work if you use it as the injection interface. The shortname of your client machine should not refer to this address. \emph{Warn:}Tsung launches a new erlang virtual machine to do the actual injection even when you have only one machine in the injection cluster (unless \varname{'use\_controller\_vm'} is set to true). This is because it needs to by-pass some limit with the number of open socket from a single process (1024 most of the time). The idea is to have several system processes (Erl beam) that can handle only a small part of the network connection from the given computer. When the \varname{maxusers} limit (simultaneous) is reach, a new Erlang beam is launched and the newest connection can be handled by the new beam). \strong{New in 1.1.0}: If you don't use the distributed feature of Tsung and have trouble to start a remote beam on a local machine, you can set the \varname{'use\_controller\_vm'} attribute to true, for ex.: \begin{Verbatim} \end{Verbatim} \subsection{Tsung crashes when I start it } Does your Erlang system has ssl support enabled ? to test it: \begin{Verbatim} > erl Eshell V5.2 (abort with ^G) 1> ssl:start(). you should see 'ok' \end{Verbatim} \subsection{Why do i have error\_connect\_emfile errors ?} \label{sec:faq:emfile} emfile error means : \emph{too many open files} This happens usually when you set a high value for \varname{maxusers} (\varname{in the } section) (the default value is 800). The errors means that you are running out of file descriptors; you must check that \varname{maxusers} is less than the maximum number of file descriptors per process in your system (see \command{ulimit -n}) You can either raise the limit of your operating system ( see \file{/etc/security/limits.conf} for Linux ) or decrease \varname{maxusers} (Tsung will have to start several virtual machine on the same host to bypass the maxusers limit). \subsection{Tsung still crashes/fails when I start it !} First look at the log file \file{~/.tsung/log/XXX/tsung_controller@yourhostname'} to see if there is a problem. If the file is not created and a crashed dump file is present, maybe you are using a binary installation of Tsung not compatible with the version of erlang you used. If you see nothing wrong, you can compile \program{Tsung} with full debugging: recompile with \command{make debug} , and don't forget to set the loglevel to "debug" in the XML file. To start the debugger or see what happen, start \program{tsung} with the \userinput{debug} argument instead of \userinput{start}. You will have an erlang shell on the \varname{tsung\_controller} node. Use \command{toolbar:start().} to launch the graphical tools provided by Erlang. \subsection{Can I dynamically follow redirect with HTTP ?} If your HTTP server sends 30X responses (redirect) with dynamic URLs, you can handle this situation using a dynamic variable: \begin{Verbatim} \end{Verbatim} You can even handle the case where the server use several redirections successively using a repeat loop (this works only with version 1.3.0 and up): \begin{Verbatim} \end{Verbatim} \subsection{What is the format of the stats file tsung.log ?} \label{sec:what-format-stats} \begin{Verbatim} # stats: dump at 1218093520 stats: users 247 247 stats: connected 184 247 stats: users_count 184 247 stats: page 187 98.324 579.441 5465.940 2.177 9.237 595 58 stats: request 1869 0.371 0.422 5.20703125 0.115 0.431 7444062 581 stats: connect 186 0.427 0.184 4.47216796875 0.174 0.894 88665254 59 stats: tr_login 187 100.848 579.742 5470.223 2.231 56.970 91567888 58 stats: size_rcv 2715777 3568647 stats: 200 1869 2450 stats: size_sent 264167 347870 # stats: dump at 1218093530 stats: users 356 356 stats: users_count 109 356 stats: connected -32 215 stats: page 110 3.346 0.408 5465.940 2.177 77.234 724492 245 stats: request 1100 0.305 0.284 5.207 0.115 0.385 26785716 2450 stats: connect 110 0.320 0.065 4.472 0.174 0.540 39158164 245 stats: tr_login 110 3.419 0.414 5470.223 2.231 90.461 548628831 245 stats: size_rcv 1602039 5170686 stats: 200 1100 3550 stats: size_sent 150660 498530 ... \end{Verbatim} the format is, for \varname{request}, \varname{page}, \varname{session} and transactions (\varname{tr\_XXX}: \texttt{ \# stats:'name' 10sec\_count, 10sec\_mean, 10sec\_stdvar, max, min, mean, count} or for HTTP returns code, size ... \texttt{ \# stats:'name' count(during the last 10sec), totalcount(since the beginning)} \subsection{How can I compute percentile/quartiles/median for transactions or requests response time ?} It's not directly possible. But since \strong{version 1.3.0}, you can use a new experimental statistic backend: set \userinput{backend="fullstats"} in the \userinput{} section of your configuration file. This will print every statistics data in a raw format in a file named \file{tsung-fullstats.log}. \strong{Warning}: this may impact the performance of the controller node (a lot of data has to be written to disk). The data looks like: \begin{Verbatim} {sum,connected,1} {sum,connected,-1} [{sample,request,214.635}, {sum,size_rcv,268}, {sample,page,831.189}, {count,200}, {sum,size_sent,182}, {sample,connect,184.787}, {sample,request,220.974}, {sum,size_rcv,785}, {count,200}, {sum,size_sent,164}, {sample,connect,185.482}] {sum,connected,1} [{count,200},{sum,size_sent,161},{sample,connect,180.812}] [{sum,size_rcv,524288},{sum,size_rcv,524288}] \end{Verbatim} You will have to write your own script to analyze the output. The format of the file may change in a future release. \subsection{How can I specify the number of concurrent users ?} You can't. But it's on purpose: the load generated by \program{Tsung} is dependent on the arrival time between new clients. Indeed, once a client has finished his session in \program{tsung}, it stops. So the number of concurrent users is a function of the arrival rate and the mean session duration. For example, if your web site has $1000$ visits/hour, the arrival rate is $1000/3600 = 0.2778$ visits/second. If you want to simulate the same load, set the inter-arrival time is to $1/0.27778 = 3.6 sec$ (\texttt{} in the \varname{arrivalphase} node in the XML config file). \subsection{SNMP monitoring doesn't work ?!} \label{sec:faq:snmp} It use SNMP v1 and the 'public' community. It has been tested with \url{http://net-snmp.sourceforge.net/}. You can try with \command{snmpwalk} to see if your snmpd config is ok: \begin{Verbatim} >snmpwalk -v 1 -c public IP-OF-YOUR-SERVER .1.3.6.1.4.1.2021.4.5.0 UCD-SNMP-MIB::memTotalReal.0 = INTEGER: 1033436 \end{Verbatim} SNMP doesn't work with erlang R10B and Tsung older than 1.2.0. There is a small bug in the \file{snmp_mgr} module in old Erlang release (R9C-0). You have to apply this patch to make it work. This is fixed in erlang R9C-1 and up. \begin{Verbatim} --- lib/snmp-3.4/src/snmp_mgr.erl.orig 2004-03-22 15:21:59.000000000 +0100 +++ lib/snmp-3.4/src/snmp_mgr.erl 2004-03-22 15:23:46.000000000 +0100 @@ -296,6 +296,10 @@ end; is_options_ok([{recbuf,Sz}|Opts]) when 0 < Sz, Sz =< 65535 -> is_options_ok(Opts); +is_options_ok([{receive_type, msg}|Opts]) -> + is_options_ok(Opts); +is_options_ok([{receive_type, pdu}|Opts]) -> + is_options_ok(Opts); is_options_ok([InvOpt|_]) -> {error,{invalid_option,InvOpt}}; is_options_ok([]) -> true. \end{Verbatim} \subsection{How can i simulate a fix number of users ?} Use \varname{maxnumber} to set the max number of concurrent users in a phase, and if you want Tsung to behave like ab, you can use a loop in a session (to send requests as fast as possible); you can also define a max \varname{duration} in \varname{}. \begin{Verbatim} \end{Verbatim} \section{Errors list} \begin{description} \item[error\_closed] Only for non persistent session (XMPP); the server unexpectedly closed the connection; the session is aborted. \item[error\_inet\_] Network error; see \url{http://www.erlang.org/doc/man/inet.html} for the list of all errors. \item[error\_unknown\_data] Data received from the server during a thinktime (not for unparsed protocol like XMPP). The session is aborted. \item[error\_unknown\_msg] Unknown message received (see the log files for more information). The session is aborted. \item[error\_unknown] Abnormal termination of a session, see log file for more information. \item[error\_repeat\_] Error in a repeat loop (undefined dynamic variable usually). \item[error\_send\_] Error while sending data to the server, see \url{http://www.erlang.org/doc/man/inet.html} for the list of all errors. \item[error\_send] Unexpected error while sending data to the server, see the logfiles for more information. \item[error\_connect\_] Error while establishing a connection to the server. See \url{http://www.erlang.org/doc/man/inet.html} for the list of all errors. \item[error\_no\_online jabber] XMPP: No online user available (usually for a chat message destinated to a online user) \item[error\_no\_offline jabber] XMPP: No offline user available (usually for a chat message destinated to a offline user) \item[error\_no\_free\_userid] For XMPP: all users Id are already used (\varname{userid\_max} is too low ?) \item[error\_next\_session] A clients fails to gets its session parameter from the config\_server; the controller may be overloaded ? \item[error\_mysql\_] Error reported by the mysql server (see \url{http://dev.mysql.com/doc/refman/5.0/en/error-messages-server.html}) \item[error\_mysql\_badpacket] Bad packet received for mysql server while parsing data. \item[error\_pgsql] Error reported by the postgresql server. \end{description} \section{CHANGELOG} \fvset{numbers=none,frame=lines,fontsize=\scriptsize,fontfamily=courier,fontshape=sl} \VerbatimInput{../CHANGES} \end{appendix} \end{document} %%% for AucTex/Emacs : %%% Local Variables: %%% eval:(setenv "TEXINPUTS" ":.:~/cvs/projetdoc//common/styles:./images:./figures:") %%% mode: latex %%% End: tsung-1.4.2/doc/Design.txt0000644000201100017670000000662711701017117015071 0ustar nniclausdream OTP Supervision tree: ==================== The application is now split in two (see tsung-inside.png for an overview): ** a single controller (tsung_controller) * ts_config_server (gen_server). Configuration server. Session's definitions are kept by the config server. * ts_mon (gen_server). Each client send reports of stats to this server. Several types of messages are handled by ts_mon. * ts_os_mon (gen_server). Use to monitor remote node activity (cpu, memory, network traffic). Currently, use an erlang agent on remote nodes. * ts_timer (used by ts_client when ack is global) (gen_fsm) servers used to construct messages: * ts_msg_server (gen_server) * ts_user_server (gen_server) used by jabber_* for unicity of users id ** several clients (tsung) Several nodes can be used simultaneously This application is simpler: * ts_launcher (gen_fsm) launch simulated users. * ts_session_cache (gen_server) cache the sessions's definition (ask the config_server if it's not yet in the cache) * 1 process per simultated client (ts_client), under the supervision of ts_clients_sup ( using simple_one_for_one ) Main modules: ============ 1/ ts_launcher. the master process that spawns other simulated clients: 1.1/ client processes: at each simulated client correspond 1 erlang process (ts_client) 1.2/ monitoring process (ts_mon) 2/ statistical module (ts_stats) 3/ protocol-specific modules (ts_jabber and ts_http, for example). tsung use different types of acknoledgements to determine when a the response of a request is over. For each requests, 4 options are possible: * parse -> the receiving process parse the response from the server dans can warn the sending process when the response is finish (function parse/2). This is used for HTTP. * no_ack: as soon as the request has been sent, the next one is executed (it can be a thinktime) * local: the request is acknoledge once a packet is received * global: the request is acknoledge once all clients has received an acknoledgement. This has been introduced for Jabber: with that, you can set that users starts talking when everyone is connected. How to add a new protocol, or extend an existing one: ==================================================== To add a new protocol, you have to create a module that implement and exports: -export([init_dynparams/0, add_dynparams/4, get_message/1, session_defaults/0, parse/2, parse_config/2, new_session/0]). There is a template file is doc/ts_template.erl References: ========== - Erlang http://www.erlang.org/ Design principles: http://www.erlang.org/doc/r7b/doc/design_principles/part_frame.html - Jabber http://docs.jabber.org/general/html/protocol.html - Stochastics models: For more details on stochastics models and application to Web workload generators, have a look at: Nicolas Niclausse. Modlisation, analyse de performance et dimensionnement du World Wide Web. Thse de Doctorat (PhD), Universit de Nice - Sophia Antipolis, Juin 1999. http://www-sop.inria.fr/mistral/personnel/Nicolas.Niclausse/these.html Z. Liu, N. Niclausse, C. Jalpa-Villanueva & S. Barbier. Traffic Model and Performance Evaluation of Web Servers Rapport de recherche INRIA, RR-3840 (http://www.inria.fr/rrrt/rr-3840.html) tsung-1.4.2/doc/tsung-recorder.10000644000201100017670000000507011701017143016132 0ustar nniclausdream.\" auto-generated by docbook2man-spec from docbook-utils package .TH "TSUNG-RECORDER" "1" "March 2009" "" "" .SH NAME tsung-recorder \- Proxy recorder for the tsung load testing tool. .SH SYNOPSIS .sp \fBtsung-recorder\fR [ \fB-l log file\fR ] [ \fB-r command\fR ] [ \fB-p plugin\fR ] [ \fB-L listen port\fR ] [ \fB-I IP\fR ] [ \fB-P port\fR ] [ \fB-u \fR ] [ \fBstart|stop|restart|record_tag\fR ] .SH "DESCRIPTION" .PP \fBtsung\fR is a distributed load testing tool. It is protocol-independent and can currently be used to stress and benchmark HTTP, WebDAV, LDAP, PostgreSQL, MySQL and Jabber/XMPP servers. \fBtsung-recorder\fR can be used to record sessions (only for HTTP, WebDAV and Postgresql) that can be edited and replayed later by tsung .PP tsung-recorder is a proxy that records a session in the tsung native XML format; it can be used by your favorite client (browser in the case of the http plugin). .TP \fBstart\fR start the proxy recorder (listening port is 8090). By default the HTTP recorder is started. With the -p option, you can select another plugin. The resulting files will be created as \fI~/.tsung/tsung_recorderYYYMMDD-HH:MM.xml\fR; if it doesn't work, take a look at \fI~/.tsung/log/tsung.log-tsunami_recorder@hostname\fR .TP \fBstop\fR stop the proxy recorder .TP \fBrecord_tag value\fR add a string (comment or tag) while recording a session. This is useful for example to add transaction tag while recording a session. .SH "MANUAL" .PP A manual should be available at \fI/usr/share/doc/tsung/user_manual.html\fR\&. It is also available online at .sp .RS .sp .nf http://tsung.erlang-projects.org/user_manual.html .sp .fi .RE .sp .SH "OPTIONS" .TP \fB-l logfile\fR Specifies the log file to use. The default log file name is \fI~/tsung/log/tsung.log\fR .TP \fB-p plugin\fR Specifies the plugin used for the recorder. Default is http, available: http, pgsql, webdav .TP \fB-L port\fR Listening port for the recorder. Default is 8090 .TP \fB-I IP\fR For the pgsql recorder (or parent proxy): server IP. default is 127.0.0.1 .TP \fB-P port\fR For the pgsql recorder (or parent proxy): server port. Default is 5432 .TP \fB-u\fR For the http recorder: use a parent proxy .SH "BUGS" .PP Please reports bugs to the mailing list , see .sp .RS .sp .nf https://lists.process-one.net/mailman/listinfo/tsung-users .sp .fi .RE .sp for archives. .SH "SEE ALSO" .PP \fBerlang\fR(3) and \fBtsung\fR(1) .SH "AUTHORS" .PP \fBTsung\fR is written by Nicolas Niclausse \&. Contributors list is available in \fI/usr/share/doc/tsung/CONTRIBUTORS\fR tsung-1.4.2/doc/tsung-recorder.1.sgml0000644000201100017670000001236511701017117017101 0ustar nniclausdream
nicolas.niclausse@niclux.org
Nicolas Niclausse March 2009 2009 Nicolas Niclausse
tsung-recorder 1 tsung-recorder Proxy recorder for the tsung load testing tool. tsung-recorder log file command plugin listen port IP port start|stop|restart|record_tag description tsung is a distributed load testing tool. It is protocol-independent and can currently be used to stress and benchmark HTTP, WebDAV, LDAP, PostgreSQL, MySQL and Jabber/XMPP servers. tsung-recorder can be used to record sessions (only for HTTP, WebDAV and Postgresql) that can be edited and replayed later by tsung tsung-recorder is a proxy that records a session in the tsung native XML format; it can be used by your favorite client (browser in the case of the http plugin). start the proxy recorder (listening port is 8090). By default the HTTP recorder is started. With the -p option, you can select another plugin. The resulting files will be created as ~/.tsung/tsung_recorderYYYMMDD-HH:MM.xml; if it doesn't work, take a look at ~/.tsung/log/tsung.log-tsunami_recorder@hostname stop the proxy recorder value add a string (comment or tag) while recording a session. This is useful for example to add transaction tag while recording a session. manual A manual should be available at /usr/share/doc/tsung/user_manual.html. It is also available online at
http://tsung.erlang-projects.org/user_manual.html
options Specifies the log file to use. The default log file name is ~/tsung/log/tsung.log Specifies the plugin used for the recorder. Default is http, available: http, pgsql, webdav Listening port for the recorder. Default is 8090 For the pgsql recorder (or parent proxy): server IP. default is 127.0.0.1 For the pgsql recorder (or parent proxy): server port. Default is 5432 For the http recorder: use a parent proxy Bugs Please reports bugs to the mailing list tsung-users@process-one.net, see
https://lists.process-one.net/mailman/listinfo/tsung-users
for archives.
see also erlang3 and tsung1 Authors Tsung is written by Nicolas Niclausse nicolas@niclux.org. Contributors list is available in /usr/share/doc/tsung/CONTRIBUTORS
tsung-1.4.2/doc/Jabber.txt0000644000201100017670000000137211701017117015035 0ustar nniclausdreamRequirements: ============ users has to be already registered: tsung users name : cXX passwd: pasXX where XX is a integer between 1 and the maximum number of users (say 1000000). or you can use jabber_register (see below). optional parameters: (can be set in tsung.xml file) ================== jabber_domain=mydomain.com modules implemented for the Jabber protocol: ============================================ - jabber_common: module regrouping common functions for building messages NOTE: currently, no XML parsing is done by the receiving process (it would be very time consuming to parse thousands of simultaneous XML flows). It use acknoledgements instead based on the first packet received after the request has been sent. tsung-1.4.2/doc/tsung-inside.dia0000644000201100017670000001641711701017117016205 0ustar nniclausdream(Ktsung-inside.fig]r7}P)4e4ΔGʑDyUQ-&]/=?Ώ͋DnX(TZ&_x6}/M/f՛_O?r<3s5~ރ^7/~۷olzd]^{8 _E .!pϿ.F{ћoW~uU}l2>c|o}|>|kRne4}_fcd% o\S_u M~H/AAtͤzy)%< |N;|&;n/ܼ_ٗ|1ېd4VQ:p]fZqX6 ~bC{5oIM޸.ƗOgdz\ݿgdӏn{Pݭqx#32/Gt4S}>W1~ۿѫ/tk^,ƿ/m<.u{Op~jo?a!7up3­eUn@hrŶ4FC;ӫhA#2@J6il>nb̕Hƃu:,_i4hL|6kg*7_wSCv萆1N@HٷqACP}6^nxۑ޸8Tdda93,h!iNnצB 7B16tVjY0jCmJmctze.7.7Ve…bcP@&(7Wn-l#67DhG`Ql.ܹMЖbs|^" F0{jH\/FqFM⛚2JcMH)9Ghh* (gdF$AgZ.DIWWt:ib rY*QUO5AtZAtFUC,ʁn js:4i؆9m Z(}T p'5@s$@GqUx悦DmJncaUiQ9cyF; VU9'|dKDqtFSšRE ˰ZJ^8xɳ$"9 B2!kI [ [l 2o]5]13/wV*[oٕ "Ty6WlxJick+b8 2ȹ` H·DR@ >q',|-|%\MY$t+"ֹ+xCI ] ]t%cruE:y]5I ] ]t%1ruEek䵓6fӝ|3a)EvMLjTi{SOFUXaSw=}'sf01ǏzWD^|n_?.֗OSuozy6gߚ?yjg~}ޯ^޻}C "i6eZ|df\wNv7p(&-JusKnֻAXy 9(b8_d"ƽ&h_}\,[_}|1òi~ݑw\qVs=PNG,6I,\F,uNqZ 4&Eg STf,iIKǮիOeyWo8.v>?' {1@ݵӟFGp>l%M_z^/O'/Sn}yo} p2~J[,{HcEG2Ec*n2 F̎K$`.$#)TO/ErTcL+22E.D4L= +a;VLSNO~=9 (R:(/o/[:e X+"Q9$J/-3Ð嘵9q4Jđ1g z֨?mʭM 1hckxjKq r.jnYN\B>iwLA+ԁO#t>qY0* p|"1ZS?8YrΊ1j3봫`$,(I ɵŔ9W6P2ny!zzKx7}(y]/ScL+cjZYV9-r`pS;{Q{֧x25ޓL5t"R-RRh?ʚU@#jc quH @8u2Vxc Zǖ'r9. lmK .A~[u}B'O}B5u2Dw75Lhnb]&N.}Jɻgo:zqʞ5t@%QwlœBrɂ3A t vQ Zj9,k +R'mkl獵Q6u@\#¢.x<5H w'*E}FK_ߦ/.J*䌱cGI=2ets(HzqRˑ[Z r*E/ q#W3:!LN娸 E?ޠ[n+-E^%J\NW!π2^W&_"O Σ#t*RH^I}BB!P.VCT_l7Vr%vP ;e5_"*d5#az# [ [ r\fu/6:wIr{I/Gi)gwL+rXҷk5<fWHh:ceREGEЩ~Wtu\B?:H bEf?R-UHճ*rM?fn@3%l8S4%+rէ\)pE;2uήBk4upaX\VND䖦dMܾwXL=jm]&Vf>RxFҎXʆXy?]C.Usl!^ 'VmrhNً_4kcmJRÀ `.PN>CQ9C;f \(!ҢkC+bFcpg {K}:ibR6_7 s 4r-Ss=&qɐzVX[RWXZXJd)|2N4JIT@2)@KO dej'm,d-d%\=kZC¤-!$Xp<䝔z:f \X0|bnFfN]T::x[ƃm})8r?Csץ,-w|:aC޻`}ՐKggY`@㜦[ QVr=&7&}rF)mFsMGz>u E)kOAeI y~e".J>a(3DzP(@iO?-8* MܐH-35ɻjFRĎYÝκMEsbxxq36,!SZܖ2csGсg-]Tތ3gATo3#‰o3O~S⌎hg+9GjqI.I4s3&g3;d \땡M9Mzqf 1՛BaknJb)̺H> -\5 NZ٪D2!7k@rpqk*اZn6:1kYn2r#9J|lן V9^v5}^ކ9&Z̿P}6^n1FPccu*K 3%R2ynAt:idq ):Otp `Qtzqxq>LxjE֒Kk"jLg[˕ W^ \ƴYkt%♧BLNB0)*0q$ Sܾv%Jsf$79V8saiv KA/W]֣N6D>4dNPg3_E#˨ZJ+vj8NZP "c<;c,-%S~xzݗ_Aj4Y3ךэjEZckڒiK?D|ٙ\%g7ٚ}dжЖJ[GZu습l&m/l7 --%o"s'wy7兴0ⴐ .$BHJ,V4iHDWvuC + 9QYuI m "k3wɋg~\cL+ۑFѫ Fa H峮`h!XsGx! QIJLJ?@7ꎮXՊ\Ç !@F i7ieR&okS?H8rΊWvj)p/c˜^h P!Mv΄:v^fe.3e" z"޹N~IG9KF3[}^\PnW+SJ r}fZc&~RUm%sOnh -JhA -%BCl,#TyMPbl`ɻ.NNߞ;<+ӫM6vddU6C7FtL!:gͺK1A.`2G#-˽O5k]: ϥϴu)y&3w葧[&<Iz}ۤG& iM,YږJȝɑV L%EPy&teUAWbN^U';$i: )cR"Ϣ:Xen(]ɬ:dVО^)\&X2$Vr/P~Im,Ƽ 3Y@"wFmO2IJT֒dW֮:r2 q :݋+tz+ta)XVa2 0A2ڙW-֮*IXECO[qi4}&=i˦Ǫ95l2o!y> |f Z$:tg'xd \矬IDu MVHoݞ=97͞:e \)r]فš+U)λT@'gH2dqY8}z1 cU@Z*s'tljSIvYl@&]fP~)(p< (j>:9Bi[4;EJkg0ԃ )":&oCCc6?Y2CWjGxg ʕSH2>%ꬒѓ?u&f9PDHMmfjla4K}j u\BSg}z~K5wE71J9rHk?)8yMo.{&Y46Ů[ߐ0u)mCC=vaіnL8ُivaJ󺘒-96RXHZ>@6QE)n$?<]Zo@ڼ ).:Qw%Md&dT< &@ϲg\׍xhz8Y1ɱ@N\Ĕ)Ma JQ#b Y[\*Ds.嬓e5j~ Dtn&?pA_.D_@B$⻵ N*|UePSUǧQ 3馶JRpd-DZV)#b#p1põ}ẂqJg Uڕp͔@>H W צ55J6JwTb -i2`#ߓ8Ya'NIj:f:|\ tsung-1.4.2/doc/Makefile0000644000201100017670000001536611701017117014557 0ustar nniclausdream# $Id$ MANPAGE_SRC = tsung.1.sgml tsplot.1.sgml tsung-recorder.1.sgml MANPAGE = tsung.1 tsplot.1 tsung-recorder.1 TOPDIR=. IMG_TOPDIR=. LOGO_DIR=$(TOPDIR)/logos FILES=$(wildcard *.tex) # classe et styles latex INCLUDES=shortcuts.sty TSUNG-en.cls # chemin des fichiers de style STYLE=$(TOPDIR)/styles CSS=IDXDOC.css # classe et styles hevea HEVEA_INC=TSUNG.hva HEVEAOPTS=-fix -I $(STYLE) -exec xxdate.exe -pedantic LANGUAGE= # logos LOGOS_JPG= LOGOS_EPS = $(LOGOS_JPG:.jpg=.eps) ############## # remove image.tex generated by hevea TEXFILES=$(sort $(subst image.,,$(FILES))) DVIFILES=$(TEXFILES:.tex=.dvi) PSFILES=$(TEXFILES:.tex=.ps) PDFFILES=$(TEXFILES:.tex=.pdf) HTMLFILES=$(TEXFILES:.tex=.html) HTML_INDEXFILES=$(TEXFILES:.tex=_index.html) # get figures to be generated from gnuplot file PLOT_DIR = $(IMG_TOPDIR)/plot PLOT_PS = $(shell grep -s output $(PLOT_DIR)/*.gplot | cut -f3 -d" " | perl -e '{ while (<>) {chomp; s/"//;s/"/ /; print} }') PLOT=$(addprefix $(PLOT_DIR)/,$(PLOT_PS)) #PLOT = $(wildcard $(PLOT_DIR)/*.ps) PLOT_PNG = $(PLOT:.ps=.png) PLOT_PDF = $(PLOT:.ps=.pdf) # figures FIGURES_DIR = $(IMG_TOPDIR)/figures FIGURES = $(wildcard $(FIGURES_DIR)/*.fig) FIGURES_PNG = $(FIGURES:.fig=.png) #FIGURES_JPG = $(FIGURES:.fig=.jpg) FIGURES_EPS = $(FIGURES_PNG:.png=.eps) # images IMAGES_DIR = $(IMG_TOPDIR)/images IMAGES_PNG = $(wildcard $(IMAGES_DIR)/*.png) IMAGES_EPS = $(IMAGES_PNG:.png=.eps) ALL_IMG = $(notdir $(FIGURES)) ALL_IMG += $(notdir $(IMAGES_PNG)) ALL_IMG += $(notdir $(PLOT)) # pour recompiler le doc latex autant de fois que necessaire : RERUN = "(There were undefined references|Rerun to get (cross-references|the bars) right)" RERUNBIB = "No file.*\.bbl|Citation.*undefined" #'-nopostscript' afin de ne pas subir l'interprtation/rendu des fichiers PS inclus XDVI=xdvi -s 0 -geometry -0+0 -margins 0cm -nopostscript XPDF=xpdf -geometry 600x768+0+0 -z page GV=gv CONVERT=convert GNUPLOT=gnuplot PDFLATEX=pdflatex --interaction=batchmode EPS2PDF=epstopdf DVIPS=dvips LATEX=latex --interaction=batchmode BIBTEX=bibtex FIG2DEV=fig2dev VIEWER=gv HEVEA=hevea HACHA=hacha IMAGEN=imagen MAKEINDEX=makeindex TIDY=tidy XSLTPROC=xsltproc XSL=$(HOME)/cvs/web/idealx3.org/xhtml.xsl # On rajoute les fichiers templates de LaTeX dans le VPATH pour make # et dans TEXINPUTS pour LaTeX TEXINPUTS = :.:$(STYLE):$(LOGO_DIR):$(IMAGES_DIR):$(FIGURES_DIR):$(PLOT_DIR): BSTINPUTS = :.:$(STYLE) VPATH = $(TEXINPUTS) # pour exporter TEXINPUTS .EXPORT_ALL_VARIABLES: .PHONY: clean distclean pdf dvi ps all html htmlsingle # Stop GNU make from overzealous deletion of intermediate files .PRECIOUS: %.dvi $(FIGURES_PNG) $(FIGURES_JPG) $(IMAGES_EPS) $(LOGOS_EPS) $(PLOT) $(PLOT_PNG) $(PLOT_PDF) all: man man: $(MANPAGE) %.1: %.1.sgml docbook2man $< >/dev/null 2>&1 psimages: $(FIGURES_EPS) $(IMAGES_EPS) $(LOGOS_EPS) pngimages: $(FIGURES_PNG) show: @echo INC = $(INCLUDES) @echo FIG = $(FIGURES_PNG) @echo IMG= $(IMAGES_PNG) @echo HEVEA= $(HEVEA_INC) @echo PLOT= $(PLOT_PNG) @echo TEX = $(TEXFILES) @echo HTML = $(HTMLFILES) @echo LOGOS = $(LOGOS_EPS) @echo ALL_IMG = $(ALL_IMG) @echo TEXINPUTS = $(TEXINPUTS) dist: pdf html tar zvcfh archive.tgz *.html *.pdf $(ALL_IMG) $(CSS) dvi: $(DVIFILES) ps: $(PSFILES) pdf: $(PDFFILES) html: $(HTMLFILES) htmlsplit: $(HTML_INDEXFILES) print: $(PSFILES) @echo "printing $<" a2ps $< #glossaire: # $(MAKEINDEX) -s tlglo.ist -o these.gls these.glo # makeindex these #makeindex -s tlglo.ist -o these.gls these.glo %_index.html: %.html @echo splitting $@ ... @$(HACHA) $(LANGUAGE) -o $@ $< #%.html: %.tex $(INCLUDES) $(FIGURES_PNG) $(IMAGES_PNG) $(HEVEA_INC) $(LOGOS_EPS) $(PLOT_PNG) user_manual.html: user_manual.tex ../CHANGES $(INCLUDES) $(FIGURES_PNG) $(IMAGES_PNG) $(HEVEA_INC) $(LOGOS_EPS) $(PLOT_PNG) @echo Generating $@... @$(HEVEA) $(HEVEAOPTS) $(LANGUAGE) $(HEVEA_INC) $< ifeq ($(strip $(IMAGES_PNG)),) @echo no images else @echo Generating images for html... @ln -sf $(IMAGES_DIR)/*.png . endif ifeq ($(strip $(FIGURES_PNG)),) @echo no figures else @echo Generating figures for html... @ln -sf $(FIGURES_DIR)/*.png . endif ifeq ($(strip $(PLOT_PNG)),) @echo no plot else @echo Generating plot for html... @ln -sf $(PLOT_DIR)/*.png . endif @echo done %.xhtml: %.html @echo "Generating XHTML" @$(TIDY) -latin1 -f $*.tidy_log -c -asxml $*.html > $*.xhtml ; true %.xml: %.xhtml $(XSL) @echo Generating $@... @$(XSLTPROC) -o $*.xml $(XSL) $*.xhtml @perl -i -pe 's@idxwebml-xhtml.dtd">@../../idxwebml.dtd" []>@g' $*.xml @echo "done" ## remove builtin GNU-make implicit rule for dvi %.dvi : %.tex gv: ps @$(GV) $(FILE) & xpdf: pdf @$(XPDF) $(FILE).pdf & %.dvi: %.tex Makefile $(INCLUDES) $(LOGOS_EPS) $(FIGURES_EPS) $(IMAGES_EPS) $(PLOT) @echo Generating $@... @echo @$(LATEX) $< @egrep -c $(RERUNBIB) $*.log && ($(BIBTEX) $*;$(LATEX) $<) ; true @egrep $(RERUN) $*.log && $(LATEX) $< ; true @egrep $(RERUN) $*.log && $(LATEX) $< ; true # Display relevant warnings @egrep -i "(Reference|Citation).*undefined" $*.log ; true %.ps: %.dvi @echo Generating $@... @dvips -q -t a4 -o $@ $< @echo done. %.pdf: %.tex ../CHANGES $(INCLUDES) $(LOGOS_JPG) $(FIGURES_PNG) $(IMAGES_PNG) $(PLOT_PDF) @echo Generating $@... @echo @$(PDFLATEX) $< @egrep -c $(RERUNBIB) $*.log && ($(BIBTEX) $*;$(LATEX) $<) ; true @egrep $(RERUN) $*.log && $(PDFLATEX) $< ; true @egrep $(RERUN) $*.log && $(PDFLATEX) $< ; true # Display relevant warnings @egrep -i "(Reference|Citation).*undefined" $*.log ; true %.eps: %.jpg @echo convert $< $@ @$(CONVERT) $< $@ %.eps: %.png @echo convert $< $@ @$(CONVERT) $< $@ clean: @echo -n Cleaning intermediate files... @rm -f *.{aux,log,toc,bak,haux,lof,out,htoc,ps,links,refs,1} $(LOGOS_EPS) @echo " done." $(PLOT_DIR)/%.ps: $(PLOT_DIR)/*.gplot @echo \* Producing ps files $< with data with gnuplot @cd $(PLOT_DIR); $(GNUPLOT) *.gplot ; cd - $(PLOT_DIR)/%.png: $(PLOT_DIR)/%.ps @echo Generating $@ @$(CONVERT) $< $@ $(PLOT_DIR)/%.pdf: $(PLOT_DIR)/%.ps @echo Generating $@ @$(EPS2PDF) $< $(FIGURES_DIR)/%.png: $(FIGURES_DIR)/%.fig @echo Generating $@ @$(FIG2DEV) -L png $< $@ $(FIGURES_DIR)/%.jpg: $(FIGURES_DIR)/%.fig @echo Generating $@ @$(FIG2DEV) -L jpeg $< $@ $(FIGURES_DIR)/%.eps: $(FIGURES_DIR)/%.fig @echo Generating $@ @$(FIG2DEV) -L eps $< $@ $(IMAGES_DIR)/%.jpg: $(IMAGES_DIR)/%.png @echo Generating $@ @$(CONVERT) $< $@ $(IMAGES_DIR)/%.eps: $(IMAGES_DIR)/%.png @echo Generating $@ @$(CONVERT) $< $@ $(LOGO_DIR)/%.eps: $(IMAGES_DIR)/%.jpg @echo Generating $@ @$(CONVERT) $< $@ mrproper: distclean distclean: clean @echo -n Cleaning result files... @rm -f $(DVIFILES) $(PSFILES) $(PDFFILES) $(LOGOS_EPS) $(FIGURES_EPS) $(FIGURES_PNG) *.html *motif.gif $(IMAGES_DIR)/*.eps *.png *.image.tex $(PLOT_PNG) $(PLOT_PDF) $(PLOT) *.bbl *.blg'' @echo " done." tsung-1.4.2/doc/ts_template.erl0000644000201100017670000000753511701017117016143 0ustar nniclausdream%%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_template). -vc('$Id$ '). -author(''). -include("ts_profile.hrl"). -export([init_dynparams/0, add_dynparams/4, get_message/1, session_defaults/0, parse/2, parse_config/2, new_session/0]). %%---------------------------------------------------------------------- %% Function: session_default/0 %% Purpose: default parameters for session %% Returns: {ok, persistent = true|false} %%---------------------------------------------------------------------- session_defaults() -> todo. %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> #myproto_session{}. %%---------------------------------------------------------------------- %% Function: get_message/21 %% Purpose: Build a message/request , %% Args: record %% Returns: binary %%---------------------------------------------------------------------- get_message(Req=#myproto_request{}) -> todo. %%---------------------------------------------------------------------- %% Function: parse/2 %% Purpose: parse the response from the server and keep information %% about the response in State#state_rcv.session %% Args: Data (binary), State (#state_rcv) %% Returns: {NewState, Options for socket (list), Close = true|false} %%---------------------------------------------------------------------- parse(Data, State) -> todo. %%---------------------------------------------------------------------- %% Function: parse_config/2 %% Purpose: parse tags in the XML config file related to the protocol %% Returns: List %%---------------------------------------------------------------------- parse_config(Element, Conf) -> todo. %%---------------------------------------------------------------------- %% Function: add_dynparams/4 %% Purpose: add dynamic parameters to build the message %% (this is used for ex. for Cookies in HTTP) %% Args: Subst (true|false), DynData = #dyndata, Param = #myproto_request %% Host = String %% Returns: #myproto_request %%---------------------------------------------------------------------- add_dynparams(false, DynData, Param, HostData) -> todo. %%---------------------------------------------------------------------- %% Function: init_dynparams/0 %% Purpose: initial dynamic parameters value %% Returns: #dyndata %%---------------------------------------------------------------------- init_dynparams() -> todo. %%---------------------------------------------------------------------- %% Function: subst/2 %% Purpose: Replace on the fly dynamic element of the request. %% Returns: #myproto_request %%---------------------------------------------------------------------- subst(Req=#myproto_request, DynData) -> todo. tsung-1.4.2/doc/user_manual.html0000644000201100017670000076461311701017117016326 0ustar nniclausdream Tsung User's manual

Tsung User’s manual

Version:1.4.2
Date :January 4, 2012

Contents

1  Introduction

1.1  What is Tsung ?

Tsung (formerly IDX-Tsunami) is a distributed load testing tool. It is protocol-independent and can currently be used to stress HTTP, WebDAV, SOAP, PostgreSQL, MySQL, LDAP, and Jabber/XMPP servers.

It is distributed under the GNU General Public License version 2.

1.2  What is Erlang and why is it important for Tsung ?

Tsung’s main strength is its ability to simulate a huge number of simultaneous user from a single machine. When used on cluster, you can generate a really impressive load on a server with a modest cluster, easy to set-up and to maintain. You can also use Tsung on a cloud like EC2.

Tsung is developed in Erlang and this is where the power of Tsung resides.

Erlang is a concurrency-oriented programming language. Tsung is based on the Erlang OTP (Open Transaction Platform) and inherits several characteristics from Erlang:

  • Performance: Erlang has been made to support hundred thousands of lightweight processes in a single virtual machine.
  • Scalability: Erlang runtime environment is naturally distributed, promoting the idea of process’s location transparency.
  • Fault-tolerance:Erlang has been built to develop robust, fault-tolerant systems. As such, wrong answer sent from the server to Tsung does not make the whole running benchmark crash.

More information on Erlang on http://www.erlang.org and http://www.erlang-projects.org/

1.3  Tsung background

History:

  • Tsung development was started by Nicolas Niclausse in 2001 as a distributed jabber load stress tool for internal use at http://IDEALX.com/ (now OpenTrust). It has evolved as an open-source multi-protocol load testing tool several months later. The HTTP support was added in 2003, and this tool has been used for several industrial projects. It is now hosted by Erlang-projects, and supported by http://process-one.net/. The list of contributors is available in the source archive (https://git.process-one.net/tsung/mainline/blobs/master/CONTRIBUTORS).
  • It is an industrial strength implementation of a stochastic model for real users simulation. User events distribution is based on a Poisson Process. More information on this topic in:

    Z. Liu, N. Niclausse, and C. Jalpa-Villanueva. Traffic Model and Performance Evaluation of Web Servers. Performance Evaluation, Volume 46, Issue 2-3, October 2001.

  • This model has already been tested in the INRIA WAGON research prototype (Web trAffic GeneratOr and beNchmark). WAGON was used in the http://www.vthd.org/ project (Very High Broadband IP/WDM test platform for new generation Internet applications, 2000-2004).

Tsung has been used for very high load tests:

  • Jabber/XMPP protocol:
    • 90 000 simultaneous jabber users on a 4-node Tsung cluster (3xSun V240 + 1 Sun V440)
    • 10 000 simultaneous users. Tsung was running on a 3-computers cluster (CPU 800MHz)
  • HTTP and HTTPS protocol:
    • 12 000 simultaneous users. Tsung were running on a 4-computers cluster (in 2003). The tested platform reached 3 000 requests per second.
    • 10 million simultaneous users running on a 75-computers cluster, generating more than one million requests per second.

Tsung has been used at:

  • DGI (Direction Générale des impôts): French finance ministry
  • Cap Gemini Ernst & Young
  • IFP (Institut Français du Pétrole): French Research Organization for Petroleum
  • LibertySurf
  • Sun™for their Mooddlerooms platform on Niagara processors: http://blogs.sun.com/kevinr/resource/Moodle-Sun-RA.pdf

2  Features

2.1  Tsung main features

  • High Performance: Tsung can simulate a huge number of simultaneous users per physical computer: It can simulates thousands of users on a single CPU (Note: a simulated user is not always active: it can be idle during a thinktime period). Traditional injection tools can hardly go further than a few hundreds (Hint: if all you want to do is requesting a single URL in a loop, use ab; but if you want to build complex scenarios with extended reports, Tsung is for you).
  • Distributed: the load can be distributed on a cluster of client machines
  • Multi-Protocols using a plug-in system: HTTP (both standard web traffic and SOAP), WebDAV, Jabber/XMPP and PostgreSQL are currently supported. LDAP and MySQL plugins were first included in the 1.3.0 release.
  • SSL support
  • Several IP addresses can be used on a single machine using the underlying OS IP Aliasing
  • OS monitoring (CPU, memory and network traffic) using Erlang agents on remote servers or SNMP
  • XML configuration system: complex user’s scenarios are written in XML. Scenarios can be written with a simple browser using the Tsung recorder (HTTP and PostgreSQL only).
  • Dynamic scenarios: You can get dynamic data from the server under load (without writing any code) and re-inject it in subsequent requests. You can also loop, restart or stop a session when a string (or regexp) matches the server response.
  • Mixed behaviours: several sessions can be used to simulate different type of users during the same benchmark. You can define the proportion of the various behaviours in the benchmark scenario.
  • Stochastic processes: in order to generate a realistic traffic, user thinktimes and the arrival rate can be randomized using a probability distribution (currently exponential)

2.2  HTTP related features

  • HTTP/1.0 and HTTP/1.1 support
  • GET, POST, PUT, DELETE and HEAD requests
  • Cookies: Automatic cookies management( but you can also manually add more cookies)
  • 'GET If-modified since' type of request
  • WWW-authentication Basic
  • User Agent support
  • Any HTTP Headers can be added
  • Proxy mode to record sessions using a Web browser
  • SOAP support using the HTTP mode (the SOAPAction HTTP header is handled).
  • HTTP server or proxy server load testing.

2.3  WEBDAV related features

The WebDAV (RFC 4918) plugin is a superset of the HTTP plugin. It adds the following features (some versionning extensions to WebDAV (RFC 3253) are also supported):

  • Methods implemented: DELETE, CONNECT, PROPFIND, PROPPATCH, COPY, MOVE, LOCK, UNLOCK, MKCOL, REPORT, OPTIONS, MKACTIVITY, CHECKOUT, MERGE
  • Recording of DEPTH, IF, TIMEOUT OVERWRITE, DESTINATION, URL and LOCK-TOKEN Headers.

2.4  Jabber/XMPP related features

  • Authentication (plain-text, digest and sip-digest)
  • presence and register messages
  • Chat messages to online or offline users
  • MUC: join room, send message in room, change nickname
  • Roster set and get requests
  • Global users' synchronization can be set on specific actions
  • raw XML messages
  • PubSub
  • Multiple vhost instances supported
  • privacy lists: get all privacy list names, set list as active

2.5  PostgreSQL related features

  • Basic and MD5 Authentication
  • Simple Protocol
  • Extended Protocol (new in version 1.4.0 )
  • Proxy mode to record sessions

2.6  MySQL related features

This plugin is experimental. It works only with MySQL version 4.1 and higher.

  • Secured Authentication method only (MySQL >= 4.1)
  • Basic Queries

2.7  LDAP related features

  • bind
  • add, modify and search queries
  • starttls

2.8  Complete reports set

Measures and statistics produced by Tsung are extremely feature-full. They are all represented as a graphic. Tsung produces statistics regarding:

  • Performance: response time, connection time, decomposition of the user scenario based on request grouping instruction (called transactions), requests per second
  • Errors: Statistics on page return code to trace errors
  • Target server behaviour: An Erlang agent can gather information from the target server(s). Tsung produces graphs for CPU and memory consumption and network traffic. SNMP and munin is also supported to monitor remote servers.

Note that Tsung takes care of the synchronization process by itself. Gathered statistics are «synchronized».

It is possible to generate graphs during the benchmark as statistics are gathered in real-time.

2.9  Highlights

Tsung has several advantages over other injection tools:

  • High performance and distributed benchmark: You can use Tsung to simulate tens of thousands of virtual users.
  • Ease of use: The hard work is already done for all supported protocol. No need to write complex scripts. Dynamic scenarios only requires small trivial piece of code.
  • Multi-protocol support: Tsung is for example one of the only tool to benchmark SOAP applications
  • Monitoring of the target server(s) to analyze the behaviour and find bottlenecks. For example, it has been used to analyze cluster symmetry (is the load properly balanced ?) and to determine the best combination of machines on the three cluster tiers (Web engine, EJB engine and database)

3  Installation

This package has been tested on Linux, FreeBSD and Solaris. A port is available on MacOS X. It should work on Erlang supported platforms (Linux, Solaris, *BSD, Win32 and MacOS-X).

3.1  Dependencies

  • Erlang/OTP R12B-5 and up (http://www.erlang.org/download.html). Erlang is now part of fedora and debian/ubuntu repositories.
  • pgsql module made by Christian Sunesson (for the PostgreSQL plugin): sources available at http://jungerl.sourceforge.net/ . The module is included in the source and binary distribution of Tsung. It is released under the EPL License.
  • mysql module made by Magnus Ahltorp & Fredrik Thulin (for the mysql plugin): sources available at http://www.stacken.kth.se/projekt/yxa/ . The modified module is included in the source and binary distribution of Tsung. It is released under the three-clause BSD License.
  • eldap module (for the LDAP plugin): sources available at http://jungerl.sourceforge.net/ . The module is included in the source and binary distribution of Tsung. It is released under the GPL License.
  • mochiweb libs (for xpath parsing, optionally used for dynamic variables in the HTTP plugin): sources available at http://code.google.com/p/mochiweb/ . The module is included in the source and binary distribution of Tsung. It is released under the MIT License.
  • gnuplot and perl5 (optional; for graphical output with tsung_stats.pl script). The Template Toolkit is used for HTML reports (see http://template-toolkit.org/)
  • python and mathplotlib (optional; for graphical output with tsung-plotter).
  • for distributed tests, you need an ssh access to remote machines without password (use a RSA/DSA key without pass-phrase or ssh-agent) (rsh is also supported)
  • bash

3.2  Compilation

./configure
 make
 make install

If you want to download the development version, use git:

git clone git://git.process-one.net/tsung/mainline.git

(see also https://git.process-one.net/tsung and the github mirror: https://github.com/processone/tsung).

You can also build packages with make deb (on debian and ubuntu) and make rpm (on fedora, rhel, and other rpm based distribution)

3.3  Configuration

The default configuration file is ~/.tsung/tsung.xml ( there are several sample files in /usr/share/doc/tsung/examples).

Log files are saved in ~/.tsung/log/ . A new sub-directory is created for each test using the current date as name (~/.tsung/log/20040217-0940 for ex.)

3.4  Running

Two commands are installed in the directory $PREFIX/bin: tsung and tsung-recorder. A man page is available for both commands.

>tsung -h
Usage: tsung <options> start|stop|debug|status
Options:
    -f <file>     set configuration file (default is ~/.tsung/tsung.xml)
                    (use - for standard input)
    -l <logdir>   set log directory (default is ~/.tsung/log/YYYYMMDD-HHMM/)
    -i <id>       set controller id (default is empty)
    -r <command>  set remote connector (default is ssh)
    -s            enable erlang smp on client nodes
    -m <file>     write monitoring output on this file (default is tsung.log)
                   (use - for standard output)
    -F            use long names (FQDN) for erlang nodes
    -w            warm-up delay (default is 10 sec)
    -v            print version information and exit
    -6            use IPv6 for tsung internal communications
    -h            display this help and exit

A typical way of using tsung is to run: tsung -f myconfigfile.xml start.

The command will print the current log directory created for the test, and wait until the test is over.

3.5  Feedback

Use the Tsung mailing list (see https://lists.process-one.net/mailman/listinfo/tsung-users) if you have suggestions or questions about Tsung. You can also use the bug-tracker available at https://support.process-one.net/browse/TSUN. You can also try the #tsung IRC channel on Freenode.

4  Benchmark approach

4.1  HTTP/WebDAV benchmark approach

4.1.1  Benchmarking a Web server

  1. Record one or more sessions: start the recorder with: tsung-recorder start, and then configure your browser to use Tsung proxy recorder (the listen port is 8090). A session file will be created. For HTTPS recording, use http://- instead of https:// in your browser.
  2. Edit / organize scenario, by adding recorded sessions in the configuration file.
  3. Write small code for dynamic parts if needed and place dynamic mark-up in the scenario.
  4. Test and adjust scenario to have a nice progression of the load. This is highly dependent of the application and of the size of the target server(s). Calculate the normal duration of the scenario and use the interarrival time between users and the duration of the phase to estimate the number of simultaneous users for each given phase.
  5. Launch benchmark with your first application parameters set-up: tsung start (run man tsung for more options)
  6. Wait for the end of the test or stop by hand with tsung stop (reports can also be generated during the test (see § 7) : the statistics are updated every 10 seconds). For a brief summary of the current activity, use tsung status
  7. Analyze results, change parameters and relaunch another benchmark

4.1.2  WEBDAV

It’s the same approach as HTTP: first you start to record one or more sessions with the recorder: tsung-recorder -p webdav start

4.1.3  Benchmarking a proxy server

By default, the HTTP plugin is used to benchmark HTTP servers. But you can also benchmark HTTP Proxy servers. To do that, you must add in the options section:

  <option type="ts_http" name="http_use_server_as_proxy" value="true"></option>

4.2  LDAP benchmark approach

An LDAP plugin for the recorder is not yet implemented, so you have to write the session by yourself; see section 6.6.6 for more information.

4.3  PostgreSQL benchmark approach

It’s the same approach as HTTP: first you start to record one or more sessions with the recorder: tsung-recorder -p pgsql start

This will start a proxy listening to port 8090 and will proxy requests to 127.0.0.0:5432.

To choose another port and/or address: tsung-recorder -L 5432 -I 10.6.1.1 -P 5433 -p pgsql start

This will start a proxy listening to port 5432 and will proxy requests to 10.6.1.1:5433.

4.4  MySQL benchmark approach

A MySQL plugin for the recorder is not yet implemented, so you have to write the session by yourself; see section 6.6.5 for more information.

4.5  Jabber/XMPP benchmark approach

4.5.1  Overview

This paragraph explains how to write a session for Jabber/XMPP.

There are two differences between HTTP and Jabber testing:

  1. There is no recorder for Jabber, so you have to write your sessions by hand (an example is provided in 6.6.3).
  2. the jabber plugin does not parse XML; instead it uses packet acknowledgments.

4.5.2  Acknowledgments of messages

Since the jabber plugin does not parse XML (historically, it was for performance reasons), you must have a way to tell when a request is finished. There are 3 possibilities:

ack=local
as soon as a packet is received from the server, the request is considered as completed. Hence if you use a local ack with a request that do not require a response from the server (presence for ex.), it will wait forever (or until a timeout is reached).
ack=no_ack
as soon as the request is send, it is considered as completed (do not wait for incoming data)
ack=global
synchronized users. its main use is for waiting for all users to connect before sending messages. To do that, set a request with global ack (it can be the first presence msg:
   <request> <jabber type="presence" ack="global"/> </request>

You also have to specify the number of users to be connected:

<option type="ts_jabber" name="global_number" value="100"></option>

To be sure that exactly global_number users are started, add the ’maxnumber’ attribute to ’users’

    <users maxnumber="100" interarrival="1.0" unit="second"></users>

If you do not specify maxnumber, the global ack will be reset every global_number users

New in 1.2.2: This version adds an new option for a session. if you set the attribute bidi (for bidirectional) in the session tag: <session ... bidi=’true’>, then incoming messages from the server will be analyzed. Currently, only roster subscription requests are handled: if a user received a subscription request (<presence ... type=’subscribe’>), it will respond with a <presence ... type=’subscribed’> message.

4.5.3  Status: Offline, Connected and Online

You can send messages to offline or online users. A user is considered online when he has send a presence:initial message (before this message , the state of the user is connected).

If you want to switch back to connected before going offline, you can use a presence:final message:

presence:final does two things:

  1. It removes the client from the list of Online users, and moves them into the list of Connected users.
  2. It sends a broadcast presence update of type=’unavailable’.

presence:final is optional.

warn: this is new in 1.2.0, in earlier version, only 2 status were available: online and offline; a user was considered online as soon as it was connected.

4.5.4  Authentication

Below are configuration examples for the possible authentication methods. Note: the regular expressions used here are only examples - they may need to be altered depending on how a particular server implementation composes messages (see also  6.5.1 for password settings).

  • plain authentication - sends clear-text passwords:
      <session probability="100" name="jabber-plain" type="ts_jabber">
    
        <request> <jabber type="connect" ack="local"></jabber> </request>
    
        <thinktime value="2"></thinktime>
    
        <transaction name="auth_plain">
          <request> <jabber type="auth_get" ack="local"></jabber> </request>
          <request> <jabber type="auth_set_plain" ack="local"></jabber> </request>
        </transaction>
        ...
      </session>
    
  • digest authentication as described in XMPP JEP-0078: Non-SASL Authentication http://www.jabber.org/jeps/jep-0078.html
      <session probability="100" name="jabber-digest" type="ts_jabber">
    
        <!-- regexp captures stream ID returned by server -->
        <request>
          <dyn_variable name="sid" re="&lt;stream:stream id=&quot;(.*)&quot; xmlns:stream"/>
          <jabber type="connect" ack="local"></jabber>
        </request>
    
        <thinktime value="2"></thinktime>
    
        <transaction name="auth_digest">
          <request> <jabber type="auth_get" ack="local"></jabber> </request>
          <request subst='true'> <jabber type="auth_set_digest" ack="local"></jabber> </request>
        </transaction>
        ...
      </session>
    
  • sip-digest authentication
      <session probability="100" name="jabber-sipdigest" type="ts_jabber">
    
        <request> <jabber type="connect" ack="local"></jabber> </request>
    
        <thinktime value="2"></thinktime>
    
        <transaction name="auth_sipdigest">
          <!-- regexp captures nonce value returned by server -->
          <request>
            <dyn_variable name="nonce"
              re="&lt;Nonce encoding=&quot;hex&quot;&gt;(.*)&lt;\/Nonce&gt;"/>
            <jabber type="auth_get" ack="local"></jabber>
          </request>
          <request subst='true'> <jabber type="auth_set_sip" ack="local"></jabber> </request>
        </transaction>
        ...
      </session>
    

4.5.5  Privacy list testing

There are two actions available to allow for rudimentary privacy lists load testing:

  • privacy:get_names - gets the list of all names of privacy lists stored by the server for a given user
  • privacy:set_active - sets a list with a predefined name as active. The list name is determined from the JID, e.g. if the user’s JID is "john@average.com" then the list name is "john@average.com_list". One should take care of properly seeding the server database in order to ensure that such a list exists.

5  Using the proxy recorder

The recorder has three plugins: for HTTP, WebDAV and for PostgreSQL.

To start it, run tsung-recorder -p <PLUGIN> start, where PLUGIN can be http, webdav or pgsql for PostgreSQL. The default plugin is http.

The proxy is listening to port 8090. You can change the port with -L portnumber.

To stop it, use tsung-recorder stop.

The recorded session is created as ~/.tsung/tsung_recorderYYYMMDD-HH:MM.xml; if it doesn’t work, take a look at ~/.tsung/log/tsung.log-tsung_recorder@hostname

During the recording, you can add custom tag in the XML file, this can be useful to set transactions or comments: tsung-recorder record_tag "<transaction name=’login’>’’

Once a session has been created, you can insert it in your main configuration file, either by editing by hand the file, or by using an ENTITY declaration, like:

<!DOCTYPE tsung SYSTEM "/usr/share/tsung/tsung-1.0.dtd" [
 <!ENTITY mysession1 SYSTEM "/home/nniclausse/.tsung/tsung_recorder20051217-13:11.xml">
]>
...
<sessions>
  &mysession1;
</sessions>

5.1  PostgreSQL

For PostgreSQL, the proxy will connect to the server at IP 127.0.0.1 and port 5432. Use -I serverIP to change the IP and -P portnumber to change the port.

5.2  HTTP and WEBDAV

For HTTPS recording, use http://- instead of https:// in your browser

New in 1.2.2: For HTTP, you can configure the recorder to use a parent proxy (but this will not work for https). Add the -u option to enable parent proxy, and use -I serverIP to set the IP and -P portnumber to set the port of the parent.

6  Understanding tsung.xml configuration file

The default encoding is utf-8. You can use a different encoding, like in:

<?xml version="1.0" encoding="ISO-8859-1"?>
 

6.1  File structure

Scenarios are enclosed into Tsung tags:

<?xml version="1.0"?>
<!DOCTYPE tsung SYSTEM "/usr/share/tsung/tsung-1.0.dtd" [] >
<tsung loglevel="info">
...
</tsung>
 

If you add the attribute dumptraffic="true", all the traffic will be logged to a file. Warn: this will considerably slow down Tsung, so use with care. It is useful for debugging purpose. You can use the attribute dumptraffic="light" to dump only the first 44 bytes.

Since version 1.4.0, you have also a specific logging per protocol, using dumptraffic="protocol". It’s currently only implemented for HTTP: this will log all requests in a CSV file, with the following data:

#date;pid;id;http method;host;URL;HTTP status;size;match;error

The loglevel can also have a great impact on performance: For high load, warning is recommended. Possible values are:

  • emergency
  • critical
  • error
  • warning
  • notice (default)
  • info
  • debug

For REALLY verbose logging, recompile tsung with make debug and set loglevel to debug.

6.2  Clients and server

Scenarios start with clients (Tsung cluster) and server definitions:

6.2.1  Basic setup

For non distributed load, you can use a basic setup like:

 <clients>
    <client host="localhost" use_controller_vm="true"/>
 </clients>

 <servers>
    <server host="192.168.1.1" port="80" type="tcp"></server>
 </servers>
 

This will start the load on the same host and on the same Erlang virtual machine as the controller.

The server is the entry point into the cluster (New in 1.2.0: if several servers are defined, a round robin algorithm is used to choose the server).

Type can be tcp, ssl or udp (for IPv6, use tcp6, ssl6 or udp6 ; only available in version 1.4.2 and newer)

6.2.2  Advanced setup

The next example is more complex, and use several features for advanced distributed testing:

  <clients>
     <client host="louxor" weight="1" maxusers="800">
         <ip value="10.9.195.12"></ip>
         <ip value="10.9.195.13"></ip>
     </client>
     <client host="memphis" weight="3" maxusers="600" cpu="2"/>
  </clients>

<servers>
  <server host="10.9.195.1" port="8080" type="tcp"></server>
</servers>
 

Several virtual IP can be used to simulate more machines. This is very useful when a load-balancer use the client's IP to distribute the traffic among a cluster of servers. New in 1.1.1: IP is no longer mandatory. If not specified, the default IP will be used.

New in 1.4.0: You can use <ip scan="yes" value="eth0"/> to scan for all the IP aliases on a given interface (eth0 in this example).

In this example, a second machine is used in the Tsung cluster, with a higher weight, and 2 cpus. Two Erlang virtual machines will be used to take advantage of the number of CPU.

Note: Even if an Erlang VM is now able to handle several CPUs (erlang SMP), benchmarks shows that it’s more efficient to use one VM per CPU (with SMP disabled) for tsung clients. Only the controller node is using SMP erlang. Therefore, cpu should be equal to the number of cores of your nodes. If you prefer to use erlang SMP, add the -s option when starting tsung (and don’t set cpu in the config file).

By default, the load is distributed uniformly on all CPU (one CPU per client by default). The weight parameter (integer) can be used to take into account the speed of the client machine. For instance, if one real client has a weight of 1 and the other client has a weight of 2, the second one will start twice the number of users as the first (the proportions will be 1/3 and 2/3). In the earlier example where for the second client has 2 CPU and weight=3, the weight is equal to 1.5 for each CPU.

The maxusers parameter is used to bypass the limit of maximum number of sockets opened by a single process (1024 by default on many OS) and the lack of scalability of the select system call. When the number of users is higher than the limit, a new erlang virtual machine will be started to handle new users. The default value of maxusers is 800 . Nowadays, with kernel polling enable, you can and should use a very large value for maxusers (30000 for example) without performance penalty (but don’t forget to raise the limit of the OS with ulimit -n, see also FAQ A.3).

6.2.3  Running Tsung with a job scheduler

Tsung is able to get its client node list from a batch/job scheduler. It currently handle PBS/torque, LSF and OAR. To do this, set the type attribute to batch, e.g.:

  <client type="batch" batch="torque" maxusers="30000">

If you need to scan IP aliases on nodes given by the batch scheduler, use scan_intf like this:

  <client type="batch" batch="torque" scan_intf='eth0'  maxusers="30000">

6.3  Monitoring

Tsung is able to monitor remote servers using several backends that communicates with remote agent; This is configured in the <monitoring> section. Available statistics are: CPU activity, load average, memory usage.

Note that you can get the nodes to monitor from a job scheduler, like:

  <monitor batch="true" host="torque" type="erlang"></monitor>

Several types of remote agents are supported (erlang is the default) :

6.3.1  Erlang

The remote agent is started by Tsung. It use erlang communications to retrieve statistics of activity on the server. For example, here is a cluster monitoring definition based on Erlang agents, for a cluster of 6 computers:

  <monitoring>
    <monitor host="geronimo" type="erlang"></monitor>
    <monitor host="bigfoot-1" type="erlang"></monitor>
    <monitor host="bigfoot-2" type="erlang"></monitor>
    <monitor host="f14-1" type="erlang"></monitor>
    <monitor host="f14-2" type="erlang"></monitor>
    <monitor host="db" type="erlang"></monitor>
  </monitoring>

Note: monitored computers needs to be accessible through the network, and erlang communications must be allowed (no firewall is better ). SSH (or rsh) needs to be configured to allow connection without password on. You must use the same version of Erlang/OTP on all nodes otherwise it may not work properly !

If you can’t have erlang installed on remote servers, you can use one of the other available agents:

6.3.2  SNMP

The type keyword snmp can replace the erlang keyword, if SNMP monitoring is preferred. They can be mixed. Since version 1.2.2, you can customize the SNMP version, community and port number. It uses the MIB provided in net-snmp (see also A.9).

  <monitoring>
    <monitor host="geronimo" type="snmp"/>
    <monitor host="f14-2" type="erlang"></monitor>
    <monitor host="db" type="snmp">
      <snmp version="v2" community="mycommunity" port="11161"/>
    </monitor>
  </monitoring>

The default version is v1, default community public and default port 161.

Since version 1.4.2, you can also customize the OIDs retrieved from the SNMP server, using one or several oid element:

   <monitor host="127.0.0.1" type="snmp">
     <snmp version="v2">
       <oid value="1.3.6.1.4.1.42.2.145.3.163.1.1.2.11.0"
            name="heapused" type="sample" eval="fun(X)-> X/100 end."/>
     </snmp>
   </monitor>

type can be sample, counter or sum, and optionally you can define a function (with erlang syntax) to be applied to the value (eval attribute).

6.3.3  Munin

Since version 1.3.1, Tsung is able to retrieve data from a munin-node agent (see http://munin.projects.linpro.no/wiki/munin-node). The type keyword must be set to munin, for example:

  <monitoring>
    <monitor host="geronimo" type="munin"/>
    <monitor host="f14-2" type="erlang"></monitor>
  </monitoring>

6.4  Defining the load progression

6.4.1  Randomly generated users

The load progression is set-up by defining several arrival phases:

 <load>
  <arrivalphase phase="1" duration="10" unit="minute">
    <users interarrival="2" unit="second"></users>
  </arrivalphase>

  <arrivalphase phase="2" duration="10" unit="minute">
    <users interarrival="1" unit="second"></users>
  </arrivalphase>

  <arrivalphase phase="3" duration="10" unit="minute">
    <users interarrival="0.1" unit="second"></users>
  </arrivalphase>
 </load>

With this setup, during the first 10 minutes of the test, a new user will be created every 2 seconds, then during the next 10 minutes, a new user will be created every second, and for the last 10 minutes, 10 users will be generated every second. The test will finish when all users have ended their session.

You can also use arrivalrate instead of interarrival. For example, if you want 10 new users per second, use:

  <arrivalphase phase="1" duration="10" unit="minute">
    <users arrivalrate="10" unit="second"></users>
  </arrivalphase>

You can limit the number of users started for each phase by using the maxnumber attribute, just like this:

  <arrivalphase phase="1" duration="10" unit="minute">
    <users maxnumber="100" arrivalrate="10" unit="second"></users>
  </arrivalphase>
  <arrivalphase phase="2" duration="10" unit="minute">
    <users maxnumber="200" arrivalrate="10" unit="second"></users>
  </arrivalphase>

In this case, only 100 users will be created in the first phases, and 200 more during the second phase.

The complete sequence can be executed several times using the loop attribute in the load tag (loop=’2’ means the sequence will be looped twice, so the complete load will be executed 3 times) (feature available since version 1.2.2).

The load generated in terms of HTTP requests / seconds will also depend on the mean number of requests within a session (if you have a mean value of 100 requests per session and 10 new users per seconds, the theoretical average throughput will be 1000 requests/ sec).

6.4.2  Statically generated users

If you want to start a given session (see  6.6) at a given time during the test, it is possible since version 1.3.1:

 <load>
  <arrivalphase phase="1" duration="10" unit="minute">
    <users interarrival="2" unit="second"></users>
  </arrivalphase>
  <user session="http-example" start_time="185" unit="second"></user>
  <user session="http-example" start_time="10" unit="minute"></user>
  <user session="foo" start_time="11" unit="minute"></user>
</load>
 <sessions>
   <session name="http-example" probability="0" type="ts_http">
    <request> <http url="/" method="GET"></http> </request>
   </session>
   <session name="foo" probability="100" type="ts_http">
    <request> <http url="/" method="GET"></http> </request>
   </session>
 <sessions>

In this example, we have two sessions, one has a "0" probability (and therefore will not be used in the first phase), and the other 100%. We define 3 users starting respectively 3mn and 5 seconds after the beginning of the test (using the http-example session), one starting after 10 minutes, and a last one starting after 11 minutes (using the foo session this time)

6.4.3  Duration of the load test

By default, tsung will end when all started users have finished their session. So it can be much longer than the duration of arrivalphases. If you want to stop Tsung after a given duration (even if phases are not finished or if some sessions are still actives), you can do this with the duration attribute in load (feature added in 1.3.2):

 <load duration="1" unit="hour">
  <arrivalphase phase="1" duration="10" unit="minute">
    <users interarrival="2" unit="second"></users>
  </arrivalphase>
</load>

Currently, the maximum value for duration is a little bit less than 50 days. unit can be second, minute or hour.

6.5  Setting options

Thinktimes, SSL, Buffers

Default values can be set-up globally: thinktime between requests in the scenario, SSL cipher algorithms, TCP/UDP buffer sizes (the default value is 32KB). These values overrides those set in session configuration tags if override is true.

  <option name="thinktime" value="3" random="false" override="true"/>
  <option name="ssl_ciphers"
           value="EXP1024-RC4-SHA,EDH-RSA-DES-CBC3-SHA"/>
  <option name="tcp_snd_buffer" value="16384"></option>
  <option name="tcp_rcv_buffer" value="16384"></option>
  <option name="udp_snd_buffer" value="16384"></option>
  <option name="udp_rcv_buffer" value="16384"></option>
Hibernate

A new option is available in version 1.3.1: hibernate. This is used to reduced memory consumption of simulated users during thinktimes. By default, hibernation will be activated for thinktimes higher than 10sec. This value can be changed like this:

<option name="hibernate" value="5"></option>

To disable hibernation, you must set the value to infinity.

Rate_limit

A new option is available in version 1.4.0: rate_limit. This will limit the bandwidth of each client (using a token bucket algorithm). The value is in KBytes per second. You can also specify a maximum burst value (eg. max=’2048’). By default the burst size is the same as the rate (1024KB in the following example). Currently, only incoming traffic is rate limited.

  <option name="rate_limit" value="1024"></option>
Ports_range

If you need to open more than 30000 simultaneous connections on a client machine, you will be limited by the number of TCP client ports, even if you use several IPs (this is true at least on Linux). To bypass this limit, Tsung must not delegate the selection of client ports and together with using several IP for each client, you have to defined a range for available clients ports, for ex:

  <option name="ports_range" min="1025" max="65535"/>
Setting the seed for random numbers

If you want to use a fixed seed for the random generator, you can use the seed option, like this (by default, tsung will use the current time to set the seed, therefore random numbers should be different for every test).

<option name="seed" value="42"/>

6.5.1  XMPP/Jabber options

Default values for specific protocols can be defined. Here is an example of option values for Jabber/XMPP:

  <option type="ts_jabber" name="global_number" value="5" />
  <option type="ts_jabber" name="userid_max" value="100" />
  <option type="ts_jabber" name="domain" value="jabber.org" />
  <option type="ts_jabber" name="username" value="myuser" />
  <option type="ts_jabber" name="passwd" value="mypasswd" />
  <option type="ts_jabber" name="muc_service" value="conference.localhost"/>

Using these values, users will be myuserXXX where XXX is an integer in the interval [1:userid_max] and passwd mypasswdXXX

If not set in the configuration file, the values will be set to:

  • global_number = 10000
  • userid_max = 100
  • domain = erlang-projects.org
  • username = tsunguser
  • passwd = sesame

You can also set the muc_service here (see previous example).

6.5.2  HTTP options

For HTTP, you can set the UserAgent values (available since Tsung 1.1.0), using a probability for each value (the sum of all probabilities must be equal to 100)

  <option type="ts_http" name="user_agent">
    <user_agent probability="80">
       Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.8) Gecko/20050513 Galeon/1.3.21
    </user_agent>
    <user_agent probability="20">
      Mozilla/5.0 (Windows; U; Windows NT 5.2; fr-FR; rv:1.7.8) Gecko/20050511 Firefox/1.0.4
    </user_agent>
  </option>

6.6  Sessions

Sessions define the content of the scenario itself. They describe the requests to execute.

Each session has a given probability. This is used to decide which session a new user will execute. The sum of all session's probabilities must be 100.

A transaction is just a way to have customized statistics. Say if you want to know the response time of the login page of your website, you just have to put all the requests of this page (HTML + embedded pictures) within a transaction. In the example above, the transaction called index_request will gives you in the statistics/reports the mean response time to get index.en.html + header.gif. Be warn that If you have a thinktime inside the transaction, the thinktime will be part of the response time.

6.6.1  Thinktimes

You can set static or random thinktimes to separate requests. By default, a random thinktime will be a exponential distribution with mean equals to value.

<thinktime value="20" random="true"></thinktime>

In this case, the thinktime will be an exponential distribution with a mean equals to 20 seconds.

Since version 1.3.0, you can also use a range [min:max] instead of a mean for random thinktimes (the distribution will be uniform in the interval):

<thinktime min="2" max="10" random="true"></thinktime>

Since version 1.4.0, you can use a dynamic variable to set the thinktime value:

<thinktime value='%%_rndthink%%' random='true'></thinktime>

6.6.2  HTTP

This example shows several features of the HTTP protocol support in Tsung: GET and POST request, basic authentication, transaction for statistics definition, conditional request (IF MODIFIED SINCE), ...

<sessions>
  <session name="http-example" probability="70" type="ts_http">

    <request> <http url="/" method="GET" version="1.1">
                    </http> </request>
    <request> <http url="/images/logo.gif"
               method="GET" version="1.1"
               if_modified_since="Fri, 14 Nov 2003 02:43:31 GMT">
              </http></request>

    <thinktime value="20" random="true"></thinktime>

    <transaction name="index_request">
     <request><http url="/index.en.html"
                          method="GET" version="1.1" >
              </http> </request>
     <request><http url="/images/header.gif"
                          method="GET" version="1.1">
              </http> </request>
    </transaction>

    <thinktime value="60" random="true"></thinktime>
    <request>
      <http url="/" method="POST" version="1.1"
               contents="bla=blu">
      </http> </request>
    <request>
       <http url="/bla" method="POST" version="1.1"
             contents="bla=blu&amp;name=glop">
       <www_authenticate userid="Aladdin"
                         passwd="open sesame"/></http>
    </request>
  </session>

  <session name="backoffice" probability="30" ...>
  ... </session>
</sessions>

If you use an absolute URL, the server used in the URL will override the one specified in the <server> section. The following relative requests in the session will also use this new server value (until a new absolute URL is set).

New in 1.2.2: You can add any HTTP header now, as in:

   <request>
       <http url="/bla" method="POST" contents="bla=blu&amp;name=glop">
         <www_authenticate userid="Aladdin" passwd="open sesame"/>
         <http_header name="Cache-Control" value="no-cache"/>
         <http_header name="Referer" value="http://www.w3.org/"/>
      </http>
   </request>

New in 1.3.0: You can also read the content of a POST or PUT request from an external file:

 <http url='mypage' method='POST' contents_from_file='/tmp/myfile' />

Since 1.3.1, you can also manually set a cookie, though the cookie is not persistent: you must add it in every <requests>:

  <http url="/">
    <add_cookie key="foo" value="bar"/>
    <add_cookie key="id"  value="123"/>
  </http>

6.6.3  Jabber/XMPP

Here is an example of a session definition for the Jabber/XMPP protocol:

<sessions>
 <session probability="70" name="jabber-example" type="ts_jabber">

    <request> <jabber type="connect" ack="local" /> </request>

    <thinktime value="2"></thinktime>

    <transaction name="authenticate">
      <request> <jabber type="auth_get" ack="local"></jabber> </request>
      <request> <jabber type="auth_set_plain" ack="local"></jabber> </request>
    </transaction>

    <request> <jabber type="presence:initial" ack="no_ack"/> </request>


    <thinktime value="30"></thinktime>

    <transaction name="online">
    <request> <jabber type="chat" ack="no_ack" size="16" destination="online"/></request>
    </transaction>
    <thinktime value="30"></thinktime>

    <transaction name="offline">
      <request> <jabber type="chat" ack="no_ack" size="56" destination="offline"/><request>
    </transaction>

    <thinktime value="30"></thinktime>

    <transaction name="close">
      <request> <jabber type="close" ack="local"> </jabber></request>
    </transaction>
  </session>
</sessions>
Roster

What you can do with rosters using Tsung:

You can

  1. Add a new contact to their roster - The new contact is added to the Tsung Group group, and their name matches their JID
  2. Send a subscribe presence notification to the new contact’s JID - This results in a pending subscription
  3. Rename a roster contact This changes the previously added contact’s name from the default JID, to Tsung Testuser
  4. Delete the previously added contact.

Note that when you add a new contact, the contact JID is stored and used for the operations that follow. It is recommended that for each session which is configured to perform these operations, only do so once. In other words, you would NOT want to ADD more than one new contact per session. If you want to alter the rate that these roster functions are used during your test, it is best to use the session ’probability’ factor to shape this.

The nice thing about this is that when you test run is complete, your roster tables should look the same as before you started the test. So, if you set it up properly, you can have pre-loaded roster entries before the test, and then use these methods to dynamically add, modify, and remove roster entries during the test as well.

Example roster modification setup:

<session probability="100" name="jabber-rostermod" type="ts_jabber">

    <!-- connect, authenticate, roster 'get', etc... -->

    <transaction name="rosteradd">
      <request>
        <jabber type="iq:roster:add" ack="no_ack" destination="online"></jabber>
      </request>
      <request>
        <jabber type="presence:subscribe" ack="no_ack"/>
      </request>
    </transaction>

    <!-- ... -->

    <transaction name="rosterrename">
      <request> <jabber type="iq:roster:rename" ack="no_ack"></jabber> </request>
    </transaction>

    <!-- ... -->

    <transaction name="rosterdelete">
      <request> <jabber type="iq:roster:remove" ack="no_ack"></jabber> </request>
    </transaction>

    <!-- remainder of session... -->

  </session>

See also 4.5.2 for automatic handling of subscribing requests.

SASL Anonymous

SASL Anonymous authentication example:

<session probability="100" name="sasl" type="ts_jabber">

    <request> <jabber type="connect" ack="local"></jabber> </request>

    <thinktime value="10"></thinktime>

    <transaction name="authenticate">
     <request>
       <jabber type="auth_sasl_anonymous" ack="local"></jabber></request>

     <request>
       <jabber type="connect" ack="local"></jabber> </request>

    <request>
       <jabber type="auth_sasl_bind" ack="local" ></jabber></request>
    <request>
       <jabber type="auth_sasl_session" ack="local" ></jabber></request>

    </transaction>
Presence
  • type can be either presence:broadcast or presence:directed.
  • show value must be either away, chat, dnd, or xa.
  • status value can be any text.

For more info, see section 2.2 of RFC 3921.

If you omit the show or status attributes, they default to chat and Available respectively.

Example of broadcast presence (broadcast to members of your roster):

    <request>
      <jabber type="presence:broadcast" show="away" status="Be right back..." ack="no_ack"/>
    </request>

    <thinktime value="5"></thinktime>

    <request>
      <jabber type="presence:broadcast" show="chat" status="Available
      to chat" ack="no_ack"/>
    </request>

    <thinktime value="5"></thinktime>

    <request>
      <jabber type="presence:broadcast" show="dnd" status="Don't bother me!" ack="no_ack"/>
    </request>
    <thinktime value="5"></thinktime>

    <request>
     <jabber type="presence:broadcast" show="xa" status="I may never come back..."
      ack="no_ack"/>
    </request>
    <thinktime value="5"></thinktime>

    <request> <jabber type="presence:broadcast" ack="no_ack"/> </request>
    <thinktime value="5"></thinktime>

Example of directed presence (sent to random online users):

    <request>
      <jabber type="presence:directed" show="away" status="Be right back..." ack="no_ack"/>
    </request>
    <thinktime value="5"></thinktime>

    <request>
      <jabber type="presence:directed" show="chat" status="Available to chat" ack="no_ack"/>
    </request>
    <thinktime value="5"></thinktime>

    <request>
      <jabber type="presence:directed" show="dnd" status="Don't bother me!" ack="no_ack"/>
    </request>
    <thinktime value="5"></thinktime>

    <request>
      <jabber type="presence:directed" show="xa" status="I may never come back..."
        ack="no_ack"/>
     </request>
    <thinktime value="5"></thinktime>

    <request>
      <jabber type="presence:directed" ack="no_ack"/>
    </request>
    <thinktime value="5"></thinktime>
MUC

Tsung supports three MUC operations:

  1. Join a room (attribute type=’muc:join’)
  2. Send a message to a room (attribute type=’muc:chat’)
  3. Change nickname (attribute type=’muc:nick’)
  4. Exit a room (attribute type=’muc:exit)

Here’s an example:

<-- First, choose an random room and random nickname: -->
<setdynvars sourcetype="random_number" start="1" end="100">
 <var name="room"/>
</setdynvars>
<setdynvars sourcetype="random_string" length="10">
 <var name="nick1"/>
</setdynvars>

<request subst="true">
  <jabber type='muc:join' ack = "local" room = "room%%_room%%" nick = "%%_nick1%%"/>
</request>

<!-- use a for loop to send several messages to the room -->
<for from="1" to="6" var="i">
 <thinktime value="30"/>
 <request subst="true">
   <jabber type="muc:chat" ack="no_ack" size="16" room =
   "room%%_room%%"/>
 </request>
</for>

<!-- change nickname-->
<thinktime value="2"/>
<setdynvars sourcetype="random_string" length="10">
 <var name="nick2"/>
</setdynvars>

<request subst="true">
 <jabber type="muc:nick" room="room%%_room%%" nick="%%_nick2%%"
         ack="no_ack"/>
</request>

MUC support is available since version 1.3.1

PubSub

Experimental support for PubSub is available in version 1.3.1

You can read the following entry: https://support.process-one.net/browse/TSUN-115

VHost

VHost support is available since version 1.3.2

Tsung is able to bench multiple vhost instances by choosing a vhost XMPP name from a list at connection time in the scenario.

The vhost list is read from a file:

<options>
...
<option name="file_server" value="domains.csv" id="vhostfileId"></option>
...
<option type="ts_jabber" name="vhost_file" value="vhostfileId"></option>
...
</options>

When each client starts a session, it chooses randomly a domain (each domain has the same probability).

Reading usernames and password from a CSV file

Since version 1.4.0, you can now use a CSV file to store the usernames and password.

Configure the CSV file:

 <options>
  <option name="file_server" id='userdb' value="/home/foo/.tsung/users.csv"/>
 </options>

And then you have to defined two variables of type file, and the first jabber request (connect) must include a xmpp_authenticate tag:

<session probability="100" name="jabber-example" type="ts_jabber">

  <setdynvars sourcetype="file" fileid="userdb" delimiter=";" order="iter">
    <var name="username" />
    <var name="password" />
  </setdynvars>

    <request subst='true'>
      <jabber type="connect" ack="no_ack">
         <xmpp_authenticate username="%%_username%%" passwd="%%_password%%"/>
      </jabber>
    </request>

   <thinktime value="2"></thinktime>

   <transaction name="authenticate">

   <request>
     <jabber type="auth_get" ack="local"> </jabber>
   </request>
   <request>
     <jabber type="auth_set_plain" ack="local"></jabber>
   </request>

  </transaction>
...
</session>
raw XML

You can send raw XML data to the server using the raw type:

<jabber type="raw" ack="no_ack" data="&lt;stream&gt;foo&lt;/stream&gt;"></jabber>

Beware: you must encode XML characters like < ,>, &, etc.

6.6.4  PostgreSQL

For PostgreSQL, 4 types of requests are available:

  1. connect (to a given database with a given username
  2. authenticate (with password or not)
  3. sql (basic protocol)
  4. close

In addition, the following parts of the extended protocol is supported:

  1. copy, copydone and copyfail
  2. parse, bind, execute, describe
  3. sync, flush

This example shows most of the features of a PostgreSQL session:

  <session probability="100" name="pgsql-example" type="ts_pgsql">
    <transaction name="connection">
      <request>
        <pgsql type="connect" database="bench" username="bench" />
      </request>
    </transaction>

    <request><pgsql type="authenticate" password="sesame"/></request>

    <thinktime value="12"/>

    <request><pgsql type="sql">SELECT * from accounts;</pgsql></request>

    <thinktime value="20"/>

    <request><pgsql type="sql">SELECT * from users;</pgsql></request>

    <request><pgsql type='sql'><![CDATA[SELECT n.nspname as "Schema",
  c.relname as "Name",
  CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'i'
  THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN '%_toto_% END as "Type",
  u.usename as "Owner"
FROM pg_catalog.pg_class c
     LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','v','S','')
      AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
      AND pg_catalog.pg_table_is_visible(c.oid)
ORDER BY 1,2;]]></pgsql></request>

    <request><pgsql type="close"></pgsql></request>

  </session>

Example with the extended protocol:

<request><pgsql type='parse' name_prepared='P0_7'><![CDATA[BEGIN;]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='parse' name_prepared='P0_8'><![CDATA[UPDATE pgbench_accounts
  SET abalance = abalance + $1 WHERE aid = $2;]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='parse' name_prepared='P0_9'><![CDATA[SELECT
  abalance FROM pgbench_accounts
  WHERE aid = $1;]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='parse' name_prepared='P0_10'><![CDATA[UPDATE pgbench_tellers
   SET tbalance = tbalance + $1 WHERE tid = $2;]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='parse' name_prepared='P0_11'><![CDATA[UPDATE pgbench_branches
   SET bbalance = bbalance + $1 WHERE bid = $2;]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='parse' name_prepared='P0_12'><![CDATA[INSERT
   INTO pgbench_history (tid, bid, aid, delta, mtime)
   VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP);]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='parse' name_prepared='P0_13'><![CDATA[END;]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='bind' name_prepared='P0_7' formats='none' formats_results='text' /></request>
<request><pgsql type='describe' name_portal=''/></request>
<request><pgsql type='execute'/></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='bind' name_portal='' name_prepared='P0_8'
   formats='none' formats_results='text'
   parameters='2924,37801'/></request>

6.6.5  MySQL

For MySQL, 4 types of requests are available (same as PostgreSQL):

  1. connect (to a given database with a given username
  2. authenticate (with password or not)
  3. sql
  4. close

This example shows most of the features of a MySQL session:

<session probability="100" name="mysql-example" type="ts_mysql">
 <request>
  <mysql type="connect" />
 </request>
 <request>
  <mysql type="authenticate" database="test" username="test" password="test" />
 </request>
 <request>
  <mysql type="sql">SHOW TABLES</mysql>
 </request>
 <request>
  <mysql type="sql">SELECT * FROM mytable</mysql>
 </request>
 <request>
  <mysql type="close" />
 </request>
</session>

6.6.6  LDAP

Authentication

The recommended mechanism used to authenticate users against a LDAP repository requires two steps to follow. Given an username and password, we:

  1. Search the user in the repository tree, using the username (so users can reside in different subtrees of the organization)
  2. Try to bind as the user, with the distinguished name found in the first step and the user’s password

If the bind is successful, the user is authenticated (this is the scheme used, among others, by the LDAP authentication module for Apache http://httpd.apache.org/docs/2.0/mod/mod_auth_ldap.html)

LDAP Setup

For this example we are going to use a simple repository with the following hierarchy:


images/ldap-hierarchy.png
Figure 1: LDAP Hierarchy

the repository has users in two organizational units

  1. users (with four members)
  2. users2 (with tree members)

For simplicity we set the password of each user to be the same as its common name (cn). Tsung Setup We will use a CSV file as input, containing the user:password pairs for our test. So we start by writing it, in this case we name the file users.csv

user1;user1
user2;user2
user3;user3
user4;user4
jane;jane
mary;mary
paul;pablo
paul;paul

(the pair paul:pablo should fail to authenticate, we will note that in the Tsung report) Then, in our Tsung scenario, we let Tsung know about this file

    <options>
       <option name="file_server" id="users" value="users.csv"/>
    </options>
We use two dynamic variables to hold the username and password
    <setdynvars sourcetype="file" fileid="users" delimiter=";" order="iter">
               <var name="username" />
               <var name="password" />
     </setdynvars>

To start the authentication process we instruct Tsung to perform a search, to find the distinguished name of the user we are trying to authenticate

  <ldap type="search" base="dc=pablo-desktop" filter="(cn=%%_username%%)"
        result_var="search_result" scope="wholeSubtree"></ldap>

As we need to access the search result, we specify it using the result_var attribute. This attribute tells Tsung in which dynamic variable we want to store the result (if the result_var attribute isn’t set, Tsung doesn’t store the search result in any place). Finally, we try to bind as that user.

<request subst="true">
<ldap type="bind" user="%%ldap_auth:user_dn%%"
      password="%%_password%%"></ldap>
</request>

The only thing that remains to do is to implement the ldap_auth:user_dn function, that extract the distinguished name from the search result.

-module(ldap_auth).
-export([user_dn/1]).
user_dn({_Pid,DynVars}) ->
      [SearchResultEntry] = proplists:get_value(search_result,DynVars),
      {_,DN,_} = SearchResultEntry,
      DN.

We aren’t covering errors here. supposing that there is always one (and only one) user found, that we extract from the search_result variable (as defined in the previous search operation). Each entry in the result set is a SearchResultEntry record. The record definition can be found in <TSUNG_DIR>/include/ELDAPv3.hrl.

As we only need to access the distinguished name of the object, we index into the result tuple directly. But if you need to access other attributes you probably will want to include the appropriate .hrl and use the record syntax instead. One of the eight user:password pairs in our users file was wrong, so we expect 1/8 of the authentication attempts to fail.

Indeed, after running the scenario we can confirm this in the Tsung report (see figure 2). The bind operation maintains two counters: ldap_bind_ok and ldap_bind_error, that counts successful and unsuccessful bind attempts.


images/ldap-results.png
Figure 2: LDAP Results

Other examples
<session probability="100" name="ldap-example" type="ts_ldap">
  <request>
     <ldap type="bind" user="uid=foo" password="bar"/>
  </request>

   <request>
       <ldap type="search" base="dc=pablo-desktop" filter="(cn=user2)"
       scope="wholeSubtree"></ldap>
   </request>

<!-- Add. Adds a new entry to the directory* -->
  <request subst="true">
  <ldap type="add" dn="%%_new_user_dn%%" >
   <attr type="objectClass">
    <value>organizationalPerson</value>
    <value>inetOrgPerson</value>
    <value>person</value>
   </attr>
   <attr type="cn"><value>%%_new_user_cn%%</value></attr>
   <attr type="sn"><value>fffs</value></attr>
 </ldap>
</request>

<!-- Modify. Modifies an existing entry; type=add|delete|modify-->
 <request subst="false">
   <ldap type="modify" dn="cn=u119843,dc=pablo-desktop" >
    <modification type="replace">
     <attr type="sn"><value>SomeSN</value></attr>
     <attr type="mail"><value>some@mail.com</value></attr>
    </modification>
  </ldap>
 </request>
</session>

6.6.7  Mixing session type

Since version 1.3.2, a new tag change_type can be used in a session to change it’s type.

    <request> <jabber type="chat" ack="no_ack" size="16"
destination="offline"/> </request>

    <thinktime value="3"/>

    <change_type new_type="ts_http" host="foo.bar" port="80"
server_type="tcp" store="true"/>

    <request> <http url="http://foo.bar/"/> </request>
    <request> <http url="/favicon"/> </request>

    <change_type new_type="ts_jabber" host="localhost" port="5222"
server_type="tcp" restore="true"/>

    <request> <jabber type="chat" ack="no_ack" size="16"
destination="previous"/> </request>

store=’true’ can be used to save the current state of the session (socket, cookies for http, …) and restore=’true’ to reuse the previous state when you switch back to the old protocol.

A dynamic variable set in the first part of the session will be available after a change_type. There is currently one caveat: you have to use a full URL in the first http request after a <change_type> (a relative URL will fail).

6.7  Advanced features

6.7.1  Dynamic substitutions

Dynamic substitution are mark-up placed in element of the scenario. For HTTP, this mark-up can be placed in basic authentication (www_authenticate tag: userid and passwd attributes), URL (to change GET parameter) and POST content.

Those mark-up are of the form %%Module:Function%%. Substitutions are executed on a request-by-request basis, only if the request tag has the attribute subst="true".

When a substitution is requested, the substitution mark-up is replaced by the result of the call to the Erlang function: Module:Function({Pid, DynData}) where Pid is the Erlang process id of the current virtual user and DynData the list of all Dynamic variables (Warn: before version 1.1.0, the argument was just the Pid !).

Here is an example of use of substitution in a Tsung scenario:

<session name="rec20040316-08:47" probability="100" type="ts_http">
 <request subst="true">
  <http url="/echo?symbol=%%symbol:new%%" method="GET">
  </http></request>
</session>

Here is the Erlang code of the module used for dynamic substitution:

-module(symbol).
-export([new/1]).

new({Pid, DynData}) ->
    case random:uniform(3) of
        1 -> "IBM";
        2 -> "MSFT";
        3 -> "RHAT"
    end.

(use erlc to compiled the code, and put the resulting .beam file in \$PREFIX/lib/erlang/lib/tsung-X.X.X/ebin/ on all client machines)

As you can see, writing scenario with dynamic substitution is simple. It can be even simpler using dynamic variables (see later).

If you want to set unique id, you can use the built-in function ts_user_server:get_unique_id.

<session name="rec20040316-08:47" probability="100" type="ts_http">
 <request subst="true">
  <http url="/echo?id=%%ts_user_server:get_unique_id%%" method="GET">
  </http></request>
</session>

6.7.2  Reading external file

New in 1.0.3: A new module ts_file_server is available. You can use it to read external files. For example, if you need to read user names and passwd from a CSV file, you can do it with it (currently, you can read only a single file).

You have to add this in the XML configuration file:

 <option name="file_server"  value="/tmp/userlist.csv"></option>

New in 1.2.2: You can read several files, using the id attribute to identify each file:

 <option name="file_server"  value="/tmp/userlist.csv"></option>
 <option name="file_server" id='random' value="/tmp/randomnumbers.csv"></option>

Now you can build you own function to use it, for example, create a file called readcsv.erl:

-module(readcsv).
-export([user/1]).

user({Pid,DynVar})->
    {ok,Line} = ts_file_server:get_next_line(),
    [Username, Passwd] = string:tokens(Line,";"),
    "username=" ++ Username ++"&password=" ++ Passwd.

The output of the function will be a string username=USER&password=PASSWORD

Then compile it with erlc readcsv.erl and put readcsv.beam in \$prefix/lib/erlang/lib/tsung-VERSION/ebin directory (if the file has an id set to random, change the call to:
ts_file_server:get_next_line(random)).

Then use something like this in your session:

  <request subst="true">
    <http url='/login.cgi' version='1.0' contents='%%readcsv:user%%&amp;op=login'
    content_type='application/x-www-form-urlencoded' method='POST'>
    </http>
  </request>

Two functions are available: ts_file_server:get_next_line and ts_file_server:get_random_line. For the get_next_line function, when the end of file is reached, the first line of the file will be the next line.

New in 1.3.0: you no longer have to create an external function to parse a simple csv file: you can use setdynvars (see next section for detailed documentation):

<setdynvars sourcetype="file" fileid="userlist.csv" delimiter=";" order="iter">
 <var name="username" />
 <var name="user_password" />
</setdynvars>

This defines two dynamic variables username and user_password filled with the next entry from the csv file. Using the previous example, the request is now:

  <request subst="true">
    <http url='/login.cgi' version='1.0'
      contents='username=%%_username%%&amp;password=%%_user_password%%&amp;op=login'
    content_type='application/x-www-form-urlencoded' method='POST'>
    </http>
  </request>

Much simpler than the old method !

6.7.3  Dynamic variables

In some cases, you may want to use a value given by the server in a response later in the session, and this value is dynamically generated by the server for each user. For this, you can use <dyn_variable> in the scenario

Let’s take an example with HTTP. You can easily grab a value in a HTML form like:

<form action="go.cgi" method="POST">
<hidden name="random_num" value="42"></form>
</form>

with:

 <request>
   <dyn_variable name="random_num" ></dyn_variable>
   <http url="/testtsung.html" method="GET" version="1.0"></http>
 </request>

Now random_num will be set to 42 during the user’s session. It’s value will be replace in all mark-up of the form %%_random_num%% if and only if the request tag has the attribute subst="true", like:

    <request subst="true">
      <http url='/go.cgi' version='1.0'
      contents='username=nic&amp;random_num=%%_random_num%%&amp;op=login'
      content_type='application/x-www-form-urlencoded' method='POST'>
      </http>
    </request>
Regexp

If the dynamic value is not a form variable, you can set a regexp by hand, for example to get the title of a HTML page: the regexp engine uses the re module, a Perl like regular expressions module for Erlang.

    <request>
      <dyn_variable name="mytitlevar"
                    re="&lt;title&gt;(.*)&lt;/title&gt;"/>
      <http url="/testtsung.html" method="GET" version="1.0"></http>
    </request>

Previously (before 1.4.0), Tsung uses the old regexp module from erlang. This is now deprecated. The syntax was:

    <request>
      <dyn_variable name="mytitlevar"
                    regexp="&lt;title&gt;\(.*\)&lt;/title&gt;"/>
      <http url="/testtsung.html" method="GET" version="1.0"></http>
    </request>
XPath

A new way to analyze the server response has been introduced in the release 1.3.0. It is available only for the HTTP and XMPP plugin since it is based on XML/HTML parsing. This feature uses the mochiweb library and only works with erlang R12B and newer version.

This give us some benefices:

  • XPath is simple to write and to read, and match very well with HTML/XML pages
  • The parser works on binaries(), and doesn’t create any string().
  • The cost of parsing the HTML/XML and build the tree is amortized between all the dyn_variables defined for a given request

To utilize xpath expression, use a xpath attribute when defining the dyn_variable, instead of re, like:

<dyn_variable name="field1_value" xpath="//input[@name='field1']/@value"/>
<dyn_variable name="title" xpath="/html/head/title/text()"/>

There is a bug in the xpath engine, result nodes from "descendant-or-self" aren’t returned in document order. This isn’t a problem for the most common cases. However, queries like //img[1]/@src are not recommended, as the order of the <img> elements returned from //img is not the expected. The order is respected for paths without "descendant-or-self" axis, so this: /html/body/div[2]/img[3]/@src is interpreted as expected and can be safely used.

It is possible to use Xpath to get a list of elements from an html page, allowing dynamic retrieval of objects. You can either create embedded erlang code to parse the list produced, or use foreach that was introduced in release (1.4.0).

For XMPP, you can get all the contacts in a dynamic variable:

<request subst="true">
    <dyn_variable name="contactJids"
      xpath="//iq[@type='result']/query[@xmlns='jabber:iq:roster']//item[string-length(@wr:type)=0]/@jid"
    />
    <jabber type="iq:roster:get" ack="local"/>
</request>
JSONPath

Another way to analyze the server response has been introduced in the release 1.3.2 when the server is sending JSON data. It is only for the HTTP plugin. This feature uses the mochiweb library and only works with erlang R13B and newer version.

Tsung implements a (very) limited subset of JSONPath as defined here http://goessner.net/articles/JsonPath/

To utilize jsonpath expression, use a jsonpath attribute when defining the dyn_variable, instead of re, like:

<dyn_variable name="array3_value" jsonpath="field.array[3].value"/>

You can also use expressions Key=Val, e.g.:

<dyn_variable name="myvar" jsonpath="field.array[?name=bar].value"/>
PostgreSQL

New in 1.3.2!

Since the PostgreSQL protocol is binary, regexp are not useful to parse the output of the server. Instead, a specific parsing can be done to extract content from the server’s response; to do this, use the pgsql_expr attribute. Use data_row[L][C] to extract the column C of the line L of the data output. You can also use the literal name of the column (ie. the field name of the table). This example extract 3 dynamic variables from the server’s response:

First one, extract the 3rd column of the fourth row, then the mtime field from the second row, and then it extract some data of the row_description.

    <request>
      <dyn_variable name="myvar" pgsql_expr="data_row[4][3]"/>
      <dyn_variable name="mtime" pgsql_expr="data_row[2].mtime"/>
      <dyn_variable name="row" pgsql_expr="row_description[1][3][1]"/>
      <pgsql type="sql">SELECT * from pgbench_history LIMIT 20;</pgsql>
     </request>

A row description looks like this:

=INFO REPORT==== 14-Apr-2010::11:03:22 ===
            ts_pgsql:(7:<0.102.0>) PGSQL: Pair={row_description,
                                                [{"tid",text,1,23,4,-1,16395},
                                                 {"bid",text,2,23,4,-1,16395},
                                                 {"aid",text,3,23,4,-1,16395},
                                                 {"delta",text,4,23,4,-1,
                                                  16395},
                                                 {"mtime",text,5,1114,8,-1,
                                                  16395},
                                                 {"filler",text,6,1042,-1,26,
                                                  16395}]}

So in the example, the row variable equals "aid".

set_dynvars

Since version 1.3.0, more powerful dynamic variables are implemented:

You can set dynamic variables not only while parsing server data, but you can build them using external files or generate them with a function or generate random numbers/strings:

Six types of dynamic variables are currently implemented (sourcetype tag):

  1. Dynamic variables defined by calling an erlang function:
          <setdynvars sourcetype="erlang" callback="ts_user_server:get_unique_id">
            <var name="id1" />
          </setdynvars>
    
  2. Dynamic variables defined by parsing an external file:
          <setdynvars sourcetype="file" fileid="userdb" delimiter=";" order="iter">
            <var name="user" />
            <var name="user_password" />
          </setdynvars>
    
    delimiter can be any string, and order can be iter or random
  3. A dynamic variable can be a random number (uniform distribution)
          <setdynvars sourcetype="random_number" start="3" end="32">
            <var name="rndint" />
          </setdynvars>
    
  4. A dynamic variable can be a random string
          <setdynvars sourcetype="random_string" length="13">
            <var name="rndstring1" />
          </setdynvars>
    
  5. A dynamic variable can be a urandom string: this is much faster than the random string, but the string is not really random: the same set of characters is always used.
  6. A dynamic variable can be generated by dynamic evaluation of erlang code:
          <setdynvars sourcetype="eval"
                      code="fun({Pid,DynVars})->
                               {ok,Val}=ts_dynvars:lookup(md5data,DynVars),
                               ts_digest:md5hex(Val) end.">
            <var name="md5sum" />
          </setdynvars>
    

    In this case, we use tsung function ts_dynvars:lookup to retrieve the dynamic variable named md5data. This dyn_variable md5data can be set in any of the ways described in the Dynamic variables section 6.7.3.

  7. A dynamic variable can be generated by applying a JSONPath specification (see 6.7.3) to an existing dynamic variable:
      <setdynvars sourcetype="jsonpath" from='notification' jsonpath="result[?state=OK].node">
          <var name="deployed" />
      </setdynvars>
    

A setdynvars can be defined anywhere in a session.

6.7.4  Checking the server’s response

With the tag match in a request tag, you can check the server’s response against a given string, and do some actions depending on the result. In any case, if it matches, this will increment the match counter, if it does not match, the nomatch counter will be incremented.

For example, let’s say you want to test a login page. If the login is ok, the server will respond with Welcome ! in the HTML body, otherwise not. To check that:

 <request>
      <match do="continue" when="match">Welcome !</match>
      <http url='/login.php' version='1.0' method='POST'
       contents='username=nic&amp;user_password=sesame'
       content_type='application/x-www-form-urlencoded' >
 </request>

You can use a regexp instead of a simple string.

The list of available actions to do is:

  • continue: do nothing, continue (only update match or nomatch counters)
  • log: log the request id, userid, sessionid in a file (in match.log)
  • abort : abort the session
  • restart: restart the session. The maximum number of restarts is 3 by default.
  • loop: repeat the request, after 5 seconds. The maximum number of loops is 20 by default.
  • dump: dump the content of the response in a file. The filename is match-<userid>-<sessionid>-<requestid>-<dumpid>.dump

You can mixed several match tag in a single request:

 <request>
      <match do="loop" sleep_loop="5" max_loop="10" when="match">Retry</match>
      <match do="abort" when="match">Error</match>
      <http url='/index.php' method=GET'>
 </request>

You can also do the action on "nomatch" instead of "match".

If you want to skip the HTTP headers, and match only on the body, you can use skip_headers=’http’. Also, you can apply a function to the content before matching; for example the following example use both features to compute the md5sum on the body of a HTTP response, and compares it to a given value:

<match do='log' when='nomatch' skip_headers='http'
  apply_to_content='ts_digest:md5hex'>01441debe3d7cc65ba843eee1acff89d</match>
<http url="/" method="GET" version="1.1"/>

You can also use dynamic variables, using the subst attribute:

<match do='log' when='nomatch' subst='true' >%%_myvar%%</match>
<http url="/" method="GET"/>

6.7.5  Loops, If, Foreach

Since 1.3.0, it’s now possible to add conditional/unconditional loops in a session.

Since 1.4.0, it is possible to loop through a list of dynamic variables thanks to foreach.

<for>

Repeat the enclosing actions a fixed number of times. A dynamic variable is used as counter, so the current iteration could be used in requests. List of attributes:

from
Initial Value
to
Last value
incr
Amount to increment in each iteration
var
Name of the variable to hold the counter
 <for from="1" to="10" incr="1" var="counter">
  [...]
  <request> <http url="/page?id=%%_counter%%"></http> </request>
  [...]
 </for>
<repeat>

Repeat the enclosing action (while|until) some condition. This is intended to be used together with dyn_variable declarations. List of attributes:

name
Name of the repeat
max_repeat
Max number of loops (default value is 20)

The last element of repeat must be either <while> or <until> example:

  <repeat name="myloop" max_repeat="40">

   [...]

   <request>
     <dyn_variable name="result" re="Result: (.*)"/>
     <http url="/random" method="GET" version="1.1"></http>
   </request>

   [...]

  <until var="result" eq="5"/> </repeat>

Since 1.3.1, it’s also possible to add if statements based on dynamic variables:

<if>
<if var="tsung_userid" eq="3">
  <request> <http url="/foo"/> </request>
  <request> <http url="/bar"/> </request>
</if>

You can use eq or neq to check the variable.

If the dynamic variable is a list (output from xpath for example), you can access to the nth element of a list like this:

<if var="myvar[1]" eq="3">

(here we compare the first element of the list to 3)

<foreach>

Repeat the enclosing actions for all the elements contained in the list specified. The basic syntax is as follows:

<foreach name="element" in="list">
  <request subst="true">
    <http url="%%_element%%" method="GET" version="1.1"/>
  </request>
</foreach>

It is possible to limit the list of elements you’re looping through, thanks to the use of the include or exclude attributes inside the foreach statement.

As an example, if you want to include only elements with a local path you can write:

<foreach name="element" in="list" include="^/.*$">

If you want to exclude all the elements from a specific URI, you would write:

<foreach name="element" in="list" exclude="http:\/\/.*\.tld\.com\/.*$">

You can combine this with a xpath query. For instance the following scenario will retrieve all the images specified on a web page:

<request subst="true">
  <dyn_variable name="img_list" xpath="//img/@src"/>
  <http url="/mypage.html" method="GET" version="1.1"/>
</request>
<foreach name="img" in="img_list">
  <request subst="true">
    <http url="%%_img%%" method="GET" version="1.1"/>
  </request>
</foreach>

6.7.6  Rate limiting

Since version 1.4.0, rate limiting can be enabled, either globally (see 6.5), or for each session separately.

For example, to limit the rate to 64KB/sec for a given session:

  <session name="http-example" probability="70" type="ts_http">
   <set_option name="rate_limit" value="64"></option>
   ...

Only the incoming traffic is rate limited currently.

7  Statistics and reports

7.1  File format

By default, tsung use it’s own format (see FAQ A.6).

Since version 1.4.2, you can configure tsung to use a JSON format; however in this case, the tools tsung_stats.pl and tsung_plotter will not work with the JSON files.

To enable JSON output, use:

<tsung backend='json' ...>

Example output file with JSON:

{
 "stats": [
 {"timestamp": 1317413841,  "samples": []},
 {"timestamp": 1317413851,  "samples": [
   {"name": "users", "value": 0, "max": 0},
   {"name": "users_count", "value": 0, "total": 0},
   {"name": "finish_users_count", "value": 0, "total": 0}]},
 {"timestamp": 1317413861,  "samples": [
   {"name": "users", "value": 0, "max": 1},
   {"name": "load", "hostname": "requiem", "value": 1, "mean":
     0.0,"stdvar": 0,"max": 0.0,"min": 0.0 ,"global_mean": 0
     ,"global_count": 0},
   {"name": "freemem", "hostname": "requiem", "value": 1, "mean":
     2249.32421875,"stdvar": 0,"max": 2249.32421875,"min":
     2249.32421875 ,"global_mean": 0 ,"global_count": 0},
   {"name": "cpu", "hostname": "requiem", "value": 1, "mean":
     4.790419161676647,"stdvar": 0,"max": 4.790419161676647,"min":
     4.790419161676647 ,"global_mean": 0 ,"global_count": 0},
   {"name": "session", "value": 1, "mean": 387.864990234375,"stdvar":
     0,"max":  387.864990234375,"min": 387.864990234375
     ,"global_mean": 0 ,"global_count": 0},
   {"name": "users_count", "value": 1, "total": 1},
   {"name": "finish_users_count", "value": 1, "total": 1},
   {"name": "request", "value": 5, "mean": 75.331787109375,"stdvar":
     46.689242405019954,"max":  168.708984375,"min": 51.744873046875
     ,"global_mean": 0 ,"global_count": 0},
   {"name": "page", "value": 1, "mean": 380.7548828125,"stdvar":
     0.0,"max":  380.7548828125,"min": 380.7548828125 ,"global_mean":
     0 ,"global_count": 0},
   {"name": "connect", "value": 1, "mean": 116.70703125,"stdvar":
     0.0,"max":  116.70703125,"min": 116.70703125 ,"global_mean": 0
     ,"global_count": 0},
   {"name": "size_rcv", "value": 703, "total": 703},
   {"name": "size_sent", "value": 1083, "total": 1083},
   {"name": "connected", "value": 0, "max": 0}, {"name": "http_304", "value": 5, "total": 5}]}]}

7.2  Available stats

  • request Response time for each request.
  • page Response time for each set of requests (a page is a group of request not separated by a thinktime).
  • connect Duration of the connection establishment.
  • reconnect number of reconnection.
  • size_rcv Size of responses in bytes.
  • size_sent Size of requests in bytes.
  • session Duration of a user’s session.
  • users Number of simultaneous users.
  • connected Number of simultaneous connected users. new in 1.2.2.
  • custom transactions

The mean response time (for requests, page, etc.) is computed every 10 sec (and reset). That’s why you have the highest mean and lowest mean values in the Stats report. Since version 1.3.0, the mean for the whole test is also computed.

HTTP specific stats:

  • counter for each response status (200, 404, etc.)

Jabber specific stats:

  • request_noack Counter of no_ack requests. Since response time is meaningless with no_ack requests, we keep a separate stats for this. new in 1.2.2.
  • async_unknown_data_rcv Only if bidi is true for a session. counter the number of messages received from the server without doing anything. new in 1.2.2.
  • async_data_sent Only if bidi is true for a session. Count the number of messages sent to the server in response of a message received from the server. new in 1.2.2.

7.3  Design

A bit of explanation on the design and internals of the statistics engine:

Tsung was designed to handle thousands of requests/sec, for very long period of times (several hours) so it do not write all data to the disk (for performance reasons). Instead it computes on the fly an estimation of the mean and standard variation for each type of data, and writes these estimations every 10 seconds to the disk (and then starts a new estimation for the next 10 sec). These computations are done for two kinds of data:

  • sample, for things like response time
  • sample_counter when the input is a cumulative one (number of packet sent for ex.).

There are also two other types of useful data (no averaging is done for those) :

  • counter: a simple counter, for HTTP status code for ex.
  • sum for ex. the cumulative HTTP response’s size (it gives an estimated bandwidth usage).

7.4  Generating the report

cd to the log directory of your test (say ~/.tsung/log/20040325-16:33/) and use the script tsung_stats.pl:

/usr/lib/tsung/bin/tsung_stats.pl

You can generate the statistics even when the test is running !

use –help to view all available options:

Available options:
        [--help] (this help text)
        [--verbose] (print all messages)
        [--debug] (print receive without send messages)
        [--dygraph] use dygraphs (http://danvk.org/dygraphs/) to render graphs
        [--noplot]  (don't make graphics)
        [--gnuplot <command>]  (path to the gnuplot binary)
        [--nohtml]  (don't create HTML reports)
        [--logy]  (logarithmic scale for Y axis)
        [--tdir <template_dir>] (Path to the HTML tsung templates)
        [--noextra  (don't generate graphics from extra data (os monitor, etc)
        [--rotate-xtics  (rotate legend of x axes)
        [--stats <file>] (stats file to analyse, default=tsung.log)
        [--img_format <format>] (output format for images, default=png
                                 available format: ps, svg, png, pdf)

Version 1.4.0 adds a new graphical output based on http://danvk.org/dygraphs/.

7.5  Tsung summary

Figure 3 shows an example of a summary report.


images/tsung-report.png
Figure 3: Report

7.6  Graphical overview

Figure 4 shows an example of a graphical report.


images/tsung-graph.png
Figure 4: Graphical output

7.7  Tsung Plotter

Tsung-Plotter (tsplot command) is an optional tool recently added in the Tsung distribution (it is written in Python), useful to compare different tests runned by Tsung. tsplot is able to plot data from several tsung.log files onto the same charts, for further comparisons and analyzes. You can easily customize the plots you want to generate by editing simple configuration files. You can get more information in the manual page of the tool (man tsplot).

Example of use:

tsplot "First test" firsttest/tsung.log "Second test" secondtest/tsung.log -d outputdir

Here’s an example of the charts generated by tsplot (figure 5):


images/connected.png
Figure 5: Graphical output of tsplot

7.8  RRD

A contributed perl script tsung-rrd.pl is able to create rrd files from the tsung log files. It’s available in /usr/lib/tsung/bin/tsung-rrd.pl

8  References

9  Acknowledgments

The first version of this document was based on a talk given by Mickael Rémond4 during an Object Web benchmarking workshop in April 2004 (more info at http://jmob.objectweb.org/).

A  Frequently Asked Questions

A.1  Can’t start distributed clients: timeout error

Most of the time, when a crash happened at startup without any traffic generated, the problem arise because the main Erlang controller node cannot create a "slave" Erlang virtual machine. The message looks like:

Can't start newbeam on host 'XXXXX (reason: timeout) ! Aborting!

The problem is that the Erlang slave module cannot start a remote slave node.

You can test this using this simple command on the controller node (remotehost is the name of the client node)

>erl -rsh ssh -sname foo -setcookie mycookie

Eshell V5.4.3 (abort with ^G)
(foo@myhostname)1>slave:start(remotehost,bar,"-setcookie mycookie").

You should see this: {ok,bar@remotehost}

If you got {error,timeout}, it can be caused by several problems:

  1. ssh in not working (you must have a key without passphrase, or use an agent)
  2. Tsung and Erlang are not installed on all clients nodes
  3. Erlang version or location (install path) is not the same on all clients nodes
  4. A firewall is dropping erlang packets: indeed erlang virtual machines use several TCP ports (dynamically generated) to communicate.
  5. SELinux: You should disable SELinux on all clients.
  6. Bad /etc/hosts: This one is wrong (real hostname should not refer to localhost/loopback):
    127.0.0.1 localhost myhostname
    
    This one is good:
    127.0.0.1 localhost
    192.168.3.2 myhostname
    
  7. sshd configuration: For example, for SuSE 9.2 sshd is compiled with restricted set of paths (ie. when you shell into the account you get the users shell, when you execute a command via ssh you don’t) and this makes it impossible to start an erlang node (if erlang is installed in /usr/local for example). Run:
    ssh myhostname erl
    

    If the erlang shell doesn’t start then check what paths sshd was compiled with (in SuSE see /etc/ssh/sshd_config) and symlink from one of the approved paths to the erlang executable (thanks to Gordon Guthrie for reporting this).

  8. old beam processes (erlang virtual machines) running on client nodes: kill all beam processes before starting tsung.

Note that you do not need to use the 127.0.0.1 address in the configuration file. It will not work if you use it as the injection interface. The shortname of your client machine should not refer to this address.

Warn:Tsung launches a new erlang virtual machine to do the actual injection even when you have only one machine in the injection cluster (unless ’use_controller_vm’ is set to true). This is because it needs to by-pass some limit with the number of open socket from a single process (1024 most of the time). The idea is to have several system processes (Erl beam) that can handle only a small part of the network connection from the given computer. When the maxusers limit (simultaneous) is reach, a new Erlang beam is launched and the newest connection can be handled by the new beam).

New in 1.1.0: If you don’t use the distributed feature of Tsung and have trouble to start a remote beam on a local machine, you can set the ’use_controller_vm’ attribute to true, for ex.:

  <client host="mymachine" use_controller_vm="true">

A.2  Tsung crashes when I start it

Does your Erlang system has ssl support enabled ?

to test it:

  > erl
  Eshell V5.2  (abort with ^G)
  1> ssl:start().
  you should see 'ok'

A.3  Why do i have error_connect_emfile errors ?

emfile error means : too many open files

This happens usually when you set a high value for maxusers (in the <client> section) (the default value is 800).

The errors means that you are running out of file descriptors; you must check that maxusers is less than the maximum number of file descriptors per process in your system (see ulimit -n)

You can either raise the limit of your operating system ( see /etc/security/limits.conf for Linux ) or decrease maxusers (Tsung will have to start several virtual machine on the same host to bypass the maxusers limit).

A.4  Tsung still crashes/fails when I start it !

First look at the log file ~/.tsung/log/XXX/tsung_controller@yourhostname' to see if there is a problem.

If the file is not created and a crashed dump file is present, maybe you are using a binary installation of Tsung not compatible with the version of erlang you used.

If you see nothing wrong, you can compile Tsung with full debugging: recompile with make debug , and don’t forget to set the loglevel to "debug" in the XML file.

To start the debugger or see what happen, start tsung with the debug argument instead of start. You will have an erlang shell on the tsung_controller node. Use toolbar:start(). to launch the graphical tools provided by Erlang.

A.5  Can I dynamically follow redirect with HTTP ?

If your HTTP server sends 30X responses (redirect) with dynamic URLs, you can handle this situation using a dynamic variable:

<request>
  <dyn_variable name="redirect" re="Location: (http://.*)\r"/>
  <http url="index.html" method="GET" ></http>
</request>

<request subst="true">
  <http url="%%_redirect%%" method="GET"></http>
</request>

You can even handle the case where the server use several redirections successively using a repeat loop (this works only with version 1.3.0 and up):

  <request>
    <dyn_variable name="redirect" re="Location: (http://.*)\r"/>
    <http url='/test/redirect.html' method='GET'></http>
  </request>

  <repeat name="redirect_loop" max_repeat="5">
    <request subst="true">
      <dyn_variable name="redirect" re="Location: (http://.*)\r"/>
      <http url="%%_redirect%%" method="GET"></http>
    </request>
    <until var="redirect" eq=""/>
  </repeat>

A.6  What is the format of the stats file tsung.log ?

# stats: dump at 1218093520
stats: users 247 247
stats: connected 184 247
stats: users_count 184 247
stats: page 187 98.324 579.441 5465.940 2.177 9.237 595 58
stats: request 1869 0.371 0.422 5.20703125 0.115 0.431 7444062 581
stats: connect 186 0.427 0.184 4.47216796875 0.174 0.894 88665254 59
stats: tr_login 187 100.848 579.742 5470.223 2.231 56.970 91567888 58
stats: size_rcv 2715777 3568647
stats: 200 1869 2450
stats: size_sent 264167 347870
# stats: dump at 1218093530
stats: users 356 356
stats: users_count 109 356
stats: connected -32 215
stats: page 110 3.346 0.408 5465.940 2.177 77.234 724492 245
stats: request 1100 0.305 0.284 5.207 0.115 0.385 26785716 2450
stats: connect 110 0.320 0.065 4.472 0.174 0.540 39158164 245
stats: tr_login 110 3.419 0.414 5470.223 2.231 90.461 548628831 245
stats: size_rcv 1602039 5170686
stats: 200 1100 3550
stats: size_sent 150660 498530
 ...

the format is, for request, page, session and transactions (tr_XXX:

# stats:’name’ 10sec_count, 10sec_mean, 10sec_stdvar, max, min, mean, count

or for HTTP returns code, size ...

# stats:’name’ count(during the last 10sec), totalcount(since the beginning)

A.7  How can I compute percentile/quartiles/median for transactions or requests response time ?

It’s not directly possible. But since version 1.3.0, you can use a new experimental statistic backend: set backend="fullstats" in the <tsung> section of your configuration file.

This will print every statistics data in a raw format in a file named tsung-fullstats.log. Warning: this may impact the performance of the controller node (a lot of data has to be written to disk).

The data looks like:

{sum,connected,1}
{sum,connected,-1}
[{sample,request,214.635},
 {sum,size_rcv,268},
 {sample,page,831.189},
 {count,200},
 {sum,size_sent,182},
 {sample,connect,184.787},
 {sample,request,220.974},
 {sum,size_rcv,785},
 {count,200},
 {sum,size_sent,164},
 {sample,connect,185.482}]
{sum,connected,1}
[{count,200},{sum,size_sent,161},{sample,connect,180.812}]
[{sum,size_rcv,524288},{sum,size_rcv,524288}]

You will have to write your own script to analyze the output. The format of the file may change in a future release.

A.8  How can I specify the number of concurrent users ?

You can’t. But it’s on purpose: the load generated by Tsung is dependent on the arrival time between new clients. Indeed, once a client has finished his session in tsung, it stops. So the number of concurrent users is a function of the arrival rate and the mean session duration.

For example, if your web site has 1000 visits/hour, the arrival rate is 1000/3600 = 0.2778 visits/second. If you want to simulate the same load, set the inter-arrival time is to 1/0.27778 = 3.6 sec (<users interarrival="3.6" unit="second"> in the arrivalphase node in the XML config file).

A.9  SNMP monitoring doesn’t work ?!

It use SNMP v1 and the ’public’ community. It has been tested with http://net-snmp.sourceforge.net/.

You can try with snmpwalk to see if your snmpd config is ok:

>snmpwalk -v 1 -c public IP-OF-YOUR-SERVER .1.3.6.1.4.1.2021.4.5.0
UCD-SNMP-MIB::memTotalReal.0 = INTEGER: 1033436

SNMP doesn’t work with erlang R10B and Tsung older than 1.2.0.

There is a small bug in the snmp_mgr module in old Erlang release (R9C-0). You have to apply this patch to make it work. This is fixed in erlang R9C-1 and up.

--- lib/snmp-3.4/src/snmp_mgr.erl.orig  2004-03-22 15:21:59.000000000 +0100
+++ lib/snmp-3.4/src/snmp_mgr.erl       2004-03-22 15:23:46.000000000 +0100
@@ -296,6 +296,10 @@
     end;
 is_options_ok([{recbuf,Sz}|Opts]) when 0 < Sz, Sz =< 65535 ->
     is_options_ok(Opts);
+is_options_ok([{receive_type, msg}|Opts]) ->
+    is_options_ok(Opts);
+is_options_ok([{receive_type, pdu}|Opts]) ->
+    is_options_ok(Opts);
 is_options_ok([InvOpt|_]) ->
     {error,{invalid_option,InvOpt}};
 is_options_ok([]) -> true.

A.10  How can i simulate a fix number of users ?

Use maxnumber to set the max number of concurrent users in a phase, and if you want Tsung to behave like ab, you can use a loop in a session (to send requests as fast as possible); you can also define a max duration in <load>.

<load duration="5" unit="minute">
   <arrivalphase phase="1" duration="10" unit="minute">
   <users maxnumber="10" arrivalrate="100" unit="second"></users>
</arrivalphase>
</load>
<sessions>
<session probability="100" name="ab">
    <for from="1" to="1000" var="i">
      <request>
        <http url="http://myserver/index.html" method="GET"></http>
      </request>
    </for>
</session>
</sessions>

B  Errors list

error_closed
Only for non persistent session (XMPP); the server unexpectedly closed the connection; the session is aborted.
error_inet_<ERRORNAME>
Network error; see http://www.erlang.org/doc/man/inet.html for the list of all errors.
error_unknown_data
Data received from the server during a thinktime (not for unparsed protocol like XMPP). The session is aborted.
error_unknown_msg
Unknown message received (see the log files for more information). The session is aborted.
error_unknown
Abnormal termination of a session, see log file for more information.
error_repeat_<REPEATNAME>
Error in a repeat loop (undefined dynamic variable usually).
error_send_<ERRORNAME>
Error while sending data to the server, see http://www.erlang.org/doc/man/inet.html for the list of all errors.
error_send
Unexpected error while sending data to the server, see the logfiles for more information.
error_connect_<ERRORNAME>
Error while establishing a connection to the server. See http://www.erlang.org/doc/man/inet.html for the list of all errors.
error_no_online jabber
XMPP: No online user available (usually for a chat message destinated to a online user)
error_no_offline jabber
XMPP: No offline user available (usually for a chat message destinated to a offline user)
error_no_free_userid
For XMPP: all users Id are already used (userid_max is too low ?)
error_next_session
A clients fails to gets its session parameter from the config_server; the controller may be overloaded ?
error_mysql_<ERRNO>
Error reported by the mysql server (see http://dev.mysql.com/doc/refman/5.0/en/error-messages-server.html)
error_mysql_badpacket
Bad packet received for mysql server while parsing data.
error_pgsql
Error reported by the postgresql server.

C  CHANGELOG

1.4.1 -> 1.4.2 Minor enhancements and bugfixes (4 Jan 2012)
Bugfix:
   * [TSUN-199] - computation of NUsers is wrong
   * [TSUN-206] - build failure with erlang R15B
Improvements:
   * [TSUN-202] - IPv6 support
   * [TSUN-203] - snmp oids should be customizable in the config file
   * [TSUN-205] - handle dyn_variables as array in test conditions (if/until/while)
New Features:
   * [TSUN-191] - allow outputting log to stdout
   * [TSUN-192] - structured log output (JSON)
   * [TSUN-193] - accept configuration from stdin
   * [TSUN-197] - Have bug and error message on stderr and not stdout.
1.4.0 -> 1.4.1 Minor bugfixes (13 Sep 2011)
Bugfix:
   * [TSUN-188] - munin plugin is not working in 1.4.0
   * [TSUN-189] - the controller VM is not used in some case
   * [TSUN-190] - pgsql recorder can record a connect request in an already connected session
1.3.3 -> 1.4.0 Major enhancements and bugfixes (5 Sep 2011)
Bugfix:
   * [TSUN-129] - regexp (defined in match or dynvars) can fail when chunk encoding is used.
   * [TSUN-150] - Munin monitoring broken by cpu stats config request
   * [TSUN-163] - Tsung doesn't detect subdomains.
   * [TSUN-166] - snmp monitoring does not work with erlang R14A
   * [TSUN-171] - maxnumber set in a phase is not always enforced
   * [TSUN-172] - auth sasl can't authenticate against ejabberd
   * [TSUN-178] - some characters can make url and headers rewriting fail in the recorder
   * [TSUN-179] - tsung generated message stanzas are not XMPP compliant
   * [TSUN-180] - file server crash if a dynamic substitution use it
   * [TSUN-182] - When many clients are configured with few static users, none of them are launched.
   * [TSUN-183] - tsung can stop too soon in some cases
   * [TSUN-184] - 'random_number' with start and end actually returns a number from start+1 to end
   * [TSUN-187] - Client IP scan is very slow on Linux; also uses obsolete "ifconfig"
Improvements:
   * [TSUN-54] - tsung is very slow when a lot of dynamic variables are set
   * [TSUN-96] - generating more than 64k connections from a single machine
   * [TSUN-106] - Add content-encoding support for dynvar extraction
   * [TSUN-123] - add option to read usernames from an external file for jabber
   * [TSUN-125] - use the new re module everywhere instead of regexp/gregexp
   * [TSUN-152] - Add "state_rcv" record as parameter to "get_message" function.
   * [TSUN-153] - dynvar used in match may contain regexp special characters
   * [TSUN-185] - handle postgresql extended protocol
New Features:
   * [TSUN-162] - add foreach tag (loop when a dyn_variable is a list)
   * [TSUN-164] - add a switch to allow light queries/replies logging
   * [TSUN-165] - add a way to synchronize users for all plugins.
   * [TSUN-167] - add do=dump option to matching
   * [TSUN-168] - thinktimes value could be dynamically generated with dyn_variable
   * [TSUN-181] - add option to simulate slow connections
1.3.2 -> 1.3.3 Minor bugfixes (17 Aug 2010)
Bugfix:
    * [TSUN-154] - parent proxy doesn't work anymore in 1.3.x (tested with 1.3.2 and 1.3.0).
    * [TSUN-155] - url substitution is broken in some cases
    * [TSUN-156] - Tsung not using sessions with low probabilities
    * [TSUN-157] - ssl doesn't work with erlang R14A
    * [TSUN-158] - failure when a proxy is used and an URL substitution is set
    * [TSUN-159] - HTTP cookies support is broken when a proxy is used
    * [TSUN-160] - tsung can sometimes hang at the beginning using distributed setup
    * [TSUN-161] - if statement not allowed in a transaction
1.3.1 -> 1.3.2 Major bugfixes and enhancements (14 Jun 2010)
Bugfix:
    * [TSUN-128] - Apostrophes cause string to convert to deep list in setdynvars with Erlang function.
    * [TSUN-130] - static users starting time is wrong
    * [TSUN-131] - tsung can stop too early when static users are used
    * [TSUN-132] - http cookies: accept domains equals to hostname with a leading "."
    * [TSUN-133] - proxy-recorder with SSL fails on large client packets (multiple TCP packets)
    * [TSUN-138] - when an error occured( for ex a timeout during a request) and a client exits, started transactions are not updated
    * [TSUN-140] - tsung does not honor the Proxy-Connection: keep-Alive or Connection: keep-Alive header if the proxy is HTTP/1.0
    * [TSUN-142] - http recorder can fail with https rewriting and chunked encoding
    * [TSUN-147] - UDP & bidi does not seem to work
    * [TSUN-148] - dynvar not found when used in match
    * [TSUN-149] - tsung doesn't work with Amazon Elastic load balancing
    * [TSUN-151] - tsung_stats.pl produces invalid *.gplot files
Improvements:
    * [TSUN-82]  - XMPP vhost support
    * [TSUN-127] - add ability tu use floats for thinktimes
    * [TSUN-139] - option to set random seed for launchers.
    * [TSUN-141] - add dynamic variable support for postgres
    * [TSUN-146] - New tsplot yfactor configuration support breaks most common configurations
New Features:
    * [TSUN-135] - add support for SASL ANONYMOUS and PLAIN authentication for XMPP
    * [TSUN-136] - add new plugin distributed testing of filesystem (nfs for ex), using a generic mode for executing erlang functions on clients nodes
    * [TSUN-137] - add a way to mix requests types inside a single session
    * [TSUN-143] - global time limit for the load test
    * [TSUN-145] - tsung should support json parsing for dynamic variable
1.3.0 -> 1.3.1 Major bugfixes and enhancements (9 Sep 2009)
Bugfix:
    * [TSUN-92] - the computation of the minimum for sample_counter is wrong
    * [TSUN-93] - maxnumber not respected if several clients are used
    * [TSUN-102] - dyn_variable configuration fails if variable name is not a valid erlang atom
    * [TSUN-103] - Network error handling in munin plugin
    * [TSUN-104] - tsung-plotter can't handle the os_mon statistics
    * [TSUN-108] - Cannot handle complicated dyn_var name
    * [TSUN-109] - Tsung status displays always phase one even if you have more than one phase
    * [TSUN-110] - Cookie header not present if the URL is dynamically generated by a previous redirection (302)
    * [TSUN-117] - Bug in HTTP: empty header can be generated in some case
    * [TSUN-118] - HTTPS proxy recorder: ts_utils:to_https incorrectly handles Content-Length for POST requests
    * [TSUN-119] - tsung can crash when reading empty values from a csv file
    * [TSUN-122] - same http cookie key with different domains don't work
Improvements:
    * [TSUN-47] - ts_mon can be a bottleneck during very high load testing
    * [TSUN-77] - Structural requests or goto-like action for match in HTTP
    * [TSUN-81] - Dynamic variables API
    * [TSUN-83] - file_server using fixed tuple instead of list
    * [TSUN-85] - external entity should be copied into the log directory of a run.
    * [TSUN-87] - add dynamic code evaluation in set_dynvars
    * [TSUN-88] - add mkactivity method support in webdav
    * [TSUN-91] - reduce memory consumption by hibernating client process while in think state
    * [TSUN-97] - disable smp erlang for client beam for performance reason
    * [TSUN-98] - try several times to connect to the server before aborting a session
    * [TSUN-99] - make substitution work in <match>
    * [TSUN-100] - improve scalability of ts_launcher
    * [TSUN-105] - Add load average statistic to server monitoring
    * [TSUN-111] - add option to manually add a cookie in http requests
    * [TSUN-113] - split tsung command into two separate tsung and tsung-recorder commands
    * [TSUN-116] - add ability to run several tsung running in parallel on the same hosts
    * [TSUN-120] - Https recorder: Remove "Secure" from "Set-Cookie" header.
New Features:
    * [TSUN-25] - add a way to start sessions in a specific order at specified times
    * [TSUN-89] - include tsung-plotter into the tsung distribution
    * [TSUN-90] - add support for monitoring server cpu/mem using munin-node
    * [TSUN-94] - add log action for match
    * [TSUN-95] - add a default dyn_variable with a unique tsung_userid
    * [TSUN-107] - add MUC support to the jabber doc/plugin
    * [TSUN-114] - add option to apply function to data before looking for a match
    * [TSUN-115] - add pubsub support to the jabber plugin

1.2.2 -> 1.3.0 Major bugfixes and enhancements (03 Sep 2008)
Bugfix:
    * [TSUN-30] - SNMP monitoring gives an error
    * [TSUN-57] - using -l with a relative path make distributed load fails with timeout error
    * [TSUN-60] - https recorder broken if an HTML document includes absolute urls
    * [TSUN-67] - Typo breaks recording of if-modified-since headers
    * [TSUN-68] - some sites doesn't work with ":443" added in the "Host" header with https
    * [TSUN-71] - Tsung does not work with R12B (httpd_util funs removed)
    * [TSUN-73] - Wrong parsing HTTP multipart/form-data in http request - POST form doesn't work
    * [TSUN-75] - can not define more -pa arguments
    * [TSUN-84] - dyn variables that don't match should be set to an empty string
Improvements:
    * [TSUN-40] - problem to rewrite url for https with gzip-encoded html.
    * [TSUN-48] - tcp/udp buffer size should be customizable in the XML config file.
    * [TSUN-59] - if a User-Agent header is set in <header>, it should override the global one.
    * [TSUN-62] - add abilty to loop back to a previous request in a session
    * [TSUN-63] - check for ssl and crypto application at compile time
    * [TSUN-65] - enhance dynamic variables.
    * [TSUN-66] - add global mean and counter computation and reporting for samples
    * [TSUN-69] - add option to read content of a POST request from an external file
    * [TSUN-79] - setting 'Host' header with http_header doesn't work as expected
New Features:
    * [TSUN-56] - ldap plugin
    * [TSUN-58] - add a new statistics backend to dump all stats in a file
    * [TSUN-61] - add a Webdav plugin
    * [TSUN-64] - add md5 authentication in the pgsql plugin
    * [TSUN-72] - Add support for defining dyn_variables using XPath
    * [TSUN-78] - mysql plugin
    * [TSUN-80] - add random thinktime with in a given range ( [min,max])
Tasks:
    * [TSUN-76] - add explanation for errors name in the documentation

1.2.1 -> 1.2.2 Minor bugfixes and enhancements (23 Feb 2008)
Bugfix:
    * [TSUN-30] - SNMP monitoring gives an error
    * [TSUN-31] - dyn_variable usage
    * [TSUN-35] - udp is not working
    * [TSUN-36] - default regexp for dyn_variable doesn't work in all case
    * [TSUN-38] - server monitoring crash if an ethernet interface's name is more than 6 chars long
    * [TSUN-39] - https recording doesn't work with most browsers
    * [TSUN-43] - session should not terminate if rosterjid is not defined
    * [TSUN-49] - <match> doesn't work with jabber plugin
    * [TSUN-51] - tsung does not work with R12B (httpd_util funs removed)
    * [TSUN-53] - postgresql errors not reported in all cases
    * [TSUN-55] - no error counter when userid_max is reached
Improvements:
    * [TSUN-14] - no_ack messages and asynchronous msg sent by the server are not available in the reports
    * [TSUN-27] - handle bidirectional protocols
    * [TSUN-28] - Refactoring needed to ease the change of the userid / password generation code
    * [TSUN-29] - Multiple file_server support
    * [TSUN-32] - make snmp server options tunable
    * [TSUN-34] - add costum http headers
    * [TSUN-44] - tsung should ignore whitespace keepalive from xmpp server
    * [TSUN-45] - add kernel-poll support for better performance
    * [TSUN-46] - add number of open connections in statistics
    * [TSUN-47] - ts_mon can be a bottleneck during very high load testing
    * [TSUN-50] - use the whole range of Id (from 0 to userid_max) before reusing already used Ids
New Features:
    * [TSUN-26] - Ability to loop on a given sequence of phase
    * [TSUN-52] - Adding comment during script capture
    * [TSUN-41] - add support for parent proxy for http only (not https)

1.2.0 -> 1.2.1 Minor bugfixes and enhancements (07 Oct 2006)
    Bugfix:
    - [TSUN-5]  get traffic from all interfaces instead of only eth0
      in erlang os monitoring (Linux)
    - [TSUN-18 the pgsql recorder fails if the client doesn't try
      first an SSL connection
    - [TSUN-19] a % character in some requests (eg. type=sql for
      pgsql) make the config_server crash.
    - [TSUN-20] pgsql client fails while parsing data from server
    - [TSUN-21] substitution in URL is not working properly when a new server
      or port is set
    - [TSUN-23] set default http version (1.1)
    - [TSUN-24] destination=previous doesn't work (jabber)
    Improvement:
    - [TSUN-15] listen port is now customizable with the command line
    - [TSUN-17] add option to setup postgresql server IP and port at runtime
      for the recorder
    - [TSUN-22] add support for PUT, DELETE and HEAD methods for http

1.1.0 -> 1.2.0 Major feature enhancements (29 May 2006)
    - change name: idx-tsunami is now called tsung
    - add new plugin: pgsql for postgresql load testing
    - new: it's now possible to set multiple servers (selected at runtime
      by round robin)
    - add size_rcv stats
    - fix beams communication problem introduced in new erlang releases.
    - import snmp_mgr src from R9C2 to enable SNMP with R10B
    - rebuild boot scripts if erlang version is different from compile time
    - many DTD improvements
    - improved match: add loop|abort|restart on (no)match behavior,
      multiple match tags is now possible (suggested by msmith@truelink.com)
    - freemem and packet stats for Solaris (jasonwtucker@gmail.com)
    - fix several small problems with 'use_controller_vm' option
    - ip is no more mandatory (default is 0.0.0.0)
    - clients and monitoring can use hosts list defined in environment
      variables, for use with batch schedulers (openpbs/torque, LSF and OAR)
    - performance improvements in stats engine for very high load
      (use session_cache)
    Recorder:
    - add plugin architecture in recorder; add pgsql plugin
    - fix regression in recorder for WWW-Authentication
      (anders.nygren@gmail.com)
    - close client socket when connection:closed is ask by the server
      (this should enable https recording with IE)
    Jabber:
    - fix presence:roster request
    - add presence:directed , presence:broadcast & presence:final requests
      for jabber (jasonwtucker@gmail.com)
    - roster enhancements (jasonwtucker@gmail.com)
    - sip-digest authentication (jasonwtucker@gmail.com)
    - fix online: must use presence:initial to switch to online status
    - add pubsub support (mickael.remond@process-one.net)
    Http:
    - fix single user agent case.
    - minor fixes for HTTP parsing

1.0.3 -> 1.1.0 Major feature enhancements (5 Sep 2005)
    - new feature: HTTP proxy load testing in now possible (set
      http_use_server_as_proxy to true)
    - add dynamic substitution support for jabber
    - add 'raw' type of msg for Jabber (use the new 'data' attribute)
    - add the dynamic variable list to dynamic substitutions
    - UserAgent is now customizable for HTTP testing
    - Add an option to run all components (controller and launcher)
      within a single erlang beam (use_controller_vm). Should ease
      idx-tsunami use for light load tests
    - fix bash script for solaris (jasonwtucker@gmail.com)
    - fix: several 'idx-tsunami status' can be run simultaneously
      (reported by Adam Spotton)
    - internal: Host header is now set during configuration phase
    - fix last phase duration
    - fix recorder: must log absolute url if only the scheme has changed

1.0.2 -> 1.0.3 Minor bugfixes (8 Jul 2005)
    - add ts_file_server module
    - fix broken https recording
    Thx to johann.messner@jku.at for bug reporting :
    - fix: forgot to add "?" when an URL is absolute and had a query
      part
    - fix regression in the recorder (introduced in 1.0.2): must use CAPS
      for method, wrong content-length in recorder causing POST requests
      to silently fail
    - allow multiple 'dyn_variable' in DTD
    - fix Host: header when port is != 80

1.0.1 -> 1.0.2: Minor bugfixes  (6 Jun 2005)
    - fix: the recorder is working now with R10B: replace call to
      httpd_parse:request_header in recorder by an
      internal func (the func was removed in R10B)
    - update configure scripts (should build on RHEL3/x86_64)
    - remote beam startup is now tunable (-r ssh/rsh)
    - internal changes in ts_os_mon (suggested by R. Lenglet)

1.0 -> 1.0.1: Major bugfixes (18 Nov 2004)
    - fix: broken free mem on non linux arch (Matthew Schulkind)
    - add script to convert apache log file (combined) to idx-tsunami XML
    - improved configure: add --with-erlang option and xmerl PATH detection
       idx-tsunami now compiles both with R9C and R10B
    - small fixes to the DTD
    Thx to Jonathan Bresler for testing and bug reporting :
    - fix: broken 'global', 'local' and 'no_ack' requests and size computation
    - fix: broken ids in jabber messages
    - fix: broken online/offline in user_server
    - default thinktime can now be overriden
    - many improvements/fixes in analyse_msg.pl

1.0.beta7 -> 1.0:  Minor bugfixes (13 Aug 2004)
    - fix: broken path when building debian package
    - add rpm target in makefile
    - implement status
    - add 'match' in graph and doc
    - fix add_dynparams for jabber

1.0.beta6 -> 1.0.beta7:  Minor bugfixes (20 Jul 2004)
    - HTTP: really (?) fix parsing of no content-length with connection:close
    - better handling of configure (--prefix is working)
    - add different types of output backend (currently, only 'text'
      works; 'rrdtool' is started but unfinished)
    - fix: ssl_ciphers option is working again

1.0.beta5 -> 1.0.beta6:  Minor feature enhancements (5 May 2004)
    - add a DTD for the configuration file
    - add dynamic request substitution (mickael.remond@erlang-fr)
    - add dynamic variable parsing from response (can be used
      later in the session for request substitution)
    - add response pattern to match (log if not match)
    - HTTP: fix partial header parsing (mickael.remond@erlang-fr.org)
    - HTTP: fix chunk parsing when the chunk-size is split across two packets
    - HTTP: fix parsing of no content-length with connection:close case
    - check for bad input (config file, <client> name)
    - merge client and client_rcv processes into a single process
    - fix: do not connect in init anymore; this fix too long phases when
      connection time is high.
    - connect stat is now for both new connections and reconnections
    - check phase duration in launcher
    - various code cleanup

1.0.beta4 -> 1.0.beta5:  Major Feature enhancements (25 Mar 2004)
    - add SNMP monitoring (not yet customizable)
    - fix remote start: log filename is now encoded to avoid bad
      parsing of log_file by 'erl'
    Patches from mickael.remond@erlang-fr.org :
    - Added ~/.idx-tsunami creation in idx-tsunami script if the directory
      does not already exist
    - Extension of XML attribute entity normalisation
    - HTTP: fix Cookie support: Cookie are not necessarily separated by "; "
    - HTTP: fix long POST request in the recorder: dorecord message
      was missing enclosing curly brackets, and the body length counter
      were mistakenly taking the header size in its total
    - HTTP: Content-type support in the recorder (needed to handle
     non-HTML form encoded posts)
    - add autoconf support to detect Erlang installation path
    - SOAP Support: IDX-Tsunami can now record and replay SOAP HTTP
      scenario. The SOAPAction HTTP header is now recorded
    - Preliminary Windows support: A workaround has been introduced in
      the code to handle behaviour difference between Erlang Un*x and
      Erlang Windows on how the command-line is handled. When an
      assumtion is made on the string type of a parameter, it should be
      check that this is actually a string and not an atom.

1.0.beta3 -> 1.0.beta4:  Minor bugfixes (16 Mar 2004)
    - fix lost cookie when transfer-encoding:chunked is used
    - fix config parsing (the last request of the last page of a
      sesssion was not marked as endpage)
    - don't crash anymore on error during start or stop

1.0.beta2 -> 1.0.beta3:  Minor feature enhancements (24 Feb 2004)
    - fix stupid bug in start script for recorder
    - HTTP: fix '&' writes in the XML recorder for 'content' attribute
    - HTTP: enhanced Cookies parsing ('domain' and 'path' implemented).
    - ssl_ciphers can be customized
    - change log directory structure: all log files in one directory per test
    - add HTML reports  (requires the perl Template toolkit)
    - change stats names:  page_resptime -> page, response_time -> request

1.0.beta1 -> 1.0.beta2:  Minor feature enhancements (11 Feb 2004)
    - reorganise the sources
    - add tools to build a debian package
    - fix documentations
    - add minimalistic man page
    - syntax change: GETIMS +date replace by GET +'if_modified_since'

0.2.1 -> 1.0.beta1:  Major Feature Enhancements (3 Feb 2004)
    - rewrite the configuration engine. Now use an XML file.
    - add recording application: use as a HTTP proxy to record session into XML
      format
    - add support to OS monitoring (cpu, memory, network). Currently, use an
      erlang agent on the remote nodes; SNMP is on the TODO list.
      (mickael.remond@erlang-fr.org)
    - can now use several IPs per client host
    - several arrival phases can be set with different arrival rates and
      duration
    - can set test duration instead of number of users
    - add user defined statistics using a 'transaction' tag
    - HTTP: fix cookies and POST handling (mickael.remond@erlang-fr.org)
    - HTTP: rewrite the parser (faster and cleaner)
    - fix bad timeout computation when close occur for persistent client
    - bugfixes and other enhancements.
    - fix memory leak with ssl (half-closed connections)

0.2.0 -> 0.2.1:  Minor bugfixes and small enhancements (9 Dec 2003)
    - optimize session memory consumption: use an ets table to store session setup
    - HTTP: fix crash when content-length is not set in headers
    - HTTP: fix POST method
    - HTTP: preliminary chunked-encoding support in HTTP/1.1
    - HTTP: Absolute URL are handled (server and port can be overridden )
    - no more .hosts.erlang required
    - add stats on simultaneous users

0.1.1 -> 0.2.0:  Major Feature Enhancements (Aug 2003)
    - add 'realtime' stats
    - add new 'parse' type of protocol
    - add reconnection support (persistent client)
    - add basic HTTP and HTTPS support
    - split the application in two parts: a single controller (tsunami_controller),
      and the clients (tsunami)
    - switch to R9C

0.1.0 -> 0.1.1:  Bugfix realease (Aug 2002)
      - fix config file
    - fix few typos in docs
    - fix init script
    - few optimizations in user_server.erl
    - switch to R8B

0.1.0: Initial release (May 2001)

1
http://www.erlang-projects.org/Members/mremond/events/dossier_de_presentat/block_10766817551485/file
2
http://www.editions-eyrolles.com/php.accueil/Ouvrages/ouvrage.php3?ouv_ean13=9782212110791
3
http://www.sics.se/~joe/thesis/armstrong_thesis_2003.pdf
4
mickael.remond@erlang-fr.org

Copyright 2004-2009 © Nicolas Niclausse


This document was translated from LATEX by HEVEA.
tsung-1.4.2/doc/tsung.10000644000201100017670000000523411701017117014332 0ustar nniclausdream.\" auto-generated by docbook2man-spec from docbook-utils package .TH "TSUNG" "1" "January 2004" "" "" .SH NAME tsung \- A distributed multi-protocol load testing tool. .SH SYNOPSIS .sp \fBtsung\fR [ \fB-f configuration file\fR ] [ \fB-l log dir\fR ] [ \fB-m filename\fR ] [ \fB-r command\fR ] [ \fB-v\fR ] [ \fB-6\fR ] [ \fB-h\fR ] [ \fBstart|stop|debug|status\fR ] .SH "DESCRIPTION" .PP \fBtsung\fR is a distributed load testing tool. It is protocol-independent and can currently be used to stress and benchmark HTTP, WebDAV, LDAP, PostgreSQL, MySQL and Jabber/XMPP servers. .PP It simulates user behaviour using an XML description file, reports many measurements in real time (statistics can be customized with transactions, and graphics generated using gnuplot). .PP For HTTP, it supports 1.0 and 1.1, has a proxy mode to record sessions, supports GET and POST methods, Cookies, and Basic WWW-authentication. It also has support for SSL. .PP Several config examples can be found in \fI/usr/share/doc/tsung/examples/\fR\&. .TP \fBstart\fR start tsung load testing .TP \fBdebug\fR start tsung with an interactive erlang shell .TP \fBstop\fR stop tsung .TP \fBstatus\fR print current status of a running instance of tsung (must be run on the controller host) .SH "MANUAL" .PP A manual should be available at \fI/usr/share/doc/tsung/user_manual.html\fR\&. It is also available online at .sp .RS .sp .nf http://tsung.erlang-projects.org/user_manual.html .sp .fi .RE .sp .SH "OPTIONS" .TP \fB-f filename\fR specifies the configuration file to use. The default file name is \fI~/.tsung/tsung.xml\fR\&. Use - for standard input .TP \fB-l logdir\fR Specifies the log directory to use. The default log dir name is \fI~/.tsung/log/YYYYMMDD-HHMM/\fR .TP \fB-m monitoring_file\fR Specifies the monitoring log file name to use. The default log file name is \fItsung.log\fR\&. Use - for standard output .TP \fB-r command\fR Specifies an alternative to ssh (rsh for ex.) for starting a slave node on a remote host .TP \fB-i id\fR set controller id (default is empty). Needed to start several controllers on the same host. .TP \fB-F\fR Use long names for erlang nodes (FQDN) .TP \fB-m\fR Enable erlang smp on client nodes .TP \fB-v\fR Show version .TP \fB-6\fR Use IPv6 for tsung internal communications .TP \fB-h\fR Show usage .SH "BUGS" .PP Please reports bugs to the mailing list , see .sp .RS .sp .nf https://lists.process-one.net/mailman/listinfo/tsung-users .sp .fi .RE .sp for archives. .SH "SEE ALSO" .PP \fBerlang\fR(3) .SH "AUTHORS" .PP \fBTsung\fR is written by Nicolas Niclausse \&. Contributors list is available in \fI/usr/share/doc/tsung/CONTRIBUTORS\fR tsung-1.4.2/doc/tsung.1.sgml0000644000201100017670000001404111701017117015267 0ustar nniclausdream
nicolas.niclausse@niclux.org
Nicolas Niclausse January 2004 2004 Nicolas Niclausse
tsung 1 tsung A distributed multi-protocol load testing tool. tsung configuration file log dir filename command start|stop|debug|status description tsung is a distributed load testing tool. It is protocol-independent and can currently be used to stress and benchmark HTTP, WebDAV, LDAP, PostgreSQL, MySQL and Jabber/XMPP servers. It simulates user behaviour using an XML description file, reports many measurements in real time (statistics can be customized with transactions, and graphics generated using gnuplot). For HTTP, it supports 1.0 and 1.1, has a proxy mode to record sessions, supports GET and POST methods, Cookies, and Basic WWW-authentication. It also has support for SSL. Several config examples can be found in /usr/share/doc/tsung/examples/. start tsung load testing start tsung with an interactive erlang shell stop tsung print current status of a running instance of tsung (must be run on the controller host) manual A manual should be available at /usr/share/doc/tsung/user_manual.html. It is also available online at
http://tsung.erlang-projects.org/user_manual.html
options specifies the configuration file to use. The default file name is ~/.tsung/tsung.xml. Use - for standard input Specifies the log directory to use. The default log dir name is ~/.tsung/log/YYYYMMDD-HHMM/ Specifies the monitoring log file name to use. The default log file name is tsung.log. Use - for standard output Specifies an alternative to ssh (rsh for ex.) for starting a slave node on a remote host set controller id (default is empty). Needed to start several controllers on the same host. Use long names for erlang nodes (FQDN) Enable erlang smp on client nodes Show version Use IPv6 for tsung internal communications Show usage Bugs Please reports bugs to the mailing list tsung-users@process-one.net, see
https://lists.process-one.net/mailman/listinfo/tsung-users
for archives.
see also erlang3 Authors Tsung is written by Nicolas Niclausse nicolas@niclux.org. Contributors list is available in /usr/share/doc/tsung/CONTRIBUTORS
tsung-1.4.2/doc/tsplot.10000644000201100017670000001371211701017117014517 0ustar nniclausdream.\\" auto-generated by docbook2man-spec $Revision: 1.1 $ .TH "TSPLOT" "1" "February 2007" "" "" .SH NAME tsplot \- Plot several tsung logs on the same charts, for comparison purpose. .SH SYNOPSIS .sp \fBtsplot\fR [ \fB-c configuration file\fR ] [ \fB-d images output directory\fR ] [ \fB-v verbose\fR ] [ \fBlegend logfile\fR ] .SH "DESCRIPTION" .PP Tsung comes with a plotting tool using \fBgnuplot\fR, producing some graphs from the \fItsung.log\fR file data. \fBtsplot\fR is able to plot data from several \fItsung.log\fR files onto the same charts serie, for further comparison and analyze. .SH "OPTIONS" .PP .TP \fB-c\fR .TP \fB--config\fR specifies the configuration file to use. Default is \fIhttp.en.plots.conf\fR. .TP \fB-d\fR .TP \fB--outdir\fR directory where \fBtsplot\fR saves the images it produces, defaults to \fI/tmp/tsung\fR. .TP \fB-v\fR .TP \fB--verbose\fR makes \fBtsplot\fR very verbose about what it does. .SH "CONFIGURATION" .PP The configuration file of \fBtsplot\fR allows to define the plots you want to obtain, from their label to the data they will show. The configuration file adopts a \fI\&.ini\fR file syntax, each section defining a chart. .PP \fBtsplot\fR comes with two sample configuration files, namely \fIhttp.plots.en.conf\fR and \fIpgsql.plots.en.conf\fR. They respectively define charts to be plotted for a \fBtsung\fR HTTP test and a \fBtsung\fR PGSQL test. .PP A \fBDEFAULT\fR section may be provided, any element configured here may then be overriden into a specific plot section. .PP Another configuration file is used by \fBtsplot\fR: the \fItsung/stats.conf\fR one. It's used to define by type the statistics to be read into \fBtsung\fR log files, and you shouldn't need to edit it, short of adding support for new \fBtsung\fR statistics. .PP Common settings, to be found into \fBDEFAULT\fR section or any specific chart section. .TP \fBencoding\fR set here the encoding used thereafter in the file, for labels and titles. .TP \fBdpi\fR dpi setting of produced charts images .TP \fBtn_dpi\fR dpi setting of produced charts thumbnail images .TP \fBimgtype\fR type of chart image to produce, as in \fIpng\fR or \fIps\fR A complete list might be obtained on the \fBpython-matplotlib\fR website, http://matplotlib.sourceforge.net/ .TP \fBxlabel\fR default label for horizontal axe, often you want seconds or minutes, depending on xfactor. Please note you can also set some defaults for ylabel, but this seems not to be a good idea in practise. .TP \fBxfactor\fR tsung logs statistics in its logfile every 10 seconds. By default, charts will not scale this and have seconds as horizontal axis units. By setting an xfactor of 60, you have a minute precision on horizontal axis. .TP \fByfactor\fR same as xfactor, but for vertical axis. Depending on the data you obtain with your tests, you may want to adapt the vertical scale of your plotting. For example, the \fBpage.mean\fR statistic is logged in milliseconds by \fBtsung\fR. You may want to display seconds if this unit better fits your measures. Then simply set \fByfactor = 1000\fR. .TP \fBstyles\fR set here any number of \fBmatplotlib\fR styles you want to use, separated by spaces, as available here: http://matplotlib.sourceforge.net/matplotlib.pylab.html#-plot. For exemple, set \fBstyles = b- g+ r- cx\fR for plotting first dataset (see \fBstats\fR below) with a blue solid line, second with green plus symbols, third with a red line and last with cyan cross symbols. This could fit a \fBstats = 200.count 400.count\fR stats setting when plotting two \fBtsung\fR logs. .PP You then can define any number of plot, one by section, and give them an arbitrary name. The name must be unique, and will be used for naming output images. .PP .PP Any option available in DEFAULT section is also available in any specific chart section, with the same meaning and effect. The specific setting will systematically override the DEFAULT one. .PP .TP \fBtitle\fR Title of the chart, as printed into the resulting image. .TP \fBstats\fR The statistics properties to use for this plotting, as named in the \fItsung/stats\fR configuration file. Please see this bundled file for a list of what is available. Tsung provide several types of statistics, as documented here: http://tsung.erlang-projects.org/user_manual.html#htoc53. The two main types of statistics used are \fBsample\fR and \fBcounter\fR. A third one is \fBgauge\fR but is only use for a single statistic (users). sample provides count, mean, stdvar, max, min and gmean (global mean) properties, and counter provides only count and totalcount. gauge provide count and max. The stats setting can accept several \fBstat.property\fR elements, separated by spaces. Examples: \fBstats = users.count\fR to plot the number of simultaneously connected users, and \fBstats = 200.count 400.count\fR to plot given HTTP return codes count, both on the same chart. Please notice \fBtsplot\fR is currently limited to use only one horizontal and only one vertical scales. \fBmatplotlib\fR is able to define some more complex drawings, but \fBtsplot\fR is not yet able to benefit from this. .TP \fBlegend\fR Legend prefix, which will be followed by the legend given on command line. Each plot on a chart has a legend entry, you configure here the meaning of the plot (say 'concurrent users') and \fBtsplot\fR will add it the name of the data serie being plotted (say 'scenario x'). You'd obtain this legend: 'concurrent users scenario x'. .TP \fBylabel\fR label for vertical axe .SH "CONFIGURATION EXAMPLE" .PP Please see the given configuration examples which should be distributed in \fI/usr/share/doc/tsung/tsung-plotter/http.plots.en.conf\fR and \fI/usr/share/doc/tsung/tsung-plotter/pgsql.plots.en.conf\fR. .SH "BUGS" .PP Please reports bugs to the mailing list or in the bug tracker , see also for archives. .SH "AUTHORS" .PP \fBtsplot\fR is written by Dimitri Fontaine . tsung-1.4.2/acinclude.m40000755000201100017670000000255311701017117014540 0ustar nniclausdreamdnl as-ac-expand.m4 0.2.0 -*- autoconf -*- dnl autostars m4 macro for expanding directories using configure's prefix dnl (C) 2003, 2004, 2005 Thomas Vander Stichele dnl Copying and distribution of this file, with or without modification, dnl are permitted in any medium without royalty provided the copyright dnl notice and this notice are preserved. dnl AS_AC_EXPAND(VAR, CONFIGURE_VAR) dnl example: dnl AS_AC_EXPAND(SYSCONFDIR, $sysconfdir) dnl will set SYSCONFDIR to /usr/local/etc if prefix=/usr/local AC_DEFUN([AS_AC_EXPAND], [ EXP_VAR=[$1] FROM_VAR=[$2] dnl first expand prefix and exec_prefix if necessary prefix_save=$prefix exec_prefix_save=$exec_prefix dnl if no prefix given, then use /usr/local, the default prefix if test "x$prefix" = "xNONE"; then prefix="$ac_default_prefix" fi dnl if no exec_prefix given, then use prefix if test "x$exec_prefix" = "xNONE"; then exec_prefix=$prefix fi full_var="$FROM_VAR" dnl loop until it doesn't change anymore while true; do new_full_var="`eval echo $full_var`" if test "x$new_full_var" = "x$full_var"; then break; fi full_var=$new_full_var done dnl clean up full_var=$new_full_var AC_SUBST([$1], "$full_var") dnl restore prefix and exec_prefix prefix=$prefix_save exec_prefix=$exec_prefix_save ]) tsung-1.4.2/Makefile.in0000644000201100017670000003726711701017117014423 0ustar nniclausdream#### CONFIGURE VARIABLE # export ERLC_EMULATOR to fix a bug in R9B with native compilation ERLC_EMULATOR=@ERL@ export ERLC_EMULATOR ERL=@ERL@ ERLC=@ERLC@ SED=@SED@ ERL_OPTS=@ERL_OPTS@ # FIXME DIALYZER=dialyzer ERLDIR=@ERLANG_ROOT_DIR@ export ERLDIR USEMOCHIWEBLIBS=@erlang_cv_orelse@ ERLANG_XMERL_DIR=@ERLANG_LIB_DIR_xmerl@/include raw_erlang_prefix=@libdir@/erlang/ PACKAGE_TARNAME=@PACKAGE_TARNAME@ prefix=@prefix@ exec_prefix=@exec_prefix@ bindir=@bindir@ libdir=@libdir@ datadir=@datadir@ datarootdir=@datarootdir@ docdir=@docdir@ TEMPLATES_SUBDIR=@TEMPLATES_SUBDIR@ CONFIGURE_DEPENDENCIES=@CONFIGURE_DEPENDENCIES@ CONFIG_STATUS_DEPENDENCIES=@CONFIG_STATUS_DEPENDENCIES@ VERSION=@PACKAGE_VERSION@ PACKAGE=@PACKAGE_NAME@ DTD=@DTD@ #### END OF SUBSTITUTION SVN_REVISION=$Revision$ ERL_COMPILER_OPTIONS="[warn_unused_vars]" export ERL_COMPILER_OPTIONS ifeq ($(TYPE),debug) OPT =+debug_info -DDEBUG else ifeq ($(TYPE),native) OPT:=+native else OPT = +strict_record_tests endif endif ifeq ($(TYPE),test) OPT =+export_all endif INC = ./include CC = $(ERLC) ESRC = ./src EBIN = ./ebin ifeq ($(TYPE),snapshot) DAY=$(shell date +"%Y%m%d") distdir = $(PACKAGE)-$(VERSION)-$(DAY) else distdir = $(PACKAGE)-$(VERSION) endif # installation path BINDIR = $(bindir) LIBDIR = $(libdir)/tsung/ TOOLS_BINDIR = $(LIBDIR)/bin CONFDIR = $(docdir)/examples SHARE_DIR = $(datadir)/tsung/ TEMPLATES_DIR = $(datadir)/$(TEMPLATES_SUBDIR) MAN_DIR = $(datadir)/man/man1/ DOC_DIR = $(docdir) # custom behaviours BEHAVIORS = ebin/ts_plugin.beam BUILDER_LOG = /tmp/builder-tsung.log ERLANG_LIB_DIR = $(libdir)/erlang/lib APPLICATION = tsung CONTROLLER_APPLICATION = tsung_controller RECORDER_APPLICATION = tsung_recorder RECORDER_TARGETDIR = $(ERLANG_LIB_DIR)/$(RECORDER_APPLICATION)-$(VERSION) CONTROLLER_TARGETDIR = $(ERLANG_LIB_DIR)/$(CONTROLLER_APPLICATION)-$(VERSION) TARGETDIR = $(ERLANG_LIB_DIR)/$(APPLICATION)-$(VERSION) TEMPLATES = $(wildcard $(ESRC)/templates/*.thtml) TEMPLATES += $(wildcard $(ESRC)/templates/*.js) TMP = $(wildcard *~) $(wildcard src/*~) $(wildcard inc/*~) INC_FILES = $(wildcard $(INC)/*.hrl) LIBSRC = $(wildcard $(ESRC)/lib/[^mochi]*.erl) ifeq ($(USEMOCHIWEBLIBS),yes) LIBSRC += $(wildcard $(ESRC)/lib/mochi*.erl) endif TESTSRC = $(wildcard $(ESRC)/test/*.erl) SRC = $(wildcard $(ESRC)/$(APPLICATION)/*.erl) CONTROLLER_SRC = $(wildcard $(ESRC)/$(CONTROLLER_APPLICATION)/*.erl) RECORDER_SRC = $(wildcard $(ESRC)/$(RECORDER_APPLICATION)/*.erl) CONFFILE_SRC = $(wildcard examples/*.xml.in) CONFFILE = $(basename $(CONFFILE_SRC)) TEST_CONFFILE_SRC = $(wildcard src/test/*.xml.in) TEST_CONFFILE = $(basename $(TEST_CONFFILE_SRC)) USERMANUAL = doc/user_manual.html doc/IDXDOC.css USERMANUAL_IMG = $(wildcard doc/images/*.png) USERMANUAL_SRC = doc/user_manual.tex MANPAGES = $(wildcard doc/*.1) PERL_SCRIPTS_SRC = $(wildcard $(ESRC)/*.pl.in) PERL_SCRIPTS = $(basename $(PERL_SCRIPTS_SRC)) TSPLOT_SRC = $(wildcard $(ESRC)/tsung-plotter/*.py.in) TSPLOT = $(basename $(TSPLOT_SRC)) TSUNG_PLOTTER_LIB= $(wildcard $(ESRC)/tsung-plotter/tsung/*.py) TSUNG_PLOTTER_CONF= $(wildcard $(ESRC)/tsung-plotter/tsung/*.conf) $(wildcard $(ESRC)/tsung-plotter/*.conf) TARGET = $(addsuffix .beam, $(basename \ $(addprefix $(EBIN)/, $(notdir $(SRC))))) LIB_TARGET = $(addsuffix .beam, $(basename \ $(addprefix $(EBIN)/, $(notdir $(LIBSRC))))) CONTROLLER_TARGET = $(addsuffix .beam, $(basename \ $(addprefix $(EBIN)/, $(notdir $(CONTROLLER_SRC))))) RECORDER_TARGET = $(addsuffix .beam, $(basename \ $(addprefix $(EBIN)/, $(notdir $(RECORDER_SRC))))) TEST_TARGET = $(addsuffix .beam, $(basename \ $(addprefix $(EBIN)/, $(notdir $(TESTSRC))))) DEBIAN = debian/changelog debian/control debian/compat debian/copyright debian/docs debian/tsung.dirs debian/rules SRC_APPFILES = $(ESRC)/$(APPLICATION)/$(APPLICATION).app.src $(ESRC)/$(APPLICATION)/$(APPLICATION).rel.src SRC_APPFILES_IN = $(ESRC)/$(APPLICATION)/$(APPLICATION).app.src.in $(ESRC)/$(APPLICATION)/$(APPLICATION).rel.src CONTROLLER_SRC_APPFILES = $(ESRC)/$(CONTROLLER_APPLICATION)/$(CONTROLLER_APPLICATION).app.src $(ESRC)/$(CONTROLLER_APPLICATION)/$(CONTROLLER_APPLICATION).rel.src CONTROLLER_SRC_APPFILES_IN = $(ESRC)/$(CONTROLLER_APPLICATION)/$(CONTROLLER_APPLICATION).app.src.in $(ESRC)/$(CONTROLLER_APPLICATION)/$(CONTROLLER_APPLICATION).rel.src RECORDER_SRC_APPFILES = $(ESRC)/$(RECORDER_APPLICATION)/$(RECORDER_APPLICATION).app.src $(ESRC)/$(RECORDER_APPLICATION)/$(RECORDER_APPLICATION).rel.src RECORDER_SRC_APPFILES_IN = $(ESRC)/$(RECORDER_APPLICATION)/$(RECORDER_APPLICATION).app.src.in $(ESRC)/$(RECORDER_APPLICATION)/$(RECORDER_APPLICATION).rel.src TGT_APPFILES_E = $(EBIN)/$(APPLICATION).app CONTROLLER_TGT_APPFILES_E = $(EBIN)/$(CONTROLLER_APPLICATION).app RECORDER_TGT_APPFILES_E = $(EBIN)/$(RECORDER_APPLICATION).app TGT_APPFILES_P = priv/$(APPLICATION)* RECORDER_TGT_APPFILES_P = priv/$(RECORDER_APPLICATION)* CONTROLLER_TGT_APPFILES_P = priv/$(CONTROLLER_APPLICATION)* SCRIPT = $(BINDIR)/tsung REC_SCRIPT = $(BINDIR)/tsung-recorder PWD = $(shell pwd) BUILD_OPTIONS = '[{systools, \ [{variables,[ \ {"TSUNGPATH", "$(PWD)/temp/"}] \ }]}, \ {sh_script, none}, \ {make_app, true }, {make_rel, true}].' BUILD_OPTIONS_DOT = $(subst $(PWD)/temp/,./,$(BUILD_OPTIONS)) BUILD_OPTIONS_FILE = ./BUILD_OPTIONS DIST_COMMON=Makefile.in $(CONFFILE_SRC) $(PERL_SCRIPTS_SRC) $(TSPLOT_SRC) tsung.sh.in tsung-recorder.sh.in tsung.spec.in $(CONTROLLER_SRC_APPFILES_IN) DOC_OPTS={def,{version,\"$(VERSION)\"}} .PHONY: doc tsung: Makefile config.status $(PERL_SCRIPTS) $(TSPLOT) tsung.sh tsung-recorder.sh tsung.spec $(TARGET) $(RECORDER_TARGET) $(CONTROLLER_TARGET) $(LIB_TARGET) $(CONTROLLER_SRC_APPFILES) $(SRC_APPFILES) $(RECORDER_SRC_APPFILES) buildtest: $(TEST_TARGET) fulltest: clean test test: $(MAKE) TYPE=test dotest dotest: tsung buildtest $(CONFFILE) $(TEST_CONFFILE) $(ERL) -noshell -pa ./ebin -s eunit test ts_test_all -s init stop edoc: $(ERL) -noshell -eval "edoc:application($(APPLICATION), \"./$(ESRC)/$(APPLICATION)\", [$(DOC_OPTS)])" -s init stop $(ERL) -noshell -eval "edoc:application($(CONTROLLER_APPLICATION), \ \"./$(ESRC)/$(CONTROLLER_APPLICATION)\", [$(DOC_OPTS)])" -s init stop $(ERL) -noshell -eval "edoc:application($(RECORDER_APPLICATION), \ \"./$(ESRC)/$(RECORDER_APPLICATION)\", [$(DOC_OPTS)])" -s init stop dialyzer: $(DIALYZER) -r ebin -I ./include/ all: clean tsung debug: $(MAKE) TYPE=debug native: $(MAKE) TYPE=native rpm: release tsung.spec rpmbuild -ta $(distdir).tar.gz validate: $(CONFFILE) @for i in $(CONFFILE); do xmlproc_val $$i; done deb: fakeroot debian/rules clean debian/rules build fakeroot debian/rules binary clean: -cd priv && rm -f $(shell ls priv | grep -v builder\.erl| grep -v CVS) && cd .. -rm -f $(TARGET) $(TMP) $(BUILD_OPTIONS_FILE) builder.beam -rm -f $(TGT_APPFILES) $(PERL_SCRIPTS) $(TSPLOT) $(CONFFILE) -rm -f ebin/*.beam tsung.sh tsung.spec tsung.xml tsung.sh tsung-recorder.sh -rm -f *.xml config.log src/test/*.xml src/test/usersdb.csv install: doc boot install_recorder install_controller $(CONFFILE) -rm -f $(TMP) install -d $(DESTDIR)$(TARGETDIR)/priv install -d $(DESTDIR)$(TARGETDIR)/ebin install -d $(DESTDIR)$(TARGETDIR)/src install -d $(DESTDIR)$(TARGETDIR)/include install -d $(DESTDIR)$(TOOLS_BINDIR)/ install -d $(DESTDIR)$(BINDIR)/ install -m 0644 $(INC_FILES) $(DESTDIR)$(TARGETDIR)/include/ install -m 0644 $(TARGET) $(DESTDIR)$(TARGETDIR)/ebin/ install -m 0644 $(LIB_TARGET) $(DESTDIR)$(TARGETDIR)/ebin/ install -m 0644 builder.beam $(DESTDIR)$(TARGETDIR)/ebin/ install -m 0644 $(TGT_APPFILES_E) $(DESTDIR)$(TARGETDIR)/ebin/ install -m 0644 $(TGT_APPFILES_P) $(DESTDIR)$(TARGETDIR)/priv/ install -m 0644 $(SRC) $(SRC_APPFILES) $(DESTDIR)$(TARGETDIR)/src/ echo $(BUILD_OPTIONS_DOT) > $(DESTDIR)$(TARGETDIR)/BUILD_OPTIONS # install the man page & user's manual install -d $(DESTDIR)$(MAN_DIR) install -m 0644 $(MANPAGES) $(DESTDIR)$(MAN_DIR) install -d $(DESTDIR)$(DOC_DIR)/images install -m 0644 $(USERMANUAL) $(DESTDIR)$(DOC_DIR) install -m 0644 $(USERMANUAL_IMG) $(DESTDIR)$(DOC_DIR)/images # create startup script install -m 0755 tsung.sh $(DESTDIR)$(SCRIPT) install -m 0755 tsung-recorder.sh $(DESTDIR)$(REC_SCRIPT) install -m 0755 $(PERL_SCRIPTS) $(DESTDIR)$(TOOLS_BINDIR) # tsung-plotter install -m 0755 $(TSPLOT) $(DESTDIR)$(BINDIR)/tsplot install -d $(DESTDIR)$(LIBDIR)/tsung_plotter install -d $(DESTDIR)$(SHARE_DIR)/tsung_plotter install -m 0644 $(TSUNG_PLOTTER_LIB) $(DESTDIR)$(LIBDIR)/tsung_plotter install -m 0644 $(TSUNG_PLOTTER_CONF) $(DESTDIR)$(SHARE_DIR)/tsung_plotter install -d $(DESTDIR)$(CONFDIR) install -m 0644 $(CONFFILE) $(DESTDIR)$(CONFDIR)/ install -d $(DESTDIR)$(TEMPLATES_DIR) install -m 0644 $(TEMPLATES) $(DESTDIR)$(TEMPLATES_DIR)/ install -m 0644 $(DTD) $(DESTDIR)$(SHARE_DIR)/ install_recorder: boot install -d $(DESTDIR)$(RECORDER_TARGETDIR)/priv install -d $(DESTDIR)$(RECORDER_TARGETDIR)/ebin install -d $(DESTDIR)$(RECORDER_TARGETDIR)/src install -d $(DESTDIR)$(RECORDER_TARGETDIR)/include install -m 0644 $(INC_FILES) $(DESTDIR)$(RECORDER_TARGETDIR)/include install -m 0644 $(RECORDER_TARGET) $(DESTDIR)$(RECORDER_TARGETDIR)/ebin install -m 0644 $(RECORDER_TGT_APPFILES_E) $(DESTDIR)$(RECORDER_TARGETDIR)/ebin install -m 0644 $(RECORDER_TGT_APPFILES_P) $(DESTDIR)$(RECORDER_TARGETDIR)/priv install -m 0644 $(RECORDER_SRC) $(RECORDER_SRC_APPFILES) $(DESTDIR)$(RECORDER_TARGETDIR)/src @echo $(BUILD_OPTIONS_DOT) > $(DESTDIR)$(RECORDER_TARGETDIR)/BUILD_OPTIONS install_controller: boot install -d $(DESTDIR)$(CONTROLLER_TARGETDIR)/priv install -d $(DESTDIR)$(CONTROLLER_TARGETDIR)/ebin install -d $(DESTDIR)$(CONTROLLER_TARGETDIR)/src install -d $(DESTDIR)$(CONTROLLER_TARGETDIR)/include install -m 0644 $(INC_FILES) $(DESTDIR)$(CONTROLLER_TARGETDIR)/include install -m 0644 $(CONTROLLER_TARGET) $(DESTDIR)$(CONTROLLER_TARGETDIR)/ebin install -m 0644 $(CONTROLLER_TGT_APPFILES_E) $(DESTDIR)$(CONTROLLER_TARGETDIR)/ebin install -m 0644 $(CONTROLLER_TGT_APPFILES_P) $(DESTDIR)$(CONTROLLER_TARGETDIR)/priv install -m 0644 $(CONTROLLER_SRC) $(CONTROLLER_SRC_APPFILES) $(DESTDIR)$(CONTROLLER_TARGETDIR)/src @echo $(BUILD_OPTIONS_DOT) > $(DESTDIR)$(CONTROLLER_TARGETDIR)/BUILD_OPTIONS uninstall: rm -rf $(TARGETDIR) $(SCRIPT) boot: tsung priv/tsung.boot priv/tsung_recorder.boot priv/tsung_controller.boot priv/tsung.boot: builder.beam $(SRC_APPFILES) # use builder to make boot file @rm -rf temp_ts @mkdir -p temp_ts/lib/$(APPLICATION)-$(VERSION)/ebin @cp $(TARGET) $(LIB_TARGET) temp_ts/lib/$(APPLICATION)-$(VERSION)/ebin @ln -sf $(PWD)/src/$(APPLICATION) temp_ts/lib/$(APPLICATION)-$(VERSION)/src @ln -sf $(PWD)/include temp_ts/lib/$(APPLICATION)-$(VERSION)/include @ln -sf $(PWD)/priv temp_ts/lib/$(APPLICATION)-$(VERSION)/priv @ln -sf $(PWD)/builder.beam temp_ts/lib/$(APPLICATION)-$(VERSION)/ @ln -sf $(PWD) temp_ts/lib/$(APPLICATION)-$(VERSION) @echo -n "build main app boot script ... " @(cd temp_ts/lib/$(APPLICATION)-$(VERSION) \ && echo $(BUILD_OPTIONS) > $(BUILD_OPTIONS_FILE) \ && $(ERL) -noshell -s builder go -s init stop >> $(BUILDER_LOG) 2>&1 \ ) @cp temp_ts/lib/$(APPLICATION)-$(VERSION)/ebin/*.app ebin @rm -rf temp_ts @echo "done" priv/tsung_controller.boot: builder.beam $(CONTROLLER_SRC_APPFILES) # use builder to make boot file @rm -rf temp_tsc @mkdir -p temp_tsc/lib/$(CONTROLLER_APPLICATION)-$(VERSION)/ebin @cp $(CONTROLLER_TARGET) temp_tsc/lib/$(CONTROLLER_APPLICATION)-$(VERSION)/ebin @ln -sf $(PWD)/src/$(CONTROLLER_APPLICATION) temp_tsc/lib/$(CONTROLLER_APPLICATION)-$(VERSION)/src @ln -sf $(PWD)/include temp_tsc/lib/$(CONTROLLER_APPLICATION)-$(VERSION)/include @ln -sf $(PWD)/priv temp_tsc/lib/$(CONTROLLER_APPLICATION)-$(VERSION)/priv @ln -sf $(PWD)/builder.beam temp_tsc/lib/$(CONTROLLER_APPLICATION)-$(VERSION)/ @echo -n "build controller boot script ... " @(cd temp_tsc/lib/$(CONTROLLER_APPLICATION)-$(VERSION) \ && echo $(BUILD_OPTIONS) > $(BUILD_OPTIONS_FILE) \ && $(ERL) -noshell -s builder go -s init stop >> $(BUILDER_LOG) 2>&1 \ ) @cp temp_tsc/lib/$(CONTROLLER_APPLICATION)-$(VERSION)/ebin/*.app ebin @rm -rf temp_tsc @echo "done" priv/tsung_recorder.boot: builder.beam $(RECORDER_SRC_APPFILES) # use builder to make boot file @rm -rf temp_tsr @mkdir -p temp_tsr/lib/$(RECORDER_APPLICATION)-$(VERSION)/ebin @cp $(RECORDER_TARGET) temp_tsr/lib/$(RECORDER_APPLICATION)-$(VERSION)/ebin @ln -sf $(PWD)/src/$(RECORDER_APPLICATION) temp_tsr/lib/$(RECORDER_APPLICATION)-$(VERSION)/src @ln -sf $(PWD)/include temp_tsr/lib/$(RECORDER_APPLICATION)-$(VERSION)/include @ln -sf $(PWD)/priv temp_tsr/lib/$(RECORDER_APPLICATION)-$(VERSION)/priv @ln -sf $(PWD)/builder.beam temp_tsr/lib/$(RECORDER_APPLICATION)-$(VERSION)/ @echo -n "build recorder boot script ... " @(cd temp_tsr/lib/$(RECORDER_APPLICATION)-$(VERSION) \ && echo $(BUILD_OPTIONS) > $(BUILD_OPTIONS_FILE) \ && $(ERL) -noshell -s builder go -s init stop >> $(BUILDER_LOG) 2>&1 \ ) @cp temp_tsr/lib/$(RECORDER_APPLICATION)-$(VERSION)/ebin/*.app ebin @rm -rf temp_tsr @echo "done" Makefile: Makefile.in config.status @$(SHELL) ./config.status --file=$@ %.pl: %.pl.in vsn.mk @$(SHELL) ./config.status --file=$@ %.py: %.py.in vsn.mk @$(SHELL) ./config.status --file=$@ %.spec: %.spec.in vsn.mk @$(SHELL) ./config.status --file=$@ %.xml: %.xml.in @$(SHELL) ./config.status --file=$@ %.sh :%.sh.in vsn.mk @$(SHELL) ./config.status --file=$@ %.app.src: %.app.src.in @$(SHELL) ./config.status --file=$@ config.status: configure $(CONFIG_STATUS_DEPENDENCIES) $(SHELL) ./config.status --recheck configure: configure.in $(CONFIGURE_DEPENDENCIES) @echo "running autoconf" @autoconf doc: $(MAKE) -C doc release: Makefile tsung.spec doc rm -fr $(distdir) mkdir -p $(distdir) tar zcf tmp.tgz $(SRC) $(SRC_APPFILES_IN) $(INC_FILES) $(LIBSRC) \ $(CONTROLLER_SRC) $(CONTROLLER_SRC_APPFILES_IN) $(TESTSRC) \ $(RECORDER_SRC_APPFILES_IN) \ $(RECORDER_SRC) $(RECORDER_SRC_APPFILES) $(TEMPLATES) \ doc/*.erl doc/*.txt doc/*.dia doc/*.png doc/Makefile doc/*.sgml doc/*.1 \ $(USERMANUAL) $(USERMANUAL_SRC) $(USERMANUAL_IMG) $(DTD) \ COPYING README LISEZMOI TODO $(CONFFILE_SRC) $(TEST_CONFFILE_SRC) \ priv/builder.erl tsung.sh.in vsn.mk src/test/*.csv src/test/*.txt \ src/test/*.out \ $(DEBIAN) $(PERL_SCRIPTS_SRC) CONTRIBUTORS CHANGES \ $(TSPLOT_SRC) $(TSUNG_PLOTTER_CONF) $(TSUNG_PLOTTER_LIB)\ configure configure.in config.guess *.m4 config.sub Makefile.in \ install-sh tsung.spec.in tsung.spec tsung-recorder.sh.in tar -C $(distdir) -zxf tmp.tgz mkdir $(distdir)/ebin tar zvcf $(distdir).tar.gz $(distdir) rm -fr $(distdir) rm -fr tmp.tgz snapshot: $(MAKE) TYPE=snapshot release builder.beam: priv/builder.erl @$(CC) -W0 $(OPT) -I $(INC) $< ebin/%.beam: src/test/%.erl $(INC_FILES) @echo "Compiling test $< ... " @$(CC) -W0 $(OPT) -I $(INC) -I $(ERLANG_XMERL_DIR) -o ebin $< ebin/%.beam: src/lib/%.erl $(INC_FILES) @echo "Compiling $< ... " @$(CC) -W0 $(OPT) -I $(INC) -I $(ERLANG_XMERL_DIR) -o ebin $< # to avoid circular dependency ebin/ts_plugin.beam: src/$(APPLICATION)/ts_plugin.erl $(INC_FILES) @echo "Compiling $< ... " @$(CC) $(OPT) -I $(INC) -I $(ERLANG_XMERL_DIR) -pa ebin -o ebin $< ebin/%.beam: src/$(APPLICATION)/%.erl $(INC_FILES) $(BEHAVIORS) @echo "Compiling $< ... " @$(CC) $(OPT) -I $(INC) -I $(ERLANG_XMERL_DIR) -pa ebin -o ebin $< ebin/%.beam: src/$(RECORDER_APPLICATION)/%.erl $(INC_FILES) $(BEHAVIORS) @echo "Compiling $< ... " @$(CC) $(OPT) -I $(INC) -I $(ERLANG_XMERL_DIR) -pa ebin -o ebin $< ebin/%.beam: src/$(CONTROLLER_APPLICATION)/%.erl $(INC_FILES) $(BEHAVIORS) @echo "Compiling $< ... " @$(CC) $(OPT) -I $(INC) -I $(ERLANG_XMERL_DIR) -pa ebin -o ebin $< %:%.sh # Override makefile default implicit rule tsung-1.4.2/TODO0000644000201100017670000000006111701017117013024 0ustar nniclausdreamSee https://support.process-one.net/browse/TSUN tsung-1.4.2/configure0000755000201100017670000037471511701017137014271 0ustar nniclausdream#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.68 for tsung 1.4.2. # # Report bugs to . # # # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, # 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software # Foundation, Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. # # Copyright (C) 2008 Nicolas Niclausse ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : else exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" if (eval "$as_required") 2>/dev/null; then : as_have_required=yes else as_have_required=no fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir/$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : CONFIG_SHELL=$as_shell as_have_required=yes if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : break 2 fi fi done;; esac as_found=false done $as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : CONFIG_SHELL=$SHELL as_have_required=yes fi; } IFS=$as_save_IFS if test "x$CONFIG_SHELL" != x; then : # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV export CONFIG_SHELL case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec "$CONFIG_SHELL" $as_opts "$as_myself" ${1+"$@"} fi if test x$as_have_required = xno; then : $as_echo "$0: This script requires a shell more modern than all" $as_echo "$0: the shells that I found on your system." if test x${ZSH_VERSION+set} = xset ; then $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" $as_echo "$0: be upgraded to zsh 4.3.4 or later." else $as_echo "$0: Please tell bug-autoconf@gnu.org and $0: tsung-users@process-one.net about your system, $0: including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -p'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -p' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi if test -x / >/dev/null 2>&1; then as_test_x='test -x' else if ls -dL / >/dev/null 2>&1; then as_ls_L_option=L else as_ls_L_option= fi as_test_x=' eval sh -c '\'' if test -d "$1"; then test -d "$1/."; else case $1 in #( -*)set "./$1";; esac; case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( ???[sx]*):;;*)false;;esac;fi '\'' sh ' fi as_executable_p=$as_test_x # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='tsung' PACKAGE_TARNAME='tsung' PACKAGE_VERSION='1.4.2' PACKAGE_STRING='tsung 1.4.2' PACKAGE_BUGREPORT='tsung-users@process-one.net' PACKAGE_URL='' ac_unique_file="src/tsung/tsung.erl" ac_subst_vars='LTLIBOBJS LIBOBJS EXPANDED_SHAREDIR EXPANDED_LIBDIR INSTALL_DATA INSTALL_SCRIPT INSTALL_PROGRAM SET_MAKE TEMPLATES_SUBDIR DTD ERLANG_APPLICATIONS ERL_OPTS erlang_cv_orelse ERLANG_LIB_VER_public_key ERLANG_LIB_DIR_public_key ERLANG_LIB_VER_crypto ERLANG_LIB_DIR_crypto ERLANG_LIB_VER_ssl ERLANG_LIB_DIR_ssl ERLANG_LIB_VER_xmerl ERLANG_LIB_DIR_xmerl ERLANG_ROOT_DIR ac_prefix_program ERL ERLCFLAGS ERLC SED CONFIGURE_DEPENDENCIES CONFIG_STATUS_DEPENDENCIES target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking with_erlang ' ac_precious_vars='build_alias host_alias target_alias ERLC ERLCFLAGS ERL' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac # Accept the important Cygnus configure options, so we can diagnose typos. case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: \`$ac_option' Try \`$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe $as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host. If a cross compiler is detected then cross compile mode will be used" >&2 elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF \`configure' configures tsung 1.4.2 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/tsung] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of tsung 1.4.2:";; esac cat <<\_ACEOF Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-erlang=PREFIX path to erlc and erl Some influential environment variables: ERLC Erlang/OTP compiler command [autodetected] ERLCFLAGS Erlang/OTP compiler flags [none] ERL Erlang/OTP interpreter command [autodetected] Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for guested configure. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF tsung configure 1.4.2 generated by GNU Autoconf 2.68 Copyright (C) 2010 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. Copyright (C) 2008 Nicolas Niclausse _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_erl_try_run LINENO # ------------------------ # Try to link conftest.$ac_ext, and return whether this succeeded. Assumes # that executables *can* be run. ac_fn_erl_try_run () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then : ac_retval=0 else $as_echo "$as_me: program exited with status $ac_status" >&5 $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=$ac_status fi rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_erl_try_run cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by tsung $as_me 1.4.2, which was generated by GNU Autoconf 2.68. Invocation command line was $ $0 $@ _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. $as_echo "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Save into config.log some information that might help in debugging. { echo $as_echo "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo $as_echo "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then $as_echo "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then $as_echo "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && $as_echo "$as_me: caught signal $ac_signal" $as_echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h $as_echo "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. cat >>confdefs.h <<_ACEOF #define PACKAGE_NAME "$PACKAGE_NAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_TARNAME "$PACKAGE_TARNAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_VERSION "$PACKAGE_VERSION" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_STRING "$PACKAGE_STRING" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_URL "$PACKAGE_URL" _ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. ac_site_file1=NONE ac_site_file2=NONE if test -n "$CONFIG_SITE"; then # We do not want a PATH search for config.site. case $CONFIG_SITE in #(( -*) ac_site_file1=./$CONFIG_SITE;; */*) ac_site_file1=$CONFIG_SITE;; *) ac_site_file1=./$CONFIG_SITE;; esac elif test "x$prefix" != xNONE; then ac_site_file1=$prefix/share/config.site ac_site_file2=$prefix/etc/config.site else ac_site_file1=$ac_default_prefix/share/config.site ac_site_file2=$ac_default_prefix/etc/config.site fi for ac_site_file in "$ac_site_file1" "$ac_site_file2" do test "x$ac_site_file" = xNONE && continue if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 $as_echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See \`config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 $as_echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 $as_echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 $as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 $as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 $as_echo "$as_me: former value: \`$ac_old_val'" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 $as_echo "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 $as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 $as_echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 $as_echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi CONFIG_STATUS_DEPENDENCIES=vsn.mk CONFIGURE_DEPENDENCIES=vsn.mk # Extract the first word of "sed", so it can be a program name with args. set dummy sed; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_SED+:} false; then : $as_echo_n "(cached) " >&6 else case $SED in [\\/]* | ?:[\\/]*) ac_cv_path_SED="$SED" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_SED="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi SED=$ac_cv_path_SED if test -n "$SED"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SED" >&5 $as_echo "$SED" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi ac_ext=erl ac_compile='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5' ac_link='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5 && echo "#!/bin/sh" > conftest$ac_exeext && $as_echo "\"$ERL\" -run conftest start -run init stop -noshell" >> conftest$ac_exeext && chmod +x conftest$ac_exeext' # Check whether --with-erlang was given. if test "${with_erlang+set}" = set; then : withval=$with_erlang; fi if test -n "$ERLC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for erlc" >&5 $as_echo_n "checking for erlc... " >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ERLC" >&5 $as_echo "$ERLC" >&6; } else if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}erlc", so it can be a program name with args. set dummy ${ac_tool_prefix}erlc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ERLC+:} false; then : $as_echo_n "(cached) " >&6 else case $ERLC in [\\/]* | ?:[\\/]*) ac_cv_path_ERLC="$ERLC" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_dummy="$with_erlang:$with_erlang/bin:$PATH" for as_dir in $as_dummy do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_ERLC="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ERLC=$ac_cv_path_ERLC if test -n "$ERLC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ERLC" >&5 $as_echo "$ERLC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_path_ERLC"; then ac_pt_ERLC=$ERLC # Extract the first word of "erlc", so it can be a program name with args. set dummy erlc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ac_pt_ERLC+:} false; then : $as_echo_n "(cached) " >&6 else case $ac_pt_ERLC in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_ERLC="$ac_pt_ERLC" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_dummy="$with_erlang:$with_erlang/bin:$PATH" for as_dir in $as_dummy do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_ac_pt_ERLC="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ac_pt_ERLC=$ac_cv_path_ac_pt_ERLC if test -n "$ac_pt_ERLC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_ERLC" >&5 $as_echo "$ac_pt_ERLC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_pt_ERLC" = x; then ERLC="erlc" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac ERLC=$ac_pt_ERLC fi else ERLC="$ac_cv_path_ERLC" fi fi if test -n "$ERL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for erl" >&5 $as_echo_n "checking for erl... " >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ERL" >&5 $as_echo "$ERL" >&6; } else if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}erl", so it can be a program name with args. set dummy ${ac_tool_prefix}erl; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ERL+:} false; then : $as_echo_n "(cached) " >&6 else case $ERL in [\\/]* | ?:[\\/]*) ac_cv_path_ERL="$ERL" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_dummy="$with_erlang:$with_erlang/bin:$PATH" for as_dir in $as_dummy do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_ERL="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ERL=$ac_cv_path_ERL if test -n "$ERL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ERL" >&5 $as_echo "$ERL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_path_ERL"; then ac_pt_ERL=$ERL # Extract the first word of "erl", so it can be a program name with args. set dummy erl; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ac_pt_ERL+:} false; then : $as_echo_n "(cached) " >&6 else case $ac_pt_ERL in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_ERL="$ac_pt_ERL" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_dummy="$with_erlang:$with_erlang/bin:$PATH" for as_dir in $as_dummy do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_ac_pt_ERL="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ac_pt_ERL=$ac_cv_path_ac_pt_ERL if test -n "$ac_pt_ERL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_ERL" >&5 $as_echo "$ac_pt_ERL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_pt_ERL" = x; then ERL="erl" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac ERL=$ac_pt_ERL fi else ERL="$ac_cv_path_ERL" fi fi if test "x$prefix" = xNONE; then $as_echo_n "checking for prefix by " >&6 # Extract the first word of "erl", so it can be a program name with args. set dummy erl; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ac_prefix_program+:} false; then : $as_echo_n "(cached) " >&6 else case $ac_prefix_program in [\\/]* | ?:[\\/]*) ac_cv_path_ac_prefix_program="$ac_prefix_program" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_ac_prefix_program="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ac_prefix_program=$ac_cv_path_ac_prefix_program if test -n "$ac_prefix_program"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_prefix_program" >&5 $as_echo "$ac_prefix_program" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test -n "$ac_prefix_program"; then prefix=`$as_dirname -- "$ac_prefix_program" || $as_expr X"$ac_prefix_program" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_prefix_program" : 'X\(//\)[^/]' \| \ X"$ac_prefix_program" : 'X\(//\)$' \| \ X"$ac_prefix_program" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$ac_prefix_program" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` prefix=`$as_dirname -- "$prefix" || $as_expr X"$prefix" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$prefix" : 'X\(//\)[^/]' \| \ X"$prefix" : 'X\(//\)$' \| \ X"$prefix" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$prefix" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` fi fi if test -n "$ERLC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for erlc" >&5 $as_echo_n "checking for erlc... " >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ERLC" >&5 $as_echo "$ERLC" >&6; } else if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}erlc", so it can be a program name with args. set dummy ${ac_tool_prefix}erlc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ERLC+:} false; then : $as_echo_n "(cached) " >&6 else case $ERLC in [\\/]* | ?:[\\/]*) ac_cv_path_ERLC="$ERLC" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_ERLC="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ERLC=$ac_cv_path_ERLC if test -n "$ERLC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ERLC" >&5 $as_echo "$ERLC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_path_ERLC"; then ac_pt_ERLC=$ERLC # Extract the first word of "erlc", so it can be a program name with args. set dummy erlc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ac_pt_ERLC+:} false; then : $as_echo_n "(cached) " >&6 else case $ac_pt_ERLC in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_ERLC="$ac_pt_ERLC" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_ac_pt_ERLC="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ac_pt_ERLC=$ac_cv_path_ac_pt_ERLC if test -n "$ac_pt_ERLC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_ERLC" >&5 $as_echo "$ac_pt_ERLC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_pt_ERLC" = x; then ERLC="not found" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac ERLC=$ac_pt_ERLC fi else ERLC="$ac_cv_path_ERLC" fi fi if test "$ERLC" = "not found"; then as_fn_error $? "Erlang/OTP compiler (erlc) not found but required" "$LINENO" 5 fi if test -n "$ERL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for erl" >&5 $as_echo_n "checking for erl... " >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ERL" >&5 $as_echo "$ERL" >&6; } else if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}erl", so it can be a program name with args. set dummy ${ac_tool_prefix}erl; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ERL+:} false; then : $as_echo_n "(cached) " >&6 else case $ERL in [\\/]* | ?:[\\/]*) ac_cv_path_ERL="$ERL" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_ERL="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ERL=$ac_cv_path_ERL if test -n "$ERL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ERL" >&5 $as_echo "$ERL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_path_ERL"; then ac_pt_ERL=$ERL # Extract the first word of "erl", so it can be a program name with args. set dummy erl; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ac_pt_ERL+:} false; then : $as_echo_n "(cached) " >&6 else case $ac_pt_ERL in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_ERL="$ac_pt_ERL" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_ac_pt_ERL="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ac_pt_ERL=$ac_cv_path_ac_pt_ERL if test -n "$ac_pt_ERL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_ERL" >&5 $as_echo "$ac_pt_ERL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_pt_ERL" = x; then ERL="not found" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac ERL=$ac_pt_ERL fi else ERL="$ac_cv_path_ERL" fi fi if test "$ERL" = "not found"; then as_fn_error $? "Erlang/OTP interpreter (erl) not found but required" "$LINENO" 5 fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Erlang/OTP root directory" >&5 $as_echo_n "checking for Erlang/OTP root directory... " >&6; } if ${ac_cv_erlang_root_dir+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=erl ac_compile='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5' ac_link='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5 && echo "#!/bin/sh" > conftest$ac_exeext && $as_echo "\"$ERL\" -run conftest start -run init stop -noshell" >> conftest$ac_exeext && chmod +x conftest$ac_exeext' if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5; } else cat > conftest.$ac_ext <<_ACEOF -module(conftest). -export([start/0]). start() -> RootDir = code:root_dir(), file:write_file("conftest.out", RootDir), ReturnValue = 0, halt(ReturnValue) . _ACEOF if ac_fn_erl_try_run "$LINENO"; then : ac_cv_erlang_root_dir=`cat conftest.out` rm -f conftest.out else rm -f conftest.out { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "test Erlang program execution failed See \`config.log' for more details" "$LINENO" 5; } fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi ac_ext=erl ac_compile='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5' ac_link='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5 && echo "#!/bin/sh" > conftest$ac_exeext && $as_echo "\"$ERL\" -run conftest start -run init stop -noshell" >> conftest$ac_exeext && chmod +x conftest$ac_exeext' fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_erlang_root_dir" >&5 $as_echo "$ac_cv_erlang_root_dir" >&6; } ERLANG_ROOT_DIR=$ac_cv_erlang_root_dir { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Erlang/OTP '-hybrid' option" >&5 $as_echo_n "checking for Erlang/OTP '-hybrid' option... " >&6; } if ! $ERL -noshell -hybrid -smp 2 -s init stop 2> /dev/null; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } ERL_OPTS="-hybrid $ERL_OPTS" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Erlang/OTP 'xmerl' library subdirectory" >&5 $as_echo_n "checking for Erlang/OTP 'xmerl' library subdirectory... " >&6; } if ${ac_cv_erlang_lib_dir_xmerl+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=erl ac_compile='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5' ac_link='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5 && echo "#!/bin/sh" > conftest$ac_exeext && $as_echo "\"$ERL\" -run conftest start -run init stop -noshell" >> conftest$ac_exeext && chmod +x conftest$ac_exeext' if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5; } else cat > conftest.$ac_ext <<_ACEOF -module(conftest). -export([start/0]). start() -> ReturnValue = case code:lib_dir("xmerl") of {error, bad_name} -> file:write_file("conftest.out", "not found\n"), 1; LibDir -> file:write_file("conftest.out", LibDir), 0 end, halt(ReturnValue) . _ACEOF if ac_fn_erl_try_run "$LINENO"; then : ac_cv_erlang_lib_dir_xmerl=`cat conftest.out` rm -f conftest.out else if test ! -f conftest.out; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "test Erlang program execution failed See \`config.log' for more details" "$LINENO" 5; } else ac_cv_erlang_lib_dir_xmerl="not found" rm -f conftest.out fi fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi ac_ext=erl ac_compile='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5' ac_link='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5 && echo "#!/bin/sh" > conftest$ac_exeext && $as_echo "\"$ERL\" -run conftest start -run init stop -noshell" >> conftest$ac_exeext && chmod +x conftest$ac_exeext' fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_erlang_lib_dir_xmerl" >&5 $as_echo "$ac_cv_erlang_lib_dir_xmerl" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Erlang/OTP 'xmerl' library version" >&5 $as_echo_n "checking for Erlang/OTP 'xmerl' library version... " >&6; } if ${ac_cv_erlang_lib_ver_xmerl+:} false; then : $as_echo_n "(cached) " >&6 else if test "$ac_cv_erlang_lib_dir_xmerl" = "not found"; then : ac_cv_erlang_lib_ver_xmerl="not found" else ac_cv_erlang_lib_ver_xmerl=`$as_echo "$ac_cv_erlang_lib_dir_xmerl" | sed -n -e 's,^.*-\([^/-]*\)$,\1,p'` fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_erlang_lib_ver_xmerl" >&5 $as_echo "$ac_cv_erlang_lib_ver_xmerl" >&6; } ERLANG_LIB_DIR_xmerl=$ac_cv_erlang_lib_dir_xmerl ERLANG_LIB_VER_xmerl=$ac_cv_erlang_lib_ver_xmerl if test "$ac_cv_erlang_lib_dir_xmerl" = "not found"; then : fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Erlang/OTP 'ssl' library subdirectory" >&5 $as_echo_n "checking for Erlang/OTP 'ssl' library subdirectory... " >&6; } if ${ac_cv_erlang_lib_dir_ssl+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=erl ac_compile='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5' ac_link='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5 && echo "#!/bin/sh" > conftest$ac_exeext && $as_echo "\"$ERL\" -run conftest start -run init stop -noshell" >> conftest$ac_exeext && chmod +x conftest$ac_exeext' if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5; } else cat > conftest.$ac_ext <<_ACEOF -module(conftest). -export([start/0]). start() -> ReturnValue = case code:lib_dir("ssl") of {error, bad_name} -> file:write_file("conftest.out", "not found\n"), 1; LibDir -> file:write_file("conftest.out", LibDir), 0 end, halt(ReturnValue) . _ACEOF if ac_fn_erl_try_run "$LINENO"; then : ac_cv_erlang_lib_dir_ssl=`cat conftest.out` rm -f conftest.out else if test ! -f conftest.out; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "test Erlang program execution failed See \`config.log' for more details" "$LINENO" 5; } else ac_cv_erlang_lib_dir_ssl="not found" rm -f conftest.out fi fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi ac_ext=erl ac_compile='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5' ac_link='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5 && echo "#!/bin/sh" > conftest$ac_exeext && $as_echo "\"$ERL\" -run conftest start -run init stop -noshell" >> conftest$ac_exeext && chmod +x conftest$ac_exeext' fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_erlang_lib_dir_ssl" >&5 $as_echo "$ac_cv_erlang_lib_dir_ssl" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Erlang/OTP 'ssl' library version" >&5 $as_echo_n "checking for Erlang/OTP 'ssl' library version... " >&6; } if ${ac_cv_erlang_lib_ver_ssl+:} false; then : $as_echo_n "(cached) " >&6 else if test "$ac_cv_erlang_lib_dir_ssl" = "not found"; then : ac_cv_erlang_lib_ver_ssl="not found" else ac_cv_erlang_lib_ver_ssl=`$as_echo "$ac_cv_erlang_lib_dir_ssl" | sed -n -e 's,^.*-\([^/-]*\)$,\1,p'` fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_erlang_lib_ver_ssl" >&5 $as_echo "$ac_cv_erlang_lib_ver_ssl" >&6; } ERLANG_LIB_DIR_ssl=$ac_cv_erlang_lib_dir_ssl ERLANG_LIB_VER_ssl=$ac_cv_erlang_lib_ver_ssl if test "$ac_cv_erlang_lib_dir_ssl" = "not found"; then : fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Erlang/OTP 'crypto' library subdirectory" >&5 $as_echo_n "checking for Erlang/OTP 'crypto' library subdirectory... " >&6; } if ${ac_cv_erlang_lib_dir_crypto+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=erl ac_compile='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5' ac_link='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5 && echo "#!/bin/sh" > conftest$ac_exeext && $as_echo "\"$ERL\" -run conftest start -run init stop -noshell" >> conftest$ac_exeext && chmod +x conftest$ac_exeext' if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5; } else cat > conftest.$ac_ext <<_ACEOF -module(conftest). -export([start/0]). start() -> ReturnValue = case code:lib_dir("crypto") of {error, bad_name} -> file:write_file("conftest.out", "not found\n"), 1; LibDir -> file:write_file("conftest.out", LibDir), 0 end, halt(ReturnValue) . _ACEOF if ac_fn_erl_try_run "$LINENO"; then : ac_cv_erlang_lib_dir_crypto=`cat conftest.out` rm -f conftest.out else if test ! -f conftest.out; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "test Erlang program execution failed See \`config.log' for more details" "$LINENO" 5; } else ac_cv_erlang_lib_dir_crypto="not found" rm -f conftest.out fi fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi ac_ext=erl ac_compile='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5' ac_link='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5 && echo "#!/bin/sh" > conftest$ac_exeext && $as_echo "\"$ERL\" -run conftest start -run init stop -noshell" >> conftest$ac_exeext && chmod +x conftest$ac_exeext' fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_erlang_lib_dir_crypto" >&5 $as_echo "$ac_cv_erlang_lib_dir_crypto" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Erlang/OTP 'crypto' library version" >&5 $as_echo_n "checking for Erlang/OTP 'crypto' library version... " >&6; } if ${ac_cv_erlang_lib_ver_crypto+:} false; then : $as_echo_n "(cached) " >&6 else if test "$ac_cv_erlang_lib_dir_crypto" = "not found"; then : ac_cv_erlang_lib_ver_crypto="not found" else ac_cv_erlang_lib_ver_crypto=`$as_echo "$ac_cv_erlang_lib_dir_crypto" | sed -n -e 's,^.*-\([^/-]*\)$,\1,p'` fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_erlang_lib_ver_crypto" >&5 $as_echo "$ac_cv_erlang_lib_ver_crypto" >&6; } ERLANG_LIB_DIR_crypto=$ac_cv_erlang_lib_dir_crypto ERLANG_LIB_VER_crypto=$ac_cv_erlang_lib_ver_crypto if test "$ac_cv_erlang_lib_dir_crypto" = "not found"; then : fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Erlang/OTP 'public_key' library subdirectory" >&5 $as_echo_n "checking for Erlang/OTP 'public_key' library subdirectory... " >&6; } if ${ac_cv_erlang_lib_dir_public_key+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=erl ac_compile='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5' ac_link='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5 && echo "#!/bin/sh" > conftest$ac_exeext && $as_echo "\"$ERL\" -run conftest start -run init stop -noshell" >> conftest$ac_exeext && chmod +x conftest$ac_exeext' if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5; } else cat > conftest.$ac_ext <<_ACEOF -module(conftest). -export([start/0]). start() -> ReturnValue = case code:lib_dir("public_key") of {error, bad_name} -> file:write_file("conftest.out", "not found\n"), 1; LibDir -> file:write_file("conftest.out", LibDir), 0 end, halt(ReturnValue) . _ACEOF if ac_fn_erl_try_run "$LINENO"; then : ac_cv_erlang_lib_dir_public_key=`cat conftest.out` rm -f conftest.out else if test ! -f conftest.out; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "test Erlang program execution failed See \`config.log' for more details" "$LINENO" 5; } else ac_cv_erlang_lib_dir_public_key="not found" rm -f conftest.out fi fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi ac_ext=erl ac_compile='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5' ac_link='$ERLC $ERLCFLAGS -b beam conftest.$ac_ext >&5 && echo "#!/bin/sh" > conftest$ac_exeext && $as_echo "\"$ERL\" -run conftest start -run init stop -noshell" >> conftest$ac_exeext && chmod +x conftest$ac_exeext' fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_erlang_lib_dir_public_key" >&5 $as_echo "$ac_cv_erlang_lib_dir_public_key" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Erlang/OTP 'public_key' library version" >&5 $as_echo_n "checking for Erlang/OTP 'public_key' library version... " >&6; } if ${ac_cv_erlang_lib_ver_public_key+:} false; then : $as_echo_n "(cached) " >&6 else if test "$ac_cv_erlang_lib_dir_public_key" = "not found"; then : ac_cv_erlang_lib_ver_public_key="not found" else ac_cv_erlang_lib_ver_public_key=`$as_echo "$ac_cv_erlang_lib_dir_public_key" | sed -n -e 's,^.*-\([^/-]*\)$,\1,p'` fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_erlang_lib_ver_public_key" >&5 $as_echo "$ac_cv_erlang_lib_ver_public_key" >&6; } ERLANG_LIB_DIR_public_key=$ac_cv_erlang_lib_dir_public_key ERLANG_LIB_VER_public_key=$ac_cv_erlang_lib_ver_public_key if test "$ac_cv_erlang_lib_dir_public_key" = "not found"; then : fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking if Erlang/OTP SSL application is running fine" >&5 $as_echo_n "checking if Erlang/OTP SSL application is running fine... " >&6; } if ${erlang_cv_ssl_runnable+:} false; then : $as_echo_n "(cached) " >&6 else erlang_cv_ssl_runnable=no if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5; } else cat > conftest.$ac_ext <<_ACEOF -module(conftest). -export([start/0]). start() -> case application:start(ssl) of ok -> ok; Err -> halt(1) end, halt(0) . _ACEOF if ac_fn_erl_try_run "$LINENO"; then : erlang_cv_ssl_runnable=yes ERLANG_APPLICATIONS="kernel,stdlib,ssl" else if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5; } else cat > conftest.$ac_ext <<_ACEOF -module(conftest). -export([start/0]). start() -> application:start(crypto), application:start(public_key), case application:start(ssl) of ok -> ok; Err -> halt(1) end, halt(0) . _ACEOF if ac_fn_erl_try_run "$LINENO"; then : erlang_cv_ssl_runnable=yes ERLANG_APPLICATIONS="kernel,stdlib,crypto,public_key,ssl" else ERLANG_APPLICATIONS="kernel,stdlib" { $as_echo "$as_me:${as_lineno-$LINENO}: result: WARNING: ssl application is not working properly !!!" >&5 $as_echo "WARNING: ssl application is not working properly !!!" >&6; } fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $erlang_cv_ssl_runnable" >&5 $as_echo "$erlang_cv_ssl_runnable" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking if Erlang/OTP crypto application is running fine" >&5 $as_echo_n "checking if Erlang/OTP crypto application is running fine... " >&6; } if ${erlang_cv_crypto_runnable+:} false; then : $as_echo_n "(cached) " >&6 else erlang_cv_crypto_runnable=no if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5; } else cat > conftest.$ac_ext <<_ACEOF -module(conftest). -export([start/0]). start() -> case application:start(crypto) of ok -> case catch crypto:md5("toto") of <<247,29,190,82,98,138,63,131,167,122,180,148,129,117,37, 198>> -> ok; _ -> halt(1) end; Err -> erlang:display(Err), halt(1) end, halt(0) . _ACEOF if ac_fn_erl_try_run "$LINENO"; then : erlang_cv_crypto_runnable=yes ERLANG_APPLICATIONS="$ERLANG_APPLICATIONS,crypto" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: WARNING: crypto application is not working properly !!!" >&5 $as_echo "WARNING: crypto application is not working properly !!!" >&6; } fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $erlang_cv_crypto_runnable" >&5 $as_echo "$erlang_cv_crypto_runnable" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking if orelse is allowed in guards" >&5 $as_echo_n "checking if orelse is allowed in guards... " >&6; } if ${erlang_cv_orelse+:} false; then : $as_echo_n "(cached) " >&6 else erlang_cv_orelse=no if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5; } else cat > conftest.$ac_ext <<_ACEOF -module(conftest). -export([start/0]). start() -> case 3 of A when A > 3 orelse A < 2 -> ok; _ -> bad end, halt(0) . _ACEOF if ac_fn_erl_try_run "$LINENO"; then : erlang_cv_orelse=yes else { $as_echo "$as_me:${as_lineno-$LINENO}: result: WARNING: orelse/andalso not allowed in guards: XPATH parsing will be disabled !!!" >&5 $as_echo "WARNING: orelse/andalso not allowed in guards: XPATH parsing will be disabled !!!" >&6; } fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $erlang_cv_orelse" >&5 $as_echo "$erlang_cv_orelse" >&6; } DTD=tsung-1.0.dtd TEMPLATES_SUBDIR=tsung/templates { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 $as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } set x ${MAKE-make} ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : $as_echo_n "(cached) " >&6 else cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @echo '@@@%%%=$(MAKE)=@@@%%%' _ACEOF # GNU make sometimes prints "make[1]: Entering ...", which would confuse us. case `${MAKE-make} -f conftest.make 2>/dev/null` in *@@@%%%=?*=@@@%%%*) eval ac_cv_prog_make_${ac_make}_set=yes;; *) eval ac_cv_prog_make_${ac_make}_set=no;; esac rm -f conftest.make fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } SET_MAKE= else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } SET_MAKE="MAKE=${MAKE-make}" fi ac_aux_dir= for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do if test -f "$ac_dir/install-sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install-sh -c" break elif test -f "$ac_dir/install.sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install.sh -c" break elif test -f "$ac_dir/shtool"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/shtool install -c" break fi done if test -z "$ac_aux_dir"; then as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5 fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 $as_echo_n "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then if ${ac_cv_path_install+:} false; then : $as_echo_n "(cached) " >&6 else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. # Account for people who put trailing slashes in PATH elements. case $as_dir/ in #(( ./ | .// | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then if test $ac_prog = install && grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else rm -rf conftest.one conftest.two conftest.dir echo one > conftest.one echo two > conftest.two mkdir conftest.dir if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" break 3 fi fi fi done done ;; esac done IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir fi if test "${ac_cv_path_install+set}" = set; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 $as_echo "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' EXP_VAR=EXPANDED_LIBDIR FROM_VAR="$libdir" prefix_save=$prefix exec_prefix_save=$exec_prefix if test "x$prefix" = "xNONE"; then prefix="$ac_default_prefix" fi if test "x$exec_prefix" = "xNONE"; then exec_prefix=$prefix fi full_var="$FROM_VAR" while true; do new_full_var="`eval echo $full_var`" if test "x$new_full_var" = "x$full_var"; then break; fi full_var=$new_full_var done full_var=$new_full_var EXPANDED_LIBDIR="$full_var" prefix=$prefix_save exec_prefix=$exec_prefix_save { $as_echo "$as_me:${as_lineno-$LINENO}: Storing library files in $EXPANDED_LIBDIR" >&5 $as_echo "$as_me: Storing library files in $EXPANDED_LIBDIR" >&6;} EXP_VAR=EXPANDED_SHAREDIR FROM_VAR="$datadir/tsung" prefix_save=$prefix exec_prefix_save=$exec_prefix if test "x$prefix" = "xNONE"; then prefix="$ac_default_prefix" fi if test "x$exec_prefix" = "xNONE"; then exec_prefix=$prefix fi full_var="$FROM_VAR" while true; do new_full_var="`eval echo $full_var`" if test "x$new_full_var" = "x$full_var"; then break; fi full_var=$new_full_var done full_var=$new_full_var EXPANDED_SHAREDIR="$full_var" prefix=$prefix_save exec_prefix=$exec_prefix_save { $as_echo "$as_me:${as_lineno-$LINENO}: Storing data files in $EXPANDED_SHAREDIR" >&5 $as_echo "$as_me: Storing data files in $EXPANDED_SHAREDIR" >&6;} ac_config_files="$ac_config_files Makefile tsung.spec tsung.sh tsung-recorder.sh examples/*.xml src/tsung_stats.pl src/tsung-plotter/tsplot.py src/log2tsung.pl src/tsung_controller/tsung_controller.app.src src/tsung_recorder/tsung_recorder.app.src src/tsung/tsung.app.src" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 $as_echo "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' # Transform confdefs.h into DEFS. # Protect against shell expansion while executing Makefile rules. # Protect against Makefile macro expansion. # # If the first sed substitution is executed (which looks for macros that # take arguments), then branch to the quote section. Otherwise, # look for a macro that doesn't take arguments. ac_script=' :mline /\\$/{ N s,\\\n,, b mline } t clear :clear s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g t quote s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g t quote b any :quote s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g s/\[/\\&/g s/\]/\\&/g s/\$/$$/g H :any ${ g s/^\n// s/\n/ /g p } ' DEFS=`sed -n "$ac_script" confdefs.h` ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`$as_echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 $as_echo "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -p'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -p' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi if test -x / >/dev/null 2>&1; then as_test_x='test -x' else if ls -dL / >/dev/null 2>&1; then as_ls_L_option=L else as_ls_L_option= fi as_test_x=' eval sh -c '\'' if test -d "$1"; then test -d "$1/."; else case $1 in #( -*)set "./$1";; esac; case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( ???[sx]*):;;*)false;;esac;fi '\'' sh ' fi as_executable_p=$as_test_x # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by tsung $as_me 1.4.2, which was generated by GNU Autoconf 2.68. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE Configuration files: $config_files Report bugs to ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ tsung config.status 1.4.2 configured by $0, generated by GNU Autoconf 2.68, with options \\"\$ac_cs_config\\" Copyright (C) 2010 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) $as_echo "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) $as_echo "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --he | --h | --help | --hel | -h ) $as_echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: \`$1' Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX $as_echo "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "tsung.spec") CONFIG_FILES="$CONFIG_FILES tsung.spec" ;; "tsung.sh") CONFIG_FILES="$CONFIG_FILES tsung.sh" ;; "tsung-recorder.sh") CONFIG_FILES="$CONFIG_FILES tsung-recorder.sh" ;; "examples/*.xml") CONFIG_FILES="$CONFIG_FILES examples/*.xml" ;; "src/tsung_stats.pl") CONFIG_FILES="$CONFIG_FILES src/tsung_stats.pl" ;; "src/tsung-plotter/tsplot.py") CONFIG_FILES="$CONFIG_FILES src/tsung-plotter/tsplot.py" ;; "src/log2tsung.pl") CONFIG_FILES="$CONFIG_FILES src/log2tsung.pl" ;; "src/tsung_controller/tsung_controller.app.src") CONFIG_FILES="$CONFIG_FILES src/tsung_controller/tsung_controller.app.src" ;; "src/tsung_recorder/tsung_recorder.app.src") CONFIG_FILES="$CONFIG_FILES src/tsung_recorder/tsung_recorder.app.src" ;; "src/tsung/tsung.app.src") CONFIG_FILES="$CONFIG_FILES src/tsung/tsung.app.src" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" eval set X " :F $CONFIG_FILES " shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 $as_echo "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`$as_echo "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 $as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi tsung-1.4.2/tsung-1.0.dtd0000644000201100017670000002564011701017117014477 0ustar nniclausdream tsung-1.4.2/examples/0000755000201100017670000000000011701017143014154 5ustar nniclausdreamtsung-1.4.2/examples/jabber_register.xml.in0000644000201100017670000000245511701017117020443 0ustar nniclausdream error tsung-1.4.2/examples/thinks2.xml.in0000644000201100017670000000226411701017117016672 0ustar nniclausdream tsung-1.4.2/examples/jabber.xml.in0000644000201100017670000001017211701017117016532 0ustar nniclausdream tsung-1.4.2/examples/jabber_roster.xml.in0000644000201100017670000000530311701017117020130 0ustar nniclausdream tsung-1.4.2/examples/http_simple.xml.in0000644000201100017670000000424011701017117017634 0ustar nniclausdream tsung-1.4.2/examples/thinks.xml.in0000644000201100017670000000227611701017117016613 0ustar nniclausdream tsung-1.4.2/examples/http_distributed.xml.in0000644000201100017670000001436011701017117020671 0ustar nniclausdream tsung-1.4.2/examples/jabber_privacy.xml.in0000644000201100017670000000327011701017117020270 0ustar nniclausdream tsung-1.4.2/examples/mysql.xml.in0000644000201100017670000000216511701017117016455 0ustar nniclausdream SHOW TABLES SELECT * FROM gens SELECT * FROM te tsung-1.4.2/examples/jabber_muc.xml.in0000644000201100017670000000607511701017117017405 0ustar nniclausdream tsung-1.4.2/examples/ldap.xml.in0000644000201100017670000000470311701017117016230 0ustar nniclausdream organizationalPerson inetOrgPerson person %%_new_user_cn%% fffs SomeSN some@mail.com tsung-1.4.2/examples/pgsql.xml.in0000644000201100017670000000344211701017117016435 0ustar nniclausdream SELECT * from accounts; SELECT * from users; tsung-1.4.2/examples/fs-nfs.xml.in0000644000201100017670000000736311701017117016511 0ustar nniclausdream tsung-1.4.2/examples/http_setdynvars.xml.in0000644000201100017670000000505211701017117020547 0ustar nniclausdream tsung-1.4.2/config.guess0000644000201100017670000012450211701017117014660 0ustar nniclausdream#! /bin/sh # Attempt to guess a canonical system name. # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, # 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. timestamp='2004-09-07' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Originally written by Per Bothner . # Please send patches to . Submit a context # diff and a properly formatted ChangeLog entry. # # This script attempts to guess a canonical system name similar to # config.sub. If it succeeds, it prints the system name on stdout, and # exits with 0. Otherwise, it exits with 1. # # The plan is that this can be called by configure scripts if you # don't specify an explicit build system type. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] Output the configuration name of the system \`$me' is run on. Operation modes: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit 0 ;; --version | -v ) echo "$version" ; exit 0 ;; --help | --h* | -h ) echo "$usage"; exit 0 ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi trap 'exit 1' 1 2 15 # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still # use `HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. set_cc_for_build=' trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; : ${TMPDIR=/tmp} ; { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; dummy=$tmp/dummy ; tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; case $CC_FOR_BUILD,$HOST_CC,$CC in ,,) echo "int x;" > $dummy.c ; for c in cc gcc c89 c99 ; do if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then CC_FOR_BUILD="$c"; break ; fi ; done ; if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found ; fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac ;' # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if (test -f /.attbin/uname) >/dev/null 2>&1 ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown # Note: order is significant - the case branches are not exclusive. case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". sysctl="sysctl -n hw.machine_arch" UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ /usr/sbin/$sysctl 2>/dev/null || echo unknown)` case "${UNAME_MACHINE_ARCH}" in armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; *) machine=${UNAME_MACHINE_ARCH}-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently, or will in the future. case "${UNAME_MACHINE_ARCH}" in arm*|i386|m68k|ns32k|sh3*|sparc|vax) eval $set_cc_for_build if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep __ELF__ >/dev/null then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case "${UNAME_VERSION}" in Debian*) release='-gnu' ;; *) release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. echo "${machine}-${os}${release}" exit 0 ;; amd64:OpenBSD:*:*) echo x86_64-unknown-openbsd${UNAME_RELEASE} exit 0 ;; amiga:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; cats:OpenBSD:*:*) echo arm-unknown-openbsd${UNAME_RELEASE} exit 0 ;; hp300:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; luna88k:OpenBSD:*:*) echo m88k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mac68k:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; macppc:OpenBSD:*:*) echo powerpc-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mvme68k:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mvme88k:OpenBSD:*:*) echo m88k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mvmeppc:OpenBSD:*:*) echo powerpc-unknown-openbsd${UNAME_RELEASE} exit 0 ;; sgi:OpenBSD:*:*) echo mips64-unknown-openbsd${UNAME_RELEASE} exit 0 ;; sun3:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; *:OpenBSD:*:*) echo ${UNAME_MACHINE}-unknown-openbsd${UNAME_RELEASE} exit 0 ;; *:ekkoBSD:*:*) echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} exit 0 ;; macppc:MirBSD:*:*) echo powerppc-unknown-mirbsd${UNAME_RELEASE} exit 0 ;; *:MirBSD:*:*) echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} exit 0 ;; alpha:OSF1:*:*) case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` ;; *5.*) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` case "$ALPHA_CPU_TYPE" in "EV4 (21064)") UNAME_MACHINE="alpha" ;; "EV4.5 (21064)") UNAME_MACHINE="alpha" ;; "LCA4 (21066/21068)") UNAME_MACHINE="alpha" ;; "EV5 (21164)") UNAME_MACHINE="alphaev5" ;; "EV5.6 (21164A)") UNAME_MACHINE="alphaev56" ;; "EV5.6 (21164PC)") UNAME_MACHINE="alphapca56" ;; "EV5.7 (21164PC)") UNAME_MACHINE="alphapca57" ;; "EV6 (21264)") UNAME_MACHINE="alphaev6" ;; "EV6.7 (21264A)") UNAME_MACHINE="alphaev67" ;; "EV6.8CB (21264C)") UNAME_MACHINE="alphaev68" ;; "EV6.8AL (21264B)") UNAME_MACHINE="alphaev68" ;; "EV6.8CX (21264D)") UNAME_MACHINE="alphaev68" ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE="alphaev69" ;; "EV7 (21364)") UNAME_MACHINE="alphaev7" ;; "EV7.9 (21364A)") UNAME_MACHINE="alphaev79" ;; esac # A Pn.n version is a patched version. # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` exit 0 ;; Alpha\ *:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # Should we change UNAME_MACHINE based on the output of uname instead # of the specific Alpha model? echo alpha-pc-interix exit 0 ;; 21064:Windows_NT:50:3) echo alpha-dec-winnt3.5 exit 0 ;; Amiga*:UNIX_System_V:4.0:*) echo m68k-unknown-sysv4 exit 0;; *:[Aa]miga[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-amigaos exit 0 ;; *:[Mm]orph[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-morphos exit 0 ;; *:OS/390:*:*) echo i370-ibm-openedition exit 0 ;; *:OS400:*:*) echo powerpc-ibm-os400 exit 0 ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) echo arm-acorn-riscix${UNAME_RELEASE} exit 0;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) echo hppa1.1-hitachi-hiuxmpp exit 0;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. if test "`(/bin/universe) 2>/dev/null`" = att ; then echo pyramid-pyramid-sysv3 else echo pyramid-pyramid-bsd fi exit 0 ;; NILE*:*:*:dcosx) echo pyramid-pyramid-svr4 exit 0 ;; DRS?6000:unix:4.0:6*) echo sparc-icl-nx6 exit 0 ;; DRS?6000:UNIX_SV:4.2*:7*) case `/usr/bin/uname -p` in sparc) echo sparc-icl-nx7 && exit 0 ;; esac ;; sun4H:SunOS:5.*:*) echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; i86pc:SunOS:5.*:*) echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; sun4*:SunOS:*:*) case "`/usr/bin/arch -k`" in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like `4.1.3-JL'. echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` exit 0 ;; sun3*:SunOS:*:*) echo m68k-sun-sunos${UNAME_RELEASE} exit 0 ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 case "`/bin/arch`" in sun3) echo m68k-sun-sunos${UNAME_RELEASE} ;; sun4) echo sparc-sun-sunos${UNAME_RELEASE} ;; esac exit 0 ;; aushp:SunOS:*:*) echo sparc-auspex-sunos${UNAME_RELEASE} exit 0 ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit 0 ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit 0 ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit 0 ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) echo m68k-milan-mint${UNAME_RELEASE} exit 0 ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) echo m68k-hades-mint${UNAME_RELEASE} exit 0 ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) echo m68k-unknown-mint${UNAME_RELEASE} exit 0 ;; m68k:machten:*:*) echo m68k-apple-machten${UNAME_RELEASE} exit 0 ;; powerpc:machten:*:*) echo powerpc-apple-machten${UNAME_RELEASE} exit 0 ;; RISC*:Mach:*:*) echo mips-dec-mach_bsd4.3 exit 0 ;; RISC*:ULTRIX:*:*) echo mips-dec-ultrix${UNAME_RELEASE} exit 0 ;; VAX*:ULTRIX*:*:*) echo vax-dec-ultrix${UNAME_RELEASE} exit 0 ;; 2020:CLIX:*:* | 2430:CLIX:*:*) echo clipper-intergraph-clix${UNAME_RELEASE} exit 0 ;; mips:*:*:UMIPS | mips:*:*:RISCos) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c \ && $dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ && exit 0 echo mips-mips-riscos${UNAME_RELEASE} exit 0 ;; Motorola:PowerMAX_OS:*:*) echo powerpc-motorola-powermax exit 0 ;; Motorola:*:4.3:PL8-*) echo powerpc-harris-powermax exit 0 ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) echo powerpc-harris-powermax exit 0 ;; Night_Hawk:Power_UNIX:*:*) echo powerpc-harris-powerunix exit 0 ;; m88k:CX/UX:7*:*) echo m88k-harris-cxux7 exit 0 ;; m88k:*:4*:R4*) echo m88k-motorola-sysv4 exit 0 ;; m88k:*:3*:R3*) echo m88k-motorola-sysv3 exit 0 ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] then if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ [ ${TARGET_BINARY_INTERFACE}x = x ] then echo m88k-dg-dgux${UNAME_RELEASE} else echo m88k-dg-dguxbcs${UNAME_RELEASE} fi else echo i586-dg-dgux${UNAME_RELEASE} fi exit 0 ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) echo m88k-dolphin-sysv3 exit 0 ;; M88*:*:R3*:*) # Delta 88k system running SVR3 echo m88k-motorola-sysv3 exit 0 ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) echo m88k-tektronix-sysv3 exit 0 ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) echo m68k-tektronix-bsd exit 0 ;; *:IRIX*:*:*) echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` exit 0 ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) echo i386-ibm-aix exit 0 ;; ia64:AIX:*:*) if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} exit 0 ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include main() { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 echo rs6000-ibm-aix3.2.5 elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then echo rs6000-ibm-aix3.2.4 else echo rs6000-ibm-aix3.2 fi exit 0 ;; *:AIX:*:[45]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${IBM_ARCH}-ibm-aix${IBM_REV} exit 0 ;; *:AIX:*:*) echo rs6000-ibm-aix exit 0 ;; ibmrt:4.4BSD:*|romp-ibm:BSD:*) echo romp-ibm-bsd4.4 exit 0 ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to exit 0 ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) echo rs6000-bull-bosx exit 0 ;; DPX/2?00:B.O.S.:*:*) echo m68k-bull-sysv3 exit 0 ;; 9000/[34]??:4.3bsd:1.*:*) echo m68k-hp-bsd exit 0 ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) echo m68k-hp-bsd4.4 exit 0 ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` case "${UNAME_MACHINE}" in 9000/31? ) HP_ARCH=m68000 ;; 9000/[34]?? ) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if [ -x /usr/bin/getconf ]; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` case "${sc_cpu_version}" in 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case "${sc_kernel_bits}" in 32) HP_ARCH="hppa2.0n" ;; 64) HP_ARCH="hppa2.0w" ;; '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 esac ;; esac fi if [ "${HP_ARCH}" = "" ]; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if [ ${HP_ARCH} = "hppa2.0w" ] then # avoid double evaluation of $set_cc_for_build test -n "$CC_FOR_BUILD" || eval $set_cc_for_build if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E -) | grep __LP64__ >/dev/null then HP_ARCH="hppa2.0w" else HP_ARCH="hppa64" fi fi echo ${HP_ARCH}-hp-hpux${HPUX_REV} exit 0 ;; ia64:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` echo ia64-hp-hpux${HPUX_REV} exit 0 ;; 3050*:HI-UX:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 echo unknown-hitachi-hiuxwe2 exit 0 ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) echo hppa1.1-hp-bsd exit 0 ;; 9000/8??:4.3bsd:*:*) echo hppa1.0-hp-bsd exit 0 ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) echo hppa1.0-hp-mpeix exit 0 ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) echo hppa1.1-hp-osf exit 0 ;; hp8??:OSF1:*:*) echo hppa1.0-hp-osf exit 0 ;; i*86:OSF1:*:*) if [ -x /usr/sbin/sysversion ] ; then echo ${UNAME_MACHINE}-unknown-osf1mk else echo ${UNAME_MACHINE}-unknown-osf1 fi exit 0 ;; parisc*:Lites*:*:*) echo hppa1.1-hp-lites exit 0 ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) echo c1-convex-bsd exit 0 ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit 0 ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) echo c34-convex-bsd exit 0 ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) echo c38-convex-bsd exit 0 ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) echo c4-convex-bsd exit 0 ;; CRAY*Y-MP:*:*:*) echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*[A-Z]90:*:*:*) echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*TS:*:*:*) echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*T3E:*:*:*) echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*SV1:*:*:*) echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; *:UNICOS/mp:*:*) echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit 0 ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit 0 ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} exit 0 ;; sparc*:BSD/OS:*:*) echo sparc-unknown-bsdi${UNAME_RELEASE} exit 0 ;; *:BSD/OS:*:*) echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} exit 0 ;; *:FreeBSD:*:*) echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` exit 0 ;; i*:CYGWIN*:*) echo ${UNAME_MACHINE}-pc-cygwin exit 0 ;; i*:MINGW*:*) echo ${UNAME_MACHINE}-pc-mingw32 exit 0 ;; i*:PW*:*) echo ${UNAME_MACHINE}-pc-pw32 exit 0 ;; x86:Interix*:[34]*) echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//' exit 0 ;; [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) echo i${UNAME_MACHINE}-pc-mks exit 0 ;; i*:Windows_NT*:* | Pentium*:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we # UNAME_MACHINE based on the output of uname instead of i386? echo i586-pc-interix exit 0 ;; i*:UWIN*:*) echo ${UNAME_MACHINE}-pc-uwin exit 0 ;; p*:CYGWIN*:*) echo powerpcle-unknown-cygwin exit 0 ;; prep*:SunOS:5.*:*) echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; *:GNU:*:*) # the GNU system echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` exit 0 ;; *:GNU/*:*:*) # other systems with GNU libc and userland echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu exit 0 ;; i*86:Minix:*:*) echo ${UNAME_MACHINE}-pc-minix exit 0 ;; arm*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; cris:Linux:*:*) echo cris-axis-linux-gnu exit 0 ;; crisv32:Linux:*:*) echo crisv32-axis-linux-gnu exit 0 ;; frv:Linux:*:*) echo frv-unknown-linux-gnu exit 0 ;; ia64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; m32r*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; m68*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; mips:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef mips #undef mipsel #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=mipsel #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=mips #else CPU= #endif #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 ;; mips64:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef mips64 #undef mips64el #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=mips64el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=mips64 #else CPU= #endif #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 ;; ppc:Linux:*:*) echo powerpc-unknown-linux-gnu exit 0 ;; ppc64:Linux:*:*) echo powerpc64-unknown-linux-gnu exit 0 ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} exit 0 ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) echo hppa1.1-unknown-linux-gnu ;; PA8*) echo hppa2.0-unknown-linux-gnu ;; *) echo hppa-unknown-linux-gnu ;; esac exit 0 ;; parisc64:Linux:*:* | hppa64:Linux:*:*) echo hppa64-unknown-linux-gnu exit 0 ;; s390:Linux:*:* | s390x:Linux:*:*) echo ${UNAME_MACHINE}-ibm-linux exit 0 ;; sh64*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; sh*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; sparc:Linux:*:* | sparc64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; x86_64:Linux:*:*) echo x86_64-unknown-linux-gnu exit 0 ;; i*86:Linux:*:*) # The BFD linker knows what the default object file format is, so # first see if it will tell us. cd to the root directory to prevent # problems with other programs or directories called `ld' in the path. # Set LC_ALL=C to ensure ld outputs messages in English. ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ | sed -ne '/supported targets:/!d s/[ ][ ]*/ /g s/.*supported targets: *// s/ .*// p'` case "$ld_supported_targets" in elf32-i386) TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" ;; a.out-i386-linux) echo "${UNAME_MACHINE}-pc-linux-gnuaout" exit 0 ;; coff-i386) echo "${UNAME_MACHINE}-pc-linux-gnucoff" exit 0 ;; "") # Either a pre-BFD a.out linker (linux-gnuoldld) or # one that does not give us useful --help. echo "${UNAME_MACHINE}-pc-linux-gnuoldld" exit 0 ;; esac # Determine whether the default compiler is a.out or elf eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include #ifdef __ELF__ # ifdef __GLIBC__ # if __GLIBC__ >= 2 LIBC=gnu # else LIBC=gnulibc1 # endif # else LIBC=gnulibc1 # endif #else #ifdef __INTEL_COMPILER LIBC=gnu #else LIBC=gnuaout #endif #endif #ifdef __dietlibc__ LIBC=dietlibc #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` test x"${LIBC}" != x && echo "${UNAME_MACHINE}-pc-linux-${LIBC}" && exit 0 test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0 ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. echo i386-sequent-sysv4 exit 0 ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} exit 0 ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. echo ${UNAME_MACHINE}-pc-os2-emx exit 0 ;; i*86:XTS-300:*:STOP) echo ${UNAME_MACHINE}-unknown-stop exit 0 ;; i*86:atheos:*:*) echo ${UNAME_MACHINE}-unknown-atheos exit 0 ;; i*86:syllable:*:*) echo ${UNAME_MACHINE}-pc-syllable exit 0 ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) echo i386-unknown-lynxos${UNAME_RELEASE} exit 0 ;; i*86:*DOS:*:*) echo ${UNAME_MACHINE}-pc-msdosdjgpp exit 0 ;; i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} else echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} fi exit 0 ;; i*86:*:5:[78]*) case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} exit 0 ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 echo ${UNAME_MACHINE}-pc-sco$UNAME_REL else echo ${UNAME_MACHINE}-pc-sysv32 fi exit 0 ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i386. echo i386-pc-msdosdjgpp exit 0 ;; Intel:Mach:3*:*) echo i386-pc-mach3 exit 0 ;; paragon:*:*:*) echo i860-intel-osf1 exit 0 ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 fi exit 0 ;; mini*:CTIX:SYS*5:*) # "miniframe" echo m68010-convergent-sysv exit 0 ;; mc68k:UNIX:SYSTEM5:3.51m) echo m68k-convergent-sysv exit 0 ;; M680?0:D-NIX:5.3:*) echo m68k-diab-dnix exit 0 ;; M68*:*:R3V[5678]*:*) test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && echo i486-ncr-sysv4.3${OS_REL} && exit 0 /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && echo i486-ncr-sysv4 && exit 0 ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) echo m68k-unknown-lynxos${UNAME_RELEASE} exit 0 ;; mc68030:UNIX_System_V:4.*:*) echo m68k-atari-sysv4 exit 0 ;; tsung:LynxOS:2.*:*) echo sparc-unknown-lynxos${UNAME_RELEASE} exit 0 ;; rs6000:LynxOS:2.*:*) echo rs6000-unknown-lynxos${UNAME_RELEASE} exit 0 ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) echo powerpc-unknown-lynxos${UNAME_RELEASE} exit 0 ;; SM[BE]S:UNIX_SV:*:*) echo mips-dde-sysv${UNAME_RELEASE} exit 0 ;; RM*:ReliantUNIX-*:*:*) echo mips-sni-sysv4 exit 0 ;; RM*:SINIX-*:*:*) echo mips-sni-sysv4 exit 0 ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` echo ${UNAME_MACHINE}-sni-sysv4 else echo ns32k-sni-sysv fi exit 0 ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says echo i586-unisys-sysv4 exit 0 ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm echo hppa1.1-stratus-sysv4 exit 0 ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. echo i860-stratus-sysv4 exit 0 ;; *:VOS:*:*) # From Paul.Green@stratus.com. echo hppa1.1-stratus-vos exit 0 ;; mc68*:A/UX:*:*) echo m68k-apple-aux${UNAME_RELEASE} exit 0 ;; news*:NEWS-OS:6*:*) echo mips-sony-newsos6 exit 0 ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if [ -d /usr/nec ]; then echo mips-nec-sysv${UNAME_RELEASE} else echo mips-unknown-sysv${UNAME_RELEASE} fi exit 0 ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. echo powerpc-be-beos exit 0 ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. echo powerpc-apple-beos exit 0 ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. echo i586-pc-beos exit 0 ;; SX-4:SUPER-UX:*:*) echo sx4-nec-superux${UNAME_RELEASE} exit 0 ;; SX-5:SUPER-UX:*:*) echo sx5-nec-superux${UNAME_RELEASE} exit 0 ;; SX-6:SUPER-UX:*:*) echo sx6-nec-superux${UNAME_RELEASE} exit 0 ;; Power*:Rhapsody:*:*) echo powerpc-apple-rhapsody${UNAME_RELEASE} exit 0 ;; *:Rhapsody:*:*) echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} exit 0 ;; *:Darwin:*:*) UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown case $UNAME_PROCESSOR in *86) UNAME_PROCESSOR=i686 ;; unknown) UNAME_PROCESSOR=powerpc ;; esac echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} exit 0 ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = "x86"; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} exit 0 ;; *:QNX:*:4*) echo i386-pc-qnx exit 0 ;; NSR-?:NONSTOP_KERNEL:*:*) echo nsr-tandem-nsk${UNAME_RELEASE} exit 0 ;; *:NonStop-UX:*:*) echo mips-compaq-nonstopux exit 0 ;; BS2000:POSIX*:*:*) echo bs2000-siemens-sysv exit 0 ;; DS/*:UNIX_System_V:*:*) echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} exit 0 ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. if test "$cputype" = "386"; then UNAME_MACHINE=i386 else UNAME_MACHINE="$cputype" fi echo ${UNAME_MACHINE}-unknown-plan9 exit 0 ;; *:TOPS-10:*:*) echo pdp10-unknown-tops10 exit 0 ;; *:TENEX:*:*) echo pdp10-unknown-tenex exit 0 ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) echo pdp10-dec-tops20 exit 0 ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) echo pdp10-xkl-tops20 exit 0 ;; *:TOPS-20:*:*) echo pdp10-unknown-tops20 exit 0 ;; *:ITS:*:*) echo pdp10-unknown-its exit 0 ;; SEI:*:*:SEIUX) echo mips-sei-seiux${UNAME_RELEASE} exit 0 ;; *:DragonFly:*:*) echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` exit 0 ;; *:*VMS:*:*) UNAME_MACHINE=`(uname -p) 2>/dev/null` case "${UNAME_MACHINE}" in A*) echo alpha-dec-vms && exit 0 ;; I*) echo ia64-dec-vms && exit 0 ;; V*) echo vax-dec-vms && exit 0 ;; esac esac #echo '(No uname command or uname output not recognized.)' 1>&2 #echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 eval $set_cc_for_build cat >$dummy.c < # include #endif main () { #if defined (sony) #if defined (MIPSEB) /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, I don't know.... */ printf ("mips-sony-bsd\n"); exit (0); #else #include printf ("m68k-sony-newsos%s\n", #ifdef NEWSOS4 "4" #else "" #endif ); exit (0); #endif #endif #if defined (__arm) && defined (__acorn) && defined (__unix) printf ("arm-acorn-riscix"); exit (0); #endif #if defined (hp300) && !defined (hpux) printf ("m68k-hp-bsd\n"); exit (0); #endif #if defined (NeXT) #if !defined (__ARCHITECTURE__) #define __ARCHITECTURE__ "m68k" #endif int version; version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; if (version < 4) printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); else printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); exit (0); #endif #if defined (MULTIMAX) || defined (n16) #if defined (UMAXV) printf ("ns32k-encore-sysv\n"); exit (0); #else #if defined (CMU) printf ("ns32k-encore-mach\n"); exit (0); #else printf ("ns32k-encore-bsd\n"); exit (0); #endif #endif #endif #if defined (__386BSD__) printf ("i386-pc-bsd\n"); exit (0); #endif #if defined (sequent) #if defined (i386) printf ("i386-sequent-dynix\n"); exit (0); #endif #if defined (ns32000) printf ("ns32k-sequent-dynix\n"); exit (0); #endif #endif #if defined (_SEQUENT_) struct utsname un; uname(&un); if (strncmp(un.version, "V2", 2) == 0) { printf ("i386-sequent-ptx2\n"); exit (0); } if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ printf ("i386-sequent-ptx1\n"); exit (0); } printf ("i386-sequent-ptx\n"); exit (0); #endif #if defined (vax) # if !defined (ultrix) # include # if defined (BSD) # if BSD == 43 printf ("vax-dec-bsd4.3\n"); exit (0); # else # if BSD == 199006 printf ("vax-dec-bsd4.3reno\n"); exit (0); # else printf ("vax-dec-bsd\n"); exit (0); # endif # endif # else printf ("vax-dec-bsd\n"); exit (0); # endif # else printf ("vax-dec-ultrix\n"); exit (0); # endif #endif #if defined (alliant) && defined (i860) printf ("i860-alliant-bsd\n"); exit (0); #endif exit (1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && $dummy && exit 0 # Apollos put the system type in the environment. test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } # Convex versions that predate uname can use getsysinfo(1) if [ -x /usr/convex/getsysinfo ] then case `getsysinfo -f cpu_type` in c1*) echo c1-convex-bsd exit 0 ;; c2*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit 0 ;; c34*) echo c34-convex-bsd exit 0 ;; c38*) echo c38-convex-bsd exit 0 ;; c4*) echo c4-convex-bsd exit 0 ;; esac fi cat >&2 < in order to provide the needed information to handle your system. config.guess timestamp = $timestamp uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` /bin/uname -X = `(/bin/uname -X) 2>/dev/null` hostinfo = `(hostinfo) 2>/dev/null` /bin/universe = `(/bin/universe) 2>/dev/null` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` /bin/arch = `(/bin/arch) 2>/dev/null` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` UNAME_MACHINE = ${UNAME_MACHINE} UNAME_RELEASE = ${UNAME_RELEASE} UNAME_SYSTEM = ${UNAME_SYSTEM} UNAME_VERSION = ${UNAME_VERSION} EOF exit 1 # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: tsung-1.4.2/COPYING0000644000201100017670000004312711701017117013401 0ustar nniclausdream GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. tsung-1.4.2/install-sh0000755000201100017670000001272011701017117014345 0ustar nniclausdream#!/bin/sh # # install - install a program, script, or datafile # This comes from X11R5 (mit/util/scripts/install.sh). # # Copyright 1991 by the Massachusetts Institute of Technology # # Permission to use, copy, modify, distribute, and sell this software and its # documentation for any purpose is hereby granted without fee, provided that # the above copyright notice appear in all copies and that both that # copyright notice and this permission notice appear in supporting # documentation, and that the name of M.I.T. not be used in advertising or # publicity pertaining to distribution of the software without specific, # written prior permission. M.I.T. makes no representations about the # suitability of this software for any purpose. It is provided "as is" # without express or implied warranty. # # Calling this script install-sh is preferred over install.sh, to prevent # `make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. It can only install one file at a time, a restriction # shared with many OS's install programs. # set DOITPROG to echo to test this script # Don't use :- since 4.3BSD and earlier shells don't like it. doit="${DOITPROG-}" # put in absolute paths if you don't have them in your path; or use env. vars. mvprog="${MVPROG-mv}" cpprog="${CPPROG-cp}" chmodprog="${CHMODPROG-chmod}" chownprog="${CHOWNPROG-chown}" chgrpprog="${CHGRPPROG-chgrp}" stripprog="${STRIPPROG-strip}" rmprog="${RMPROG-rm}" mkdirprog="${MKDIRPROG-mkdir}" transformbasename="" transform_arg="" instcmd="$mvprog" chmodcmd="$chmodprog 0755" chowncmd="" chgrpcmd="" stripcmd="" rmcmd="$rmprog -f" mvcmd="$mvprog" src="" dst="" dir_arg="" while [ x"$1" != x ]; do case $1 in -c) instcmd="$cpprog" shift continue;; -d) dir_arg=true shift continue;; -m) chmodcmd="$chmodprog $2" shift shift continue;; -o) chowncmd="$chownprog $2" shift shift continue;; -g) chgrpcmd="$chgrpprog $2" shift shift continue;; -s) stripcmd="$stripprog" shift continue;; -t=*) transformarg=`echo $1 | sed 's/-t=//'` shift continue;; -b=*) transformbasename=`echo $1 | sed 's/-b=//'` shift continue;; *) if [ x"$src" = x ] then src=$1 else # this colon is to work around a 386BSD /bin/sh bug : dst=$1 fi shift continue;; esac done if [ x"$src" = x ] then echo "install: no input file specified" exit 1 else true fi if [ x"$dir_arg" != x ]; then dst=$src src="" if [ -d $dst ]; then instcmd=: else instcmd=mkdir fi else # Waiting for this to be detected by the "$instcmd $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if [ -f $src -o -d $src ] then true else echo "install: $src does not exist" exit 1 fi if [ x"$dst" = x ] then echo "install: no destination specified" exit 1 else true fi # If destination is a directory, append the input filename; if your system # does not like double slashes in filenames, you may need to add some logic if [ -d $dst ] then dst="$dst"/`basename $src` else true fi fi ## this sed command emulates the dirname command dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` # Make sure that the destination directory exists. # this part is taken from Noah Friedman's mkinstalldirs script # Skip lots of stat calls in the usual case. if [ ! -d "$dstdir" ]; then defaultIFS=' ' IFS="${IFS-${defaultIFS}}" oIFS="${IFS}" # Some sh's can't handle IFS=/ for some reason. IFS='%' set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` IFS="${oIFS}" pathcomp='' while [ $# -ne 0 ] ; do pathcomp="${pathcomp}${1}" shift if [ ! -d "${pathcomp}" ] ; then $mkdirprog "${pathcomp}" else true fi pathcomp="${pathcomp}/" done fi if [ x"$dir_arg" != x ] then $doit $instcmd $dst && if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi else # If we're going to rename the final executable, determine the name now. if [ x"$transformarg" = x ] then dstfile=`basename $dst` else dstfile=`basename $dst $transformbasename | sed $transformarg`$transformbasename fi # don't allow the sed command to completely eliminate the filename if [ x"$dstfile" = x ] then dstfile=`basename $dst` else true fi # Make a temp file name in the proper directory. dsttmp=$dstdir/#inst.$$# # Move or copy the file name to the temp name $doit $instcmd $src $dsttmp && trap "rm -f ${dsttmp}" 0 && # and set any options; do chmod last to preserve setuid bits # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $instcmd $src $dsttmp" command. if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && # Now rename the file to the real destination. $doit $rmcmd -f $dstdir/$dstfile && $doit $mvcmd $dsttmp $dstdir/$dstfile fi && exit 0 tsung-1.4.2/config.sub0000644000201100017670000007511311701017117014326 0ustar nniclausdream#! /bin/sh # Configuration validation subroutine script. # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, # 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. timestamp='2004-08-29' # This file is (in principle) common to ALL GNU software. # The presence of a machine in this file suggests that SOME GNU software # can handle that machine. It does not imply ALL GNU software can. # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Please send patches to . Submit a context # diff and a properly formatted ChangeLog entry. # # Configuration subroutine to validate and canonicalize a configuration type. # Supply the specified configuration type as an argument. # If it is invalid, we print an error message on stderr and exit with code 1. # Otherwise, we print the canonical config type on stdout and succeed. # This file is supposed to be the same for all GNU packages # and recognize all the CPU types, system types and aliases # that are meaningful with *any* GNU software. # Each package is responsible for reporting which valid configurations # it does not support. The user should be able to distinguish # a failure to support a valid configuration from a meaningless # configuration. # The goal of this file is to map all the various variations of a given # machine specification into a single specification in the form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or in some cases, the newer four-part form: # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # It is wrong to echo any other type of specification. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] CPU-MFR-OPSYS $0 [OPTION] ALIAS Canonicalize a configuration name. Operation modes: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.sub ($timestamp) Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit 0 ;; --version | -v ) echo "$version" ; exit 0 ;; --help | --h* | -h ) echo "$usage"; exit 0 ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" exit 1 ;; *local*) # First pass through any local machine types. echo $1 exit 0;; * ) break ;; esac done case $# in 0) echo "$me: missing argument$help" >&2 exit 1;; 1) ;; *) echo "$me: too many arguments$help" >&2 exit 1;; esac # Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). # Here we must recognize all the valid KERNEL-OS combinations. maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` case $maybe_os in nto-qnx* | linux-gnu* | linux-dietlibc | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | \ kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | storm-chaos* | os2-emx* | rtmk-nova*) os=-$maybe_os basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` ;; *) basic_machine=`echo $1 | sed 's/-[^-]*$//'` if [ $basic_machine != $1 ] then os=`echo $1 | sed 's/.*-/-/'` else os=; fi ;; esac ### Let's recognize common machines as not being operating systems so ### that things like config.sub decstation-3100 work. We also ### recognize some manufacturers as not being operating systems, so we ### can provide default operating systems below. case $os in -sun*os*) # Prevent following clause from handling this invalid input. ;; -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ -apple | -axis | -knuth | -cray) os= basic_machine=$1 ;; -sim | -cisco | -oki | -wec | -winbond) os= basic_machine=$1 ;; -scout) ;; -wrs) os=-vxworks basic_machine=$1 ;; -chorusos*) os=-chorusos basic_machine=$1 ;; -chorusrdb) os=-chorusrdb basic_machine=$1 ;; -hiux*) os=-hiuxwe2 ;; -sco5) os=-sco3.2v5 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco4) os=-sco3.2v4 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco3.2.[4-9]*) os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco3.2v[4-9]*) # Don't forget version if it is 3.2v4 or newer. basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco*) os=-sco3.2v2 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -udk*) basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -isc) os=-isc2.2 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -clix*) basic_machine=clipper-intergraph ;; -isc*) basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -lynx*) os=-lynxos ;; -ptx*) basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` ;; -windowsnt*) os=`echo $os | sed -e 's/windowsnt/winnt/'` ;; -psos*) os=-psos ;; -mint | -mint[0-9]*) basic_machine=m68k-atari os=-mint ;; esac # Decode aliases for certain CPU-COMPANY combinations. case $basic_machine in # Recognize the basic CPU types without company name. # Some are omitted here because they have special meanings below. 1750a | 580 \ | a29k \ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ | am33_2.0 \ | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \ | c4x | clipper \ | d10v | d30v | dlx | dsp16xx \ | fr30 | frv \ | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ | i370 | i860 | i960 | ia64 \ | ip2k | iq2000 \ | m32r | m32rle | m68000 | m68k | m88k | mcore \ | mips | mipsbe | mipseb | mipsel | mipsle \ | mips16 \ | mips64 | mips64el \ | mips64vr | mips64vrel \ | mips64orion | mips64orionel \ | mips64vr4100 | mips64vr4100el \ | mips64vr4300 | mips64vr4300el \ | mips64vr5000 | mips64vr5000el \ | mipsisa32 | mipsisa32el \ | mipsisa32r2 | mipsisa32r2el \ | mipsisa64 | mipsisa64el \ | mipsisa64r2 | mipsisa64r2el \ | mipsisa64sb1 | mipsisa64sb1el \ | mipsisa64sr71k | mipsisa64sr71kel \ | mipstx39 | mipstx39el \ | mn10200 | mn10300 \ | msp430 \ | ns16k | ns32k \ | openrisc | or32 \ | pdp10 | pdp11 | pj | pjl \ | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ | pyramid \ | sh | sh[1234] | sh[23]e | sh[34]eb | shbe | shle | sh[1234]le | sh3ele \ | sh64 | sh64le \ | sparc | sparc64 | sparc86x | sparclet | sparclite | sparcv8 | sparcv9 | sparcv9b \ | strongarm \ | tahoe | thumb | tic4x | tic80 | tron \ | v850 | v850e \ | we32k \ | x86 | xscale | xstormy16 | xtensa \ | z8k) basic_machine=$basic_machine-unknown ;; m6811 | m68hc11 | m6812 | m68hc12) # Motorola 68HC11/12. basic_machine=$basic_machine-unknown os=-none ;; m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) ;; # We use `pc' rather than `unknown' # because (1) that's what they normally are, and # (2) the word "unknown" tends to confuse beginning users. i*86 | x86_64) basic_machine=$basic_machine-pc ;; # Object if more than one company name word. *-*-*) echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 exit 1 ;; # Recognize the basic CPU types with company name. 580-* \ | a29k-* \ | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \ | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ | avr-* \ | bs2000-* \ | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \ | clipper-* | craynv-* | cydra-* \ | d10v-* | d30v-* | dlx-* \ | elxsi-* \ | f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \ | h8300-* | h8500-* \ | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ | i*86-* | i860-* | i960-* | ia64-* \ | ip2k-* | iq2000-* \ | m32r-* | m32rle-* \ | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ | m88110-* | m88k-* | mcore-* \ | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ | mips16-* \ | mips64-* | mips64el-* \ | mips64vr-* | mips64vrel-* \ | mips64orion-* | mips64orionel-* \ | mips64vr4100-* | mips64vr4100el-* \ | mips64vr4300-* | mips64vr4300el-* \ | mips64vr5000-* | mips64vr5000el-* \ | mipsisa32-* | mipsisa32el-* \ | mipsisa32r2-* | mipsisa32r2el-* \ | mipsisa64-* | mipsisa64el-* \ | mipsisa64r2-* | mipsisa64r2el-* \ | mipsisa64sb1-* | mipsisa64sb1el-* \ | mipsisa64sr71k-* | mipsisa64sr71kel-* \ | mipstx39-* | mipstx39el-* \ | mmix-* \ | msp430-* \ | none-* | np1-* | ns16k-* | ns32k-* \ | orion-* \ | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ | pyramid-* \ | romp-* | rs6000-* \ | sh-* | sh[1234]-* | sh[23]e-* | sh[34]eb-* | shbe-* \ | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ | sparc-* | sparc64-* | sparc86x-* | sparclet-* | sparclite-* \ | sparcv8-* | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* | sx?-* \ | tahoe-* | thumb-* \ | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ | tron-* \ | v850-* | v850e-* | vax-* \ | we32k-* \ | x86-* | x86_64-* | xps100-* | xscale-* | xstormy16-* \ | xtensa-* \ | ymp-* \ | z8k-*) ;; # Recognize the various machine names and aliases which stand # for a CPU type and a company and sometimes even an OS. 386bsd) basic_machine=i386-unknown os=-bsd ;; 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) basic_machine=m68000-att ;; 3b*) basic_machine=we32k-att ;; a29khif) basic_machine=a29k-amd os=-udi ;; abacus) basic_machine=abacus-unknown ;; adobe68k) basic_machine=m68010-adobe os=-scout ;; alliant | fx80) basic_machine=fx80-alliant ;; altos | altos3068) basic_machine=m68k-altos ;; am29k) basic_machine=a29k-none os=-bsd ;; amd64) basic_machine=x86_64-pc ;; amd64-*) basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'` ;; amdahl) basic_machine=580-amdahl os=-sysv ;; amiga | amiga-*) basic_machine=m68k-unknown ;; amigaos | amigados) basic_machine=m68k-unknown os=-amigaos ;; amigaunix | amix) basic_machine=m68k-unknown os=-sysv4 ;; apollo68) basic_machine=m68k-apollo os=-sysv ;; apollo68bsd) basic_machine=m68k-apollo os=-bsd ;; aux) basic_machine=m68k-apple os=-aux ;; balance) basic_machine=ns32k-sequent os=-dynix ;; c90) basic_machine=c90-cray os=-unicos ;; convex-c1) basic_machine=c1-convex os=-bsd ;; convex-c2) basic_machine=c2-convex os=-bsd ;; convex-c32) basic_machine=c32-convex os=-bsd ;; convex-c34) basic_machine=c34-convex os=-bsd ;; convex-c38) basic_machine=c38-convex os=-bsd ;; cray | j90) basic_machine=j90-cray os=-unicos ;; craynv) basic_machine=craynv-cray os=-unicosmp ;; cr16c) basic_machine=cr16c-unknown os=-elf ;; crds | unos) basic_machine=m68k-crds ;; crisv32 | crisv32-* | etraxfs*) basic_machine=crisv32-axis ;; cris | cris-* | etrax*) basic_machine=cris-axis ;; crx) basic_machine=crx-unknown os=-elf ;; da30 | da30-*) basic_machine=m68k-da30 ;; decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) basic_machine=mips-dec ;; decsystem10* | dec10*) basic_machine=pdp10-dec os=-tops10 ;; decsystem20* | dec20*) basic_machine=pdp10-dec os=-tops20 ;; delta | 3300 | motorola-3300 | motorola-delta \ | 3300-motorola | delta-motorola) basic_machine=m68k-motorola ;; delta88) basic_machine=m88k-motorola os=-sysv3 ;; dpx20 | dpx20-*) basic_machine=rs6000-bull os=-bosx ;; dpx2* | dpx2*-bull) basic_machine=m68k-bull os=-sysv3 ;; ebmon29k) basic_machine=a29k-amd os=-ebmon ;; elxsi) basic_machine=elxsi-elxsi os=-bsd ;; encore | umax | mmax) basic_machine=ns32k-encore ;; es1800 | OSE68k | ose68k | ose | OSE) basic_machine=m68k-ericsson os=-ose ;; fx2800) basic_machine=i860-alliant ;; genix) basic_machine=ns32k-ns ;; gmicro) basic_machine=tron-gmicro os=-sysv ;; go32) basic_machine=i386-pc os=-go32 ;; h3050r* | hiux*) basic_machine=hppa1.1-hitachi os=-hiuxwe2 ;; h8300hms) basic_machine=h8300-hitachi os=-hms ;; h8300xray) basic_machine=h8300-hitachi os=-xray ;; h8500hms) basic_machine=h8500-hitachi os=-hms ;; harris) basic_machine=m88k-harris os=-sysv3 ;; hp300-*) basic_machine=m68k-hp ;; hp300bsd) basic_machine=m68k-hp os=-bsd ;; hp300hpux) basic_machine=m68k-hp os=-hpux ;; hp3k9[0-9][0-9] | hp9[0-9][0-9]) basic_machine=hppa1.0-hp ;; hp9k2[0-9][0-9] | hp9k31[0-9]) basic_machine=m68000-hp ;; hp9k3[2-9][0-9]) basic_machine=m68k-hp ;; hp9k6[0-9][0-9] | hp6[0-9][0-9]) basic_machine=hppa1.0-hp ;; hp9k7[0-79][0-9] | hp7[0-79][0-9]) basic_machine=hppa1.1-hp ;; hp9k78[0-9] | hp78[0-9]) # FIXME: really hppa2.0-hp basic_machine=hppa1.1-hp ;; hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) # FIXME: really hppa2.0-hp basic_machine=hppa1.1-hp ;; hp9k8[0-9][13679] | hp8[0-9][13679]) basic_machine=hppa1.1-hp ;; hp9k8[0-9][0-9] | hp8[0-9][0-9]) basic_machine=hppa1.0-hp ;; hppa-next) os=-nextstep3 ;; hppaosf) basic_machine=hppa1.1-hp os=-osf ;; hppro) basic_machine=hppa1.1-hp os=-proelf ;; i370-ibm* | ibm*) basic_machine=i370-ibm ;; # I'm not sure what "Sysv32" means. Should this be sysv3.2? i*86v32) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv32 ;; i*86v4*) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv4 ;; i*86v) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv ;; i*86sol2) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-solaris2 ;; i386mach) basic_machine=i386-mach os=-mach ;; i386-vsta | vsta) basic_machine=i386-unknown os=-vsta ;; iris | iris4d) basic_machine=mips-sgi case $os in -irix*) ;; *) os=-irix4 ;; esac ;; isi68 | isi) basic_machine=m68k-isi os=-sysv ;; m88k-omron*) basic_machine=m88k-omron ;; magnum | m3230) basic_machine=mips-mips os=-sysv ;; merlin) basic_machine=ns32k-utek os=-sysv ;; mingw32) basic_machine=i386-pc os=-mingw32 ;; miniframe) basic_machine=m68000-convergent ;; *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) basic_machine=m68k-atari os=-mint ;; mips3*-*) basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` ;; mips3*) basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown ;; monitor) basic_machine=m68k-rom68k os=-coff ;; morphos) basic_machine=powerpc-unknown os=-morphos ;; msdos) basic_machine=i386-pc os=-msdos ;; mvs) basic_machine=i370-ibm os=-mvs ;; ncr3000) basic_machine=i486-ncr os=-sysv4 ;; netbsd386) basic_machine=i386-unknown os=-netbsd ;; netwinder) basic_machine=armv4l-rebel os=-linux ;; news | news700 | news800 | news900) basic_machine=m68k-sony os=-newsos ;; news1000) basic_machine=m68030-sony os=-newsos ;; news-3600 | risc-news) basic_machine=mips-sony os=-newsos ;; necv70) basic_machine=v70-nec os=-sysv ;; next | m*-next ) basic_machine=m68k-next case $os in -nextstep* ) ;; -ns2*) os=-nextstep2 ;; *) os=-nextstep3 ;; esac ;; nh3000) basic_machine=m68k-harris os=-cxux ;; nh[45]000) basic_machine=m88k-harris os=-cxux ;; nindy960) basic_machine=i960-intel os=-nindy ;; mon960) basic_machine=i960-intel os=-mon960 ;; nonstopux) basic_machine=mips-compaq os=-nonstopux ;; np1) basic_machine=np1-gould ;; nsr-tandem) basic_machine=nsr-tandem ;; op50n-* | op60c-*) basic_machine=hppa1.1-oki os=-proelf ;; or32 | or32-*) basic_machine=or32-unknown os=-coff ;; os400) basic_machine=powerpc-ibm os=-os400 ;; OSE68000 | ose68000) basic_machine=m68000-ericsson os=-ose ;; os68k) basic_machine=m68k-none os=-os68k ;; pa-hitachi) basic_machine=hppa1.1-hitachi os=-hiuxwe2 ;; paragon) basic_machine=i860-intel os=-osf ;; pbd) basic_machine=sparc-tti ;; pbb) basic_machine=m68k-tti ;; pc532 | pc532-*) basic_machine=ns32k-pc532 ;; pentium | p5 | k5 | k6 | nexgen | viac3) basic_machine=i586-pc ;; pentiumpro | p6 | 6x86 | athlon | athlon_*) basic_machine=i686-pc ;; pentiumii | pentium2 | pentiumiii | pentium3) basic_machine=i686-pc ;; pentium4) basic_machine=i786-pc ;; pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentiumpro-* | p6-* | 6x86-* | athlon-*) basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentium4-*) basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pn) basic_machine=pn-gould ;; power) basic_machine=power-ibm ;; ppc) basic_machine=powerpc-unknown ;; ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppcle | powerpclittle | ppc-le | powerpc-little) basic_machine=powerpcle-unknown ;; ppcle-* | powerpclittle-*) basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppc64) basic_machine=powerpc64-unknown ;; ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppc64le | powerpc64little | ppc64-le | powerpc64-little) basic_machine=powerpc64le-unknown ;; ppc64le-* | powerpc64little-*) basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ps2) basic_machine=i386-ibm ;; pw32) basic_machine=i586-unknown os=-pw32 ;; rom68k) basic_machine=m68k-rom68k os=-coff ;; rm[46]00) basic_machine=mips-siemens ;; rtpc | rtpc-*) basic_machine=romp-ibm ;; s390 | s390-*) basic_machine=s390-ibm ;; s390x | s390x-*) basic_machine=s390x-ibm ;; sa29200) basic_machine=a29k-amd os=-udi ;; sb1) basic_machine=mipsisa64sb1-unknown ;; sb1el) basic_machine=mipsisa64sb1el-unknown ;; sei) basic_machine=mips-sei os=-seiux ;; sequent) basic_machine=i386-sequent ;; sh) basic_machine=sh-hitachi os=-hms ;; sh64) basic_machine=sh64-unknown ;; sparclite-wrs | simso-wrs) basic_machine=sparclite-wrs os=-vxworks ;; sps7) basic_machine=m68k-bull os=-sysv2 ;; spur) basic_machine=spur-unknown ;; st2000) basic_machine=m68k-tandem ;; stratus) basic_machine=i860-stratus os=-sysv4 ;; sun2) basic_machine=m68000-sun ;; sun2os3) basic_machine=m68000-sun os=-sunos3 ;; sun2os4) basic_machine=m68000-sun os=-sunos4 ;; sun3os3) basic_machine=m68k-sun os=-sunos3 ;; sun3os4) basic_machine=m68k-sun os=-sunos4 ;; sun4os3) basic_machine=sparc-sun os=-sunos3 ;; sun4os4) basic_machine=sparc-sun os=-sunos4 ;; sun4sol2) basic_machine=sparc-sun os=-solaris2 ;; sun3 | sun3-*) basic_machine=m68k-sun ;; sun4) basic_machine=sparc-sun ;; sun386 | sun386i | roadrunner) basic_machine=i386-sun ;; sv1) basic_machine=sv1-cray os=-unicos ;; symmetry) basic_machine=i386-sequent os=-dynix ;; t3e) basic_machine=alphaev5-cray os=-unicos ;; t90) basic_machine=t90-cray os=-unicos ;; tic54x | c54x*) basic_machine=tic54x-unknown os=-coff ;; tic55x | c55x*) basic_machine=tic55x-unknown os=-coff ;; tic6x | c6x*) basic_machine=tic6x-unknown os=-coff ;; tx39) basic_machine=mipstx39-unknown ;; tx39el) basic_machine=mipstx39el-unknown ;; toad1) basic_machine=pdp10-xkl os=-tops20 ;; tower | tower-32) basic_machine=m68k-ncr ;; tpf) basic_machine=s390x-ibm os=-tpf ;; udi29k) basic_machine=a29k-amd os=-udi ;; ultra3) basic_machine=a29k-nyu os=-sym1 ;; v810 | necv810) basic_machine=v810-nec os=-none ;; vaxv) basic_machine=vax-dec os=-sysv ;; vms) basic_machine=vax-dec os=-vms ;; vpp*|vx|vx-*) basic_machine=f301-fujitsu ;; vxworks960) basic_machine=i960-wrs os=-vxworks ;; vxworks68) basic_machine=m68k-wrs os=-vxworks ;; vxworks29k) basic_machine=a29k-wrs os=-vxworks ;; w65*) basic_machine=w65-wdc os=-none ;; w89k-*) basic_machine=hppa1.1-winbond os=-proelf ;; xps | xps100) basic_machine=xps100-honeywell ;; ymp) basic_machine=ymp-cray os=-unicos ;; z8k-*-coff) basic_machine=z8k-unknown os=-sim ;; none) basic_machine=none-none os=-none ;; # Here we handle the default manufacturer of certain CPU types. It is in # some cases the only manufacturer, in others, it is the most popular. w89k) basic_machine=hppa1.1-winbond ;; op50n) basic_machine=hppa1.1-oki ;; op60c) basic_machine=hppa1.1-oki ;; romp) basic_machine=romp-ibm ;; mmix) basic_machine=mmix-knuth ;; rs6000) basic_machine=rs6000-ibm ;; vax) basic_machine=vax-dec ;; pdp10) # there are many clones, so DEC is not a safe bet basic_machine=pdp10-unknown ;; pdp11) basic_machine=pdp11-dec ;; we32k) basic_machine=we32k-att ;; sh3 | sh4 | sh[34]eb | sh[1234]le | sh[23]ele) basic_machine=sh-unknown ;; sh64) basic_machine=sh64-unknown ;; sparc | sparcv8 | sparcv9 | sparcv9b) basic_machine=sparc-sun ;; cydra) basic_machine=cydra-cydrome ;; orion) basic_machine=orion-highlevel ;; orion105) basic_machine=clipper-highlevel ;; mac | mpw | mac-mpw) basic_machine=m68k-apple ;; pmac | pmac-mpw) basic_machine=powerpc-apple ;; *-unknown) # Make sure to match an already-canonicalized machine name. ;; *) echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 exit 1 ;; esac # Here we canonicalize certain aliases for manufacturers. case $basic_machine in *-digital*) basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` ;; *-commodore*) basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` ;; *) ;; esac # Decode manufacturer-specific aliases for certain operating systems. if [ x"$os" != x"" ] then case $os in # First match some system type aliases # that might get confused with valid system types. # -solaris* is a basic system type, with this one exception. -solaris1 | -solaris1.*) os=`echo $os | sed -e 's|solaris1|sunos4|'` ;; -solaris) os=-solaris2 ;; -svr4*) os=-sysv4 ;; -unixware*) os=-sysv4.2uw ;; -gnu/linux*) os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` ;; # First accept the basic system types. # The portable systems comes first. # Each alternative MUST END IN A *, to match a version number. # -sysv* is not here because it comes later, after sysvr4. -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ | -aos* \ | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* | -openbsd* \ | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ | -chorusos* | -chorusrdb* \ | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ | -mingw32* | -linux-gnu* | -linux-uclibc* | -uxpv* | -beos* | -mpeix* | -udk* \ | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly*) # Remember, each alternative MUST END IN *, to match a version number. ;; -qnx*) case $basic_machine in x86-* | i*86-*) ;; *) os=-nto$os ;; esac ;; -nto-qnx*) ;; -nto*) os=`echo $os | sed -e 's|nto|nto-qnx|'` ;; -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ | -windows* | -osx | -abug | -netware* | -os9* | -beos* \ | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) ;; -mac*) os=`echo $os | sed -e 's|mac|macos|'` ;; -linux-dietlibc) os=-linux-dietlibc ;; -linux*) os=`echo $os | sed -e 's|linux|linux-gnu|'` ;; -sunos5*) os=`echo $os | sed -e 's|sunos5|solaris2|'` ;; -sunos6*) os=`echo $os | sed -e 's|sunos6|solaris3|'` ;; -opened*) os=-openedition ;; -os400*) os=-os400 ;; -wince*) os=-wince ;; -osfrose*) os=-osfrose ;; -osf*) os=-osf ;; -utek*) os=-bsd ;; -dynix*) os=-bsd ;; -acis*) os=-aos ;; -atheos*) os=-atheos ;; -syllable*) os=-syllable ;; -386bsd) os=-bsd ;; -ctix* | -uts*) os=-sysv ;; -nova*) os=-rtmk-nova ;; -ns2 ) os=-nextstep2 ;; -nsk*) os=-nsk ;; # Preserve the version number of sinix5. -sinix5.*) os=`echo $os | sed -e 's|sinix|sysv|'` ;; -sinix*) os=-sysv4 ;; -tpf*) os=-tpf ;; -triton*) os=-sysv3 ;; -oss*) os=-sysv3 ;; -svr4) os=-sysv4 ;; -svr3) os=-sysv3 ;; -sysvr4) os=-sysv4 ;; # This must come after -sysvr4. -sysv*) ;; -ose*) os=-ose ;; -es1800*) os=-ose ;; -xenix) os=-xenix ;; -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) os=-mint ;; -aros*) os=-aros ;; -kaos*) os=-kaos ;; -none) ;; *) # Get rid of the `-' at the beginning of $os. os=`echo $os | sed 's/[^-]*-//'` echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 exit 1 ;; esac else # Here we handle the default operating systems that come with various machines. # The value should be what the vendor currently ships out the door with their # machine or put another way, the most popular os provided with the machine. # Note that if you're going to try to match "-MANUFACTURER" here (say, # "-sun"), then you have to tell the case statement up towards the top # that MANUFACTURER isn't an operating system. Otherwise, code above # will signal an error saying that MANUFACTURER isn't an operating # system, and we'll never get to this point. case $basic_machine in *-acorn) os=-riscix1.2 ;; arm*-rebel) os=-linux ;; arm*-semi) os=-aout ;; c4x-* | tic4x-*) os=-coff ;; # This must come before the *-dec entry. pdp10-*) os=-tops20 ;; pdp11-*) os=-none ;; *-dec | vax-*) os=-ultrix4.2 ;; m68*-apollo) os=-domain ;; i386-sun) os=-sunos4.0.2 ;; m68000-sun) os=-sunos3 # This also exists in the configure program, but was not the # default. # os=-sunos4 ;; m68*-cisco) os=-aout ;; mips*-cisco) os=-elf ;; mips*-*) os=-elf ;; or32-*) os=-coff ;; *-tti) # must be before sparc entry or we get the wrong os. os=-sysv3 ;; sparc-* | *-sun) os=-sunos4.1.1 ;; *-be) os=-beos ;; *-ibm) os=-aix ;; *-knuth) os=-mmixware ;; *-wec) os=-proelf ;; *-winbond) os=-proelf ;; *-oki) os=-proelf ;; *-hp) os=-hpux ;; *-hitachi) os=-hiux ;; i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) os=-sysv ;; *-cbm) os=-amigaos ;; *-dg) os=-dgux ;; *-dolphin) os=-sysv3 ;; m68k-ccur) os=-rtu ;; m88k-omron*) os=-luna ;; *-next ) os=-nextstep ;; *-sequent) os=-ptx ;; *-crds) os=-unos ;; *-ns) os=-genix ;; i370-*) os=-mvs ;; *-next) os=-nextstep3 ;; *-gould) os=-sysv ;; *-highlevel) os=-bsd ;; *-encore) os=-bsd ;; *-sgi) os=-irix ;; *-siemens) os=-sysv4 ;; *-masscomp) os=-rtu ;; f30[01]-fujitsu | f700-fujitsu) os=-uxpv ;; *-rom68k) os=-coff ;; *-*bug) os=-coff ;; *-apple) os=-macos ;; *-atari*) os=-mint ;; *) os=-none ;; esac fi # Here we handle the case where we know the os, and the CPU type, but not the # manufacturer. We pick the logical manufacturer. vendor=unknown case $basic_machine in *-unknown) case $os in -riscix*) vendor=acorn ;; -sunos*) vendor=sun ;; -aix*) vendor=ibm ;; -beos*) vendor=be ;; -hpux*) vendor=hp ;; -mpeix*) vendor=hp ;; -hiux*) vendor=hitachi ;; -unos*) vendor=crds ;; -dgux*) vendor=dg ;; -luna*) vendor=omron ;; -genix*) vendor=ns ;; -mvs* | -opened*) vendor=ibm ;; -os400*) vendor=ibm ;; -ptx*) vendor=sequent ;; -tpf*) vendor=ibm ;; -vxsim* | -vxworks* | -windiss*) vendor=wrs ;; -aux*) vendor=apple ;; -hms*) vendor=hitachi ;; -mpw* | -macos*) vendor=apple ;; -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) vendor=atari ;; -vos*) vendor=stratus ;; esac basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` ;; esac echo $basic_machine$os exit 0 # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: tsung-1.4.2/tsung.spec0000644000201100017670000000447511701017132014362 0ustar nniclausdream%define name tsung %define version 1.4.2 %define release 1 Name: %{name} Version: %{version} Release: %{release}%{?dist} Summary: A distributed multi-protocol load testing tool Group: Development/Tools License: GPLv2 URL: http://tsung.erlang-projects.org/ Source0: http://tsung.erlang-projects.org/dist/%{name}-%{version}.tar.gz Vendor: Process-one Packager: Nicolas Niclausse BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: erlang Requires: erlang Requires: perl(Template) %description tsung is a distributed load testing tool. It is protocol-independent and can currently be used to stress and benchmark HTTP, Jabber/XMPP, PostgreSQL, MySQL and LDAP servers. It simulates user behaviour using an XML description file, reports many measurements in real time (statistics can be customized with transactions, and graphics generated using gnuplot). For HTTP, it supports 1.0 and 1.1, has a proxy mode to record sessions, supports GET and POST methods, Cookies, and Basic WWW-authentication. It also has support for SSL. More information is available at http://tsung.erlang-projects.org/ . %prep %setup -q %build %configure --docdir=%{_docdir}/%{name}-%{version} make %{?_smp_mflags} %install rm -rf $RPM_BUILD_ROOT make install DESTDIR=$RPM_BUILD_ROOT install -p -m 644 CHANGES CONTRIBUTORS COPYING README TODO \ $RPM_BUILD_ROOT%{_docdir}/%{name}-%{version}/ %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %doc %{_docdir}/%{name}-%{version}/* %{_bindir}/tsung %{_bindir}/tsung-recorder %{_bindir}/tsplot %{_libdir}/erlang/lib %{_libdir}/tsung %{_datadir}/tsung %{_mandir}/man1/tsung.1* %{_mandir}/man1/tsplot.1* %{_mandir}/man1/tsung-recorder.1* %changelog * Wed Sep 20 2006 Nicolas Niclausse 1.2.1-1 - update 'requires': erlang (as in fedora extra) instead of erlang-otp * Wed Apr 27 2005 Nicolas Niclausse 1.0.2-1 - new release * Thu Nov 18 2004 Nicolas Niclausse 1.0.1-1 - new release * Mon Aug 9 2004 Nicolas Niclausse 1.0-1 - new release * Mon Aug 9 2004 Nicolas Niclausse 1.0.beta7-2 - fix doc * Mon Aug 9 2004 Nicolas Niclausse 1.0.beta7-1 - initial rpm # end of file tsung-1.4.2/include/0000755000201100017670000000000011701017143013761 5ustar nniclausdreamtsung-1.4.2/include/ts_fs.hrl0000644000201100017670000000277111701017117015616 0ustar nniclausdream%%% %%% Copyright 2010 © INRIA %%% %%% Author : Nicolas Niclausse %%% Created: 13 january 2010 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). %% use by the client to create the request -record(fs_dyndata, { position=0, iodev } ). -record(fs, { command, mode, path, iodev, size, dest, position }). -record(fs_sess, { fixme }). tsung-1.4.2/include/ELDAPv3.hrl0000644000201100017670000000352611701017117015575 0ustar nniclausdream%% Generated by the Erlang ASN.1 compiler version:1.4.5 %% Purpose: Erlang record definitions for each named and unnamed %% SEQUENCE and SET, and macro definitions for each value %% definition,in module ELDAPv3 -record('LDAPMessage',{ messageID, protocolOp, controls = asn1_NOVALUE}). -record('AttributeValueAssertion',{ attributeDesc, assertionValue}). -record('Attribute',{ type, vals}). -record('LDAPResult',{ resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE}). -record('Control',{ controlType, criticality = asn1_DEFAULT, controlValue = asn1_NOVALUE}). -record('BindRequest',{ version, name, authentication}). -record('SaslCredentials',{ mechanism, credentials = asn1_NOVALUE}). -record('BindResponse',{ resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, serverSaslCreds = asn1_NOVALUE}). -record('SearchRequest',{ baseObject, scope, derefAliases, sizeLimit, timeLimit, typesOnly, filter, attributes}). -record('SubstringFilter',{ type, substrings}). -record('MatchingRuleAssertion',{ matchingRule = asn1_NOVALUE, type = asn1_NOVALUE, matchValue, dnAttributes = asn1_DEFAULT}). -record('SearchResultEntry',{ objectName, attributes}). -record('PartialAttributeList_SEQOF',{ type, vals}). -record('ModifyRequest',{ object, modification}). -record('ModifyRequest_modification_SEQOF',{ operation, modification}). -record('AttributeTypeAndValues',{ type, vals}). -record('AddRequest',{ entry, attributes}). -record('AttributeList_SEQOF',{ type, vals}). -record('ModifyDNRequest',{ entry, newrdn, deleteoldrdn, newSuperior = asn1_NOVALUE}). -record('CompareRequest',{ entry, ava}). -record('ExtendedRequest',{ requestName, requestValue = asn1_NOVALUE}). -record('ExtendedResponse',{ resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, responseName = asn1_NOVALUE, response = asn1_NOVALUE}). -define('maxInt', 2147483647). tsung-1.4.2/include/ts_shell.hrl0000644000201100017670000000262711701017117016315 0ustar nniclausdream%%% %%% Copyright 2010 © INRIA %%% %%% Author : Nicolas Niclausse %%% Created: 13 january 2010 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). %% use by the client to create the request -record(shell_dyndata, { fixme } ). -record(shell, { command, args }). -record(shell_sess, { fixme }). tsung-1.4.2/include/eldap.hrl0000644000201100017670000000136211701017117015560 0ustar nniclausdream-ifndef( _ELDAP_HRL ). -define( _ELDAP_HRL , 1 ). %%% %%% Search input parameters %%% -record(eldap_search, { base = [], % Baseobject filter = [], % Search conditions scope, % Search scope attributes = [], % Attributes to be returned types_only = false, % Return types+values or types timeout = 0 % Timelimit for search }). %%% %%% Returned search result %%% -record(eldap_search_result, { entries = [], % List of #eldap_entry{} records referrals = [] % List of referrals }). %%% %%% LDAP entry %%% -record(eldap_entry, { object_name = "", % The DN for the entry attributes = [] % List of {Attribute, Value} pairs }). -endif. tsung-1.4.2/include/ts_ldap.hrl0000644000201100017670000000250611701017117016122 0ustar nniclausdream%%% Copyright (C) 2008 Pablo Polvorin %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -vc('$Id: ts_http.hrl 826 2008-04-01 16:07:38Z nniclausse $ '). -record(ldap_request, { type, user, password, base, filter, result_var, attributes, scope, cacertfile, keyfile, certfile, dn, attrs, modifications }). tsung-1.4.2/include/ts_profile.hrl0000644000201100017670000001511611701017117016643 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -record(dyndata, {dynvars = [], % dynamic variables proto % dynamic data specific to protocol (#http_dyndata for HTTP) }). -record(match, { regexp, subst = false, 'when' = false, do = continue, %(continue | loop | abort | log ) sleep_loop, % in seconds apply_to_content, skip_headers = no, max_loop, loop_back, max_restart }). -record(ts_request, { ack, subst=false, match=[], dynvar_specs=[], % [] | [{VarName, Regexp} |...] param, endpage=false, host, % override global server hostname port, % override global server port scheme % override global server type (ssl or gen_tcp) }). % protocol options -record(proto_opts, {ssl_ciphers = negociate, % for ssl only retry_timeout = 10, % retry sending in microsec idle_timeout = 600000, tcp_rcv_size = 32768, % tcp buffers size tcp_snd_size = 32768, udp_rcv_size, % udp buffers size udp_snd_size}). -record(token_bucket, {rate, burst, last_packet_date = 0, current_size = 0 }). -define(size_mon_thresh, 524288). % 512KB % state of ts_client gen_server -record(state_rcv, {socket=none, % ip, % local ip to bind to timeout, % ? retries=0, % number of connect retries hibernate = 10000, % hibernate if thinktime is >= to this (10sec by default) host, % hostname (or IP) of remote server port, % server port protocol, % gen_udp, gen_tcp or ssl proto_opts = #proto_opts{}, % bidi = false,% true if bidirectional protocol session_id, request, % current request specs persistent, % if true, don't exit when connexion is closed timestamp, % previous message date starttime, % date of the beginning of the session count, % number of requests waiting to be sent maxcount, % number of requests waiting to be sent ack_done=false, % 'true' if the ack was sent, else 'false' (unused if ack=no_ack) send_timestamp, % date when the 'request' was sent page_timestamp=0,% date when the first 'request' of a page was sent acc=[], % Accumulator to store temporary unparsable data % (Waiting for more data) buffer = <<>>, % buffer when we have to keep the response (we need % all the response to do pattern matching) session, % record of session status; depends on 'clienttype' datasize=0, id, % user id size_mon_thresh=?size_mon_thresh, % if rcv data is > to this, update stats size_mon=0, % current size (used for threshold computation) dyndata=[], % persistent data dynamically added during the % session (Cookies for examples) clienttype, % module name (ts_jabber, etc.) transactions=[], % current transactions rate_limit, % rate limiting parameters dump % type of dump (full, light, none) }). -define(restart_sleep, 2000). -define(infinity_timeout, 15000). -define(short_timeout, 1). -define(config_timeout, 60000). -define(check_noclient_timeout, 60000). -define(retries, 4). -record(launcher, {nusers, phases =[], myhostname, intensity, static_done = false, started_users = 0, phase_nusers, % total number of users to start in the current phase phase_duration, % expected phase duration phase_start, % timestamp start_date, short_timeout = ?short_timeout, maxusers %% if maxusers are currently active, launch a %% new beam to handle the new users }). -define(TIMEOUT_PARALLEL_SPAWN, 60000). -define(MAX_PHASE_EXCEED_PERCENT, 20). -define(MAX_PHASE_EXCEED_NUSERS, 10). -define(CRLF, "\r\n"). -define(CR,13). -define(LF,10). %% retry sending message after this timeout (in microsec.) -define(config(Var), ts_utils:get_val(Var)). -define(messages_intensity, 1/(ts_utils:get_val(messages_interarrival)*1000)). -define(clients_intensity, 1/(ts_utils:get_val(interarrival)*1000)). %% errors messages -define(LOGF(Msg, Args, Level), ts_utils:debug(?MODULE, Msg, Args, Level)). -define(LOG(Msg, Level), ts_utils:debug(?MODULE, Msg, Level)). %% Debug messages can be completely disabled if DEBUG is not defined -ifdef(DEBUG). -define(TRACE, [{debug, [trace]}]). -define(DebugF(Msg, Args), ts_utils:debug(?MODULE, Msg, Args, ?DEB)). -define(Debug(Msg), ts_utils:debug(?MODULE, Msg, ?DEB)). -else. -define(TRACE, []). -define(DebugF(Msg, Args), ok). -define(Debug(Msg), ok). -endif. -define(EMERG, 0). % The system is unusable. -define(ALERT, 1). % Action should be taken immediately to address the problem. -define(CRIT, 2). % A critical condition has occurred. -define(ERR, 3). % An error has occurred. -define(WARN, 4). % A significant event that may require attention has occurred. -define(NOTICE, 5).% An event that does not affect system operation has occurred. -define(INFO, 6). % An normal operation has occurred. -define(DEB, 7). % Debugging info tsung-1.4.2/include/ts_config.hrl0000644000201100017670000000765311701017117016457 0ustar nniclausdream%%% %%% Copyright IDEALX S.A.S. 2003 %%% %%% Author : Nicolas Niclausse %%% Created: 03 Dec 2003 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -define(TSUNGPATH, "TSUNGPATH"). -define(SESSION_POP_ERROR_MSG, "Total sum of session popularity is not equal to 100"). -define(DEF_REGEXP_DYNVAR_BEGIN, "name=(\"|')").%' -define(DEF_REGEXP_DYNVAR_END, "(\"|') ([^>]* )?value=(\"|')\\([^(\"|')]*\\)(\"|')").%' -define(DEF_RE_DYNVAR_BEGIN, "name=[\"']").%' -define(DEF_RE_DYNVAR_END, "[\"'] (?:[^>]* )?value=[\"']([^\"']*)[\"']").%' -record(config, { name, duration, % max duration of test (by default: end when all clients are done) loglevel = ?WARN, dump = none, stats_backend, controller, % controller machine clients = [], % client machines servers = [], % server(s) to test ports_range, % client ports range monitor_hosts = [], % Cluster host to monitor (for CPU, MEM usage) arrivalphases = [], % arrival process specs thinktime, % default thinktime specs subst = false, % Substitution should be applied on the request match, % Match regexp in response dynvar = [], main_sess_type , % main type of session sessions = [], static_users=[], session_tab, use_controller_vm = false, % if true, start the first launcher in the % same vm as the controller if possible curthink, % temporary var (current request think) curid = 0, % temporary var (current request id (can be transaction)) cur_req_id = 0, % temporary var (current real request id) file_server = [], % filenames for file_server load_loop, % loop phases if > 0 hibernate = 10000, % hibernate timeout (millisec) 10sec by default proto_opts, % tcp/udp buffer sizes seed = now, % random seed: (default= current time) vhost_file = none, % file server user for virtual host jabber testing user_server_maxuid = none, % user_id max oids=[], rate_limit, job_notify_port }). -record(client, {host, weight = 1, maxusers, ip = [] }). -record(server, {host, port, type }). -record(session, { id, popularity, type, name, persistent = false, bidi = false, hibernate, proto_opts, rate_limit, size, client_ip, server, userid, seed, dump }). -record(arrivalphase, {phase, duration, unit, intensity, maxnumber, curnumber = 0 }). tsung-1.4.2/include/ts_http.hrl0000644000201100017670000000663111701017117016164 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). %% use by the client to create the request -record(http_request, { url, version="1.1", % default is HTTP/1.1 host_header, % use for the 'Host:' header get_ims_date, % used when the method is getims cookie = [], method = get, content_type = [], headers = [], body = [], id = 0, user_agent, userid, % for www_authentication passwd, % for www_authentication soap_action % for SOAP support }). -record(http_dyndata, { user_agent, cookies = [] % HTTP Cookies } ). -record(url, {scheme, %% http, https, ... host, port, %% undefined means use default (80 or 443) path = [], querypart = []}). %% use by the client process to store information about the current request during %% the parsing of the response -record(http, {content_length= 0, % HTTP header: content length body_size = 0, % current size of body, chunk_toread = -1, % chunk data to be read (-1 = not chunked, -2 = not chunked, but last response was) status = {none,none}, % HTTP resp. status :200, etc. 'none' % if no current cnx. close = false, % true if HTTP/1.0 or 'connection: close' % has been received partial=false, % true if headers are partially received compressed={false,false}, % type of compression if body is compressed cookie=[] }). -record(cookie,{ key, value, quoted, comment, comment_url, discard, domain, max_age, expires, path, port, secure, version}). %% HTTP Protocol -define(GET, "GET"). -define(POST, "POST"). -define(PUT, "PUT"). -define(HEAD, "HEAD"). -define(DELETE, "DELETE"). -define(OPTIONS, "OPTIONS"). -define(USER_AGENT, "Tsung"). -define(USER_AGENT_ERROR_MSG, "Total sum of user agents frequency is not equal to 100"). -define(MAX_HEADER_SIZE, 65536). % used for http_chunk:decode tsung-1.4.2/include/ts_pgsql.hrl0000644000201100017670000000414711701017117016333 0ustar nniclausdream%%% %%% Copyright Nicolas Niclausse 2005 %%% %%% Author : Nicolas Niclausse %%% Created: 6 Nov 2005 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). %% use by the client to create the request -record(pgsql_request, { type, username, passwd, salt, auth_method, database, name_portal, name_prepared, equery, parameters, formats, formats_results, max_rows, % used for type='execute' sql }). %% -record(pgsql_dyndata, { auth_method, username, % FIXME: add this at config time in #pgsql_request ? salt } ). -record(pgsql, { username } ). %%% Version 3.0 of the protocol. %%% Supported in postgres from version 7.4 -define(PROTOCOL_MAJOR, 3). -define(PROTOCOL_MINOR, 0). -define(PG_PASSWORD_MSG, $p). -define(PG_AUTH_OK, 0). -define(PG_AUTH_KRB4, 1). -define(PG_AUTH_KRB5, 2). -define(PG_AUTH_PASSWD, 3). -define(PG_AUTH_CRYPT, 4). -define(PG_AUTH_MD5, 5). tsung-1.4.2/include/ts_mysql.hrl0000644000201100017670000000360011701017117016343 0ustar nniclausdream%%% Created : July 2008 by Grgoire Reboul %%% From : ts_pgsql.hrl by Nicolas Niclausse %%% Note : Based on erlang-mysql by Magnus Ahltorp & Fredrik Thulin %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -author('gregoire.reboul@laposte.net'). %% use by the client to create the request -record(mysql_request, { type, username, passwd, salt, database, sql }). %% -record(mysql_dyndata, { salt } ). %% unused -record(mysql, { fixme } ). %% Support for MySQL 4.1.x et 5.0.x -define(MYSQL_4_0, 40). -define(MYSQL_4_1, 41). -define(LONG_PASSWORD, 1). -define(LONG_FLAG, 4). -define(PROTOCOL_41, 512). -define(TRANSACTIONS, 8192). -define(SECURE_CONNECTION, 32768). -define(CONNECT_WITH_DB, 8). -define(MAX_PACKET_SIZE, 1000000). -define(MYSQL_QUERY_OP, 3). -define(MYSQL_CLOSE_OP, 1). tsung-1.4.2/include/ts_jabber.hrl0000644000201100017670000000454511701017117016434 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -record(jabber_dyndata, {id, regexp}). -record(jabber, {dest, size, data, type, jud_param, regexp, cle, id = 0, domain, %% jabber domain user_server, %%user_server to use for the domain username, %% first chars of username (will append id dynamically) passwd, %% first chars of passwd (will append id dynamically) nonce, %% used to generate sip-digest passwd sid, %% used to generate digest passwd show, %% presence - see RFC 3921, section 2.2 status, %% presence - see RFC 3921, section 2.2 muc_service, %% ej: conference.localhost room, %% MUC room name nick, %% nickname in MUC room pubsub_service, %%ej: pubsub.localhost group, %% roster group node, %% pubsub node node_type }). -define(setroster_intensity, 1/(ts_utils:get_val(setroster)*1000)). tsung-1.4.2/include/ts_job.hrl0000644000201100017670000000341711701017117015756 0ustar nniclausdream%%% %%% Copyright 2011 © INRIA %%% %%% Author : Nicolas Niclausse %%% Created: 4 mai 2011 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -author('nicolas.niclausse@inria.fr'). %% use by the client to create the request -record(job_dyndata, { fixme } ). -record(job, { script, resources, walltime, queue, duration, jobid, req, % submit|stat|delete type, % oar|torque notify_port, notify_script, name, user, options, args }). -record(job_session, { jobid, owner, submission_time, queue_time, start_time, end_time, dump, status }). tsung-1.4.2/include/ts_raw.hrl0000644000201100017670000000257011701017117015774 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -vc('$Id$ '). -author('nicolas.niclausse@IDEALX.com'). -record(raw, { data, datasize, bug %% FIXME: ugly: if only a single name is set, handle_next_request will fail with add_dynparams (it will think that the server has changed) }). tsung-1.4.2/include/ts_os_mon.hrl0000644000201100017670000000204111701017117016466 0ustar nniclausdream%%% Copyright (C) 2008 Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -define(INTERVAL, 10000). -define(INIT_WAIT, 1000). tsung-1.4.2/include/ts_recorder.hrl0000644000201100017670000000213211701017117017002 0ustar nniclausdream -define(tcp_buffer, 65536). -define(lifetime, 120000). -record(state_rec, {log_file, % logfile name logfd, % logfile IODevice prev_port, % previous port prev_scheme, % previous scheme prev_host, % previous hostname timestamp=0, % last request date ext_file_id, % counter of external files (use for ex. in HTTP POST req) plugin_state, % can be used by the plugin to store some state plugin, thinktime_low = 1000 % dot not record thinktime less than this % value (msec) }). -record(proxy, { clientsock, http_version, close, % must close client socket (connection:close header was send by server) parse_status = new, %% http status = body|new body_size = 0, content_length = 0, parent_proxy = false, buffer = [], plugin, serversock }). tsung-1.4.2/configure.in0000644000201100017670000000771411701017117014661 0ustar nniclausdreamdnl DNA define([AC_CACHE_LOAD], )dnl AC_INIT([tsung], m4_normalize(m4_include([vsn.mk])),[tsung-users@process-one.net]) AC_PREREQ(2.59c) AC_COPYRIGHT(Copyright (C) 2008 Nicolas Niclausse) AC_CONFIG_SRCDIR(src/tsung/tsung.erl) dnl AM_INIT_AUTOMAKE() AC_CACHE_LOAD AC_SUBST([CONFIG_STATUS_DEPENDENCIES],[vsn.mk]) AC_SUBST([CONFIGURE_DEPENDENCIES],[vsn.mk]) AC_PATH_PROG(SED, sed) AC_LANG(Erlang) AC_ARG_WITH(erlang, [ --with-erlang=PREFIX path to erlc and erl ]) AC_ERLANG_PATH_ERLC(erlc, $with_erlang:$with_erlang/bin:$PATH) AC_ERLANG_PATH_ERL(erl, $with_erlang:$with_erlang/bin:$PATH) AC_PREFIX_PROGRAM(erl) AC_ERLANG_SUBST_ROOT_DIR() AC_MSG_CHECKING(for Erlang/OTP '-hybrid' option) if ! $ERL -noshell -hybrid -smp 2 -s init stop 2> /dev/null; then AC_MSG_RESULT(no) else AC_MSG_RESULT(yes) ERL_OPTS="-hybrid $ERL_OPTS" fi dnl check for xmerl include path AC_ERLANG_CHECK_LIB(xmerl) AC_ERLANG_CHECK_LIB(ssl) AC_ERLANG_CHECK_LIB(crypto) AC_ERLANG_CHECK_LIB(public_key) dnl check if ssl is working AC_CACHE_CHECK([if Erlang/OTP SSL application is running fine], [erlang_cv_ssl_runnable], [erlang_cv_ssl_runnable=no AC_RUN_IFELSE( [AC_LANG_PROGRAM([], [dnl case application:start(ssl) of ok -> ok; Err -> halt(1) end, halt(0)])], [erlang_cv_ssl_runnable=yes ERLANG_APPLICATIONS="kernel,stdlib,ssl"], [ AC_RUN_IFELSE( [AC_LANG_PROGRAM([], [dnl application:start(crypto), application:start(public_key), case application:start(ssl) of ok -> ok; Err -> halt(1) end, halt(0)])], [erlang_cv_ssl_runnable=yes ERLANG_APPLICATIONS="kernel,stdlib,crypto,public_key,ssl"], [ERLANG_APPLICATIONS="kernel,stdlib" AC_MSG_RESULT(WARNING: ssl application is not working properly !!!)]) ]) ]) dnl check if crypto is working AC_CACHE_CHECK([if Erlang/OTP crypto application is running fine], [erlang_cv_crypto_runnable], [erlang_cv_crypto_runnable=no AC_RUN_IFELSE( [AC_LANG_PROGRAM([], [dnl case application:start(crypto) of ok -> case catch crypto:md5("toto") of <<247,29,190,82,98,138,63,131,167,122,180,148,129,117,37, 198>> -> ok; _ -> halt(1) end; Err -> erlang:display([Err]), halt(1) end, halt(0) ])], [ erlang_cv_crypto_runnable=yes ERLANG_APPLICATIONS="$ERLANG_APPLICATIONS,crypto" ], [ AC_MSG_RESULT([WARNING: crypto application is not working properly !!!])]) ]) dnl check if orelse is allowed in guards AC_CACHE_CHECK([if orelse is allowed in guards], [erlang_cv_orelse], [erlang_cv_orelse=no AC_RUN_IFELSE( [AC_LANG_PROGRAM([], [dnl case 3 of A when A > 3 orelse A < 2 -> ok; _ -> bad end, halt(0)])], [erlang_cv_orelse=yes], [AC_MSG_RESULT(WARNING: orelse/andalso not allowed in guards: XPATH parsing will be disabled !!!)]) ]) AC_SUBST(erlang_cv_orelse) AC_SUBST(ERL_OPTS) AC_SUBST(ERLANG_APPLICATIONS) AC_SUBST(DTD,[tsung-1.0.dtd]) AC_SUBST(TEMPLATES_SUBDIR,[tsung/templates]) AC_PROG_MAKE_SET AC_PROG_INSTALL AS_AC_EXPAND(EXPANDED_LIBDIR, "$libdir") AC_MSG_NOTICE(Storing library files in $EXPANDED_LIBDIR) AS_AC_EXPAND(EXPANDED_SHAREDIR, "$datadir/tsung") AC_MSG_NOTICE(Storing data files in $EXPANDED_SHAREDIR) AC_CONFIG_FILES([\ Makefile \ tsung.spec \ tsung.sh \ tsung-recorder.sh \ examples/*.xml \ src/tsung_stats.pl \ src/tsung-plotter/tsplot.py \ src/log2tsung.pl \ src/tsung_controller/tsung_controller.app.src \ src/tsung_recorder/tsung_recorder.app.src \ src/tsung/tsung.app.src \ ]) AC_OUTPUT tsung-1.4.2/tsung-recorder.sh.in0000644000201100017670000001313511701017117016246 0ustar nniclausdream#!/usr/bin/env bash UNAME=`uname` case $UNAME in "Linux") HOST=`hostname -s`;; "SunOS") HOST=`hostname`;; *) HOST=`hostname -s`;; esac INSTALL_DIR=@prefix@/lib/erlang/ ERL=@ERL@ MAIN_DIR=$HOME/.tsung LOG_DIR=$MAIN_DIR/log LOG_OPT="log_file \"$LOG_DIR/tsung.log\"" VERSION=@PACKAGE_VERSION@ NAMETYPE="-sname" LISTEN_PORT=8090 USE_PARENT_PROXY=false PGSQL_SERVER_IP=127.0.0.1 PGSQL_SERVER_PORT=5432 NAME=tsung RECORDER=tsung_recorder TSUNGPATH=$INSTALL_DIR/lib/tsung-$VERSION/ebin RECORDERPATH=$INSTALL_DIR/lib/tsung_recorder-$VERSION/ebin CONTROLLERPATH=$INSTALL_DIR/lib/tsung_controller-$VERSION/ebin CONF_OPT_FILE="$HOME/.tsung/tsung.xml" BOOT_OPT="-boot $INSTALL_DIR/lib/tsung_controller-$VERSION/priv/tsung_controller -boot_var TSUNGPATH $INSTALL_DIR " REC_BOOT_OPT="-boot $INSTALL_DIR/lib/tsung_recorder-$VERSION/priv/tsung_recorder -boot_var TSUNGPATH $INSTALL_DIR " DEBUG_LEVEL=5 RECORDER_PLUGIN="http" ERL_RSH=" -rsh ssh " ERL_OPTS=" -smp auto +P 250000 +A 16 +K true @ERL_OPTS@ " COOKIE='tsung' ERTS_RUN=`$ERL -version 2>&1 | tr -cd 0123456789.` ERTS_BOOT=`grep erts $TSUNGPATH/../priv/tsung.rel 2> /dev/null| tr -cd 0123456789.` stop() { $ERL $ERL_OPTS $ERL_RSH -noshell $NAMETYPE killer -setcookie $COOKIE -pa $TSUNGPATH -pa $RECORDERPATH -s tsung_recorder stop_all $HOST -s init stop RET=$? if [ $RET == 1 ]; then echo "FAILED" else echo "[OK]" rm $PIDFILE fi } status() { PIDFILE="/tmp/tsung_recorder.pid" if [ -f $PIDFILE ]; then echo "Tsung recorder started [OK]" else echo "Tsung recorder not started " fi } checkversion() { if [ $ERTS_RUN != $ERTS_BOOT ] then echo "Erlang version has changed ! [$ERTS_BOOT] != [$ERTS_RUN]" echo "Must create new boot files (you may have to run this one time as root ! )" makebootfiles fi } makebootfiles() { echo "creating boot file for tsung_recorder application" cd $RECORDERPATH/.. $ERL $ERL_OPTS -noshell -pa $TSUNGPATH -s builder go -s init stop > /dev/null } start() { echo "Starting Tsung recorder on port $LISTEN_PORT" $ERL $ERL_OPTS $ERL_RSH -noshell $NAMETYPE $RECORDER -setcookie $COOKIE $REC_BOOT_OPT \ -pa $TSUNGPATH -pa $RECORDERPATH -pa $CONTROLLERPATH \ -tsung_recorder debug_level $DEBUG_LEVEL \ -tsung_recorder $LOG_OPT \ -tsung_recorder parent_proxy $USE_PARENT_PROXY \ -tsung_recorder plugin ts_proxy_$RECORDER_PLUGIN \ -tsung_recorder proxy_log_file \"$MAIN_DIR/tsung_recorder.xml\" \ -tsung_recorder pgsql_server \"${PGSQL_SERVER_IP}\" \ -tsung_recorder pgsql_port ${PGSQL_SERVER_PORT} \ -tsung_recorder proxy_listen_port $LISTEN_PORT & echo $! > /tmp/tsung_recorder.pid } version() { echo "Tsung Recorder version $VERSION" exit 0 } checkconfig() { if [ ! -e $CONF_OPT_FILE ] then echo "Config file $CONF_OPT_FILE doesn't exist, aborting !" exit 1 fi } maindir() { if [ ! -d $MAIN_DIR ] then echo "Creating local Tsung directory $MAIN_DIR" mkdir $MAIN_DIR fi } logdir() { if [ ! -d $LOG_DIR ] then echo "Creating Tsung log directory $LOG_DIR" mkdir $LOG_DIR fi } record_tag() { shift SNAME=tsung_recordtag $ERL -noshell $NAMETYPE $SNAME -setcookie $COOKIE -pa $TSUNGPATH -pa $RECORDERPATH -run ts_proxy_recorder recordtag $HOST "$*" -s init stop } checkrunning(){ if [ -f $PIDFILE ]; then CURPID=`cat $PIDFILE` if kill -0 $CURPID 2> /dev/null ; then echo "Can't start: Tsung recorder already running !" exit 1 fi fi } usage() { prog=`basename $0` echo "Usage: $prog start|stop|restart" echo "Options:" echo " -p plugin used for the recorder" echo " available: http, pgsql,webdav (default is http)" echo " -L listening port for the recorder (default is 8090)" echo " -I for the pgsql recorder (or parent proxy): server IP" echo " (default is 127.0.0.1)" echo " -P for the pgsql recorder (or parent proxy): server port" echo " (default is 5432)" echo " -u for the http recorder: use a parent proxy" echo " -d set log level from 0 to 7 (default is 5)" echo " -v print version information and exit" echo " -h display this help and exit" exit } while getopts "hvf:p:l:d:r:i:P:L:I:u" Option do case $Option in f) CONF_OPT_FILE=$OPTARG;; l) # must add absolute path echo "$OPTARG" | grep -q "^/" RES=$? if [ "$RES" == 0 ]; then LOG_OPT="log_file \"$OPTARG\" " else LOG_OPT="log_file \"$PWD/$OPTARG\" " fi ;; d) DEBUG_LEVEL=$OPTARG;; p) RECORDER_PLUGIN=$OPTARG;; I) PGSQL_SERVER_IP=$OPTARG;; u) USE_PARENT_PROXY=true;; P) PGSQL_SERVER_PORT=$OPTARG;; L) LISTEN_PORT=$OPTARG;; v) version;; h) usage;; *) usage ;; esac done shift $(($OPTIND - 1)) case $1 in start) PIDFILE="/tmp/tsung_recorder.pid" checkversion maindir logdir checkrunning start ;; record_tag) record_tag $* ;; boot) checkversion ;; stop) PIDFILE="/tmp/tsung_recorder.pid" stop ;; status) status ;; restart) stop start ;; *) usage $0 ;; esac tsung-1.4.2/priv/0000755000201100017670000000000011701017143013316 5ustar nniclausdreamtsung-1.4.2/priv/builder.erl0000644000201100017670000013523411701017117015461 0ustar nniclausdream%%%---------------------------------------------------------------------- %%% File : builder.erl %%% Author : Mats Cronqvist %%% : Ulf Wiger %%% Purpose : Simplify build of OTP applications & boot scripts %%% Created : 2 Jan 2001 by Mats Cronqvist %%%---------------------------------------------------------------------- %%% @doc OTP release script builder. %%% %%%

This program compiles .rel, .script, .boot and sys.config files %%% for erlang applications. It supports incremental and recursive builds, %%% and is intended to be (much) easier and safer to use than doing it all %%% manually. This program does not generate beam code from Erlang source %%% files.

%%% %%%

The program makes some assumptions:

%%%
  • The application is store in a directory whose name is %%% composed with the name of the application '-' the version %%% number of the application (i.e. myapp-1.0)
  • %%%
  • The release and app file used in input are simplified subset %%% of the final Erlang release and app file. Some differences: %%% Versions are handled automatically; You do not need to add %%% the erts version tuple in the rel.src file; the application, %%% currently being built is mandatory in the list of apps in the %%% rel.src file; etc.
  • %%%
%%% %%%

The program (henceforth called 'builder') can be customized %%% using a number of options. The options are prioritized in %%% the following order: (1) those given as arguments to the %%% builder:go/1 function, those in the BUILD_OPTIONS %%% file, and (3) the hard-coded defaults.

%%% %%%

Valid options are

%%%
%%%
{app_dir, dirname()}
%%%
The application directory of the application being built. %%% The default value is Current Working Directory.
%%% %%%
{build_options, filename()}
%%%
A filename pointing to a BUILD_OPTIONS file. This is a file %%% containing erlang expressions, each terminated by "." -- just %%% like a file processed using file:eval/1. The last expression %%% in the file should result in a list of %%% {Key,Value} options. %%% If this option is not given, builder will look in [AppDir] and %%% [AppDir]/src for a file called BUILD_OPTIONS.
%%% Pre-bound variables are: %%%
    %%%
  • ScriptName : filename(), %%% the name of the script file.
  • %%%
%%%
{report, Level : atom()}
%%%
Specifies the reporting level. The following levels are recognized: %%% none, progress, verbose, debug.
%%% %%%
{out_dir, dirname()}
%%%
Specifies where [AppName].script, [AppName].boot, and sys.config %%% should be written. Default is [AppDir]/priv.
%%% %%%
{sys_config, filename()}
%%%
Specifies the location of the sys.config file. Default is %%% [OutDir]/sys.config.
%%% %%%
{rel_file, filename()}
%%%
Specifies which .rel file should be used as input for the %%% build process. Default is %%% [AppDir]/src/[AppName].rel.src.
%%% %%%
{rel_name, string()}
%%%
This option can be used if the release should be called something %%% other than [AppName] or whatever is in the %%% .rel file.
%%% %%%
{app_vsn, string()}
%%%
This option can be used to assign a version to the current %%% application. If the application directory has a version suffix, %%% the version suffix will override this option. If the directory %%% has no suffix, the default vsn will be "BLDR", unless 'app_vsn' %%% has been specified.
%%% %%%
{apps, [App : AppName | {AppName,Vsn} | {AppName,Vsn,EbinDir}]}
%%%
This is a way to identify which applications should be included %%% in the build. The way builder determines which applications should %%% be included is this:
    %%%
  • If there is a .rel.src file, the applications %%% listed in this file are included first, in the order in which %%% they are listed.
  • %%%
  • After this, all applications listed in the 'applications' %%% attribute of the .app.src file are added (if not %%% already listed), also in the order in which they are listed.
  • %%%
  • Then, all remaining applications in the 'apps' list are added %%% in order.
  • %%%
  • Finally, any included applications listed in the %%% .rel.src file, not already listed, are added. %%% (note that applications listed in 'included_applications' of %%% the .app.src file are not included here. The %%% .rel file can be used to override this definition, %%% so it would be an error to include it at this point.)
  • %%%
%%%
%%% %%%
{path, [PathExpr]}
%%%
Specifies the search path when locating applications. Default is %%% code:get_path(). PathExpr can contain wildcards, e.g. %%% "/OTP/lib/kernel*/ebin" (basically anything that %%% regexp:sh_to_awk/1 understands.)
%%% %%%
{skip, [ModuleName : atom()]}
%%%
Lists modules that should not be included in the generated .app %%% file for the current application. If not specified, builder will %%% locate all modules under [AppDir]/src, extract %%% their module names, and include these module names in the %%% 'modules' list of the .app file.
%%% %%%
{make_app, true | false | auto}
%%%
If true, builder will generate an .app file from an .app.src %%% file; if false, it will try to use an existing .app file; if %%% auto, it will build an .app file if no .app file exists %%% Default is 'auto'.
%%% %%%
{make_rel, true | false}
%%%
If true, builder will generate a .rel file from a .rel.src file; %%% if false, it will assume that there is a working .rel file. %%% Default: false if the rel_file option has been specified; true %%% otherwise.
%%% %%%
{make_boot, true | false}
%%%
If true, builder will run systools:make_script() %%% to generate .script and .boot files in [OutDir]. If false, it %%% will assume that there is a working .boot file. Default is true.
%%%
%%% %%%
{systools, Options : [Opt]}
%%%
If specified, Options will be passed to the %%% systools:make_script() command for building the %%% .boot script. Note that the 'path' option %%% is generated by builder. It cannot be overridden. %%% See erl -man systools for available options.
%%% %%%
{sh_script, auto | none}
%%%
If 'auto', builder will generate a small sh script %%% that makes it easier to start the system; If 'none', %%% no script will be generated. Default is 'none' on %%% non-UNIX systems and 'auto' on UNIX systems.
%%% %%%
{erl_opts, Opts : string()}
%%%
If specified, and if a sh_script is generated (see above), %%% Opts will be appended to the erl %%% command line. The builder will automatically put in options to %%% identify the boot script, sys.config, and possible boot variables.
%%% %%%
{config, Config : {file, filename()} | {M,F,A} | Data}
%%%
If present, this option should either point to a file that %%% contains a subset of data for a sys.config file (same format as %%% sys.config), or give a {M,F,A} tuple, where %%% apply(M,F,A) results in a list, %%% [{AppName, [{Key, Value}]}], or finally just specify %%% the data in place. The builder will look for similar config %%% directives in any BUILD_OPTIONS files of other applications that %%% are part of the build, and all inputs will be merged %%% into a common sys.config.
%%% %%%
{{config,OtherApp}, Config}
%%%
This is a way to specify environment variables for another %%% application in the build options. Config in this case %%% is the same as Config above. The difference between %%% this option and specifying environment variables for the other %%% application using the above option, is that with a simple %%% {config, ...} directive, builder will also check %%% for a similar directive in the other application, and all %%% different inputs will be merged. Using a {{config,App},...} %%% option, builder will not look into that application further.
%%% @end %%% ===================================================================== -module(builder). -author('mats.cronquist@etx.ericsson.se'). -author('ulf.wiger@ericsson.com'). -export([go/0, go/1, find_app/2]). -export([early_debug_on/0]). -compile(export_all). -include_lib("kernel/include/file.hrl"). -import(dict, [fetch/2]). -define(report(Level, Format, Args), case do_report(Level) of true -> io:format("[~p:~p] " ++ Format, [?MODULE,?LINE|Args]); false -> ok end). -define(f(D, Expr), fun(D) -> Expr end). -define(herewith(D), fun(D) -> ?report(debug, "herewith~n", []), D end). early_debug_on() -> put(builder_debug, true). go() -> go([]). go(Options) -> %% We use a dictionary for the options. The options are prioritized in %% the following order: (1) those given as arguments to the go/1 function, %% those in the BUILD_OPTIONS file, and (3) the hard-coded defaults. %% The options stored last in the dictionary take precedence, but since %% we want to allow for the Options list to guide us in finding the %% BUILD_OPTIONS file (under certain circumstances), we store the defaults %% and Options list in the dictionary, then locate and read the file, then %% store the Options list again. Dict0 = mk_dict(default_options() ++ Options), Dict = with(Dict0, [fun(D) -> read_options_file(D) end, ?herewith(D), % to help debugging fun(D) -> store_options(Options, D) end, ?herewith(D), fun(D) -> post_process_options(D) end, ?herewith(D), fun(D) -> {Path, [App, Vsn]} = get_app(D), store_options([{app_name, list_to_atom(App)}, {app_vsn, Vsn}], D) end, ?herewith(D), fun(D) -> [Descr, Id, Reg, Apps, Env, Mod, Phases] = read_app_file(D), Vsn = fetch(app_vsn, D), store_options([{app, [{vsn,Vsn}, Descr, Id, Reg, Apps, Env, Mod, Phases]}], D) end, ?herewith(D), fun(D) -> case dict:find(rel_name, D) of {ok,_} -> D; error -> RelName = atom_to_list( fetch(app_name, D)), dict:store(rel_name, RelName, D) end end, ?herewith(D), fun(D) -> RelFname = get_rel_filename(D), case file:consult(RelFname) of {ok, [{release,{_Name,_RelVsn}, _RelApps}= Rel]} -> ?report(debug,"rel_src = ~p~n",[Rel]), dict:store(rel_src,Rel, D); _ -> D end end, ?herewith(D), fun(D) -> AppInfo = find_apps(D), ?report(debug, "find_apps(D) -> ~p~n", [AppInfo]), dict:store(app_info, AppInfo, D) end, ?herewith(D), fun(D) -> BootVars = boot_vars(D), dict:store(boot_vars, BootVars, D) end]), case lists:member({ok,true}, [dict:find(make_boot,Dict), dict:find(make_rel,Dict), dict:find(make_config, Dict)]) of true -> verify_dir(fetch(out_dir,Dict)); false -> ok end, Dict1 = make_rel(Dict), make_app(Dict1), Res = make_boot(Dict1), make_config(Dict1), make_sh_script(Dict1), cleanup(Dict1), Res. find_app(App, Path) -> {Found,_} = expand_path(Path, [App], [], []), Found. post_process_options(Dict) -> D = with(Dict, [fun(D) -> case dict:find(out_dir, D) of {ok,_} -> D; error -> dict:store(out_dir, out_dir(D), D) end end, fun(D) -> case dict:find(rel_file, D) of {ok,_} -> dict:store(make_rel, false); error -> dict:store(make_rel, true, D) end end, fun(D) -> case dict:find(config, D) of {ok,_} -> dict:store(make_config, true, D); _ -> D end end, fun(D) -> case dict:find(make_boot, D) of {ok, _} -> D; error -> dict:store(make_boot, true, D) end end, fun(D) -> case dict:find(make_app, D) of {ok,_} -> D; error -> dict:store(make_app, auto, D) end end, fun(D) -> case dict:find(sh_script, D) of {ok, _} -> D; error -> case os:type() of {unix,_} -> dict:store(sh_script,auto,D); _ -> dict:store(sh_script,none,D) end end end, fun(D) -> case dict:find(systools, D) of {ok, _} -> D; error -> dict:store(systools, [], D) end end]), ?report(debug, "Options = ~p~n", [dict:to_list(D)]), D. with(Dict, Actions) -> lists:foldl(fun(F,D) -> F(D) end, Dict, Actions). cleanup(Dict) -> pop_report_level(). default_options() -> io:format("default app_dir is ~p~n",[cwd()]), [{app_dir, cwd()}, {skip, []}, {path, code:get_path()}]. read_options_file(Dict) -> case dict:find(build_options, Dict) of {ok, BuildOptsF} -> case script(BuildOptsF) of {error, empty_script} -> %% We accept this Dict; {ok, Terms} -> ?report(debug, "Stored terms (~s) =~n ~p~n", [BuildOptsF, Terms]), store_options(Terms, Dict); {error, Reason} -> exit({bad_options_file, {BuildOptsF, Reason}}) end; error -> Path = case dict:find(app_dir, Dict) of {ok, AppDir} -> [AppDir, filename:join(AppDir, "src")]; error -> [".", "src"] end, case path_script(Path, "BUILD_OPTIONS") of {ok, Terms, Fullname} -> Dict1 = store_options(Terms, Dict), ?report(debug, "Stored terms (~s) =~n ~p~n", [Fullname, Terms]), Dict1; {error, enoent} -> Dict; Error -> exit({build_options, Error}) end end. mk_dict(Options) -> %% pust a unique ref onto the dictionary. This will allow us to %% redefine e.g. the report_level (for the same instance of %% the dictionary, and stack the same (for new instances of the %% dictionary -- recursive build.) store_options([{'#ref#', make_ref()}|Options], dict:new()). store_options(Options, Dict) -> ?report(debug, "store_options(~p)~n", [Options]), lists:foldl( fun({report, Level}, D) -> push_report_level(Level, D), dict:store(report, Level, D); ({Key,Value}, D) -> dict:store(Key,Value,D) end, Dict, Options). make_rel(Dict) -> case dict:find(make_rel, Dict) of {ok,true} -> ?report(debug, "will make rel~n", []), App = atom_to_list(fetch(app_name, Dict)), Vsn = fetch_key(vsn, fetch(app, Dict)), Apps = fetch(app_info, Dict), AppInfo = [{A,V} || {A,V,F} <- Apps], ?report(debug,"AppInfo = ~p~n", [AppInfo]), {RelName, Rel} = case dict:find(rel_src, Dict) of %% mremond: Added this case clause: %% Without it you get an error if you define %% the erts version in your release file subset. %% The ERTS version is anyway replace by the %% system ERTS {ok, {release,{Name,RelVsn}, {erts, ErtsVsn}, RelApps}} -> ?report(debug,"found rel_src: RelApps=~p~n", [RelApps]), {Name, {release, {Name,RelVsn}, {erts, get_erts_vsn()}, AppInfo}}; {ok, {release,{Name,RelVsn},RelApps}} -> %% here we should check that Apps is a subset of %% RelApps; if so, use RelApps; otherwise exit ?report(debug,"found rel_src: RelApps=~p~n", [RelApps]), {Name, {release, {Name,RelVsn}, {erts, get_erts_vsn()}, AppInfo}}; error -> {App, {release, {App, Vsn}, {erts, get_erts_vsn()}, AppInfo}} end, RelFileTarget = filename:join(fetch(out_dir,Dict), RelName ++ ".rel"), out(RelFileTarget, Rel), store_options([{rel_name, RelName}, {rel_file, RelFileTarget}], Dict); _ -> ?report(debug, "will NOT make rel~n", []), Dict end. make_config(Dict) -> AppInfo = fetch(app_info, Dict), AppName = fetch(app_name, Dict), ForeignData = lists:foldl( fun({A,V,Ebin}, Acc) when A =/= AppName -> Acc ++ try_get_config(A,Ebin,Dict); (_, Acc) -> Acc end, [], AppInfo), Data = case dict:find(config, Dict) of {ok, Type} -> get_config_data(Type); error -> [] end, merge_config(Data ++ ForeignData, Dict). try_get_config(AppName, Ebin, Dict) -> case dict:find({config, AppName}, Dict) of {ok, Type} -> get_config_data(Type); error -> BuildOpts = filename:join( filename:join(filename:dirname(Ebin), "src"), "BUILD_OPTIONS"), case script(BuildOpts) of {error, enoent} -> []; {ok, Result} -> ?report(debug, "script(~p) ->~n ~p~n", [BuildOpts, Result]), case lists:keysearch(config, 1, Result) of {value, {_, Type}} -> get_config_data(Type); false -> [] end end end. get_config_data({file,F}) -> ?report(debug, "get_config_data({file, ~p})~n", [F]), case file:consult(F) of {ok, [Terms]} when list(Terms) -> Terms; {error, Reason} -> exit({config, {F, Reason}}) end; get_config_data({M,F,A}) -> ?report(debug, "get_config_data(~p)~n", [{M,F,A}]), apply(M,F,A); get_config_data(Data) when list(Data) -> Data. merge_config([], Dict) -> ok; merge_config(Data, Dict) -> ConfigF = sys_config(Dict), case file:consult(ConfigF) of {error, enoent} -> out(ConfigF, Data); {ok, [Terms]} -> ?report(debug, "merging terms (~p,~p), F = ~p~n", [Data,Terms, ConfigF]), NewData = merge_terms(Data, Terms), out(ConfigF, NewData) end. merge_terms([{App,Vars}|Data], Old) -> case lists:keysearch(App, 1, Old) of false -> merge_terms(Data, Old ++ [{App,Vars}]); {value, {_, OldVars}} -> ?report(debug, "merging vars (~p,~p) for ~p~n", [Vars,OldVars, App]), NewVars = merge_vars(Vars, OldVars, App), merge_terms(Data, lists:keyreplace(App,1,Old,{App,NewVars})) end; merge_terms([], Data) -> Data. merge_vars([{Key,Vals}|Data], Old, App) -> case lists:keysearch(Key, 1, Old) of {value, {_, OldVals}} when OldVals =/= Vals -> exit({config_conflict, {App, Key}}); {value, {_, OldVals}} when OldVals == Vals -> merge_vars(Data, Old, App); false -> merge_vars(Data, Old ++ [{Key,Vals}], App) end; merge_vars([], Data, _App) -> Data. fetch_key(Key, L) -> {value, {_, Value}} = lists:keysearch(Key, 1, L), Value. make_app(Dict) -> case dict:find(make_app, Dict) of {ok,true} -> do_make_app(Dict); {ok,auto} -> AppName = fetch(app_name, Dict), AppF = filename:join(ebin_dir(Dict), atom_to_list(AppName) ++ ".app"), case file:read_file_info(AppF) of {ok, _} -> ok; {error, enoent} -> do_make_app(AppF, AppName, Dict) end; _ -> ok end. do_make_app(Dict) -> AppName = fetch(app_name, Dict), AppF = filename:join(ebin_dir(Dict), atom_to_list(AppName) ++ ".app"), do_make_app(AppF, AppName, Dict). do_make_app(AppF, AppName, Dict) -> [Vsn, Descr, Id, Reg, Apps, Env, Mod, Phases] = fetch(app, Dict), Modules = modules(Dict), Remove = case {Mod, Phases} of {{_,[]},{_,undefined}} -> [Mod, Phases]; {{_,{_,_}},{_,undefined}} -> [Phases]; {{_,[]}, {_,_}} -> [Mod]; _ -> [] end, Options = [Vsn, Descr, Id, Modules, Reg, Apps, Env, Mod, Phases] -- Remove, out(AppF, {application, AppName, Options}). make_boot(Dict) -> case dict:find(make_boot, Dict) of {ok,true} -> ensure_rel_file(Dict), App = fetch(app_name, Dict), CWD = cwd(), ok = file:set_cwd(fetch(out_dir,Dict)), SystoolsOpts0 = merge_opts([{path, systools_path(Dict)}], fetch(systools, Dict)), SystoolsOpts = maybe_local(SystoolsOpts0, Dict), ?report(debug, "SystoolsOpts = ~p~n", [SystoolsOpts]), RelName = fetch(rel_name, Dict), Res = systools:make_script(RelName, SystoolsOpts), ?report(progress, "systools:make_script() -> ~p~n", [Res]), make_load_script(RelName), ok = file:set_cwd(CWD), case Res of error -> exit(error_in_make_script); ok -> ok end; _ -> ok end. maybe_local(Opts, Dict) -> case lists:keymember(variables, 1, Opts) of true -> Opts; false -> [local|Opts -- [local]] end. make_load_script(RelName) -> {ok, Bin} = file:read_file(RelName ++ ".boot"), {script,Id,Cmds} = binary_to_term(Bin), Keep = lists:filter( fun({apply,{application,start_boot,[kernel,Type]}}) -> true; ({apply,{application,start_boot,[stdlib,Type]}}) -> true; ({apply,{application,start_boot,[sasl,Type]}}) -> true; ({apply,{application,start_boot,[_,Type]}}) -> false; (_) -> true end, Cmds), NewScript = {script,Id,Keep}, file:write_file(RelName ++ "_load.boot", term_to_binary(NewScript)), out(RelName ++ "_load.script", NewScript). boot_vars(Dict) -> case dict:find(systools, Dict) of {ok, Opts} when Opts =/= [] -> ?report(debug, "systools opts = ~p~n", [Opts]), case lists:keysearch(variables, 1, Opts) of {value, {_, Vars}} -> Vars; false -> [] end; _ -> ?report(debug,"will make boot_vars~n", []), {ok,[[Root]]} = init:get_argument(root), Apps = dict:fetch(app_info, Dict), prefixes(Apps, [{"ROOT",Root}]) end. prefixes([{A,_,D}|Apps], Prefixes) -> case [P || {_,P} <- Prefixes, lists:prefix(P, D)] of [] -> prefixes(Apps, guess_root(D, Prefixes)); [_|_] -> prefixes(Apps, Prefixes) end; prefixes([], Prefixes) -> Prefixes. guess_root(D, Pfxs) -> case lists:reverse(filename:split(D)) of ["ebin",App,"lib",Dir|T] -> guess(to_upper(Dir), 0, filename:join(lists:reverse([Dir|T])), Pfxs); ["ebin",App,Dir|T] -> guess(to_upper(Dir), 0, filename:join(lists:reverse([Dir|T])), Pfxs) end. guess(Guess,N,RootDir, Pfxs) -> case lists:keymember(Guess,1,Pfxs) of false -> ?report(debug, "manufactured boot_var {~p,~p}~n", [Guess,RootDir]), [{Guess,RootDir}|Pfxs]; true -> ?report(debug, "duplicate Guess = ~p~n", [Guess]), guess(increment(Guess,N), N+1, RootDir, Pfxs) end. increment(Guess,0) -> Guess ++ "-1"; increment(Guess,N) when N < 10 -> [_,$-|T] = lists:reverse(Guess), lists:reverse(T) ++ [$-|integer_to_list(N+1)]. to_upper([H|T]) when H >= $a, H =< $z -> [$A+(H-$a)|to_upper(T)]; to_upper([H|T]) -> [H|T]; to_upper([]) -> []. make_sh_script(Dict) -> case dict:find(sh_script, Dict) of {ok, auto} -> OutDir = fetch(out_dir, Dict), RelName = fetch(rel_name, Dict), StartF = filename:join(OutDir, RelName ++ ".start"), LoadF = filename:join(OutDir, RelName ++ ".load"), do_make_sh_script(StartF,start,Dict), do_make_sh_script(LoadF,load,Dict); _ -> ok end. do_make_sh_script(ScriptF, Type, Dict) -> ok = file:write_file(ScriptF, list_to_binary( sh_script(ScriptF, Type,Dict))), {ok,FI} = file:read_file_info(ScriptF), Mode = FI#file_info.mode bor 8#00100, ok = file:write_file_info(ScriptF, FI#file_info{mode = Mode}). sh_script(Fname, Mode,Dict) -> OutDir = fetch(out_dir, Dict), AppDir = fetch(app_dir, Dict), BaseName = filename:basename(Fname), XOpts = case dict:find(erl_opts, Dict) of {ok, Opts} -> Opts; error -> [] end, OptionalSname = case re:run(XOpts, "-sname",[{capture,all_but_first,binary},dotall]) of {match,_,_} -> false; nomatch -> true end, ?report(debug, "OptionalSname (~p) = ~p~n", [XOpts, OptionalSname]), %% mremond: The path it need to be sure the script is predictible % Path = [" -pa ", filename:join(AppDir, "ebin")], RelName = fetch(rel_name, Dict), Boot = case Mode of start -> [" -boot ", filename:join(OutDir, RelName)]; load -> [" -boot ", filename:join(OutDir, RelName++"_load")] end, Sys = case dict:find(make_config, Dict) of {ok, true} -> [" -config ", filename:join(OutDir, "sys")]; _ -> [] end, BootVars = [[" -boot_var ", Var, " ", Dir] || {Var,Dir} <- dict:fetch(boot_vars, Dict)], % ["#/bin/sh\n" % "erl ", Boot, Sys, BootVars, XOpts, "\n"]. ["#!/bin/bash\n", "ERL=\"`which erl`\"\n", case OptionalSname of true -> "SNAME=\"\"\n"; false -> "" end, "\n" "while [ \$# -gt 0 ]\n" " do\n" " case \$1 in\n" " -erl)\n" " ERL=\"\$2\"\n" " shift\n" " ;;\n", case OptionalSname of true -> (" -sname)\n" " SNAME=\"\-sname $2\"\n" " shift\n" " ;;\n"); false -> "" end, " *)\n" " echo \"Faulty arguments: \$*\"\n" " echo \"usage: ", BaseName, ( [" [-erl ]", case OptionalSname of true -> " [-sname ]"; false -> "" end, "\"\n"]), " exit 1\n" " ;;\n" " esac\n" " shift\n" " done\n" "\n", case OptionalSname of true -> ["\$ERL \$SNAME", Boot, Sys, BootVars, opt_space(XOpts), "\n"]; false -> ["\$ERL", Boot, Sys, BootVars, opt_space(XOpts), "\n"] end ]. opt_space([]) -> []; opt_space([_|_] = L) -> [$\s|L]. ensure_rel_file(Dict) -> OutDir = fetch(out_dir, Dict), case dict:find(rel_file, Dict) of {ok, F} -> case filename:dirname(F) of D when D == OutDir -> ok; _ -> %% This doesn't necessarily mean that it's not there RelName = fetch(rel_name, Dict), {ok, [{release, {N,Vsn}, Erts, Apps}]} = file:consult(F), RelF = filename:join(OutDir, fetch(rel_name,Dict) ++ ".rel"), out(RelF, {release, {RelName,Vsn}, Erts, Apps}) end; error -> exit(no_rel_file) end. systools_path(Dict) -> AppInfo = fetch(app_info, Dict), [D || {_A,_V,D} <- AppInfo]. merge_opts(Opts, [{path,P}|Opts2]) -> exit({not_allowed, {systools, {path,P}}}); merge_opts(Opts, [Opt|Opts2]) -> [Opt|merge_opts(Opts, Opts2)]; merge_opts(Opts, []) -> Opts. cwd() -> {ok, CWD} = file:get_cwd(), CWD. out_dir(Dict) -> case dict:find(out_dir, Dict) of {ok, OutDir} -> OutDir; error -> filename:join(fetch(app_dir,Dict), "priv") end. ebin_dir(Dict) -> AppDir = fetch(app_dir, Dict), filename:join(AppDir, "ebin"). sys_config(Dict) -> case dict:find(sys_config, Dict) of {ok, Filename} -> Filename; error -> filename:join(fetch(out_dir,Dict), "sys.config") end. get_app(Dict) -> AppDir = fetch(app_dir, Dict), %% mremond: The program was crashing when the directory where not %% containing the application version %% Now we assume that version number is "BLDR" by default. case string:tokens(hd(lists:reverse(string:tokens(AppDir, "/"))), "-") of [App, Vsn] -> {AppDir, [App, Vsn]}; [App] -> case dict:find(app_vsn, Dict) of {ok, Vsn} -> {AppDir, [App, Vsn]}; error -> {AppDir, [App, "BLDR"]} end end. get_app_filename(App, Dict) -> AppDir = fetch(app_dir, Dict), filename:join([AppDir, "src", App++".app.src"]). get_rel_filename(Dict) -> App = atom_to_list(dict:fetch(app_name, Dict)), AppDir = fetch(app_dir, Dict), filename:join([AppDir, "src", App++".rel.src"]). modules(Dict) -> Ebin = ebin_dir(Dict), Ext = code:objfile_extension(), ?report(debug, "looking for modules (~p) in ~p~n", [Ext,Ebin]), L = recursive_list_dir(Ebin), ?report(debug, "L = ~p~n", [L]), Skip = fetch(skip, Dict), ?report(debug, "Skip = ~p~n", [Skip]), Modules = lists:foldr( fun(F, Acc) -> case make_mod(F) of {ok, M} -> [M|Acc]; error -> Acc end end, [], [F || F <- L, check_f(F, Ext)]), {modules, Modules -- Skip}. recursive_list_dir(Dir) -> ?report(debug, "recursive_list_dir(~p)~n", [Dir]), {ok, Fs} = file:list_dir(Dir), lists:concat([f_expand(F,Dir) || F <- Fs]). f_expand(Name,Dir) -> Fname = filename:join(Dir,Name), case file:read_file_info(Fname) of {ok, #file_info{type=regular}} -> [Fname]; {ok, #file_info{type=directory}} -> ?report(debug, "~p is a directory under ~p~n", [Fname, Dir]), {ok, Fs} = file:list_dir(Fname), lists:concat([f_expand(F,Fname) || F <- Fs]); {ok, Finfo} -> ?report(debug, "~p not a directory or a regular file. Ignoring~n", [Fname]), []; {error, Reason} -> ?report(error, "*** read_file_info(~p) -> {error, ~p}~n", [Fname, Reason]), [] end. make_mod(F) -> case beam_lib:version(F) of {ok, {Module, Vsn}} -> {ok, Module}; _ -> error end. % {ok,Fd} = file:open(F, [read]), % parse_for_mod(Fd, F). % {ok,Bin} = file:read_file(F), % Text = binary_to_list(Bin), % {match,Start,Len} = % regexp:first_match(Text, % "^[ ]*-[ ]*module[ ]*[(][ ]*.*[ ]*[)][ ]*\."), % Name = case string:substr(Text, Start+8,Len-10) of % "'" ++ Quoted -> % "'" ++ Rest = lists:reverse(Quoted), % list_to_atom(lists:reverse(Rest)); % Str -> % list_to_atom(Str) % end. parse_for_mod(Fd, F) -> parse_for_mod(Fd, io:parse_erl_exprs(Fd,''), F). parse_for_mod(Fd, {ok,[{op,_,'-',{call,_,{atom,_,module},[Mod]}}],_}, F) -> {ok,parse_mod_expr(Mod)}; % parse_for_mod(Fd, {ok,[{op,_,'-', % {call,_,{atom,_,module},[{atom,_,Mod}]}}],_}, F) -> % {ok,Mod}; parse_for_mod(Fd, {ok,_,_}, F) -> parse_for_mod(Fd, io:parse_erl_exprs(Fd,''), F); parse_for_mod(Fd, {error,_,_}=Error, F) -> io:format("*** Error parsing ~s: ~p~n", [F, Error]), error; parse_for_mod(Fd, {eof,_}, F) -> io:format("*** No module attribute found in ~s~n", [F]), error. parse_mod_expr(Expr) -> list_to_atom(parse_mod_expr1(Expr)). parse_mod_expr1({atom,_,A}) -> atom_to_list(A); parse_mod_expr1({record_field,_,R,{atom,_,A}}) -> parse_mod_expr1(R) ++ "." ++ atom_to_list(A). check_f(F, Ext) -> case re:run(F, ".*"++Ext++"\$",[{capture,all_but_first,binary},dotall]) of {match, _, _} -> true; _ -> false end. find_apps(Dict) -> App = fetch(app, Dict), {value, {_, NeededApps}} = lists:keysearch(applications, 1, App), AppInfo = case dict:find(apps, Dict) of {ok, As} -> app_info(As, Dict); error -> [] end, RelApps = case dict:find(rel_src, Dict) of {ok, {release, _, RA}} -> RA; error -> [] end, AppName = dict:fetch(app_name, Dict), Apps = merge_apps(RelApps, NeededApps, AppInfo) -- [AppName], ?report(debug, "Apps = ~p~n", [Apps]), KnownApps = [{A,V,D} || {A,V,D} <- AppInfo, V =/= unknown, D =/= unknown], search_apps(Apps, Dict, AppInfo, KnownApps). merge_apps(RelApps, AppApps, AppInfo) -> ?report(debug, "merge_apps(~p, ~p, ~p)~n", [RelApps, AppApps, AppInfo]), Acc0 = lists:map(fun({App,Vsn,Incl}) -> App; ({App,Vsn}) -> App; (App) when atom(App) -> App end, RelApps), with(Acc0, [fun(Acc) -> Acc ++ [A || {A,_,_} <- AppInfo, not(lists:member(A, Acc))] end, fun(Acc) -> Acc ++ [A || A <- AppApps, not(lists:member(A, Acc))] end, fun(Acc) -> InclApps = lists:foldl( fun({_,_,Incls}, AccX) -> AccX ++ Incls; (_, AccX) -> AccX end, [], RelApps), Acc ++ [A || A <- InclApps, not(lists:member(A, Acc))] end]). app_info(Apps, Dict) -> MyName = dict:fetch(app_name, Dict), MyVsn = dict:fetch(app_vsn, Dict), MyEbin = ebin_dir(Dict), Info = lists:map( fun(A) when atom(A) -> {A,unknown,unknown}; ({A,V}) -> {A,V,unknown}; ({A,V,D}) -> {A,V,D} end, Apps), AppName = dict:fetch(app_name, Dict), case lists:keysearch(AppName, 1, Info) of {value, {Ai,Vi,Di}} -> [Version,Dir] = lists:foldr( fun({unknown,Vx}, Acc) -> [Vx|Acc]; ({Vx,Vx}, Acc) -> [Vx|Acc]; ({V1,V2},_) -> exit({conflicting_info_in_apps, {{Ai,Vi,Di}, {MyName,MyVsn,MyEbin}}}) end, [], [{Vi,MyVsn},{Di,MyEbin}]), lists:keyreplace(AppName, 1, Info, {AppName, Version, Dir}); false -> Info ++ [{AppName, dict:fetch(app_vsn, Dict), ebin_dir(Dict)}] end. search_apps(Apps, Dict, AppInfo, InitAcc) -> ?report(debug,"search_apps(~p,Dict,~p,~p)~n", [Apps,AppInfo,InitAcc]), Path = fetch(path, Dict), Ebin = ebin_dir(Dict), Path1 = Path ++ [Ebin], MyAppName = dict:fetch(app_name, Dict), MyApp = {MyAppName, dict:fetch(app_vsn,Dict), ebin_dir(Dict)}, ?report(debug, "Search Path = ~p~n", [Path1]), Found = case expand_path(Path ++ [Ebin], Apps, AppInfo, InitAcc) of {FoundInfo, []} -> ?report(debug, "LeftApps = [], FoundInfo = ~p~n", [FoundInfo]), check_found(FoundInfo, Dict); {FoundInfo, LeftApps} -> %% LeftApps may still include applications for which we've %% found versions (but didn't know if they were the right ones) ?report(debug, "LeftApps = ~p,~nFoundInfo = ~p~n", [LeftApps, FoundInfo]), case [A || A <- LeftApps, not(lists:keymember(A, 1, FoundInfo))] of [] -> check_found(FoundInfo, Dict); NotFound -> exit({not_found, NotFound}) end end, replaceadd(MyAppName,1,Found,MyApp). check_found(FoundInfo, Dict) -> case lists:foldr( fun({A,unknown,D}, {Found,Left}) -> AppF = filename:join(D,atom_to_list(A) ++ ".app"), case vsn_from_app_file(AppF) of unknown -> {Found,[{A,unknown,D}|Left]}; FoundVsn -> {[{A,FoundVsn,D}|Found], Left} end; (GoodApp, {Found,Left}) -> {[GoodApp|Found], Left} end, {[],[]}, FoundInfo) of {Found, []} -> ?report(debug, "Found apps:~n ~p~n", [FoundInfo]), Found; {Found, UnknownVsns} -> %% These app directories didn't have a version %% suffix. This is allowed, but we must then %% extract the version some other way.... FFS ?report(debug, "Should find the versions of" " these apps:~n ~p~n", [UnknownVsns]), Found end. replaceadd(Key,Pos,[H|T],Obj) when element(Pos,H) == Key -> [Obj|T]; replaceadd(Key,Pos,[H|T],Obj) -> [H|replaceadd(Key,Pos,T,Obj)]; replaceadd(Key,Pos,[],Obj) -> [Obj]. vsn_from_app_file(AppF) -> case file:consult(AppF) of {ok, [{application,_,Opts}]} -> case lists:keysearch(vsn,1,Opts) of {value,{_,Vsn}} -> Vsn; false -> unknown end; Other -> ?report(debug, "file:consult(~p) -> ~p~n", [AppF,Other]), unknown end. %%% expand_path/2 takes a list of path expressions, where each expression %%% may contain wildcards (sh expressions) expand_path([Dir|Ds], Apps, AppInfo, Acc) -> ?report(debug, "Dir = ~p~n", [Dir]), SplitDir = filename:split(Dir), case file:read_file_info(Dir) of {ok, #file_info{type = directory}} -> case lists:reverse(SplitDir) of ["ebin",AppDir|_] -> case string:tokens(AppDir, "-") of [App,Vsn] -> ?report(debug, "App = ~p, Vsn = ~p~n", [App,Vsn]), AppA = list_to_atom(App), {Acc1,Apps1} = case lists:member(AppA, Apps) of true -> maybe_save_app(AppA, Vsn, Dir, Apps, AppInfo, Acc); false -> {Acc, Apps} end, expand_path(Ds, Apps1, AppInfo, Acc1); [App] -> %% App dir without version suffix -- legal, %% but we must extract the version somehow AppA = list_to_atom(App), AppF = filename:join(Dir, App ++ ".app"), Vsn = vsn_from_app_file(AppF), case lists:member(AppA, Apps) of true -> {Acc1,Apps1} = maybe_save_app(AppA, Vsn, Dir, Apps, AppInfo, Acc), expand_path(Ds, Apps1, AppInfo, Acc1); false -> expand_path(Ds, Apps, AppInfo, Acc) end; Other -> ?report(debug, "*** strange app dir ~p~n", [AppDir]), expand_path(Ds, Apps, AppInfo, Acc) end; _ -> expand_path(Ds, Apps, AppInfo, Acc) end; {error, enoent} -> %% assume it is a regexp {Acc1, RestApps} = expand_path( d_expand(SplitDir), Apps, AppInfo, Acc), expand_path(Ds, RestApps, AppInfo, Acc1); _ -> %% something else -- ignore expand_path(Ds, Apps, AppInfo, Acc) end; expand_path([], Apps, AppInfo, Acc) -> {Acc, Apps}. maybe_save_app(AppA, Vsn, Dir, Apps, AppInfo, Acc) -> ?report(debug, ("maybe_save_app(~p,~p,~p,_,~n" " ~p,~n" " ~p)~n"), [AppA,Vsn,Dir,AppInfo, Acc]), case lists:keysearch(AppA, 1, Acc) of {value, {_,unknown,unknown}} -> {lists:keyreplace( AppA,1,Acc,{AppA,Vsn,Dir}), Apps}; {value, {_,Vsn,_}} -> %% We already have this version {Acc, Apps}; Result -> case lists:keysearch(AppA, 1, AppInfo) of {value, {_,unknown,_}} -> case Result of {value, {_,OtherVsn,_}} -> case later_vsn(Vsn, OtherVsn) of true -> {lists:keyreplace( AppA,1,Acc,{AppA,Vsn,Dir}), Apps}; false -> {Acc, Apps} end; false -> {[{AppA,Vsn,Dir}|Acc], Apps} end; {value, {_, Vsn, unknown}} -> %% This is the version we want. Look no further for %% this app. {[{AppA,Vsn,Dir}|Acc], Apps -- [AppA]}; {value, {_, OtherVsn, _}} -> %% This is not the version we want {Acc, Apps}; false -> %% We have no prior knowledge of this app, other than %% that it should be included case Result of {value, {_,OtherVsn,_}} -> case later_vsn(Vsn, OtherVsn) of true -> {[{AppA,Vsn,Dir}|Acc], Apps}; false -> {Acc, Apps} end; false -> {[{AppA,Vsn,Dir}|Acc], Apps} end end end. %%% later_vsn(VsnA, VsnB) -> true | false. %%% true if VsnA > VsnB, false otherwise later_vsn(unknown,_) -> false; later_vsn(_,unknown) -> true; later_vsn(Vsn, Vsn) -> false; later_vsn(VsnA, VsnB) -> case get_newest([{VsnB,[]},{VsnA,[]}]) of {VsnB,_} -> false; {VsnA,[]} -> true end. %%% get_newest([{Vsn,Info}]) -> {NewestVsn,RelatedInfo}. %%% get_newest([{Vsn,_}] =X) -> X; get_newest([{Vsn1,_}=X1, {Vsn2,_}=X2 | Rest]) -> V1 = split(Vsn1), V2 = split(Vsn2), Newest = get_newest1(V1, V2, X1, X2), get_newest([Newest | Rest]). get_newest1([H1|T1], [H2|T2], X1, X2) -> if H1 > H2 -> X1; H2 > H1 -> X2; H1 == H2 -> get_newest1(T1, T2, X1, X2) end; get_newest1([], [], X1, X2) -> X1; get_newest1([H1|_], [], X1, X2) -> X1; get_newest1([], [H2|_], X1, X2) -> X2. split(VStr) -> split(VStr, undefined, [], []). split([], Mode, Acc1, Acc) -> lists:reverse([subv(Mode, lists:reverse(Acc1)) | Acc]); split("." ++ T, Mode, Acc1, Acc) -> split(T, Mode, [], [subv(Mode, lists:reverse(Acc1)) | Acc]); split([I|T], string, Acc1, Acc) when $0 =< I, I =< $9 -> split(T, integer, [I], [subv(string, lists:reverse(Acc1)) | Acc]); split([I|T], integer, Acc1, Acc) when $0 =< I, I =< $9 -> split(T, integer, [I|Acc1], Acc); split([C|T], integer, Acc1, Acc) -> split(T, string, [C], [subv(integer, lists:reverse(Acc1)) | Acc]); split([I|T], undefined, [], Acc) when $0 =< I, I =< $9 -> split(T, integer, [I], Acc); split([C|T], undefined, [], Acc) -> split(T, string, [C], Acc). subv(integer, SubV) -> list_to_integer(SubV); subv(string, SubV) -> SubV. %%% end later_vsn() member_of(AppName, [AppName|As]) -> {true, AppName}; member_of(AppName, [{AppName,Vsn}|As]) -> {true, {AppName, Vsn}}; member_of(AppName, [_|As]) -> member_of(AppName, As); member_of(_, []) -> false. d_expand(["/"|Ds]) -> d_expand(Ds, ["/"]); d_expand(Ds) -> {ok,Cwd} = file:get_cwd(), d_expand(Ds, [Cwd]). d_expand([D|Ds], Cur) -> %% Pat = re:sh_to_awk(D), Pat = D, Cur1 = lists:foldl( fun(C, Acc) -> case list_dir(C) of [] -> Acc; Fs -> Matches = [F || F <- Fs, matches(F, Pat)], Acc ++ [filename:join(C,F) || F <- Matches, is_dir(C,F)] end end, [], Cur), d_expand(Ds, Cur1); d_expand([], Cur) -> Cur. matches(Str, Pat) -> case re:run(Str,Pat,[{capture,all_but_first,binary},dotall]) of {match,_,_} -> true; nomatch -> false end. is_dir(Parent, Dir) -> case file:read_file_info(filename:join(Parent, Dir)) of {ok, #file_info{type=directory}} -> true; _ -> false end. list_dir(Dir) -> case file:read_file_info(Dir) of {ok, #file_info{type=directory}} -> case file:list_dir(Dir) of {ok, Fs} -> Fs; _ -> [] end; _ -> [] end. apps(Apps, Dict) -> Path = fetch(path, Dict), Pat = [filename:basename(filename:dirname(Dir))||Dir<-get_path(Dict)], Vss = [string:tokens(D,"-") || D <- Pat, app(D)], do_apps(Apps, Vss). app(D) -> case re:run(D,".*-[0-9].*",[{capture,all_but_first,binary},dotall]) of nomatch -> false; _ -> true end. do_apps([], Vss) -> []; do_apps([App|Apps], Vss) when list(App) -> do_apps([list_to_atom(App)|Apps], Vss); do_apps([App|Apps], Vss) when atom(App) -> [{App, get_vsn(atom_to_list(App), Vss)}|do_apps(Apps, Vss)]. get_vsn(App, [[App, Vsn]|_]) -> Vsn; get_vsn(App, [_|T]) -> get_vsn(App, T). get_erts_vsn() -> {ok, [[Root]]} = init:get_argument(root), {ok, Fs} = file:list_dir(Root), get_erts_vsn(Fs). get_erts_vsn([[$e,$r,$t,$s,$-|Vsn]|_]) -> Vsn; get_erts_vsn([_|T]) -> get_erts_vsn(T). out(Dir, App, Ext, Term) -> FN = filename:join(Dir, App++Ext), out(FN, Term). out(FN, Term) -> ?report(debug, "out(~p,~p)~n", [FN,Term]), case file:open(FN, [write]) of {ok, FD} -> io:fwrite(FD, "~p.~n", [Term]), file:close(FD); {error, R} -> exit({bad_filename, {FN, R}}) end. read_app_file(Dict) -> AppName = fetch(app_name, Dict), AppNameS = atom_to_list(AppName), AppF = get_app_filename(AppNameS, Dict), App = case file:consult(AppF) of {ok, [{application, _Name, Options}]} -> app_options(Options); {ok, [Options]} when list(Options) -> app_options(Options); {error, enoent} -> RealAppF = filename:join(ebin_dir(Dict), AppNameS ++ ".app"), case file:consult(RealAppF) of {ok, [{application, _Name, Options}]} -> app_options(Options); {error, enoent} -> ?report(debug, "no app file (~p) -- must generate one.~n", [AppName]), Options = app_options( [{description, "auto-generated app file for " ++ AppNameS}, {applications, [kernel,stdlib]}]) end; Other -> exit({error_reading_app_file, {AppF, Other}}) end, ?report(debug, "App = ~p~n", [App]), App. app_options(Opts) -> [opt(description,Opts,""), opt(id,Opts,""), opt(registered,Opts,[]), opt(applications,Opts,[kernel,stdlib]), opt(env,Opts,[]), opt(mod,Opts,[]), opt(start_phases,Opts,undefined)]. opt(Key,List,Default) -> case lists:keysearch(Key,1,List) of {value, {_, Value}} -> {Key, Value}; false -> {Key, Default} end. get_path(Dict) -> AppPath = ebin_dir(Dict), Path = code:get_path(), case lists:member(AppPath, Path) of true -> Path; false -> Path ++ [AppPath] end. % otp_path(Dict) -> % case dict:find(otp_path, Dict) of % {ok, Path} -> % Path; % error -> % {ok,[[Root]]} = init:get_argument(root), % filename:join([Root,"lib","*","ebin"]) % end. script(File) -> script(File, erl_eval:new_bindings()). script(File, Bs) -> case file:open(File, [read]) of {ok, Fd} -> Bs1 = erl_eval:add_binding('ScriptName',File,Bs), R = case eval_stream(Fd, Bs1) of {ok, Result} -> {ok, Result}; Error -> Error end, file:close(Fd), R; Error -> Error end. path_script(Path, File) -> path_script(Path, File, erl_eval:new_bindings()). path_script(Path, File, Bs) -> case file:path_open(Path, File, [read]) of {ok,Fd,Full} -> Bs1 = erl_eval:add_binding('ScriptName',Full,Bs), case eval_stream(Fd, Bs1) of {error,E} -> file:close(Fd), {error, {E, Full}}; {ok, R} -> file:close(Fd), {ok,R,Full} end; Error -> Error end. eval_stream(Fd, Bs) -> eval_stream(Fd, undefined, Bs). eval_stream(Fd, Last, Bs) -> eval_stream(io:parse_erl_exprs(Fd, ''), Fd, Last, Bs). eval_stream({ok,Form,EndLine}, Fd, Last, Bs0) -> case catch erl_eval:exprs(Form, Bs0) of {value,V,Bs} -> eval_stream(Fd, {V}, Bs); {'EXIT',Reason} -> {error, Reason} end; eval_stream({error,What,EndLine}, Fd, L, Bs) -> {error, {parse_error, {What,EndLine}}}; eval_stream({eof,EndLine}, Fd, Last, Bs) -> case Last of {Val} -> {ok, Val}; undefined -> %% empty script {error, empty_script} end. do_report(error) -> %% always report errors true; do_report(Level) -> LevelN = rpt_level(Level), case get(builder_debug) of true -> true; false -> false; undefined -> case get(builder_report_level) of undefined -> false; [{AtLevel,_}|_] when AtLevel >= LevelN -> true; _ -> false end end. rpt_level(none) -> 0; rpt_level(progress) -> 1; rpt_level(verbose) -> 2; rpt_level(debug) -> 3; rpt_level(N) when integer(N), N >= 0 -> N. push_report_level(Level, Dict) -> Ref = fetch('#ref#', Dict), N = rpt_level(Level), case get(builder_report_level) of undefined -> put(builder_report_level, [{N,Ref}]), io:format("pushed 1st report level ~p(~p)~n",[Level,N]); [{Prev,Ref}|Tail] -> put(builder_report_level, [{N,Ref}|Tail]), io:format("redefined report level ~p(~p)~n",[Level,N]); [_|_] = Levels -> put(builder_report_level, [{N,Ref}|Levels]), io:format("pushed next report level ~p(~p)~n",[Level,N]) end. pop_report_level() -> Level = get(builder_report_level), case Level of [_] -> erase(builder_report_level); [_|Tail] -> put(builder_report_level, Tail); undefined -> ok end. verify_dir(Dir) -> case file:read_file_info(Dir) of {ok, FI} when FI#file_info.type == directory, FI#file_info.access == read_write -> ok; {ok, FI} when FI#file_info.type == directory -> exit({access, Dir}); {error, enoent} -> try_create(Dir) end. try_create("/") -> exit({create, "/"}); try_create(".") -> exit({create, "/"}); try_create(Dir) -> case file:make_dir(Dir) of {error, Reason} -> try_create(filename:dirname(Dir)), try_create(Dir); ok -> ok end. tsung-1.4.2/CONTRIBUTORS0000644000201100017670000000266111701017117014224 0ustar nniclausdream$Id$ AUTHOR: ====================== o Nicolas Niclausse : Maintainer; CONTRIBUTORS: ====================== o Jean Franois Lecomte : several enhancements for Jabber o Mickal Rmond : erlang server monitoring; various patches for HTTP; configure support; SOAP support; initial dynamic substitution implementation. o Jrome Sautret: Multiple file patch for file_server, costum header for HTTP, bug reports o Jason Tucker: Solaris testing and fixes, jabber patches (sip_digest, roster and presence enhancements, bidi support for presence:subscribe ). o Pablo Polvorin: LDAP plugin, set_dynvars, xpath search for html, for/repeat loop, dynvars_api, hibernate. PubSub and MUC support. o Gregoire Reboul: MySQL plugin. o Dimitri Fontaine: SNMP & postgresql testing, patch for snmp, tsung-plotter o Oleg Nitz: Fix for Cookies over https, fix rewrite of POST (http recorder) o David Jez: allow substitutions in match o Will Brant: load info for monitoring, fix for tsplot o Jonathan Bresler: Jabber testing and bug reporting o Gordon Guthrie: tips for ssh setup on Suse o Romain Lenglet: Suggestions for ts_os_mon o Johann Messner: Bug reports o Anders Nygren: Documentation updates/suggestions, fix for recorder o Adam Spotton: Bug reports and tests (status, HTTP proxy load testing) o Matthew Schulkind: small fix to freemem computation o t ty: plugin tutorial o Jesper Wilhelmsson: testing tsung-1.4.2/tsung.sh.in0000755000201100017670000001401211701017117014441 0ustar nniclausdream#!/usr/bin/env bash UNAME=`uname` case $UNAME in "Linux") HOST=`hostname -s 2>/dev/null` RET=$? if [ $RET != 0 ]; then HOST=`hostname` echo "WARN: hostname -s failed, use '$HOST' as hostname" > /dev/stderr fi ;; "SunOS") HOST=`hostname`;; *) HOST=`hostname -s`;; esac INSTALL_DIR=@EXPANDED_LIBDIR@/erlang/ ERL=@ERL@ MAIN_DIR=$HOME/.tsung LOG_DIR=$MAIN_DIR/log LOG_OPT="log_dir \"$LOG_DIR/\"" MON_FILE="mon_file \"tsung.log\"" VERSION=@PACKAGE_VERSION@ NAMETYPE="-sname" PROTO_DIST=" -proto_dist inet_tcp " LISTEN_PORT=8090 USE_PARENT_PROXY=false PGSQL_SERVER_IP=127.0.0.1 PGSQL_SERVER_PORT=5432 NAME=tsung CONTROLLER=tsung_controller SMP_DISABLE=true WARM_TIME=10 MAX_PROCESS=250000 TSUNGPATH=$INSTALL_DIR/lib/tsung-$VERSION/ebin CONTROLLERPATH=$INSTALL_DIR/lib/tsung_controller-$VERSION/ebin CONF_OPT_FILE="$HOME/.tsung/tsung.xml" BOOT_OPT="-boot $INSTALL_DIR/lib/tsung_controller-$VERSION/priv/tsung_controller -boot_var TSUNGPATH $INSTALL_DIR " DEBUG_LEVEL=5 ERL_RSH=" -rsh ssh " ERL_DIST_PORTS=" -kernel inet_dist_listen_min 64000 -kernel inet_dist_listen_max 65500 " ERL_OPTS=" $ERL_DIST_PORTS -smp auto +P $MAX_PROCESS +A 16 +K true @ERL_OPTS@ " COOKIE='tsung' ERTS_RUN=`$ERL -version 2>&1 | tr -cd 0123456789.` ERTS_BOOT=`grep erts $TSUNGPATH/../priv/tsung.rel 2> /dev/null| tr -cd 0123456789.` stop() { $ERL $ERL_OPTS $ERL_RSH -noshell $PROTO_DIST $NAMETYPE killer -setcookie $COOKIE -pa $TSUNGPATH -pa $CONTROLLERPATH -s tsung_controller stop_all $HOST -s init stop } checkversion() { if [ $ERTS_RUN != $ERTS_BOOT ] then echo "Erlang version has changed ! [$ERTS_BOOT] != [$ERTS_RUN]" echo "Must create new boot files (you may have to run this one time as root ! )" makebootfiles fi } makebootfiles() { cd $TSUNGPATH/.. echo "creating boot file for tsung application" $ERL $ERL_OPTS -noshell -pa $TSUNGPATH -s builder go -s init stop > /dev/null cd $CONTROLLERPATH/.. echo "creating boot file for tsung_controller application" $ERL $ERL_OPTS -noshell -pa $TSUNGPATH -s builder go -s init stop > /dev/null } start() { echo "Starting Tsung" $ERL $ERL_OPTS $ERL_RSH -noshell $PROTO_DIST $NAMETYPE $CONTROLLER -setcookie $COOKIE $BOOT_OPT \ -pa $TSUNGPATH -pa $CONTROLLERPATH \ -tsung_controller smp_disable $SMP_DISABLE \ -tsung_controller debug_level $DEBUG_LEVEL \ -tsung_controller warm_time $WARM_TIME \ -tsung_controller config_file \"$CONF_OPT_FILE\" -tsung_controller $LOG_OPT -tsung_controller $MON_FILE } debug() { $ERL $ERL_OPTS $ERL_RSH $NAMETYPE $PROTO_DIST $CONTROLLER -setcookie $COOKIE $BOOT_OPT \ -pa $TSUNGPATH -pa $CONTROLLERPATH \ -tsung_controller config_file \"$CONF_OPT_FILE\" \ -tsung_controller $LOG_OPT -tsung_controller $MON_FILE } version() { echo "Tsung version $VERSION" exit 0 } checkconfig() { if [ ! -e $CONF_OPT_FILE ] && [ $CONF_OPT_FILE != "-" ] then echo "Config file $CONF_OPT_FILE doesn't exist, aborting !" exit 1 fi } maindir() { if [ ! -d $MAIN_DIR ] then echo "Creating local Tsung directory $MAIN_DIR" mkdir $MAIN_DIR fi } logdir() { if [ ! -d $LOG_DIR ] then echo "Creating Tsung log directory $LOG_DIR" mkdir $LOG_DIR fi } status() { SNAME=tsung_status_$RANDOM $ERL -noshell $NAMETYPE $SNAME -setcookie $COOKIE -pa $TSUNGPATH -pa $CONTROLLERPATH -s tsung_controller status $HOST -s init stop } checkrunning_controller() { RES=`status` if [ "$RES" != "Tsung is not started" ]; then echo "Tsung is already running, exit." exit 1 fi } usage() { prog=`basename $0` echo "Usage: $prog start|stop|debug|status" echo "Options:" echo " -f set configuration file (default is ~/.tsung/tsung.xml)" echo " (use - for standard input)" echo " -l set log directory (default is ~/.tsung/log/YYYYMMDD-HHMM/)" echo " -i set controller id (default is empty)" echo " -r set remote connector (default is ssh)" echo " -s enable erlang smp on client nodes" echo " -p set maximum erlang processes per vm (default is 250000)" echo " -m write monitoring output on this file (default is tsung.log)" echo " (use - for standard output)" echo " -F use long names (FQDN) for erlang nodes" echo " -w warmup delay (default is 10 sec)" echo " -v print version information and exit" echo " -6 use IPv6 for Tsung internal communications" echo " -h display this help and exit" exit } while getopts "6vhf:l:d:r:i:Fsw:m:p:" Option do case $Option in f) CONF_OPT_FILE=$OPTARG;; l) # must add absolute path echo "$OPTARG" | grep -q "^/" RES=$? if [ "$RES" == 0 ]; then LOG_OPT="log_dir \"$OPTARG/\" " else LOG_OPT="log_dir \"$PWD/$OPTARG/\" " fi ;; m) MON_FILE="mon_file \"$OPTARG\"";; d) DEBUG_LEVEL=$OPTARG;; p) MAX_PROCESS=$OPTARG;; r) ERL_RSH=" -rsh $OPTARG ";; 6) PROTO_DIST=" -proto_dist inet6_tcp ";; F) NAMETYPE="-name";; w) WARM_TIME=$OPTARG;; s) SMP_DISABLE="false";; v) version;; i) ID=$OPTARG COOKIE=$COOKIE"_"$ID CONTROLLER=$CONTROLLER"_"$ID ;; h) usage;; *) usage ;; esac done shift $(($OPTIND - 1)) case $1 in start) checkconfig checkversion maindir logdir start ;; boot) checkversion ;; debug) checkconfig checkversion maindir logdir debug ;; stop) stop ;; status) status ;; *) usage $0 ;; esac tsung-1.4.2/README0000644000201100017670000000137311701017117013223 0ustar nniclausdream# $Id$ tsung README 1. Introduction 1.1. General This document gives pointers for information on this package which is distributed under the GNU General Public License version 2 (see file COPYING). 1.2. What This Package Is Tsung is multi-protocol distributed load testing tool. It can be used to test the scalability and performances of IP based client/server applications (supported protocols: HTTP, WebDAV, SOAP, PostgreSQL, MySQL, LDAP and Jabber/XMPP) A User's manual is available : http://tsung.erlang-projects.org/user_manual.html 1.3. Problems/Bugs Join the mailing-list: https://lists.process-one.net/mailman/listinfo/tsung-users or use the tracker https://support.process-one.net/browse/TSUN tsung-1.4.2/ebin/0000755000201100017670000000000011701017143013253 5ustar nniclausdreamtsung-1.4.2/CHANGES0000644000201100017670000005473211701017117013345 0ustar nniclausdream1.4.1 -> 1.4.2 Minor enhancements and bugfixes (4 Jan 2012) Bugfix: * [TSUN-199] - computation of NUsers is wrong * [TSUN-206] - build failure with erlang R15B Improvements: * [TSUN-202] - IPv6 support * [TSUN-203] - snmp oids should be customizable in the config file * [TSUN-205] - handle dyn_variables as array in test conditions (if/until/while) New Features: * [TSUN-191] - allow outputting log to stdout * [TSUN-192] - structured log output (JSON) * [TSUN-193] - accept configuration from stdin * [TSUN-197] - Have bug and error message on stderr and not stdout. 1.4.0 -> 1.4.1 Minor bugfixes (13 Sep 2011) Bugfix: * [TSUN-188] - munin plugin is not working in 1.4.0 * [TSUN-189] - the controller VM is not used in some case * [TSUN-190] - pgsql recorder can record a connect request in an already connected session 1.3.3 -> 1.4.0 Major enhancements and bugfixes (5 Sep 2011) Bugfix: * [TSUN-129] - regexp (defined in match or dynvars) can fail when chunk encoding is used. * [TSUN-150] - Munin monitoring broken by cpu stats config request * [TSUN-163] - Tsung doesn't detect subdomains. * [TSUN-166] - snmp monitoring does not work with erlang R14A * [TSUN-171] - maxnumber set in a phase is not always enforced * [TSUN-172] - auth sasl can't authenticate against ejabberd * [TSUN-178] - some characters can make url and headers rewriting fail in the recorder * [TSUN-179] - tsung generated message stanzas are not XMPP compliant * [TSUN-180] - file server crash if a dynamic substitution use it * [TSUN-182] - When many clients are configured with few static users, none of them are launched. * [TSUN-183] - tsung can stop too soon in some cases * [TSUN-184] - 'random_number' with start and end actually returns a number from start+1 to end * [TSUN-187] - Client IP scan is very slow on Linux; also uses obsolete "ifconfig" Improvements: * [TSUN-54] - tsung is very slow when a lot of dynamic variables are set * [TSUN-96] - generating more than 64k connections from a single machine * [TSUN-106] - Add content-encoding support for dynvar extraction * [TSUN-123] - add option to read usernames from an external file for jabber * [TSUN-125] - use the new re module everywhere instead of regexp/gregexp * [TSUN-152] - Add "state_rcv" record as parameter to "get_message" function. * [TSUN-153] - dynvar used in match may contain regexp special characters * [TSUN-185] - handle postgresql extended protocol New Features: * [TSUN-162] - add foreach tag (loop when a dyn_variable is a list) * [TSUN-164] - add a switch to allow light queries/replies logging * [TSUN-165] - add a way to synchronize users for all plugins. * [TSUN-167] - add do=dump option to matching * [TSUN-168] - thinktimes value could be dynamically generated with dyn_variable * [TSUN-181] - add option to simulate slow connections 1.3.2 -> 1.3.3 Minor bugfixes (17 Aug 2010) Bugfix: * [TSUN-154] - parent proxy doesn't work anymore in 1.3.x (tested with 1.3.2 and 1.3.0). * [TSUN-155] - url substitution is broken in some cases * [TSUN-156] - Tsung not using sessions with low probabilities * [TSUN-157] - ssl doesn't work with erlang R14A * [TSUN-158] - failure when a proxy is used and an URL substitution is set * [TSUN-159] - HTTP cookies support is broken when a proxy is used * [TSUN-160] - tsung can sometimes hang at the beginning using distributed setup * [TSUN-161] - if statement not allowed in a transaction 1.3.1 -> 1.3.2 Major bugfixes and enhancements (14 Jun 2010) Bugfix: * [TSUN-128] - Apostrophes cause string to convert to deep list in setdynvars with Erlang function. * [TSUN-130] - static users starting time is wrong * [TSUN-131] - tsung can stop too early when static users are used * [TSUN-132] - http cookies: accept domains equals to hostname with a leading "." * [TSUN-133] - proxy-recorder with SSL fails on large client packets (multiple TCP packets) * [TSUN-138] - when an error occured( for ex a timeout during a request) and a client exits, started transactions are not updated * [TSUN-140] - tsung does not honor the Proxy-Connection: keep-Alive or Connection: keep-Alive header if the proxy is HTTP/1.0 * [TSUN-142] - http recorder can fail with https rewriting and chunked encoding * [TSUN-147] - UDP & bidi does not seem to work * [TSUN-148] - dynvar not found when used in match * [TSUN-149] - tsung doesn't work with Amazon Elastic load balancing * [TSUN-151] - tsung_stats.pl produces invalid *.gplot files Improvements: * [TSUN-82] - XMPP vhost support * [TSUN-127] - add ability tu use floats for thinktimes * [TSUN-139] - option to set random seed for launchers. * [TSUN-141] - add dynamic variable support for postgres * [TSUN-146] - New tsplot yfactor configuration support breaks most common configurations New Features: * [TSUN-135] - add support for SASL ANONYMOUS and PLAIN authentication for XMPP * [TSUN-136] - add new plugin distributed testing of filesystem (nfs for ex), using a generic mode for executing erlang functions on clients nodes * [TSUN-137] - add a way to mix requests types inside a single session * [TSUN-143] - global time limit for the load test * [TSUN-145] - tsung should support json parsing for dynamic variable 1.3.0 -> 1.3.1 Major bugfixes and enhancements (9 Sep 2009) Bugfix: * [TSUN-92] - the computation of the minimum for sample_counter is wrong * [TSUN-93] - maxnumber not respected if several clients are used * [TSUN-102] - dyn_variable configuration fails if variable name is not a valid erlang atom * [TSUN-103] - Network error handling in munin plugin * [TSUN-104] - tsung-plotter can't handle the os_mon statistics * [TSUN-108] - Cannot handle complicated dyn_var name * [TSUN-109] - Tsung status displays always phase one even if you have more than one phase * [TSUN-110] - Cookie header not present if the URL is dynamically generated by a previous redirection (302) * [TSUN-117] - Bug in HTTP: empty header can be generated in some case * [TSUN-118] - HTTPS proxy recorder: ts_utils:to_https incorrectly handles Content-Length for POST requests * [TSUN-119] - tsung can crash when reading empty values from a csv file * [TSUN-122] - same http cookie key with different domains don't work Improvements: * [TSUN-47] - ts_mon can be a bottleneck during very high load testing * [TSUN-77] - Structural requests or goto-like action for match in HTTP * [TSUN-81] - Dynamic variables API * [TSUN-83] - file_server using fixed tuple instead of list * [TSUN-85] - external entity should be copied into the log directory of a run. * [TSUN-87] - add dynamic code evaluation in set_dynvars * [TSUN-88] - add mkactivity method support in webdav * [TSUN-91] - reduce memory consumption by hibernating client process while in think state * [TSUN-97] - disable smp erlang for client beam for performance reason * [TSUN-98] - try several times to connect to the server before aborting a session * [TSUN-99] - make substitution work in * [TSUN-100] - improve scalability of ts_launcher * [TSUN-105] - Add load average statistic to server monitoring * [TSUN-111] - add option to manually add a cookie in http requests * [TSUN-113] - split tsung command into two separate tsung and tsung-recorder commands * [TSUN-116] - add ability to run several tsung running in parallel on the same hosts * [TSUN-120] - Https recorder: Remove "Secure" from "Set-Cookie" header. New Features: * [TSUN-25] - add a way to start sessions in a specific order at specified times * [TSUN-89] - include tsung-plotter into the tsung distribution * [TSUN-90] - add support for monitoring server cpu/mem using munin-node * [TSUN-94] - add log action for match * [TSUN-95] - add a default dyn_variable with a unique tsung_userid * [TSUN-107] - add MUC support to the jabber doc/plugin * [TSUN-114] - add option to apply function to data before looking for a match * [TSUN-115] - add pubsub support to the jabber plugin 1.2.2 -> 1.3.0 Major bugfixes and enhancements (03 Sep 2008) Bugfix: * [TSUN-30] - SNMP monitoring gives an error * [TSUN-57] - using -l with a relative path make distributed load fails with timeout error * [TSUN-60] - https recorder broken if an HTML document includes absolute urls * [TSUN-67] - Typo breaks recording of if-modified-since headers * [TSUN-68] - some sites doesn't work with ":443" added in the "Host" header with https * [TSUN-71] - Tsung does not work with R12B (httpd_util funs removed) * [TSUN-73] - Wrong parsing HTTP multipart/form-data in http request - POST form doesn't work * [TSUN-75] - can not define more -pa arguments * [TSUN-84] - dyn variables that don't match should be set to an empty string Improvements: * [TSUN-40] - problem to rewrite url for https with gzip-encoded html. * [TSUN-48] - tcp/udp buffer size should be customizable in the XML config file. * [TSUN-59] - if a User-Agent header is set in
, it should override the global one. * [TSUN-62] - add abilty to loop back to a previous request in a session * [TSUN-63] - check for ssl and crypto application at compile time * [TSUN-65] - enhance dynamic variables. * [TSUN-66] - add global mean and counter computation and reporting for samples * [TSUN-69] - add option to read content of a POST request from an external file * [TSUN-79] - setting 'Host' header with http_header doesn't work as expected New Features: * [TSUN-56] - ldap plugin * [TSUN-58] - add a new statistics backend to dump all stats in a file * [TSUN-61] - add a Webdav plugin * [TSUN-64] - add md5 authentication in the pgsql plugin * [TSUN-72] - Add support for defining dyn_variables using XPath * [TSUN-78] - mysql plugin * [TSUN-80] - add random thinktime with in a given range ( [min,max]) Tasks: * [TSUN-76] - add explanation for errors name in the documentation 1.2.1 -> 1.2.2 Minor bugfixes and enhancements (23 Feb 2008) Bugfix: * [TSUN-30] - SNMP monitoring gives an error * [TSUN-31] - dyn_variable usage * [TSUN-35] - udp is not working * [TSUN-36] - default regexp for dyn_variable doesn't work in all case * [TSUN-38] - server monitoring crash if an ethernet interface's name is more than 6 chars long * [TSUN-39] - https recording doesn't work with most browsers * [TSUN-43] - session should not terminate if rosterjid is not defined * [TSUN-49] - doesn't work with jabber plugin * [TSUN-51] - tsung does not work with R12B (httpd_util funs removed) * [TSUN-53] - postgresql errors not reported in all cases * [TSUN-55] - no error counter when userid_max is reached Improvements: * [TSUN-14] - no_ack messages and asynchronous msg sent by the server are not available in the reports * [TSUN-27] - handle bidirectional protocols * [TSUN-28] - Refactoring needed to ease the change of the userid / password generation code * [TSUN-29] - Multiple file_server support * [TSUN-32] - make snmp server options tunable * [TSUN-34] - add costum http headers * [TSUN-44] - tsung should ignore whitespace keepalive from xmpp server * [TSUN-45] - add kernel-poll support for better performance * [TSUN-46] - add number of open connections in statistics * [TSUN-47] - ts_mon can be a bottleneck during very high load testing * [TSUN-50] - use the whole range of Id (from 0 to userid_max) before reusing already used Ids New Features: * [TSUN-26] - Ability to loop on a given sequence of phase * [TSUN-52] - Adding comment during script capture * [TSUN-41] - add support for parent proxy for http only (not https) 1.2.0 -> 1.2.1 Minor bugfixes and enhancements (07 Oct 2006) Bugfix: - [TSUN-5] get traffic from all interfaces instead of only eth0 in erlang os monitoring (Linux) - [TSUN-18 the pgsql recorder fails if the client doesn't try first an SSL connection - [TSUN-19] a % character in some requests (eg. type=sql for pgsql) make the config_server crash. - [TSUN-20] pgsql client fails while parsing data from server - [TSUN-21] substitution in URL is not working properly when a new server or port is set - [TSUN-23] set default http version (1.1) - [TSUN-24] destination=previous doesn't work (jabber) Improvement: - [TSUN-15] listen port is now customizable with the command line - [TSUN-17] add option to setup postgresql server IP and port at runtime for the recorder - [TSUN-22] add support for PUT, DELETE and HEAD methods for http 1.1.0 -> 1.2.0 Major feature enhancements (29 May 2006) - change name: idx-tsunami is now called tsung - add new plugin: pgsql for postgresql load testing - new: it's now possible to set multiple servers (selected at runtime by round robin) - add size_rcv stats - fix beams communication problem introduced in new erlang releases. - import snmp_mgr src from R9C2 to enable SNMP with R10B - rebuild boot scripts if erlang version is different from compile time - many DTD improvements - improved match: add loop|abort|restart on (no)match behavior, multiple match tags is now possible (suggested by msmith@truelink.com) - freemem and packet stats for Solaris (jasonwtucker@gmail.com) - fix several small problems with 'use_controller_vm' option - ip is no more mandatory (default is 0.0.0.0) - clients and monitoring can use hosts list defined in environment variables, for use with batch schedulers (openpbs/torque, LSF and OAR) - performance improvements in stats engine for very high load (use session_cache) Recorder: - add plugin architecture in recorder; add pgsql plugin - fix regression in recorder for WWW-Authentication (anders.nygren@gmail.com) - close client socket when connection:closed is ask by the server (this should enable https recording with IE) Jabber: - fix presence:roster request - add presence:directed , presence:broadcast & presence:final requests for jabber (jasonwtucker@gmail.com) - roster enhancements (jasonwtucker@gmail.com) - sip-digest authentication (jasonwtucker@gmail.com) - fix online: must use presence:initial to switch to online status - add pubsub support (mickael.remond@process-one.net) Http: - fix single user agent case. - minor fixes for HTTP parsing 1.0.3 -> 1.1.0 Major feature enhancements (5 Sep 2005) - new feature: HTTP proxy load testing in now possible (set http_use_server_as_proxy to true) - add dynamic substitution support for jabber - add 'raw' type of msg for Jabber (use the new 'data' attribute) - add the dynamic variable list to dynamic substitutions - UserAgent is now customizable for HTTP testing - Add an option to run all components (controller and launcher) within a single erlang beam (use_controller_vm). Should ease idx-tsunami use for light load tests - fix bash script for solaris (jasonwtucker@gmail.com) - fix: several 'idx-tsunami status' can be run simultaneously (reported by Adam Spotton) - internal: Host header is now set during configuration phase - fix last phase duration - fix recorder: must log absolute url if only the scheme has changed 1.0.2 -> 1.0.3 Minor bugfixes (8 Jul 2005) - add ts_file_server module - fix broken https recording Thx to johann.messner@jku.at for bug reporting : - fix: forgot to add "?" when an URL is absolute and had a query part - fix regression in the recorder (introduced in 1.0.2): must use CAPS for method, wrong content-length in recorder causing POST requests to silently fail - allow multiple 'dyn_variable' in DTD - fix Host: header when port is != 80 1.0.1 -> 1.0.2: Minor bugfixes (6 Jun 2005) - fix: the recorder is working now with R10B: replace call to httpd_parse:request_header in recorder by an internal func (the func was removed in R10B) - update configure scripts (should build on RHEL3/x86_64) - remote beam startup is now tunable (-r ssh/rsh) - internal changes in ts_os_mon (suggested by R. Lenglet) 1.0 -> 1.0.1: Major bugfixes (18 Nov 2004) - fix: broken free mem on non linux arch (Matthew Schulkind) - add script to convert apache log file (combined) to idx-tsunami XML - improved configure: add --with-erlang option and xmerl PATH detection idx-tsunami now compiles both with R9C and R10B - small fixes to the DTD Thx to Jonathan Bresler for testing and bug reporting : - fix: broken 'global', 'local' and 'no_ack' requests and size computation - fix: broken ids in jabber messages - fix: broken online/offline in user_server - default thinktime can now be overriden - many improvements/fixes in analyse_msg.pl 1.0.beta7 -> 1.0: Minor bugfixes (13 Aug 2004) - fix: broken path when building debian package - add rpm target in makefile - implement status - add 'match' in graph and doc - fix add_dynparams for jabber 1.0.beta6 -> 1.0.beta7: Minor bugfixes (20 Jul 2004) - HTTP: really (?) fix parsing of no content-length with connection:close - better handling of configure (--prefix is working) - add different types of output backend (currently, only 'text' works; 'rrdtool' is started but unfinished) - fix: ssl_ciphers option is working again 1.0.beta5 -> 1.0.beta6: Minor feature enhancements (5 May 2004) - add a DTD for the configuration file - add dynamic request substitution (mickael.remond@erlang-fr) - add dynamic variable parsing from response (can be used later in the session for request substitution) - add response pattern to match (log if not match) - HTTP: fix partial header parsing (mickael.remond@erlang-fr.org) - HTTP: fix chunk parsing when the chunk-size is split across two packets - HTTP: fix parsing of no content-length with connection:close case - check for bad input (config file, name) - merge client and client_rcv processes into a single process - fix: do not connect in init anymore; this fix too long phases when connection time is high. - connect stat is now for both new connections and reconnections - check phase duration in launcher - various code cleanup 1.0.beta4 -> 1.0.beta5: Major Feature enhancements (25 Mar 2004) - add SNMP monitoring (not yet customizable) - fix remote start: log filename is now encoded to avoid bad parsing of log_file by 'erl' Patches from mickael.remond@erlang-fr.org : - Added ~/.idx-tsunami creation in idx-tsunami script if the directory does not already exist - Extension of XML attribute entity normalisation - HTTP: fix Cookie support: Cookie are not necessarily separated by "; " - HTTP: fix long POST request in the recorder: dorecord message was missing enclosing curly brackets, and the body length counter were mistakenly taking the header size in its total - HTTP: Content-type support in the recorder (needed to handle non-HTML form encoded posts) - add autoconf support to detect Erlang installation path - SOAP Support: IDX-Tsunami can now record and replay SOAP HTTP scenario. The SOAPAction HTTP header is now recorded - Preliminary Windows support: A workaround has been introduced in the code to handle behaviour difference between Erlang Un*x and Erlang Windows on how the command-line is handled. When an assumtion is made on the string type of a parameter, it should be check that this is actually a string and not an atom. 1.0.beta3 -> 1.0.beta4: Minor bugfixes (16 Mar 2004) - fix lost cookie when transfer-encoding:chunked is used - fix config parsing (the last request of the last page of a sesssion was not marked as endpage) - don't crash anymore on error during start or stop 1.0.beta2 -> 1.0.beta3: Minor feature enhancements (24 Feb 2004) - fix stupid bug in start script for recorder - HTTP: fix '&' writes in the XML recorder for 'content' attribute - HTTP: enhanced Cookies parsing ('domain' and 'path' implemented). - ssl_ciphers can be customized - change log directory structure: all log files in one directory per test - add HTML reports (requires the perl Template toolkit) - change stats names: page_resptime -> page, response_time -> request 1.0.beta1 -> 1.0.beta2: Minor feature enhancements (11 Feb 2004) - reorganise the sources - add tools to build a debian package - fix documentations - add minimalistic man page - syntax change: GETIMS +date replace by GET +'if_modified_since' 0.2.1 -> 1.0.beta1: Major Feature Enhancements (3 Feb 2004) - rewrite the configuration engine. Now use an XML file. - add recording application: use as a HTTP proxy to record session into XML format - add support to OS monitoring (cpu, memory, network). Currently, use an erlang agent on the remote nodes; SNMP is on the TODO list. (mickael.remond@erlang-fr.org) - can now use several IPs per client host - several arrival phases can be set with different arrival rates and duration - can set test duration instead of number of users - add user defined statistics using a 'transaction' tag - HTTP: fix cookies and POST handling (mickael.remond@erlang-fr.org) - HTTP: rewrite the parser (faster and cleaner) - fix bad timeout computation when close occur for persistent client - bugfixes and other enhancements. - fix memory leak with ssl (half-closed connections) 0.2.0 -> 0.2.1: Minor bugfixes and small enhancements (9 Dec 2003) - optimize session memory consumption: use an ets table to store session setup - HTTP: fix crash when content-length is not set in headers - HTTP: fix POST method - HTTP: preliminary chunked-encoding support in HTTP/1.1 - HTTP: Absolute URL are handled (server and port can be overridden ) - no more .hosts.erlang required - add stats on simultaneous users 0.1.1 -> 0.2.0: Major Feature Enhancements (Aug 2003) - add 'realtime' stats - add new 'parse' type of protocol - add reconnection support (persistent client) - add basic HTTP and HTTPS support - split the application in two parts: a single controller (tsunami_controller), and the clients (tsunami) - switch to R9C 0.1.0 -> 0.1.1: Bugfix realease (Aug 2002) - fix config file - fix few typos in docs - fix init script - few optimizations in user_server.erl - switch to R8B 0.1.0: Initial release (May 2001) tsung-1.4.2/aclocal.m40000644000201100017670000000114411701017117014177 0ustar nniclausdream# generated automatically by aclocal 1.10 -*- Autoconf -*- # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, # 2005, 2006 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. m4_include([acinclude.m4]) tsung-1.4.2/debian/0000755000201100017670000000000011701017143013560 5ustar nniclausdreamtsung-1.4.2/debian/changelog0000644000201100017670000000643211701017117015440 0ustar nniclausdreamtsung (1.4.2-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Tue,4 Jan 2012 10:53:05 +0200 tsung (1.4.1-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Tue, 13 Sep 2011 10:53:05 +0200 tsung (1.4.0-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Mon, 5 Sep 2011 10:58:05 +0200 tsung (1.4.0a-1) unstable; urgency=low * working on 1.4.0 -- Nicolas Niclausse Tue, 26 Apr 2011 13:11:05 +0200 tsung (1.3.3-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Wed, 17 Aug 2010 18:11:05 +0200 tsung (1.3.2-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Wed, 14 Jun 2010 20:11:05 +0200 tsung (1.3.1-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Wed, 09 Aug 2009 08:11:05 +0200 tsung (1.3.0-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Wed, 03 Sep 2008 07:11:05 +0200 tsung (1.2.2-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Sat, 23 Feb 2008 08:11:05 +0200 tsung (1.2.1-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Wed, 20 Sep 2006 08:11:05 +0200 tsung (1.2.0-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Mon, 29 May 2006 09:11:05 +0200 tsung (1.1.0-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Mon, 6 Sep 2005 09:11:05 +0200 tsung (1.0.3-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Mon, 8 Jul 2005 17:11:05 +0200 tsung (1.0.2-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Mon, 6 Jun 2005 17:34:05 +0200 tsung (1.0.1-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Thu, 18 Nov 2004 08:34:05 +0200 tsung (1.0-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Thu, 12 Aug 2004 13:34:05 +0200 tsung (1.0.beta7-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Tue, 20 Jul 2004 18:57:01 +0200 tsung (1.0.beta6-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Tue, 4 May 2004 13:07:17 +0200 tsung (1.0.beta5-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Thu, 25 Mar 2004 17:41:25 +0100 tsung (1.0.beta4-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Tue, 16 Mar 2004 15:23:43 +0100 tsung (1.0.beta3-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Tue, 24 Feb 2004 19:06:03 +0100 tsung (1.0.beta2-1) unstable; urgency=low * Initial Release. -- Nicolas Niclausse Tue, 10 Feb 2004 12:09:23 +0100 tsung-1.4.2/debian/tsung.dirs0000644000201100017670000000011011701017117015574 0ustar nniclausdreamusr/bin/ usr/lib/erlang/lib usr/lib/tsung/bin usr/share/tsung/templates tsung-1.4.2/debian/rules0000755000201100017670000000233411701017117014643 0ustar nniclausdream#!/usr/bin/make -f # Sample debian/rules that uses debhelper. # GNU copyright 1997 to 1999 by Joey Hess. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 # This is the debhelper compatibility version to use. export DH_COMPAT=4 configure: configure-stamp configure-stamp: dh_testdir # Add here commands to configure the package. ./configure touch configure-stamp build: build-stamp build-stamp: configure-stamp dh_testdir # Add here commands to compile the package. $(MAKE) touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp configure-stamp # Add here commands to clean up after the build process. -$(MAKE) clean dh_clean install: build dh_testdir dh_testroot #dh_clean -k dh_installdirs # Add here commands to install the package into debian/tsung make install DESTDIR=$(CURDIR)/debian/tsung # Build architecture-independent files here. binary-indep: build install # We have nothing to do by default. dh_testdir dh_testroot dh_installdocs dh_installchangelogs dh_link dh_strip dh_compress dh_fixperms # dh_makeshlibs dh_installdeb # dh_perl dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep .PHONY: build clean binary-indep install configure tsung-1.4.2/debian/copyright0000644000201100017670000000143111701017117015513 0ustar nniclausdreamThis package was debianized by Nicolas Niclausse on Tue, 10 Feb 2004 12:09:23 +0100. It was downloaded from http://tsung.erlang-projects.org/ Upstream Author(s): Nicolas Niclausse Copyright: Tsung is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Tsung is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. See /usr/share/common-licenses/GPL tsung-1.4.2/debian/control0000644000201100017670000000203611701017117015165 0ustar nniclausdreamSource: tsung Section: net Priority: optional Maintainer: Nicolas Niclausse Build-Depends: debhelper (>= 4.0.0), erlang-nox (>= 10.b.5-1) , docbook-utils, erlang-src, erlang-dev, autoconf Standards-Version: 3.6.0 Package: tsung Architecture: all Depends: erlang-nox (>= 10.b.5-1) Recommends: gnuplot, perl, ssh, libtemplate-perl, python-matplotlib Description: A distributed multi-protocol load testing tool. Tsung is a distributed load testing tool. It is protocol-independent and can currently be used to stress and benchmark HTTP, Jabber/XMPP, LDAP, MySQL and PostgreSQL servers. It simulates user behaviour using an XML description file, reports many measurements in real time (statistics can be customized with transactions, and graphics generated using gnuplot). For HTTP, it supports 1.0 and 1.1, has a proxy mode to record sessions, supports GET and POST methods, Cookies, and Basic WWW-authentication. It also has support for SSL. . More information is available at http://tsung.erlang-projects.org/ . tsung-1.4.2/debian/docs0000644000201100017670000000006611701017117014436 0ustar nniclausdreamCHANGES doc/user_manual.html CONTRIBUTORS README TODO tsung-1.4.2/debian/compat0000644000201100017670000000000211701017117014757 0ustar nniclausdream4 tsung-1.4.2/src/0000755000201100017670000000000011701017143013125 5ustar nniclausdreamtsung-1.4.2/src/log2tsung.pl.in0000644000201100017670000002467611701017117016033 0ustar nniclausdream#!/usr/bin/env perl # -*- Mode: CPerl -*- # # Copyright (C) 2004 Nicolas Niclausse # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # Auteur: Nicolas Niclausse (nicolas@niclux.org) # Version: $Id$ # purpose: create a config file for tsung from a Combined Log file use strict; use Getopt::Long; use Time::Local; use vars qw ($help *verbose $version $thinktime_threshold $visit_timeout $session_threshold $max_pages $max_duration); my %Months=('Jan','0', 'Feb','1', 'Mar','2', 'Apr','3', 'May','4', 'Jun','5', 'Jul','6', 'Aug','7', 'Sep','8', 'Oct','9', 'Nov','10', 'Dec','11'); my $tagvsn = '@PACKAGE_VERSION@'; GetOptions( "help",\$help, "verbose",\$verbose, "tt=i",\$thinktime_threshold, "st=i",\$session_threshold, "visit_timeout=i",\$visit_timeout, "max_pages=i",\$max_pages, "max_duration=i",\$max_duration, "version",\$version ); my $prefix ="@prefix@"; my $dtd ="@datadir@/@DTD@"; # remove thinktime less than 1 sec $thinktime_threshold ="1" unless $thinktime_threshold; # remove session with less than 2 requests $session_threshold ="2"unless $session_threshold; my $ims = "Fri, 14 Nov 2003 02:43:31 GMT"; # if modified since ... for 304 # if thinktime is more than $visit_timeout, it's a new session $visit_timeout=600 unless $visit_timeout; $max_pages = 100 unless $max_pages ; # 100 pages max per session $max_duration = 3600 unless $max_duration; # 1hour max session duration my %hit; my %http; my $visite; my ($time,$sec,$min,$hour,$mday,$mon,$year); my $total; my $bad = 0; my $user; my $id; my $visit_tot=0; &usage if $help or $Getopt::Long::error; &version if $version; while (<>) { if (m@^([\w\.]+) \S+ \S+ \[(\w+/\w+/\w+:\d+:\d+:\d+)([^\]]+)\] \"(\w+) ([^\"]+)\" (\d+) (\S+) \"([^\"]*)\" \"([^\"]*)\"$@) { my $ip = $1; my $date = $2; my $code = $6; my $referer = $8; my $method = $4; my $user_agent = $9; my $req = $5; my ($url, $protocole) = split(/\s+/,$req); $url = &replace_entities($url); my $version; if ($protocole =~ /HTTP\/(\d\.\d)/) { $version=$1; } else { $version="1.0"; } $date =~ m'(\d+)/(\w+)/(\d+):(\d+):(\d+):(\d+)'; $mday = $1; $mon = $Months{$2}; $year = $3 - 1900; $hour = $4; $min = $5; $sec = $6; $time = timelocal($sec,$min,$hour,$mday,$mon,$year); $user = "$ip-$user_agent"; if ($visite->{$user}) { if ($time - $visite->{$user}->{'last_visit'} > $visit_timeout) { # new visit $visit_tot ++; $visite->{$user}->{'id'}++; $id = $visite->{$user}->{'id'}; $visite->{$user}->{'last_visit'}=$time; $visite->{$user}->{'last_referer'}=$referer; $visite->{$user}->{$id}->{'started'}=$time; $visite->{$user}->{$id}->{'last_request'}=$time; $visite->{$user}->{$id}->{'page'}=1; $visite->{$user}->{$id}->{'hit'}=1; $visite->{$user}->{$id}->{'duration'}=0; $visite->{$user}->{$id}->{'tsung'} = ''."\n"; $visite->{$user}->{$id}->{'tsung'} .= "\t".'{$user}->{$id}->{'tsung'} .= ' if_modified_since="'.$ims.'">'; } else { $visite->{$user}->{$id}->{'tsung'} .= '>'; } $visite->{$user}->{$id}->{'tsung'} .= "\n"; } else { # same visit $id = $visite->{$user}->{'id'}; $visite->{$user}->{$id}->{'hit'}++; my $thinktime = $time - $visite->{$user}->{$id}->{'last_request'}; $visite->{$user}->{'last_visit'}=$time; $visite->{$user}->{$id}->{'last_request'}=$time; $visite->{$user}->{$id}->{'tsung'} .= "\t".''."\n\n" if $thinktime > $thinktime_threshold; $visite->{$user}->{$id}->{'tsung'} .= "\t".''."\n"; # update duration $visite->{$user}->{$id}->{'duration'} = $time - $visite->{$user}->{$id}->{'started'} ; if ($visite->{$user}->{'last_referer'} eq $referer) { # same page/frame } else { # new frame/page $visite->{$user}->{$id}->{'page'}++; $visite->{$user}->{'last_referer'}=$referer; } } } else {# new visitor $visit_tot ++; $visite->{$user}->{'id'}=1; $id = 1; $visite->{$user}->{'last_visit'}=$time; $visite->{$user}->{'last_referer'}=$referer; $visite->{$user}->{$id}->{'started'}=$time; $visite->{$user}->{$id}->{'last_request'}=$time; $visite->{$user}->{$id}->{'hit'}=1; $visite->{$user}->{$id}->{'page'}=1; $visite->{$user}->{$id}->{'duration'}=0; $visite->{$user}->{$id}->{'tsung'} = ''."\n"; $visite->{$user}->{$id}->{'tsung'} .= "\t".''."\n"; } $total ++; } else { # print STDERR "$_\n"; $bad ++; } } my $users_tot=scalar %{$visite}; my $page_tot=0; my $hit_tot=0; my $bad_visit =0; my $bad_pages =0; print STDERR "number of unique users is $users_tot\n" if $verbose; print ' '; print ' '; my $real_visit = 0; foreach my $key (keys %$visite) { foreach my $id (1..$visite->{$key}->{'id'}) { my $page = $visite->{$key}->{$id}->{'page'}; my $hit = $visite->{$key}->{$id}->{'hit'}; $real_visit ++ if $hit > $session_threshold; } } foreach my $key (sort {$visite->{$a}->{'id'} cmp $visite->{$b}->{'id'}} keys %$visite) { my $tot_id = $visite->{$key}->{'id'}; print STDERR "number of visit for $key is $tot_id\n" if $verbose; foreach my $id (1..$tot_id) { my $page = $visite->{$key}->{$id}->{'page'}; my $hit = $visite->{$key}->{$id}->{'hit'}; my $duration = $visite->{$key}->{$id}->{'duration'}; if ($page < $max_pages and $duration < $max_duration) { $page_tot += $page; $hit_tot += $hit; print STDERR " page=$page hit=$hit duration=$duration\n" if $verbose; } else { $bad_visit++; $bad_pages +=$page; print STDERR "# page=$page hit=$hit duration=$duration\n" if $verbose; } next unless $hit > $session_threshold; my $pop=sprintf "%.3f",100/$real_visit; my $tsung = $visite->{$key}->{$id}->{'tsung'}; $tsung =~ s/\\n"; } } print ''; if ($verbose) { select STDERR; print "real_visit = $real_visit\n"; print "total_visit = $visit_tot , bad visit = $bad_visit "; printf "page/visit = %.2f\n",($page_tot/($visit_tot-$bad_visit)); print "good_pages = $page_tot , bad pages = $bad_pages "; printf "hit/page = %.2f\n",($hit_tot/$page_tot); print "bad = $bad\n"; } sub replace_entities { my $str = shift; $str =~ s/\&/\&/g; $str =~ s/\'/\'/g; $str =~ s/\"/\"/g; $str =~ s/>/\>/g; $str =~ s/] \n","Available options:\n\t", "[--help] (this help text)\n\t", "[--version] (print version)\n\t", "[--tt ] (thinktime threshold: min thinktime (def=2))\n\t", "[--st ] (session threshold : min number of requests (def=2))\n\t", "[--max_duration ] (maximum session duration in sec. (3600))\n\t", "[--max_pages ] (maximum number of pages winthin a session. (100))\n\t"; exit; } sub version { print "this script is part of tsung version $tagvsn Written by Nicolas Niclausse Copyright (C) 2004-2006 Nicolas Niclausse This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program (see COPYING); if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA."; exit; } tsung-1.4.2/src/tsung_recorder/0000755000201100017670000000000011701017143016152 5ustar nniclausdreamtsung-1.4.2/src/tsung_recorder/ts_recorder_sup.erl0000644000201100017670000000613211701017117022063 0ustar nniclausdream%%% %%% Copyright IDEALX S.A.S. 2003 %%% %%% Author : Nicolas Niclausse %%% Created: 22 Dec 2003 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%%------------------------------------------------------------------- %%% File : ts_recorder_sup.erl %%% Author : %%% Description : %%% Created : 22 Dec 2003 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_recorder_sup). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -include("ts_profile.hrl"). -behaviour(supervisor). %% External exports -export([start_link/0]). %% supervisor callbacks -export([init/1]). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start_link() -> ?LOG("starting supervisor ...~n",?INFO), supervisor:start_link({local, ?MODULE}, ?MODULE, []). %%%---------------------------------------------------------------------- %%% Callback functions from supervisor %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, {SupFlags, [ChildSpec]}} | %% ignore | %% {error, Reason} %%---------------------------------------------------------------------- init([]) -> ?LOG("starting",?INFO), ClientsSup = {ts_client_proxy_sup, {ts_client_proxy_sup, start_link, []}, permanent, 2000, supervisor, [ts_client_proxy_sup]}, Recorder = {ts_proxy_recorder, {ts_proxy_recorder, start, [?config(proxy_log_file)]}, transient, 2000, worker, [ts_proxy_recorder]}, Listener = {ts_proxy_listener, {ts_proxy_listener, start, []}, transient, 2000, worker, [ts_proxy_listener]}, {ok,{{one_for_one,?retries,10}, [ClientsSup, Recorder,Listener ]}}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- tsung-1.4.2/src/tsung_recorder/tsung_recorder.app.src0000644000201100017670000000147011701017133022470 0ustar nniclausdream{application, tsung_recorder, [{description, "tsung recorder"}, {vsn, "%VSN%"}, {modules, [ tsung_recorder, ts_recorder_sup, ts_client_proxy_sup, ts_proxy_recorder, ts_proxy_listener ]}, {registered, [ ts_proxy_recorder, ts_proxy_listener ]}, {env, [ {debug_level, 6}, {ts_cookie, "humhum"}, {log_file, "./tsung.log"}, {plugin, ts_proxy_http}, {parent_proxy, false}, {pgsql_server, "127.0.0.1"}, {pgsql_port, 5432}, {proxy_log_file, "./tsung_recorder"}, {proxy_listen_port, 8090} ]}, {applications, [kernel,stdlib,crypto,public_key,ssl,crypto]}, {mod, {tsung_recorder, []}} ]}. tsung-1.4.2/src/tsung_recorder/ts_proxy_webdav.erl0000644000201100017670000001363111701017117022102 0ustar nniclausdream%%% %%% Copyright (C) Nicolas Niclausse 2008 %%% %%% Author : Nicolas Niclausse %%% Created: 31 Mar 2008 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_proxy_webdav). -vc('$Id: ts_proxy_webdav.erl 822 2008-03-31 13:18:34Z nniclausse $ '). -author('Nicolas.Niclausse@niclux.org'). -include("ts_profile.hrl"). -include("ts_http.hrl"). -include("ts_recorder.hrl"). -export([parse/4, record_request/2, socket_opts/0]). -export([gettype/0]). -export([client_close/2]). -export([rewrite_serverdata/1]). -export([rewrite_ssl/1]). %%-------------------------------------------------------------------- %% Func: socket_opts/0 %%-------------------------------------------------------------------- socket_opts() -> [{packet, 0}]. %%-------------------------------------------------------------------- %% Func: gettype/0 %%-------------------------------------------------------------------- gettype() -> "ts_webdav". %%-------------------------------------------------------------------- %% Func: rewrite_serverdata/1 %%-------------------------------------------------------------------- rewrite_serverdata(Data)-> ts_utils:from_https(Data). %%-------------------------------------------------------------------- %% Func: rewrite_ssl/1 %%-------------------------------------------------------------------- rewrite_ssl(Data)-> ts_utils:to_https(Data). %%-------------------------------------------------------------------- %% Func: client_close/2 %%-------------------------------------------------------------------- client_close(Data,State)-> ts_proxy_http:client_close(Data,State). %%-------------------------------------------------------------------- %% Func: parse/4 %% Purpose: parse HTTP/WEBDAV request %% Returns: {ok, NewState} %%-------------------------------------------------------------------- parse(State,ClientSocket,ServerSocket,String) -> ts_proxy_http:parse(State, ClientSocket,ServerSocket,String). %%-------------------------------------------------------------------- %% Func: record_http_request/2 %% Purpose: record request given State=#state_rec and Request=#http_request %% Returns: {ok, NewState} %%-------------------------------------------------------------------- record_request(State=#state_rec{prev_host=Host, prev_port=Port, prev_scheme=Scheme}, #http_request{method = Method, url = RequestURI, version = HTTPVersion, headers = ParsedHeader,body=Body}) -> FullURL = ts_utils:to_https({url, RequestURI}), {URL,NewPort,NewHost, NewScheme} = case ts_config_http:parse_URL(FullURL) of #url{path=RelURL,host=Host,port=Port,querypart=[],scheme=Scheme}-> {RelURL, Port, Host, Scheme}; #url{path=RelURL,host=Host,port=Port,querypart=Args,scheme=Scheme}-> {RelURL++"?"++Args, Port, Host, Scheme}; #url{host=Host2,port=Port2,scheme=Sc2}-> {FullURL,Port2,Host2,Sc2 } end, Fd = State#state_rec.logfd, URL2 = ts_utils:export_text(URL), io:format(Fd," ok; _ -> Body2 = ts_utils:export_text(Body), io:format(Fd," contents='~s' ", [Body2]) % must be a POST method end, %% Content-type recording (This is useful for SOAP post for example): ts_proxy_http:record_header(Fd,ParsedHeader,"content-type", "content_type='~s' "), ts_proxy_http:record_header(Fd,ParsedHeader,"if-modified-since", "if_modified_since='~s' "), io:format(Fd,"method='~s'>", [Method]), %% authentication ts_proxy_http:record_header(Fd,ParsedHeader,"authorization", "~n "), %% webdav ts_proxy_http:record_header(Fd,ParsedHeader,"depth", "~n ~n"), ts_proxy_http:record_header(Fd,ParsedHeader,"if", "~n ~n"), ts_proxy_http:record_header(Fd,ParsedHeader,"timeout", "~n ~n"), ts_proxy_http:record_header(Fd,ParsedHeader,"overwrite", "~n ~n"), ts_proxy_http:record_header(Fd,ParsedHeader,"destination", "~n ~n", fun(A) -> ts_utils:to_https({url, A}) end), ts_proxy_http:record_header(Fd,ParsedHeader,"url", "~n ~n"), ts_proxy_http:record_header(Fd,ParsedHeader,"lock-token", "~n ~n"), ts_proxy_http:record_header(Fd,ParsedHeader,"x-svn-options", "~n ~n"), %% subversion use x-svn-result-fulltext-md5 ; add this ? %% http://svn.collab.net/repos/svn/branches/artem-soc-work/notes/webdav-protocol io:format(Fd,"~n",[]), {ok,State#state_rec{prev_port=NewPort,prev_host=NewHost,prev_scheme=NewScheme}}. tsung-1.4.2/src/tsung_recorder/tsung_recorder.erl0000644000201100017670000000531111701017117021704 0ustar nniclausdream%%% %%% Copyright IDEALX S.A.S. 2003 %%% %%% Author : Nicolas Niclausse %%% Created: 22 Dec 2003 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%%------------------------------------------------------------------- %%% File : tsung_recorder.erl %%% Author : %%% Description : tsung_recorder application %%% Created : 22 Dec 2003 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(tsung_recorder). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -export([start/2,stop/1, stop_all/1]). -behaviour(application). -include("ts_profile.hrl"). %%---------------------------------------------------------------------- %% Func: start/2 %% Returns: {ok, Pid} | %% {ok, Pid, State} | %% {error, Reason} %%---------------------------------------------------------------------- start(_Type, _StartArgs) -> error_logger:tty(false), error_logger:logfile({open, ?config(log_file) ++ "-" ++ atom_to_list(node())}), case ts_recorder_sup:start_link() of {ok, Pid} -> {ok, Pid}; Error -> ?LOGF("Can't start ! ~p ~n",[Error], ?ERR), Error end. %%---------------------------------------------------------------------- %% Func: stop/1 %% Returns: any %%---------------------------------------------------------------------- stop(_State) -> stop. %%---------------------------------------------------------------------- %% Func: stop_all/1 %% Returns: any %%---------------------------------------------------------------------- stop_all(Arg) -> ts_utils:stop_all(Arg,'ts_proxy_listener', "tsung recorder", fun ts_proxy_recorder:stop/1). tsung-1.4.2/src/tsung_recorder/ts_proxy_recorder.erl0000644000201100017670000002056111701017117022437 0ustar nniclausdream%%% %%% @copyright IDEALX S.A.S. 2003-2005 %%% %%% @author Nicolas Niclausse %%% @doc Record request by calling the plugin involved %%% @since 1.0.beta1, 22 Dec 2003 by Nicolas Niclausse %%% @version {@version} %%% @end %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_proxy_recorder). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -behaviour(gen_server). %%-------------------------------------------------------------------- %% Include files %%-------------------------------------------------------------------- -include("ts_profile.hrl"). -include("ts_http.hrl"). -include("ts_recorder.hrl"). %%-------------------------------------------------------------------- %% External exports -export([start/1, dorecord/1, recordtag/1, stop/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %%==================================================================== %% External functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: start/1 %% Description: Starts the server %%-------------------------------------------------------------------- start(Config) -> gen_server:start_link({global, ?MODULE}, ?MODULE, Config, []). %%-------------------------------------------------------------------- %% Function: stop/1 %%-------------------------------------------------------------------- stop(_) -> gen_server:call({global, ?MODULE},{stop}). %%-------------------------------------------------------------------- %% Function: dorecord/1 %% Description: record a new request %%-------------------------------------------------------------------- dorecord(Args)-> gen_server:cast({global, ?MODULE},{record, Args}). %%-------------------------------------------------------------------- %% Function: recordtag/1 %% Description: record a string (for use on the command line) %%-------------------------------------------------------------------- recordtag([Host,Args]) when is_list(Host)-> recordtag(list_to_atom(Host), Args). %% @spec recordtag(Host::string(), Args::term()) -> ok recordtag(Host, Args) when is_list(Args)-> _List = net_adm:world_list([Host]), global:sync(), gen_server:cast({global,?MODULE},{record, Args}). %%==================================================================== %% Server functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: init/1 %% Description: Initiates the server %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%-------------------------------------------------------------------- init(Filename) -> Date = ts_utils:datestr(), %% add date to filename File = case re:replace(Filename,"\.xml$", Date ++ ".xml", [{return,list},global]) of %% " Filename -> Date ++ "-" ++ Filename; RealName -> RealName end, case file:open(File,[write]) of {ok, Stream} -> Plugin = ?config(plugin), erlang:display(lists:flatten(["Record file: ",File])), ?LOGF("starting recorder with plugin ~s : ~s~n",[Plugin,File],?NOTICE), {ok, #state_rec{ log_file = File, logfd = Stream, ext_file_id=1, plugin = Plugin }}; {error, Reason} -> ?LOGF("Can't open log file ~p! ~p~n",[File,Reason], ?ERR), {stop, Reason} end. %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_call({stop}, _From, State) -> io:format(State#state_rec.logfd,"~n",[]), file:close(State#state_rec.logfd), {stop, normal, ok, State}; handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. %%-------------------------------------------------------------------- %% Function: handle_cast/2 %% Description: Handling cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_cast({record, endsession}, State) -> io:format(State#state_rec.logfd,""), {noreply, State}; handle_cast({record, {Request,PluginState}}, State) -> handle_cast({record, {Request}}, State#state_rec{plugin_state=PluginState}); handle_cast({record, {Request}}, State=#state_rec{timestamp=0,plugin=Plugin}) -> % first record Name= ts_utils:datestr(), Type = Plugin:gettype(), io:format(State#state_rec.logfd,"~n",["rec"++Name, Type]), {ok, NewState} = Plugin:record_request(State, Request), {noreply, NewState#state_rec{timestamp=now()}}; handle_cast({record, {Request}}, State=#state_rec{plugin=Plugin}) -> TimeStamp=now(), Elapsed = ts_utils:elapsed(State#state_rec.timestamp,TimeStamp), case Elapsed < State#state_rec.thinktime_low of true -> ?LOGF("skip too low thinktime, assuming it's an embedded object (~p)~n", [Elapsed],?INFO); false -> io:format(State#state_rec.logfd, "~n~n~n", [round(Elapsed/1000)]) end, {ok, NewState} = Plugin:record_request(State, Request), {noreply, NewState#state_rec{timestamp=TimeStamp}}; handle_cast({record, String}, State) when is_list(String)-> ?LOGF("Record string ~p~n",[String], ?NOTICE), io:format(State#state_rec.logfd, "~n~s~n", [String]), {noreply, State}; handle_cast(Msg, State) -> ?LOGF("IGNORE Msg ~p~n",[Msg], ?WARN), {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info/2 %% Description: Handling all non call/cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate/2 %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- tsung-1.4.2/src/tsung_recorder/ts_proxy_http.erl0000644000201100017670000004126111701017117021611 0ustar nniclausdream%%% %%% Copyright (C) Nicolas Niclausse 2005 %%% %%% Author : Nicolas Niclausse %%% Created: 09 Nov 2005 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_proxy_http). -vc('$Id$ '). -author('Nicolas.Niclausse@niclux.org'). -include("ts_profile.hrl"). -include("ts_http.hrl"). -include("ts_recorder.hrl"). -export([parse/4, record_request/2, socket_opts/0]). -export([decode_basic_auth/1, gettype/0]). -export([client_close/2]). -export([rewrite_serverdata/1]). -export([rewrite_ssl/1]). %% for webdav: -export([record_header/4, record_header/5]). %%-------------------------------------------------------------------- %% Func: socket_opts/0 %%-------------------------------------------------------------------- socket_opts() -> [{packet, 0}]. %%-------------------------------------------------------------------- %% Func: gettype/0 %%-------------------------------------------------------------------- gettype() -> "ts_http". %%-------------------------------------------------------------------- %% Func: rewrite_serverdata/1 %%-------------------------------------------------------------------- rewrite_serverdata(Data)-> %% FIXME: content length may have changed ! ts_utils:from_https(Data). %%-------------------------------------------------------------------- %% Func: rewrite_ssl/1 %%-------------------------------------------------------------------- rewrite_ssl(Data)-> %% FIXME: content length may have changed ! ts_utils:to_https(Data). %%-------------------------------------------------------------------- %% Func: client_close/2 %%-------------------------------------------------------------------- client_close(_Socket,State)-> State. %%-------------------------------------------------------------------- %% Func: parse/4 %% Purpose: parse HTTP request %% Returns: {ok, NewState} %%-------------------------------------------------------------------- parse(State=#proxy{parse_status=Status, parent_proxy=Parent},_,ServerSocket,NewString) when Status==new -> String = lists:append(State#proxy.buffer,NewString), case ts_http_common:parse_req(String) of {more, _Http, _Head} -> ?LOGF("Headers incomplete (~p), buffering ~n",[String],?DEB), {ok, State#proxy{parse_status=new, buffer=String}}; %FIXME: not optimal {ok, Http=#http_request{url=RequestURI, version=HTTPVersion}, Body} -> ?LOGF("URL ~p ~n",[RequestURI],?DEB), ?LOGF("Method ~p ~n",[Http#http_request.method],?DEB), ?LOGF("Headers ~p ~n",[Http#http_request.headers],?DEB), case ts_utils:key1search(Http#http_request.headers,"content-length") of undefined -> % no body, everything received ts_proxy_recorder:dorecord({Http }), {ok, NewSocket} = check_and_send(String,Parent,ServerSocket,Http,State), case Http#http_request.method of 'CONNECT' -> {ok, State#proxy{http_version=HTTPVersion, parse_status = connect, buffer=[], serversock=NewSocket}}; _ -> {ok, State#proxy{http_version=HTTPVersion, parse_status = new, buffer=[], serversock=NewSocket}} end; Length -> CLength = list_to_integer(Length), ?LOGF("HTTP Content-Length:~p~n",[CLength], ?DEB), BodySize = length(Body), if BodySize == CLength -> % end of response {ok, NewSocket} = check_and_send(String,Parent,ServerSocket,Http,State), ?LOG("End of response, recording~n", ?DEB), ts_proxy_recorder:dorecord({Http#http_request{body=Body}}), {ok, State#proxy{http_version = HTTPVersion, parse_status = new, buffer=[], serversock=NewSocket}}; BodySize > CLength -> {error, bad_content_length}; true -> {ok, NewSocket} = check_and_send(String,Parent,ServerSocket,Http,State), ?LOG("More data to come, continue before recording~n", ?DEB), {ok, State#proxy{http_version=HTTPVersion, content_length = CLength, body_size = BodySize, serversock=NewSocket, buffer = Http#http_request{body=Body }, parse_status = body } } end end end; parse(State=#proxy{parse_status=body, buffer=Http},_,ServerSocket,String) -> DataSize = length(String), ?LOGF("HTTP Body size=~p ~n",[DataSize], ?DEB), Size = State#proxy.body_size + DataSize, CLength = State#proxy.content_length, case ServerSocket of {sslsocket, _, _} -> ts_client_proxy:send(ServerSocket, {body,String}, ?MODULE); _ -> ts_client_proxy:send(ServerSocket, String, ?MODULE) end, Buffer=lists:append(Http#http_request.body,String), %% Should be checked before case Size of CLength -> % end of response ?LOG("End of response, recording~n", ?DEB), ts_proxy_recorder:dorecord( {Http#http_request{ body=Buffer }} ), {ok, State#proxy{body_size=0,parse_status=new, content_length=0,buffer=[]}}; _ -> ?LOGF("Received ~p bytes of data, wait for ~p, continue~n", [Size,CLength],?DEB), {ok, State#proxy{body_size = Size, buffer = Http#http_request{body=Buffer}}} end; parse(State=#proxy{parse_status=connect},_,ServerSocket,String) -> ?LOGF("Received data from client: ~s~n",[String],?DEB), ts_client_proxy:send(ServerSocket, String, ?MODULE), {ok, State}. %%-------------------------------------------------------------------- %% Func: check_and_send/5 %%-------------------------------------------------------------------- check_and_send(String,Parent,ServerSocket,#http_request{url=RequestURI},State)-> {NewSocket,RelURL} = check_serversocket(Parent,ServerSocket,RequestURI,State#proxy.clientsock), ?LOGF("Remove server info from url:[ ~p ] [ ~p ] in [ ~p ] ~n", [RequestURI,RelURL,String], ?INFO), {ok, String2} = relative_url(Parent,String,RequestURI,RelURL), %% needed to remove accept-encoding headers in the http request: {ok, RealString} = ts_utils:to_https({request,String2}), ?LOGF("send data to server: ~p ~n",[RealString],?DEB), ts_client_proxy:send(NewSocket,RealString, ?MODULE), {ok, NewSocket}. %%-------------------------------------------------------------------- %% Func: relative_url/4 %%-------------------------------------------------------------------- relative_url(_,"CONNECT"++_Tail,_RequestURI,[])-> {ok, []}; relative_url(true,String,_RequestURI,_RelURL)-> {ok, String}; relative_url(false,String,RequestURI,RelURL)-> [FullURL_noargs|_] = string:tokens(RequestURI,"?"), [RelURL_noargs|_] = string:tokens(RelURL,"?"), FullURL = re:replace(FullURL_noargs,"(\\)|\\()","\\\\&",[global,{return,list}]), RealString = re:replace(String,FullURL,RelURL_noargs,[{return,list}]), {ok, RealString}. %%-------------------------------------------------------------------- %% Func: check_serversocket/4 %% Purpose: If the socket is not defined, or if the server is not the %% same, connect to the server as specified in URL %% Check if we use a parent proxy, otherwise use check_serversocket/3 %% Returns: {Socket, URL (String)} %%-------------------------------------------------------------------- check_serversocket(false, Socket, URL , ClientSock) -> check_serversocket(Socket, URL , ClientSock); check_serversocket(true, Socket, "http://-"++URL, ClientSock) -> check_serversocket(true, Socket, "https://"++URL, ClientSock); check_serversocket(true, undefined, URL, _ClientSock) -> ?LOGF("Connecting to parent proxy ~p:~p ...~n", [?config(pgsql_server),?config(pgsql_port)],?WARN), {ok ,Socket} = connect(http,?config(pgsql_server),?config(pgsql_port)), {Socket,URL}; check_serversocket(true, Socket, URL, _ClientSock) -> {Socket,URL}. %%-------------------------------------------------------------------- %% Func: check_serversocket/3 %% Purpose: If the socket is not defined, or if the server is not the %% same, connect to the server as specified in URL %% Returns: {Socket, RelativeURL (String)} %%-------------------------------------------------------------------- check_serversocket(Socket, "http://-" ++ Rest, ClientSock) -> check_serversocket(Socket, ts_config_http:parse_URL("https://"++Rest), ClientSock); check_serversocket(Socket, URL, ClientSock) when is_list(URL)-> check_serversocket(Socket, ts_config_http:parse_URL(URL), ClientSock); check_serversocket(undefined, URL = #url{}, ClientSock) -> Port = ts_config_http:set_port(URL), ?LOGF("Connecting to ~p:~p ...~n", [URL#url.host, Port],?DEB), {ok, Socket} = connect(URL#url.scheme, URL#url.host,Port), ?LOGF("Connected to server ~p on port ~p (socket is ~p)~n", [URL#url.host,Port,Socket],?INFO), case URL#url.scheme of connect -> ?LOGF("CONNECT: Send 'connection established' to client socket (~p)",[ClientSock],?DEB), ts_client_proxy:send(ClientSock, "HTTP/1.0 200 Connection established\r\nProxy-agent: tsung\r\n\r\n", ?MODULE), { Socket, [] }; _ -> {Socket, url_with_query(URL)} end; check_serversocket(Socket, URL=#url{host=Host}, _ClientSock) -> RealPort = ts_config_http:set_port(URL), {ok, RealIP} = inet:getaddr(Host,inet), case ts_client_proxy:peername(Socket) of {ok, {RealIP, RealPort}} -> % same as previous URL ?LOGF("Reuse socket ~p on URL ~p~n", [Socket, URL],?DEB), {Socket, url_with_query(URL)}; Other -> ?LOGF("New server configuration (~p:~p, was ~p) on URL ~p~n", [RealIP, RealPort, Other, URL],?DEB), case Socket of {sslsocket, _, _} -> ssl:close(Socket); _ -> gen_tcp:close(Socket) end, {ok, NewSocket} = connect(URL#url.scheme, Host, RealPort), {NewSocket, url_with_query(URL)} end. url_with_query(#url{path=Path, querypart=[]}) -> Path; url_with_query(#url{path=Path, querypart=Query}) -> Path ++"?"++Query. connect(Scheme, Host, Port)-> case Scheme of https -> {ok, _} = ssl:connect(Host,Port, [{active, once}]); _ -> {ok, _} = gen_tcp:connect(Host,Port, [{active, once}, {recbuf, ?tcp_buffer}, {sndbuf, ?tcp_buffer} ]) end. %%-------------------------------------------------------------------- %% Func: record_http_request/2 %% Purpose: record request given State=#state_rec and Request=#http_request %% Returns: {ok, NewState} %%-------------------------------------------------------------------- record_request(State=#state_rec{prev_host=Host, prev_port=Port, prev_scheme=Scheme}, #http_request{method = Method, url = RequestURI, version = HTTPVersion, headers = ParsedHeader,body=Body}) -> FullURL = ts_utils:to_https({url, RequestURI}), {URL,NewPort,NewHost, NewScheme} = case ts_config_http:parse_URL(FullURL) of #url{path=RelURL,host=Host,port=Port,querypart=[],scheme=Scheme}-> {RelURL, Port, Host, Scheme}; #url{path=RelURL,host=Host,port=Port,querypart=Args,scheme=Scheme}-> {RelURL++"?"++Args, Port, Host, Scheme}; #url{host=Host2,port=Port2,scheme=Sc2}-> {FullURL,Port2,Host2,Sc2 } end, Fd = State#state_rec.logfd, URL2 = ts_utils:export_text(URL), io:format(Fd," State#state_rec.ext_file_id; _ -> Id=State#state_rec.ext_file_id, case save_binary_post(ts_utils:key1search(ParsedHeader,"content-type")) of true -> FileName=ts_utils:append_to_filename(State#state_rec.log_file,".xml","-"++integer_to_list(Id)++".bin"), ?LOGF("multipart/form-data, write body data in external binary file ~s~n",[FileName],?NOTICE), ok = file:write_file(FileName,list_to_binary(Body)), io:format(Fd," contents_from_file='~s' ", [FileName]), Id+1; false -> Body2 = ts_utils:export_text(Body), ?LOG("Write body data in XML encoded string ~n",?NOTICE), io:format(Fd," contents='~s' ", [Body2]), Id end end, %% Content-type recording (This is useful for SOAP post for example): record_header(Fd,ParsedHeader,"content-type", "content_type='~s' "), record_header(Fd,ParsedHeader,"if-modified-since", "if_modified_since='~s' "), io:format(Fd,"method='~s'>", [Method]), record_header(Fd,ParsedHeader,"authorization", "~n "), %% SOAP Support: Need to record use of the SOAPAction header record_header(Fd,ParsedHeader,"soapaction", "~n ~n", fun(A) -> string:strip(A,both,$") end ), %" io:format(Fd,"~n",[]), {ok,State#state_rec{prev_port=NewPort,ext_file_id=NewId,prev_host=NewHost,prev_scheme=NewScheme}}. %% should we save the content of a POST in an external binary file ? save_binary_post("multipart/form-data"++_Tail) -> true; save_binary_post("application/x-amf") -> true; save_binary_post(_) -> false. %%-------------------------------------------------------------------- %% Func: decode_basic_auth/1 %% Purpose: decode base64 encoded user passwd for basic authentication %% Returns: {User, Passwd} %%-------------------------------------------------------------------- decode_basic_auth(Base64)-> AuthStr= ts_utils:decode_base64(Base64), Sep = string:chr(AuthStr,$:), {string:substr(AuthStr,1,Sep-1),string:substr(AuthStr,Sep+1)}. %%-------------------------------------------------------------------- %% Func: record_header/4 %%-------------------------------------------------------------------- record_header(Fd, Headers, "authorization", Msg)-> %% special case for authorization case ts_utils:key1search(Headers,"authorization") of "Basic " ++ Base64 -> {User,Passwd} = decode_basic_auth(Base64), io:format(Fd, Msg, [User,Passwd]); _ -> ok end; record_header(Fd, Headers, HeaderName, Msg)-> %% record Msg as it is given record_header(Fd, Headers,HeaderName, Msg, fun(A)->A end). %%-------------------------------------------------------------------- record_header(Fd, Headers,HeaderName, Msg, Fun)-> case ts_utils:key1search(Headers,HeaderName) of undefined -> ok; Value -> io:format(Fd,Msg,[Fun(Value)]) end. tsung-1.4.2/src/tsung_recorder/ts_proxy_listener.erl0000644000201100017670000002042711701017117022460 0ustar nniclausdream%%% %%% Copyright IDEALX S.A.S. 2003 %%% %%% Author : Nicolas Niclausse %%% Created: 22 Dec 2003 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%%------------------------------------------------------------------- %%% File : ts_proxy_listener.erl %%% Author : %%% Description : %%% Created : 22 Dec 2003 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_proxy_listener). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -behaviour(gen_server). %%-------------------------------------------------------------------- %% Include files %%-------------------------------------------------------------------- -include("ts_profile.hrl"). -include("ts_recorder.hrl"). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% API -export([start/0]). %% Self callbacks -record(state, { plugin, acceptsock, % The socket we are accept()ing at acceptloop_pid, % The PID of the companion process that blocks % in accept(). accept_count = 0 % The number of accept()s done so far. }). %%==================================================================== %% Server and API functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: start/0 %% Description: starts a listener process. %%-------------------------------------------------------------------- start()-> gen_server:start_link({global, ?MODULE}, ?MODULE, [], []). %%==================================================================== %% gen_server callback functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: init/1 %% Description: Initiates the server. This is launched from the %% subprocess and should return a state record. The argument %% is a configuration function %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%-------------------------------------------------------------------- init(_Config) -> State=#state{plugin=?config(plugin)}, activate(State). %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages %% Purpose: The companion process does synchronous calls to %% us everytime accept() returns (either as a new socket or an error). %% We get to tell him whether it should continue or stop in the %% return value of the call. We also honor destroy requests from %% , shutting down the whole listener. %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_call(stop, _From, State) -> case State#state.acceptsock of undefined -> nothing; Socket -> ssl:close(Socket) end, NewState=State#state{acceptsock=undefined}, {stop, normal, ok, NewState}; handle_call({accepted, _Tag, ClientSock}, _From, State) -> ?LOGF("New socket:~p~n", [ClientSock],?DEB), case ts_client_proxy_sup:start_child(ClientSock) of {ok, Pid} -> ?LOGF("New connection from~p~n", [inet:peername(ClientSock)],?INFO), ok = gen_tcp:controlling_process(ClientSock, Pid); Error -> ?LOGF("Failed to launch new client ~p~n",[Error],?ERR), gen_tcp:close(ClientSock) end, NumCnx = State#state.accept_count, {reply, continue, State#state{accept_count=NumCnx+1}}; handle_call({accept_error, _Tag, Error}, _From, State) -> ?LOGF("accept() failed ~p~n",[Error],?ERR), case Error of {error, esslaccept} -> %% Someone may be testing the app by trying plain telnets. %% Let go. {reply, continue, State}; _ -> {stop, Error, stop, State} end; handle_call(_, _From, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_cast/2 %% Description: Handling cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_cast(_, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info/2 %% Description: Handling all non call/cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate/2 %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- terminate(_Reason, State) -> case State#state.acceptsock of undefined -> nothing; Socket -> gen_tcp:close(Socket) end, ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%==================================================================== %%% Internal functions %%==================================================================== %%-------------------------------------------------------------------- %% Func: do_activate/1 %% Params: State %% Return: NewState %% Description: activates the listener instance described by State %% and returns the new state. If the instance is already active, do %% nothing. %%-------------------------------------------------------------------- activate(State=#state{plugin=Plugin})-> case State#state.acceptsock of undefined -> Portno=?config(proxy_listen_port), Opts = lists:append(Plugin:socket_opts(), [{reuseaddr, true}, {active, once}]), case gen_tcp:listen(Portno, Opts) of {ok, ServerSock} -> {ok, State#state {acceptsock=ServerSock, acceptloop_pid = spawn_link(ts_utils, accept_loop, [self(), unused, ServerSock])}}; {error, Reason} -> io:format("Error when trying to listen to socket: ~p~n",[Reason]), {stop, Reason} end; _ -> %% Already active {ok, State} end. %% Local Variables: %% tab-width:4 %% End: tsung-1.4.2/src/tsung_recorder/ts_client_proxy.erl0000644000201100017670000002203511701017117022106 0ustar nniclausdream%%% %%% Copyright IDEALX S.A.S. 2003 %%% %%% Author : Nicolas Niclausse %%% Created: 22 Dec 2003 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%%------------------------------------------------------------------- %%% File : ts_client_proxy.erl %%% Author : Nicolas Niclausse %%% Description : handle communication with client and server. %%% %%% Created : 22 Dec 2003 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_client_proxy). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -behaviour(gen_server). %%-------------------------------------------------------------------- %% Include files %%-------------------------------------------------------------------- -include("ts_profile.hrl"). -include("ts_recorder.hrl"). %%-------------------------------------------------------------------- %% External exports -export([start/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, peername/1, send/3]). %%==================================================================== %% External functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: start_link/0 %% Description: Starts the gen_server with the socket given by the listener %%-------------------------------------------------------------------- start(Socket) -> gen_server:start_link(?MODULE, [Socket], []). %%==================================================================== %% Server functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: init/1 %% Description: Initiates the server %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%-------------------------------------------------------------------- init([Socket]) -> ?LOGF("Parent proxy: ~p~n",[?config(parent_proxy)],?DEB), {ok, #proxy{clientsock=Socket, plugin=?config(plugin), parent_proxy=?config(parent_proxy)}}. %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. %%-------------------------------------------------------------------- %% Function: handle_cast/2 %% Description: Handling cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info/2 %% Description: Handling all non call/cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- % client data, parse and send it to the server. handle_info({tcp, ClientSock, String}, State=#proxy{plugin=Plugin}) when ClientSock == State#proxy.clientsock -> ts_utils:inet_setopts(tcp, ClientSock,[{active, once}]), {ok, NewState} = Plugin:parse(State,ClientSock,State#proxy.serversock,String), {noreply, NewState, ?lifetime}; % server data, send it to the client handle_info({Type, ServerSock, Data}, State=#proxy{plugin=Plugin}) when ServerSock == State#proxy.serversock, ((Type == tcp) or (Type == ssl)) -> ts_utils:inet_setopts(Type, ServerSock,[{active, once}]), ?LOGF("Received data from server: ~s~n",[Data],?DEB), {ok,NewData} = Plugin:rewrite_serverdata(Data), send(State#proxy.clientsock, NewData, Plugin), case re:run(NewData, "[cC]onnection: [cC]lose",[{capture,none}]) of nomatch -> {noreply, State, ?lifetime}; _ -> ?LOG("Connection close received,set close=true~n",?DEB), {noreply, State#proxy{close=true}, ?lifetime} end; %%%%%%%%%%%% Errors and termination %%%%%%%%%%%%%%%%%%% % Log who did close the connection, and exit. handle_info({Msg, Socket}, #proxy{ serversock = Socket, close = true }) when Msg==tcp_close; Msg==ssl_closed -> ?LOG("socket closed by server, close client socket also~n",?INFO), {stop, normal, ?lifetime};% close ask by server in previous request handle_info({Msg,Socket},State=#proxy{http_version = HTTPVersion, serversock = Socket }) when Msg==tcp_close; Msg==ssl_closed -> ?LOG("socket closed by server~n",?INFO), case HTTPVersion of "HTTP/1.0" -> {stop, normal, ?lifetime};%Disconnect client if it requires HTTP/1.0 _ -> {noreply, State#proxy{serversock=undefined}, ?lifetime} end; handle_info({Msg, Socket}, State=#proxy{plugin=Plugin}) when Msg == tcp_closed; Msg == ssl_closed-> ?LOG("socket closed by client~n",?INFO), NewState = Plugin:client_close(Socket, State), {stop, normal, NewState}; % Log properly who caused an error, and exit. handle_info({Msg, Socket, Reason}, State) when Msg == tcp_error; Msg == ssl_error -> ?LOGF("error on socket ~p ~p~n",[Socket,Reason],?ERR), {stop, {error, sockname(Socket,State), Reason}, State}; handle_info(timeout, State) -> {stop, timeout, State}; handle_info(Info, State) -> ?LOGF("Uknown data ~p~n",[Info],?ERR), {stop, unknown, State}. %%-------------------------------------------------------------------- %% Function: terminate/2 %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- terminate(_Reason, _State) -> % ts_proxy_recorder:dorecord(endsession), ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- %% Function: sockname/2 %% sockname(Socket,State) %% Purpose: decides whether some socket is the client or the server %% Description: State contains two fields, "serversock" and "clientsock". %% This function searches Socket among the two, and returns %% an appropriate atom among 'server', 'client' and 'unknown'. %% Returns: Sockname %% Types: Sockname -> server | client | unknown %% State -> state_record() sockname(Socket,#proxy{serversock=Socket})-> server; sockname(Socket,#proxy{clientsock=Socket})-> client; sockname(_Socket,_State)-> unknown. peername({sslsocket,A,B})-> ssl:peername({sslsocket,A,B}); peername(Socket) -> prim_inet:peername(Socket). send(_,[],_) -> ok; % no data send({sslsocket,A,B},Data, Plugin) -> ?LOGF("Received data to send to an ssl socket ~p, using plugin ~p ~n", [Data,Plugin],?DEB), {ok, RealData } = Plugin:rewrite_ssl({request,Data}), ?LOGF("Sending data to ssl socket ~p ~p (~p)~n", [A, B, RealData],?DEB), ssl:send({sslsocket,A,B}, RealData); send(undefined,_,_) -> ?LOG("No socket ! Error ~n",?CRIT), erlang:error(error_no_socket_open); send(Socket,Data,_) -> gen_tcp:send(Socket,Data). tsung-1.4.2/src/tsung_recorder/ts_client_proxy_sup.erl0000644000201100017670000000565711701017117023010 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_client_proxy_sup). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -behaviour(supervisor). -include("ts_profile.hrl"). %% External exports -export([start_link/0, start_child/1, active_clients/0]). %% supervisor callbacks -export([init/1]). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). start_child(Profile) -> supervisor:start_child(?MODULE,[Profile]). %%%---------------------------------------------------------------------- %%% Callback functions from supervisor %%%---------------------------------------------------------------------- %%-------------------------------------------------------------------- %% Func: active_clients/0 %% Returns: [ Client ] %% Description: returns the list of all active children on this beam's %% client supervisor. %%-------------------------------------------------------------------- active_clients()-> length(supervisor:which_children(?MODULE)). %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, {SupFlags, [ChildSpec]}} | %% ignore | %% {error, Reason} %%---------------------------------------------------------------------- init([]) -> ?LOG("Starting ~n", ?INFO), SupFlags = {simple_one_for_one,1, ?restart_sleep}, ChildSpec = [ {ts_client_proxy,{ts_client_proxy, start, []}, temporary,2000,worker,[ts_client_proxy]} ], {ok, {SupFlags, ChildSpec}}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- tsung-1.4.2/src/tsung_recorder/tsung_recorder.rel.src0000644000201100017670000000020711701017117022471 0ustar nniclausdream{release, {"tsung_recorder", "&tsung_recorder_vsn&"}, {erts, "&erts_vsn&"}, [ {kernel,"&kernel_vsn&"}, {stdlib,"&stdlib_vsn&"}]}. tsung-1.4.2/src/tsung_recorder/ts_proxy_pgsql.erl0000644000201100017670000005004711701017117021762 0ustar nniclausdream%%% %%% Copyright (C) Nicolas Niclausse 2005 %%% %%% Author : Nicolas Niclausse %%% Created: 09 Nov 2005 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_proxy_pgsql). -vc('$Id$ '). -author('Nicolas.Niclausse@niclux.org'). -include("ts_profile.hrl"). -include("ts_pgsql.hrl"). -include("ts_recorder.hrl"). -export([parse/4, record_request/2, socket_opts/0, gettype/0]). -export([client_close/2]). -export([rewrite_serverdata/1]). -export([rewrite_ssl/1]). %%-------------------------------------------------------------------- %% Func: socket_opts/0 %%-------------------------------------------------------------------- socket_opts() -> [binary]. %%-------------------------------------------------------------------- %% Func: gettype/0 %%-------------------------------------------------------------------- gettype() -> "ts_pgsql". %%-------------------------------------------------------------------- %% Func: rewrite_serverdata/1 %%-------------------------------------------------------------------- rewrite_serverdata(Data)->{ok, Data}. %%-------------------------------------------------------------------- %% Func: rewrite_ssl/1 %%-------------------------------------------------------------------- rewrite_ssl(Data)->{ok, Data}. %%-------------------------------------------------------------------- %% Func: client_close/2 %%-------------------------------------------------------------------- client_close(_Socket,State)-> ts_proxy_recorder:dorecord({#pgsql_request{type=close}}), State. %%-------------------------------------------------------------------- %% Func: parse/4 %% Purpose: parse PGSQL request %% Returns: {ok, NewState} %%-------------------------------------------------------------------- parse(State=#proxy{parse_status=Status},_,_SSocket,Data= << 0,0,0,8,4,210,22,47 >>) when Status==new -> ?LOG("SSL req: ~n",?DEB), Socket = connect(undefined), ts_client_proxy:send(Socket, Data, ?MODULE), {ok, State#proxy{buffer= << >>,serversock = Socket }}; parse(State=#proxy{parse_status=Status},_,ServerSocket,Data) when Status==new -> <> = Data, ?LOGF("Received data from client: size=~p [~p]~n",[PacketSize, StartupPacket],?DEB), <> = StartupPacket, ?LOGF("Received data from client: proto maj=~p min=~p~n",[ProtoMaj, ProtoMin],?DEB), Res= pgsql_util:split_pair_rec(Data2), case get_db_user(Res) of #pgsql_request{database=undefined} -> ?LOGF("Received data from client: split = ~p~n",[Res],?DEB), Socket = connect(ServerSocket), ts_client_proxy:send(Socket, Data, ?MODULE), {ok, State#proxy{buffer= <<>>, serversock = Socket} }; Req -> ?LOGF("Received data from client: split = ~p~n",[Res],?DEB), ts_proxy_recorder:dorecord({Req#pgsql_request{type=connect}}), Socket = connect(ServerSocket), ts_client_proxy:send(Socket, Data, ?MODULE), {ok, State#proxy{parse_status=open, buffer= <<>>, serversock = Socket} } end; parse(State=#proxy{},_,ServerSocket,Data) -> NewData = << (State#proxy.buffer)/binary, Data/binary >>, ?LOGF("Received data from client: ~p~n",[NewData],?DEB), NewState = process_data(State,NewData), ts_client_proxy:send(ServerSocket, Data, ?MODULE), {ok,NewState}. process_data(State,<< >>) -> State; process_data(State,RawData = <>) -> ?LOGF("PGSQL: received [~p] size=~p Pckt size= ~p ~n",[Code, Size, size(Tail)],?DEB), RealSize = Size-4, case RealSize =< size(Tail) of true -> << Packet:RealSize/binary, Data/binary >> = Tail, NewState=case decode_packet(Code, Packet) of {sql, SQL} -> SQLStr= binary_to_list(SQL), ?LOGF("sql = ~s~n",[SQLStr],?DEB), ts_proxy_recorder:dorecord({#pgsql_request{type=sql, sql=SQLStr}}), State#proxy{buffer= <<>>}; terminate -> ts_proxy_recorder:dorecord({#pgsql_request{type=close}}), State#proxy{buffer= <<>>}; {password, Password} -> PwdStr= binary_to_list(Password), ?LOGF("password = ~s~n",[PwdStr],?DEB), ts_proxy_recorder:dorecord({#pgsql_request{type=authenticate, passwd=PwdStr}}), State#proxy{buffer= <<>>}; {parse,{<< >>,StringQuery,Params} } -> %% TODO: handle Parameters if defined ts_proxy_recorder:dorecord({#pgsql_request{type=parse,equery=StringQuery, parameters=Params}}), State#proxy{buffer= <<>>}; {parse,{StringName,StringQuery,Params} } -> %% TODO: handle Parameters if defined ts_proxy_recorder:dorecord({#pgsql_request{type=parse,name_prepared=StringName, parameters=Params, equery=StringQuery}}), State#proxy{buffer= <<>>}; {bind,{Portal,StringQuery,Params, ParamsFormat,ResFormats} } -> R={#pgsql_request{type=bind, name_prepared=StringQuery, name_portal=Portal, parameters=Params,formats=ParamsFormat, formats_results=ResFormats}}, ts_proxy_recorder:dorecord(R), State#proxy{buffer= <<>>}; {copy, CopyData} -> ts_proxy_recorder:dorecord({#pgsql_request{type=copy,equery=CopyData}}), State#proxy{buffer= <<>>}; copydone -> ts_proxy_recorder:dorecord({#pgsql_request{type=copydone}}), State#proxy{buffer= <<>>}; {copyfail,Msg} -> ts_proxy_recorder:dorecord({#pgsql_request{type=copyfail,equery=Msg}}), State#proxy{buffer= <<>>}; {describe,{<<"S">>,Name} } -> ts_proxy_recorder:dorecord({#pgsql_request{type=describe,name_prepared=Name}}), State#proxy{buffer= <<>>}; {describe,{<<"P">>,Name} } -> ts_proxy_recorder:dorecord({#pgsql_request{type=describe,name_portal=Name}}), State#proxy{buffer= <<>>}; {execute,{NamePortal,Max} } -> ts_proxy_recorder:dorecord({#pgsql_request{type=execute,name_portal=NamePortal,max_rows=Max}}), State#proxy{buffer= <<>>}; sync -> ts_proxy_recorder:dorecord({#pgsql_request{type=sync}}), State#proxy{buffer= <<>>}; flush -> ts_proxy_recorder:dorecord({#pgsql_request{type=flush}}), State#proxy{buffer= <<>>} end, process_data(NewState,Data); false -> ?LOG("need more~n",?DEB), State#proxy{buffer=RawData} end; process_data(State,RawData) -> ?LOG("need more~n",?DEB), State#proxy{buffer=RawData}. get_db_user(Arg) -> get_db_user(Arg,#pgsql_request{}). get_db_user([], Req)-> Req; get_db_user([{"user",User}| Rest], Req)-> get_db_user(Rest,Req#pgsql_request{username=User}); get_db_user([{"database",DB}| Rest], Req) -> get_db_user(Rest,Req#pgsql_request{database=DB}); get_db_user([_| Rest], Req) -> get_db_user(Rest,Req). decode_packet($Q, Data)-> Size= size(Data)-1, <> = Data, {sql, SQL}; decode_packet($p, Data) -> Size= size(Data)-1, <> = Data, {password, Password}; decode_packet($X, _) -> terminate; decode_packet($D, << Type:1/binary, Name/binary >>) -> %describe ?LOGF("Extended protocol: describe ~s ~p~n",[Type,Name], ?DEB), case Name of << 0 >> -> {describe,{Type,[]}}; Bin -> {describe,{Type,Bin}} end; decode_packet($S, _Data) -> %sync sync; decode_packet($H, _Data) -> %flush flush; decode_packet($E, Data) -> %execute {NamePortal,PortalSize} = pgsql_util:to_string(Data), S1=PortalSize+1, << _:S1/binary, MaxParams:32/integer >> = Data, ?LOGF("Extended protocol: execute ~p ~p~n",[NamePortal,MaxParams], ?DEB), case MaxParams of 0 -> {execute,{NamePortal,unlimited}}; Val -> {execute,{NamePortal,Val}} end; decode_packet($d, Data) -> %copy ?LOGF("Extended protocol: copy ~p~n",[Data], ?DEB), {copy, Data}; decode_packet($c, _) -> %copy-complete ?LOG("Extended protocol: copydone~n", ?DEB), copydone; decode_packet($f, Data) -> %copy-fail ?LOGF("Extended protocol: copy failure~p~n", [Data],?DEB), {copyfail, Data}; decode_packet($B, Data) -> %bind [NamePortal, StringQuery | _] = split(Data,<<0>>,[global,trim]), Size = size(NamePortal)+size(StringQuery)+2, << _:Size/binary, NParamsFormat:16/integer,Tail1/binary >> = Data, SizeParamsFormat=2*NParamsFormat, % 16 bits << Formats:SizeParamsFormat/binary, NParams:16/integer, Tail2/binary>> = Tail1, ParamsFormat = case {NParamsFormat,Formats} of {0,_} -> none; {1,<< 0:16/integer >> } -> text; {1,<< 1:16/integer >> } -> binary; _ -> auto end, {Params,<< _NFormatRes:16/integer,FormatsResBin/binary >> }=get_params(NParams,Tail2,[]), ResFormats=get_params_format(FormatsResBin,[]), ?LOGF("Extended protocol: bind ~p ~p ~p ~p ~p~n",[NamePortal,StringQuery,Params,ParamsFormat,ResFormats ], ?DEB), {bind,{NamePortal,StringQuery,Params,ParamsFormat,ResFormats}}; decode_packet($P, Data) -> % parse [StringName, StringQuery | _] = split(Data,<<0>>,[global,trim]), Size = size(StringName)+size(StringQuery)+2, << _:Size/binary, NParams:16/integer,ParamsBin/binary >> = Data, Params=get_params_int(NParams,ParamsBin,[]), ?LOGF("Extended protocol: parse ~p ~p ~p~n",[StringName,StringQuery,Params], ?DEB), {parse,{StringName,StringQuery,Params}}. get_params_format(<<>>,Acc) -> lists:reverse(Acc); get_params_format(<<0:16/integer,Tail/binary>>,Acc) -> get_params_format(Tail,[text|Acc]); get_params_format(<<1:16/integer,Tail/binary>>,Acc) -> get_params_format(Tail,[binary|Acc]). get_params(0,Tail,Acc) -> {lists:reverse(Acc), Tail}; get_params(N,<<-1:32/integer-signed,Tail/binary>>,Acc) -> get_params(N-1,Tail,['null'|Acc]); get_params(N,<>,Acc) -> get_params(N-1,Tail,[S|Acc]). get_params_int(0,_,Acc) -> lists:reverse(Acc); get_params_int(N,<>,Acc) -> get_params_int(N-1,Tail,[Val|Acc]). split(Bin,Pattern,Options)-> %% we should remove this once R13B and older are no longer supported by tsung case ts_utils:release_is_newer_or_eq("5.8") of false -> do_split(Bin,Pattern,<<>>,[]); true-> binary:split(Bin,Pattern,Options) end. %% simple binary split; only works when pattern size is 1 do_split(<<>>,_,_,L) -> L; do_split(<>,Pattern,Head,L) -> do_split(Tail,Pattern,<<>>,L ++ [Head]); do_split(<>,Pattern,Head,L) -> do_split(Tail,Pattern,<>,L). %%-------------------------------------------------------------------- %% Func: record_request/2 %% Purpose: record request given State=#state_rec and Request=#pgsql_request %% Returns: {ok, NewState} %%-------------------------------------------------------------------- record_request(State=#state_rec{logfd=Fd, plugin_state=connected}, #pgsql_request{type=connect, username=User, database=DB})-> %% connect request while already connected ?LOG("PGSQL: connect request but we are already connected ! record a close request first ~n", ?WARN), io:format(Fd,"~n ~n", []), io:format(Fd,"", [DB,User]), io:format(Fd,"~n",[]), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=connect, username=User, database=DB})-> io:format(Fd,"", [DB,User]), io:format(Fd,"~n",[]), {ok,State#state_rec{plugin_state=connected }}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=sql, sql=SQL})-> io:format(Fd," ", [SQL]), io:format(Fd,"~n",[]), {ok,State}; record_request(State=#state_rec{plugin_state=undefined}, #pgsql_request{type=close})-> %% not connected, don't record {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=close})-> io:format(Fd,"~n", []), {ok,State#state_rec{plugin_state=undefined}}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=sync})-> io:format(Fd,"~n", []), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=flush})-> io:format(Fd,"~n", []), {ok,State}; record_request(State=#state_rec{logfd=Fd,ext_file_id=Id}, #pgsql_request{type=copy,equery=Bin}) when size(Bin) > 1024-> FileName=ts_utils:append_to_filename(State#state_rec.log_file,".xml","-"++integer_to_list(Id)++".bin"), ok = file:write_file(FileName,Bin), io:format(Fd,"~n", [FileName]), {ok,State#state_rec{ext_file_id=Id+1} }; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=copy,equery=Bin}) -> Str=ts_utils:join(",",binary_to_list(Bin)), io:format(Fd,"~s~n", [Str]), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=copyfail,equery=Bin}) -> Str=binary_to_list(Bin), io:format(Fd,"~n", [Str]), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=copydone}) -> io:format(Fd,"~n", []), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=describe,name_prepared=undefined,name_portal=Val}) -> io:format(Fd,"~n", [Val]), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=describe,name_portal=undefined,name_prepared=Val}) -> io:format(Fd,"~n", [Val]), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=parse,name_portal=undefined, name_prepared=undefined,equery=Query,parameters=Params})-> ParamsStr=ts_utils:join(",",Params), io:format(Fd,"~n", [Query,ParamsStr]), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=parse,name_portal=undefined, name_prepared=Val,equery=Query,parameters=Params})-> ParamsStr=ts_utils:join(",",Params), io:format(Fd,"~n", [Val,ParamsStr,Query]), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=parse,name_portal=Portal, parameters=Params, name_prepared=Prep,equery=Query})-> ParamsStr=ts_utils:join(",",Params), io:format(Fd,"~n", [Portal,Prep,ParamsStr,Query]), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=bind,name_portal = <<>>,name_prepared=Val, parameters=[],formats=ParamsFormat, formats_results=ResFormats})-> ResFormatsStr=ts_utils:join(",",ResFormats), io:format(Fd,"~n", [Val,ParamsFormat,ResFormatsStr]), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=bind, name_portal=Portal, name_prepared=Prep, parameters=Params, formats=ParamsFormat, formats_results=ResFormats})-> ParamsStr=ts_utils:join(",",Params), ResFormatsStr=ts_utils:join(",",ResFormats), io:format(Fd,"~n", [Portal,Prep,ParamsFormat,ResFormatsStr,ParamsStr]), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=execute,name_portal=[],max_rows=unlimited})-> io:format(Fd,"~n", []), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=execute,name_portal=[],max_rows=Max})-> io:format(Fd,"~n", [Max]), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type=execute,name_portal=Portal,max_rows=Max})-> io:format(Fd,"~n", [Portal,Max]), {ok,State}; record_request(State=#state_rec{logfd=Fd}, #pgsql_request{type = authenticate , passwd = Pass }) -> Fd = State#state_rec.logfd, io:format(Fd,"", [Pass]), io:format(Fd,"~n",[]), {ok,State}. connect(undefined) -> {ok, Socket} = gen_tcp:connect(?config(pgsql_server),?config(pgsql_port), [{active, once}, {recbuf, ?tcp_buffer}, {sndbuf, ?tcp_buffer} ]++ socket_opts()), ?LOGF("ok, connected ~p~n",[Socket],?DEB), Socket; connect(Socket) -> Socket. tsung-1.4.2/src/tsung_recorder/tsung_recorder.app.src.in0000644000201100017670000000144311701017117023077 0ustar nniclausdream{application, tsung_recorder, [{description, "tsung recorder"}, {vsn, "%VSN%"}, {modules, [ tsung_recorder, ts_recorder_sup, ts_client_proxy_sup, ts_proxy_recorder, ts_proxy_listener ]}, {registered, [ ts_proxy_recorder, ts_proxy_listener ]}, {env, [ {debug_level, 6}, {ts_cookie, "humhum"}, {log_file, "./tsung.log"}, {plugin, ts_proxy_http}, {parent_proxy, false}, {pgsql_server, "127.0.0.1"}, {pgsql_port, 5432}, {proxy_log_file, "./tsung_recorder"}, {proxy_listen_port, 8090} ]}, {applications, [@ERLANG_APPLICATIONS@]}, {mod, {tsung_recorder, []}} ]}. tsung-1.4.2/src/tsung_controller/0000755000201100017670000000000011701017143016530 5ustar nniclausdreamtsung-1.4.2/src/tsung_controller/ts_os_mon_erlang.erl0000644000201100017670000002561511701017117022576 0ustar nniclausdream%%% %%% Copyright 2008 © Nicolas Niclausse %%% %%% Author : Nicolas Niclausse %%% Created: 21 oct 2008 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_os_mon_erlang). -vc('$Id: ts_os_mon_snmp.erl,v 0.0 2008/10/21 12:57:49 nniclaus Exp $ '). -author('nicolas.niclausse@niclux.org'). -include("ts_profile.hrl"). -include("ts_os_mon.hrl"). -export([start/1, updatestats/2, client_start/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define(NODE, 'os_mon'). -define(PROCNET, "/proc/net/dev"). -record(state,{ mon, % pid of mon server interval, % interval node, % name of node to monitor host, % hostname of server to monitor pid % remote pid }). start(Args) -> ?LOGF("starting os_mon_erlang with args ~p",[Args],?NOTICE), gen_server:start_link(?MODULE, Args, []). %%-------------------------------------------------------------------- %% Function: init/1 %% Description: Initiates the server %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%-------------------------------------------------------------------- init( {Host, {}, Interval, MonServer} ) -> {ok, LocalHost} = ts_utils:node_to_hostname(node()), %% to get the EXIT signal from spawned processes on remote nodes process_flag(trap_exit,true), %% because the stats for cpu has to be called from the same %% process (otherwise the same value (mean cpu% since the system %% last boot) is returned by cpu_sup:util), we must spawn a process %% on the remote node that will do the stats collection and send it back %% to ts_mon case list_to_atom(LocalHost) of Host -> % same host, don't start a new beam ?LOG("Running os_mon on the same host as the controller, use the same beam~n",?INFO), Pid = spawn_link(?MODULE, updatestats, [Interval, MonServer]), {ok, #state{node=node(),mon=MonServer, host=Host,interval=Interval,pid=Pid}}; _ -> erlang:start_timer(?INIT_WAIT, self(), start_beam), {ok, #state{host=Host, mon=MonServer, interval=Interval}} end. %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. %%-------------------------------------------------------------------- %% Function: handle_cast/2 %% Description: Handling cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_cast(Msg, State) -> ?LOGF("handle cast: unknown msg ~p~n",[Msg],?WARN), {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info/2 %% Description: Handling all non call/cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_info({timeout,_Ref,start_beam},State=#state{host=Host})-> case start_beam(Host) of {ok, Node} -> Pong = net_adm:ping(Node), ?LOGF("ping ~p: ~p~n", [Node, Pong],?INFO), load_code([Node]), Pid = spawn_link(Node, ?MODULE, updatestats, [State#state.interval, State#state.mon]), {noreply, State#state{node=Node,pid=Pid}}; {error,{already_running,Node}} -> ?LOGF("Node ~p is already running, start updatestats process~n", [Node],?NOTICE), Pid = spawn_link(Node, ?MODULE, updatestats, [State#state.interval, State#state.mon]), {noreply, State#state{node=Node,pid=Pid}}; Error -> ?LOGF("Fail to start beam on host ~p (~p)~n", [Host, Error],?ERR), {stop, Error, State} end. %%-------------------------------------------------------------------- %% Function: terminate/2 %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %% Function: updatestats/2 %% Purpose: update stats for erlang monitoring. Executed on the remote host %%-------------------------------------------------------------------- updatestats(Interval,Mon_Server) -> Node = atom_to_list(node()), {Cpu, FreeMem, RecvPackets, SentPackets, Load} = node_data(), ts_os_mon:send(Mon_Server,[{sample, {cpu, Node}, Cpu}, {sample, {freemem, Node}, FreeMem}, {sample, {load, Node}, Load}, {sample_counter, {recvpackets, Node}, RecvPackets}, {sample_counter, {sentpackets, Node}, SentPackets}]), timer:sleep(Interval), updatestats(Interval,Mon_Server). %%-------------------------------------------------------------------- %% Function: client_start/0 %% Purpose: Start the monitor tools on the node that you want to spy on %%-------------------------------------------------------------------- client_start() -> application:start(stdlib), application:start(sasl), application:start(os_mon). %%-------------------------------------------------------------------- %% Function: load_code/1 %% Purpose: Load ts_os_mon code on all Erlang nodes %%-------------------------------------------------------------------- load_code(Nodes) -> ?LOGF("loading tsung monitor on nodes ~p~n", [Nodes], ?NOTICE), LoadCode = fun(Mod)-> {_, Binary, _} = code:get_object_code(Mod), rpc:multicall(Nodes, code, load_binary, [Mod, Mod, Binary], infinity) end, LoadRes = lists:map(LoadCode, [ts_mon, ?MODULE, ts_os_mon, ts_utils]), Res = rpc:multicall(Nodes, ?MODULE, client_start, [], infinity), %% first value of load call is garbage ?LOGF("load_code: ~p start: ~p ~n", [LoadRes, Res],?DEB), ok. %%-------------------------------------------------------------------- %% Func: node_data/0 %%-------------------------------------------------------------------- node_data() -> {RecvPackets, SentPackets} = get_os_data(packets), {get_os_data(cpu), get_os_data(freemem), RecvPackets, SentPackets, get_os_data(load)}. %%-------------------------------------------------------------------- %% Func: get_os_data/1 %%-------------------------------------------------------------------- %% Return node cpu utilisation get_os_data(cpu) -> cpu_sup:util(); %% Return node cpu average load on 1 minute; get_os_data(load) -> cpu_sup:avg1()/256; get_os_data(DataName) -> get_os_data(DataName,os:type()). %%-------------------------------------------------------------------- %% Func: get_os_data/2 %%-------------------------------------------------------------------- %% Return free memory in bytes. %% Use the result of the free commands on Linux and os_mon on all %% other platforms get_os_data(freemem, {unix, linux}) -> Result = os:cmd("free | grep '\\-/\\+'"), [_, _, _, Free] = string:tokens(Result, " \n"), list_to_integer(Free)/1024; get_os_data(freemem, {unix, sunos}) -> Result = os:cmd("vmstat 1 2 | tail -1"), [_, _, _, _, Free | _] = string:tokens(Result, " "), list_to_integer(Free)/1024; get_os_data(freemem, _OS) -> Data = memsup:get_system_memory_data(), {value,{free_memory,FreeMem}} = lists:keysearch(free_memory, 1, Data), %% We use Megabytes FreeMem/1048576; %% Return packets sent/received on network interface get_os_data(packets, {unix, linux}) -> get_os_data(packets, {unix, linux},?PROCNET); %% solaris, contributed by Jason Tucker get_os_data(packets, {unix, sunos}) -> Result = os:cmd("netstat -in 1 1 | tail -1"), [_, _, _, _, _, RecvPackets, _, SentPackets | _] = string:tokens(Result, " "), {list_to_integer(RecvPackets), list_to_integer(SentPackets)}; get_os_data(packets, _OS) -> {0, 0 }. % FIXME: not implemented for other arch. %% packets Linux, special case with File as a variable to easy testing get_os_data(packets, {unix, linux},File) -> {ok, Lines} = ts_utils:file_to_list(File), %% get the cumulative traffic of all ethX interfaces Eth=[io_lib:fread("~d~d~d~d~d~d~d~d~d~d", X) || {E,X}<-lists:map(fun(Y)->ts_utils:split2(Y,$:,strip) end ,Lines), string:str(E,"eth") /= 0], Fun = fun (A, {Rcv, Sent}) -> {ok,[_RcvBytes,RcvPkt,_,_,_,_,_,_,_SentBytes,SentPkt],_}=A, {Rcv+RcvPkt,Sent+SentPkt} end, lists:foldl(Fun, {0,0}, Eth). %%-------------------------------------------------------------------- %% Function: start_beam/1 %% Purpose: Start an Erlang node on given host %%-------------------------------------------------------------------- start_beam(Host) -> Args = ts_utils:erl_system_args(), ?LOGF("starting os_mon beam (~p) on host ~p with Args ~p~n", [?NODE,Host, Args], ?INFO), slave:start(list_to_atom(Host), ?NODE, Args). tsung-1.4.2/src/tsung_controller/ts_config_http.erl0000644000201100017670000004107411701017117022255 0ustar nniclausdream%%% %%% Copyright IDEALX S.A.S. 2004 %%% %%% Author : Nicolas Niclausse %%% Created: 20 Apr 2004 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% common functions used by http clients to parse config -module(ts_config_http). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -export([parse_config/2, parse_URL/1, set_port/1, set_scheme/1, check_user_agent_sum/1, set_query/1]). -include("ts_profile.hrl"). -include("ts_http.hrl"). -include("ts_config.hrl"). -include("xmerl.hrl"). %%---------------------------------------------------------------------- %% Func: parse_config/2 %% Args: Element, Config %% Returns: List %% Purpose: parse a request defined in the XML config file %%---------------------------------------------------------------------- %% Parsing other elements parse_config(Element = #xmlElement{name=dyn_variable}, Conf = #config{}) -> ts_config:parse(Element,Conf); parse_config(Element = #xmlElement{name=http}, Config=#config{curid = Id, session_tab = Tab, servers = Servers, sessions = [CurS | _], dynvar=DynVar, subst = SubstFlag, match=MatchRegExp}) -> Version = ts_config:getAttr(string,Element#xmlElement.attributes, version, "1.1"), URL = ts_config:getAttr(Element#xmlElement.attributes, url), Contents = case ts_config:getAttr(string, Element#xmlElement.attributes, contents_from_file) of [] -> C=ts_config:getAttr(Element#xmlElement.attributes, contents), list_to_binary(C); FileName -> {ok, FileContent} = file:read_file(FileName), FileContent end, UseProxy = case ets:lookup(Tab,{http_use_server_as_proxy}) of [] -> false; _ -> true end, %% Apache Tomcat applications need content-type informations to read post forms ContentType = ts_config:getAttr(string,Element#xmlElement.attributes, content_type, "application/x-www-form-urlencoded"), Date = ts_config:getAttr(string, Element#xmlElement.attributes, 'if_modified_since', undefined), Method = list_to_atom(ts_utils:to_lower(ts_config:getAttr(string, Element#xmlElement.attributes, method, "get"))), Request = #http_request{url = URL, method = Method, version = Version, get_ims_date= Date, content_type= ContentType, body = Contents}, %% SOAP Support: Add SOAPAction header to the message Request2 = case lists:keysearch(soap,#xmlElement.name, Element#xmlElement.content) of {value, SoapEl=#xmlElement{} } -> SOAPAction = ts_config:getAttr(SoapEl#xmlElement.attributes, action), Request#http_request{soap_action=SOAPAction}; _ -> Request end, PreviousHTTPServer = get_previous_http_server(Tab, CurS#session.id), %% client side cookies Cookies = parse_cookie(Element#xmlElement.content, []), %% Custom HTTP headers Headers= parse_headers(Element#xmlElement.content, Request2#http_request.headers), Request3 = Request2#http_request{headers=Headers,cookie=Cookies}, %% HTTP Authentication Msg = case lists:keysearch(www_authenticate, #xmlElement.name, Element#xmlElement.content) of {value, AuthEl=#xmlElement{} } -> UserId= ts_config:getAttr(string,AuthEl#xmlElement.attributes, userid, undefined), Passwd= ts_config:getAttr(string,AuthEl#xmlElement.attributes, passwd, undefined), NewReq=Request3#http_request{userid=UserId, passwd=Passwd}, set_msg(NewReq, {SubstFlag, MatchRegExp, UseProxy, Servers, PreviousHTTPServer, Tab, CurS#session.id} ); _ -> set_msg(Request3, {SubstFlag, MatchRegExp, UseProxy, Servers, PreviousHTTPServer, Tab, CurS#session.id} ) end, ts_config:mark_prev_req(Id-1, Tab, CurS), ets:insert(Tab,{{CurS#session.id, Id},Msg#ts_request{endpage=true, dynvar_specs=DynVar}}), lists:foldl( fun(A,B)->ts_config:parse(A,B) end, Config#config{dynvar=[]}, Element#xmlElement.content); %% Parsing default values parse_config(Element = #xmlElement{name=option}, Conf = #config{session_tab = Tab}) -> case ts_config:getAttr(Element#xmlElement.attributes, name) of "user_agent" -> lists:foldl( fun(A,B)->parse_config(A,B) end, Conf, Element#xmlElement.content); "http_use_server_as_proxy" -> Val = ts_config:getAttr(Element#xmlElement.attributes, value), ets:insert(Tab,{{http_use_server_as_proxy}, Val}) end, lists:foldl( fun(A,B)->ts_config:parse(A,B) end, Conf, Element#xmlElement.content); %% Parsing user_agent parse_config(Element = #xmlElement{name=user_agent}, Conf = #config{session_tab = Tab}) -> Proba = ts_config:getAttr(integer,Element#xmlElement.attributes, probability), ValRaw = ts_config:getText(Element#xmlElement.content), Val = ts_utils:clean_str(ValRaw), ?LOGF("Get user agent: ~p ~p ~n",[Proba, Val],?WARN), Previous = case ets:lookup(Tab, {http_user_agent, value}) of [] -> []; [{_Key,Old}] -> Old end, ets:insert(Tab,{{http_user_agent, value}, [{Proba, Val}|Previous]}), lists:foldl( fun(A,B)->parse_config(A,B) end, Conf, Element#xmlElement.content); %% Parsing other elements parse_config(Element = #xmlElement{}, Conf = #config{}) -> ts_config:parse(Element,Conf); %% Parsing non #xmlElement elements parse_config(_, Conf = #config{}) -> Conf. get_previous_http_server(Ets, Id) -> case ets:lookup(Ets, {http_server, Id}) of [] -> []; [{_Key,PrevServ}] -> PrevServ end. parse_headers([], Headers) -> Headers; parse_headers([Element = #xmlElement{name=http_header} | Tail], Headers) -> Name = ts_config:getAttr(string, Element#xmlElement.attributes, name), Value = ts_config:getAttr(string, Element#xmlElement.attributes, value), EncodedValue = case ts_config:getAttr(atom, Element#xmlElement.attributes, encoding, none) of base64 -> ts_utils:encode_base64(Value); none -> Value end, parse_headers(Tail, [{Name, EncodedValue} | Headers]); parse_headers([_| Tail], Headers) -> parse_headers(Tail, Headers). parse_cookie([], Cookies) -> Cookies; parse_cookie([Element = #xmlElement{name=add_cookie} | Tail], Cookies) -> Key = ts_config:getAttr(string, Element#xmlElement.attributes, key), Value = ts_config:getAttr(string, Element#xmlElement.attributes, value), Path = ts_config:getAttr(string, Element#xmlElement.attributes, path,"/"), Domain= ts_config:getAttr(string,Element#xmlElement.attributes,domain,"."), parse_cookie(Tail,[#cookie{key=Key,value=Value,path=Path,domain=Domain}|Cookies]); parse_cookie([_| Tail], Cookies) -> parse_cookie(Tail, Cookies). %%---------------------------------------------------------------------- %% Func: set_msg/2 %% Returns: #ts_request record %% Purpose: build the #ts_request record from an #http_request, %% and Substition def. %%---------------------------------------------------------------------- %% if the URL is full (http://...), we parse it and get server host, %% port and scheme from the URL and override the global setup of the %% server. These informations are stored in the #ts_request record. set_msg(HTTP=#http_request{url="http" ++ URL}, {SubstFlag, MatchRegExp, UseProxy, [Server|_], _PrevHTTPServer, Tab, Id}) -> URLrec = parse_URL("http" ++ URL), Path = set_query(URLrec), HostHeader = set_host_header(URLrec), Port = set_port(URLrec), Scheme = set_scheme({URLrec#url.scheme,Server#server.type}), ets:insert(Tab,{{http_server, Id}, {HostHeader, URLrec#url.scheme}}), {RealServer, RealPath} = case UseProxy of true -> {Server, "http"++ URL}; false -> {#server{host=URLrec#url.host, port=Port, type=Scheme}, Path} end, set_msg2(HTTP#http_request{url=RealPath, host_header = HostHeader}, #ts_request{ack = parse, subst = SubstFlag, match = MatchRegExp, host = RealServer#server.host, scheme = RealServer#server.type, port = RealServer#server.port}); %% relative URL, no previous HTTP server, use proxy, error ! set_msg(_, {_, _, true, _Server, [],_Tab,_Id}) -> ?LOG("Need absolute URL when using a proxy ! Abort",?ERR), throw({error, badurl_proxy}); %% url head is a dynvar, don't preset host header set_msg(HTTP=#http_request{url="%%" ++ _TailURL}, {true, MatchRegExp, _Proxy, _Servers, _Headers,_Tab,_Id}) -> set_msg2(HTTP, #ts_request{ack = parse, subst = true, match = MatchRegExp }); %% relative URL, no proxy, a single server => we can preset host header at configuration time set_msg(HTTPRequest, Args={_SubstFlag, _MatchRegExp, false, [Server], [],_Tab,_Id}) -> ?LOG("Relative URL, single server ",?NOTICE), URL = server_to_url(Server) ++ HTTPRequest#http_request.url, set_msg(HTTPRequest#http_request{url=URL}, Args); %% relative URL, no proxy, several servers: don't set host header %% since the real server will be choose at run time set_msg(HTTPRequest, {SubstFlag, MatchRegExp, false, _Servers, [],_Tab,_Id}) -> set_msg2(HTTPRequest, #ts_request{ack = parse, subst = SubstFlag, match = MatchRegExp }); %% relative URL, no proxy set_msg(HTTPRequest, {SubstFlag, MatchRegExp, false, _Server, {HostHeader,_},_Tab,_Id}) -> set_msg2(HTTPRequest#http_request{host_header= HostHeader}, #ts_request{ack = parse, subst = SubstFlag, match = MatchRegExp }); %% relative URL, use proxy set_msg(HTTPRequest, {SubstFlag, MatchRegExp, true, Server, {HostHeader, PrevScheme},Tab,Id}) -> %% set absolute URL using previous Server used. URL = atom_to_list(PrevScheme) ++ "://" ++ HostHeader ++ HTTPRequest#http_request.url, set_msg(HTTPRequest#http_request{url=URL}, {SubstFlag, MatchRegExp, true, Server, {HostHeader, PrevScheme},Tab,Id}). %% Func: set_mgs2/3 %% Purpose: set param in ts_request set_msg2(HTTPRequest, Msg) -> Msg#ts_request{ param = HTTPRequest }. server_to_url(#server{port=443, host= Host, type= Type}) when Type==ssl orelse Type==ssl6-> "https://" ++ encode_ipv6_address(Host); server_to_url(#server{port=Port, host= Host, type= Type})when Type==ssl orelse Type==ssl6-> "https://" ++ encode_ipv6_address(Host) ++ ":" ++ integer_to_list(Port); server_to_url(#server{port=80, host= Host})-> "http://" ++ encode_ipv6_address(Host); server_to_url(#server{port=Port, host= Host})-> "http://" ++ encode_ipv6_address(Host) ++ ":" ++ integer_to_list(Port). %%-------------------------------------------------------------------- %% Func: set_host_header/1 %%-------------------------------------------------------------------- %% if port is undefined, don't need to set port, because it use the default (80 or 443) set_host_header(#url{host=Host,port=undefined}) -> encode_ipv6_address(Host); set_host_header(#url{host=Host,port=Port}) when is_integer(Port) -> encode_ipv6_address(Host) ++ ":" ++ integer_to_list(Port). encode_ipv6_address(Host) when hd(Host)==$[ -> %% ipv6; already using [] (rfc2732), no need to add Host; encode_ipv6_address(Host)-> case string:chr(Host,$:) of 0 -> Host; % regular name or ipv4 address _ -> "["++Host++"]" end. %%-------------------------------------------------------------------- %% Func: set_port/1 %% Purpose: Returns port according to scheme if not already defined %% Returns: PortNumber (integer) %%-------------------------------------------------------------------- set_port(#url{scheme=https,port=undefined}) -> 443; set_port(#url{scheme=http,port=undefined}) -> 80; set_port(#url{port=Port}) when is_integer(Port) -> Port; set_port(#url{port=Port}) -> integer_to_list(Port). %% @spec set_scheme({http|https,gen_tcp|gen_tcp6|ssl|ssl6})-> gen_tcp|gen_tcp6|ssl|ssl6 %% @doc set scheme for given protocol and server setup. If the main %% server is configured with IPv6, we assume that the we should also %% use IPv6 for the given absolut URL %% @end set_scheme({http, gen_tcp6}) -> gen_tcp6; set_scheme({http, ssl6}) -> gen_tcp6; set_scheme({http, _}) -> gen_tcp; set_scheme({https, ssl6}) -> ssl6; set_scheme({https, gen_tcp6})-> ssl6; set_scheme({https, _}) -> ssl. set_query(URLrec = #url{querypart=""}) -> URLrec#url.path; set_query(URLrec = #url{}) -> URLrec#url.path ++ "?" ++ URLrec#url.querypart. %%---------------------------------------------------------------------- %% Func: parse_URL/1 %% Returns: #url %%---------------------------------------------------------------------- parse_URL("https://" ++ String) -> parse_URL(host, String, [], #url{scheme=https}); parse_URL("http://" ++ String) -> parse_URL(host, String, [], #url{scheme=http}); parse_URL(String) when is_list(String) -> case string:tokens(String,":") of [Host, Port] -> #url{scheme=connect, host=Host, port=list_to_integer(Port)}; RelativeURL when is_list(RelativeURL) -> parse_URL(path, RelativeURL, [], #url{scheme=http}) end. %%---------------------------------------------------------------------- %% Func: parse_URL/4 (inspired by yaws_api.erl) %% Returns: #url record %%---------------------------------------------------------------------- % parse host parse_URL(host, [$[|Tail] , [], URL) -> % host starts with '[': ipv6 address in url {match,[Host,Rest]} = re:run(Tail,"^([^\\]]*)\\](.*)",[{capture,all_but_first,list}]), parse_URL(host, Rest, lists:reverse(Host), URL); parse_URL(host, [], Acc, URL) -> % no path or port URL#url{host=lists:reverse(Acc), path= "/"}; parse_URL(host, [$/|Tail], Acc, URL) -> % path starts here parse_URL(path, Tail, "/", URL#url{host=lists:reverse(Acc)}); parse_URL(host, [$:|Tail], Acc, URL) -> % port starts here parse_URL(port, Tail, [], URL#url{host=lists:reverse(Acc)}); parse_URL(host, [H|Tail], Acc, URL) -> parse_URL(host, Tail, [H|Acc], URL); % parse port parse_URL(port,[], Acc, URL) -> URL#url{port=list_to_integer(lists:reverse(Acc)), path= "/"}; parse_URL(port,[$/|T], Acc, URL) -> parse_URL(path, T, "/", URL#url{port=list_to_integer(lists:reverse(Acc))}); parse_URL(port,[H|T], Acc, URL) -> parse_URL(port, T, [H|Acc], URL); % parse path parse_URL(path,[], Acc, URL) -> URL#url{path=lists:reverse(Acc)}; parse_URL(path,[$?|T], Acc, URL) -> URL#url{path=lists:reverse(Acc), querypart=T}; parse_URL(path,[H|T], Acc, URL) -> parse_URL(path, T, [H|Acc], URL). % check if the sum of all user agent probabilities is equal to 100% check_user_agent_sum(Tab) -> case ets:lookup(Tab, {http_user_agent, value}) of [] -> ok; % no user agent, will use the default one. [{_Key, UserAgents}] -> ts_utils:check_sum(UserAgents, 1, ?USER_AGENT_ERROR_MSG) end. tsung-1.4.2/src/tsung_controller/ts_os_mon.erl0000644000201100017670000000511211701017117021234 0ustar nniclausdream%%% This code was developped by Mickael Remond %%% and contributors (their names can %%% be found in the CONTRIBUTORS file). Copyright (C) 2003 Mickael %%% Remond %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% Created : 23 Dec 2003 by Mickael Remond -module(ts_os_mon). -author('mickael.remond@erlang-fr.org'). -modifiedby('nicolas@niclux.org'). -vc('$Id$ '). %%-------------------------------------------------------------------- %% Include files %%-------------------------------------------------------------------- -include("ts_profile.hrl"). -include("ts_os_mon.hrl"). %%-------------------------------------------------------------------- %% External exports -export([activate/0, send/2]). %%% send data back to the controlling node send(Mon_Server, Data) when is_pid(Mon_Server) -> Mon_Server ! {add, Data}; send(Mon_Server, Data) -> gen_server:cast(Mon_Server, {add, Data}). %%-------------------------------------------------------------------- %% Function: activate/0 %% Purpose: This is used by tsung to start the cluster monitor service %% It will only be started if there are cluster/monitor@host element %% in the config file. %%-------------------------------------------------------------------- activate() -> case ts_config_server:get_monitor_hosts() of [] -> ?LOG("os_mon disabled",?NOTICE), ok; Hosts -> Fun = fun({HostStr,{Type,Options}}) -> Args= {HostStr, Options, ?INTERVAL,{global, ts_mon}}, ts_os_mon_sup:start_child(Type, Args) end, lists:foreach(Fun,Hosts) end. tsung-1.4.2/src/tsung_controller/ts_config_raw.erl0000644000201100017670000000631011701017117022061 0ustar nniclausdream%%% %%% Copyright IDEALX S.A.S. 2004 %%% %%% Author : Nicolas Niclausse %%% Created: 20 Apr 2004 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% common functions used by http clients to parse config -module(ts_config_raw). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -export([parse_config/2]). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("ts_raw.hrl"). -include("xmerl.hrl"). %%---------------------------------------------------------------------- %% Func: parse_config/2 %% Args: Element, Config %% Returns: List %% Purpose: parse a request defined in the XML config file %%---------------------------------------------------------------------- %% Parsing other elements parse_config(Element = #xmlElement{name=dyn_variable}, Conf = #config{}) -> ts_config:parse(Element,Conf); parse_config(Element = #xmlElement{name=raw, attributes=Attrs}, Config=#config{curid = Id, session_tab = Tab, sessions = [CurS | _], dynvar=DynVar, subst = SubstFlag, match=MatchRegExp}) -> Ack = ts_config:getAttr(atom,Attrs, ack, no_ack), Req = case ts_config:getAttr(string,Attrs, datasize) of [] -> Data = ts_config:getAttr(string,Attrs, data), #raw{data=Data}; "%%" ++ Tail -> #raw{datasize="%%"++Tail}; _ -> % datasize is not a dynamic variable; must be an integer #raw{datasize=ts_config:getAttr(integer,Attrs, datasize)} end, ts_config:mark_prev_req(Id-1, Tab, CurS), Msg=#ts_request{ack = Ack, subst = SubstFlag, match = MatchRegExp, param = Req}, ets:insert(Tab,{{CurS#session.id, Id},Msg#ts_request{endpage=true, dynvar_specs=DynVar}}), lists:foldl( fun(A,B)->ts_config:parse(A,B) end, Config#config{dynvar=[]}, Element#xmlElement.content); %% Parsing other elements parse_config(Element = #xmlElement{}, Conf = #config{}) -> ts_config:parse(Element,Conf); %% Parsing non #xmlElement elements parse_config(_, Conf = #config{}) -> Conf. tsung-1.4.2/src/tsung_controller/ts_config.erl0000644000201100017670000013432611701017117021221 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2003 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%%---------------------------------------------------------------------- %%% @author Nicolas Niclausse %%% @todo learn how to use xmerl correctly %%% @doc Read the tsung XML config file. Currently, it %%% work by parsing the #xmlElement record by hand ! %%% @end %%% Created : 3 Dec 2003 by Nicolas Niclausse %%%---------------------------------------------------------------------- -module(ts_config). -author('nicolas@niclux.org'). -vc('$Id$ '). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("xmerl.hrl"). -export([read/2, getAttr/2, getAttr/3, getAttr/4, getText/1, parse/2, get_default/3, mark_prev_req/3, get_batch_nodes/1 ]). %%%---------------------------------------------------------------------- %%% @spec: read(Filename::string(), LogDir::string()) -> %%% {ok, Config::#config{} } | {error, Reason::term()} %%% @doc: read and parse the xml config file %%% @end %%%---------------------------------------------------------------------- read(Filename=standard_io, LogDir) -> ?LOG("Reading config file from stdin~n", ?NOTICE), XML = read_stdio(), handle_read(catch xmerl_scan:string(XML, [{fetch_path,["/usr/share/tsung/","./"]}, {validation,true}]),Filename,LogDir); read(Filename, LogDir) -> ?LOGF("Reading config file: ~s~n", [Filename], ?NOTICE), handle_read(catch xmerl_scan:file(Filename, [{fetch_path,["/usr/share/tsung/","./"]}, {validation,true}]),Filename,LogDir). handle_read( {Root = #xmlElement{}, _Tail}, Filename, LogDir) -> Table = ets:new(sessiontable, [ordered_set, protected]), backup_config(LogDir, Filename, Root), {ok, parse(Root, #config{session_tab = Table, proto_opts=#proto_opts{}})}; handle_read({error,Reason},_,_) -> {error, Reason}; handle_read({'EXIT',Reason},_,_) -> {error, Reason}. %%%---------------------------------------------------------------------- %%% Function: parse/2 %%% Purpose: parse the xmerl structure %%%---------------------------------------------------------------------- parse(Element = #xmlElement{parents = [], attributes=Attrs}, Conf=#config{}) -> Loglevel = getAttr(string, Attrs, loglevel, "notice"), Dump = getAttr(string, Attrs, dumptraffic, "false"), BackEnd = getAttr(atom, Attrs, backend, text), DumpType = case Dump of "false" -> none; "true" -> full; "light" -> light; "protocol" -> protocol end, lists:foldl(fun parse/2, Conf#config{dump= DumpType, stats_backend=BackEnd, loglevel= ts_utils:level2int(Loglevel)}, Element#xmlElement.content); %% parsing the Server elements parse(Element = #xmlElement{name=server, attributes=Attrs}, Conf=#config{servers=ServerList}) -> Server = getAttr(Attrs, host), Port = getAttr(integer, Attrs, port), Type = set_net_type(getAttr(Attrs, type)), lists:foldl(fun parse/2, Conf#config{servers = [#server{host=Server, port=Port, type=Type }|ServerList]}, Element#xmlElement.content); %% Parsing the cluster monitoring element (monitor) parse(Element = #xmlElement{name=monitor, attributes=Attrs}, Conf = #config{monitor_hosts=MHList}) -> Host = getAttr(Attrs, host), Type = case getAttr(atom, Attrs, type, erlang) of erlang -> {erlang, {}}; snmp -> case lists:keysearch(snmp,#xmlElement.name, Element#xmlElement.content) of {value, SnmpEl=#xmlElement{} } -> Port = getAttr(integer,SnmpEl#xmlElement.attributes, port, ?config(snmp_port)), Community = getAttr(string,SnmpEl#xmlElement.attributes, community, ?config(snmp_community)), Version = getAttr(atom,SnmpEl#xmlElement.attributes, version, ?config(snmp_version)), %% parse OIDS def TmpConf = lists:foldl(fun parse/2, Conf#config{oids=[]}, SnmpEl#xmlElement.content), {snmp, {Port, Community, Version,TmpConf#config.oids}}; _ -> {snmp, {?config(snmp_port), ?config(snmp_community), ?config(snmp_version),[]}} end; munin -> case lists:keysearch(munin,#xmlElement.name, Element#xmlElement.content) of {value, MuninEl=#xmlElement{} } -> Port = getAttr(integer,MuninEl#xmlElement.attributes, port, ?config(munin_port)), {munin, {Port}}; _ -> {munin, {?config(munin_port) }} end end, NewMon = case getAttr(atom, Attrs, batch, false) of true -> Nodes = lists:usort(get_batch_nodes(list_to_atom(Host))), lists:map(fun(N)-> {N, Type} end, Nodes); _ -> [{Host, Type}] end, lists:foldl(fun parse/2, Conf#config{monitor_hosts = lists:append(MHList, NewMon)}, Element#xmlElement.content); parse(#xmlElement{name=oid, attributes=Attrs}, Conf=#config{oids=OIDS}) -> OIDStr = getAttr(Attrs, value), OID = lists:map(fun erlang:list_to_integer/1, string:tokens(OIDStr,".")), Name = getAttr(atom, Attrs, name), Type = case getAttr(atom, Attrs, type, sample) of sample -> sample; counter -> sample_counter; sum -> sum end, Snippet = getAttr(string, Attrs, eval, "fun(X)-> X end."), Fun= ts_utils:eval(Snippet), true = is_function(Fun, 1), Conf#config{oids=[{OID,Name,Type,Fun}| OIDS]}; %% parse(Element = #xmlElement{name=load, attributes=Attrs}, Conf) -> Loop = getAttr(integer, Attrs, loop, 0), IDuration = getAttr(integer, Attrs, duration, 0), Unit = getAttr(string, Attrs, unit, "second"), Duration = to_seconds(Unit, IDuration), lists:foldl(fun parse/2, Conf#config{load_loop=Loop,duration=Duration}, Element#xmlElement.content); %% Parsing the Client element parse(Element = #xmlElement{name=client, attributes=Attrs}, Conf = #config{clients=CList}) -> Host = getAttr(Attrs, host), Weight = getAttr(integer,Attrs, weight,1), MaxUsers = getAttr(integer,Attrs, maxusers, 800), SingleNode = getAttr(atom, Attrs, use_controller_vm, false) or Conf#config.use_controller_vm, NewClients = case getAttr(atom, Attrs, type) of batch -> ?LOG("Get client nodes from batch scheduler~n",?DEB), Batch = getAttr(atom, Attrs, batch), Scan_Intf = getAttr(Attrs, scan_intf), NodesTmp = get_batch_nodes(Batch), case NodesTmp of []-> ?LOGF("Warning: empty list of nodes from batch: ~p~n",[NodesTmp],?WARN); _ -> ?LOGF("nodes: ~p~n",[NodesTmp],?DEB) end, %% remove controller host from list to avoid %% overloading the machine running the controller {ok, ControllerHost} = ts_utils:node_to_hostname(node()), DeleteController=fun(A) when A == ControllerHost -> false; (_) -> true end, Nodes = lists:filter(DeleteController, NodesTmp), Fun = fun(N)-> IP = case Scan_Intf of "" -> {ok, TmpIP } = inet:getaddr(N,inet), TmpIP; Interface -> case os:type() of {unix, linux} -> {scan, Interface}; OS -> io:format(standard_error,"Scan interface is not supported on OS ~p, abort~n",[OS]), exit({error, scan_interface_not_supported_on_os}) end end, #client{host=N,weight=Weight,ip=[IP],maxusers=MaxUsers} end, lists:map(Fun, Nodes); _ -> CPU = case {getAttr(integer,Attrs, cpu, 1), SingleNode} of {Val, true} when Val > 1 -> erlang:display("Can't use CPU > 1 when use_controller_vm is true ! Set CPU to 1."), 1; {Val, _} -> Val end, %% must be hostname and not ip: case ts_utils:is_ip(Host) of true -> io:format(standard_error,"ERROR: client config: 'host' attribute must be a hostname, "++ "not an IP ! (was ~p)~n",[Host]), exit({error, badhostname}); false -> %% add a new client for each CPU lists:duplicate(CPU,#client{host = Host, weight = Weight/CPU, maxusers = MaxUsers}) end end, lists:foldl(fun parse/2, Conf#config{clients = lists:append(NewClients,CList), use_controller_vm = SingleNode}, Element#xmlElement.content); %% Parsing the ip element parse(Element = #xmlElement{name=ip, attributes=Attrs}, Conf = #config{clients=[CurClient|CList]}) -> IPList = CurClient#client.ip, IP = case getAttr(atom, Attrs, scan, false) of true -> {scan, getAttr(string,Attrs, value, "eth0")}; _ -> ToResolve = case getAttr(Attrs, value) of "resolve" -> CurClient#client.host; StrIP -> StrIP end, ?LOGF("resolving host ~p~n",[ToResolve],?WARN), {ok,IPtmp} = case inet:getaddr(ToResolve,inet) of {error,nxdomain} -> % retry with IPv6 inet:getaddr(ToResolve,inet6); Val -> Val end, IPtmp end, ?LOGF("resolved host ~p~n",[IP],?WARN), lists:foldl(fun parse/2, Conf#config{clients = [CurClient#client{ip = [IP|IPList]} |CList]}, Element#xmlElement.content); %% Parsing the arrivalphase element parse(Element = #xmlElement{name=arrivalphase, attributes=Attrs}, Conf = #config{arrivalphases=AList}) -> Phase = getAttr(integer,Attrs, phase), IDuration = getAttr(integer, Attrs, duration), Unit = getAttr(string,Attrs, unit, "second"), D = to_milliseconds(Unit, IDuration), case lists:keysearch(Phase,#arrivalphase.phase,AList) of false -> lists:foldl(fun parse/2, Conf#config{arrivalphases = [#arrivalphase{phase=Phase, duration=D } |AList]}, Element#xmlElement.content); _ -> % already existing phase, wrong configuration. io:format(standard_error,"Client config error: phase ~p already defined, abort !~n",[Phase]), exit({error, already_defined_phase}) end; %% Parsing the user element parse(Element = #xmlElement{name=user, attributes=Attrs}, Conf = #config{static_users=Users}) -> Start = getAttr(float_or_integer,Attrs, start_time), Unit = getAttr(string,Attrs, unit, "second"), Session = getAttr(string,Attrs, session), Delay = to_milliseconds(Unit,Start), NewUsers= Users++[{Delay,Session}], lists:foldl(fun parse/2, Conf#config{static_users = NewUsers}, Element#xmlElement.content); %% Parsing the users element parse(Element = #xmlElement{name=users, attributes=Attrs}, Conf = #config{arrivalphases=[CurA | AList]}) -> Max = getAttr(integer,Attrs, maxnumber, infinity), ?LOGF("Maximum number of users ~p~n",[Max],?INFO), Unit = getAttr(string,Attrs, unit, "second"), Intensity = case {getAttr(float_or_integer,Attrs, interarrival), getAttr(float_or_integer,Attrs, arrivalrate) } of {[],[]} -> exit({invalid_xml,"arrival or interarrival must be specified"}); {[], Rate} when Rate > 0 -> Rate / to_milliseconds(Unit,1); {InterArrival,[]} when InterArrival > 0 -> 1/to_milliseconds(Unit,InterArrival); {_Value, _Value2} -> exit({invalid_xml,"arrivalrate and interarrival can't be defined simultaneously"}) end, lists:foldl(fun parse/2, Conf#config{arrivalphases = [CurA#arrivalphase{maxnumber = Max, intensity=Intensity} |AList]}, Element#xmlElement.content); %% Parsing the session element parse(Element = #xmlElement{name=session, attributes=Attrs}, Conf = #config{curid= PrevReqId, sessions=SList}) -> Id = length(SList), Type = getAttr(atom,Attrs, type), {Persistent_def, Bidi_def} = case Type:session_defaults() of {ok, Pdef, Bdef} -> {Pdef, Bdef}; {ok, Pdef} -> {Pdef, false} end, Persistent = getAttr(atom,Attrs, persistent, Persistent_def), Bidi = getAttr(atom,Attrs, bidi, Bidi_def), Name = getAttr(Attrs, name), ?LOGF("Session name for id ~p is ~p~n",[Id+1, Name],?NOTICE), ?LOGF("Session type: persistent=~p, bidi=~p~n",[Persistent,Bidi],?NOTICE), Probability = getAttr(float_or_integer, Attrs, probability), NewSList = case SList of [] -> []; % first session [Previous|Tail] -> % set total requests count in previous session [Previous#session{size=PrevReqId,type=Conf#config.main_sess_type}|Tail] end, lists:foldl(fun parse/2, Conf#config{sessions = [#session{id = Id + 1, popularity = Probability, type = Type, name = Name, persistent = Persistent, bidi = Bidi, rate_limit = Conf#config.rate_limit, hibernate = Conf#config.hibernate, proto_opts = Conf#config.proto_opts } |NewSList], main_sess_type = Type, curid=0, cur_req_id=0},% re-initialize request id Element#xmlElement.content); %%%% Parsing the transaction element parse(Element = #xmlElement{name=transaction, attributes=Attrs}, Conf = #config{session_tab = Tab, sessions=[CurS|_], curid=Id}) -> RawName = getAttr(Attrs, name), {ok, [{atom,1,Name}],1} = erl_scan:string("tr_"++RawName), ?LOGF("Add start transaction ~p in session ~p as id ~p", [Name,CurS#session.id,Id+1],?INFO), ets:insert(Tab, {{CurS#session.id, Id+1}, {transaction,start,Name}}), NewConf=lists:foldl( fun parse/2, Conf#config{curid=Id+1}, Element#xmlElement.content), NewId = NewConf#config.curid, ?LOGF("Add end transaction ~p in session ~p as id ~p", [Name,CurS#session.id,NewId+1],?INFO), ets:insert(Tab, {{CurS#session.id, NewId+1}, {transaction,stop,Name}}), NewConf#config{curid=NewId+1} ; %%%% Parsing the 'if' element parse(_Element = #xmlElement{name='if', attributes=Attrs,content=Content}, Conf = #config{session_tab = Tab, sessions=[CurS|_], curid=Id}) -> VarName=get_dynvar_name(getAttr(string,Attrs,var)), {Rel,Value} = case getAttr(string,Attrs,eq,none) of none -> {neq,getAttr(string,Attrs,neq)}; X -> {eq,X} end, ?LOGF("Add if_start action in session ~p as id ~p", [CurS#session.id,Id+1],?INFO), NewConf = lists:foldl(fun parse/2, Conf#config{curid=Id+1}, Content), NewId = NewConf#config.curid, ?LOGF("endif in session ~p as id ~p",[CurS#session.id,NewId+1],?INFO), InitialAction = {ctrl_struct, {if_start, Rel, VarName, list_to_binary(Value) , NewId+1}}, %%NewId+1 -> id of the first action after the if ets:insert(Tab,{{CurS#session.id,Id+1},InitialAction}), NewConf; %%%% Parsing the 'for' element parse(_Element = #xmlElement{name=for, attributes=Attrs,content=Content}, Conf = #config{session_tab = Tab, sessions=[CurS|_], curid=Id}) -> VarName = getAttr(atom,Attrs,var), InitValue = getAttr(integer_or_string,Attrs,from), EndValue = getAttr(integer_or_string,Attrs,to), Increment = getAttr(integer,Attrs,incr,1), InitialAction = {ctrl_struct, {for_start, InitValue, VarName}}, ?LOGF("Add for_start action in session ~p as id ~p", [CurS#session.id,Id+1],?INFO), ets:insert(Tab,{{CurS#session.id,Id+1},InitialAction}), NewConf = lists:foldl(fun parse/2, Conf#config{curid=Id+1}, Content), NewId = NewConf#config.curid, EndAction= {ctrl_struct,{for_end,VarName,EndValue,Increment,Id+2}}, %%Id+2 -> id of the first action inside the loop %% (id+1 is the for_start action) ?LOGF("Add for_end action in session ~p as id ~p, Jump to:~p", [CurS#session.id,NewId+1,Id+2],?INFO), ets:insert(Tab, {{CurS#session.id,NewId+1},EndAction}), NewConf#config{curid=NewId+1}; %%%% Parsing the 'foreach' element parse(_Element = #xmlElement{name=foreach, attributes=Attrs,content=Content}, Conf = #config{session_tab = Tab, sessions=[CurS|_], curid=Id}) -> VarName = getAttr(atom,Attrs,in), ForName = getAttr(atom,Attrs,name), Filter = case getAttr(string,Attrs,include) of "" -> case getAttr(string,Attrs,exclude) of "" -> undefined; Re2 -> {ok, RegExp} = re:compile(Re2), {false,RegExp} end; Re -> {ok, CompiledRegExp} = re:compile(Re), {true,CompiledRegExp} end, InitialAction = {ctrl_struct, {foreach_start, ForName, VarName, Filter}}, ?LOGF("Add foreach_start action in session ~p as id ~p", [CurS#session.id,Id+1],?INFO), ets:insert(Tab,{{CurS#session.id,Id+1},InitialAction}), NewConf = lists:foldl(fun parse/2, Conf#config{curid=Id+1}, Content), NewId = NewConf#config.curid, EndAction= {ctrl_struct,{foreach_end,ForName,VarName,Filter,Id+2}}, %%Id+2 -> id of the first action inside the loop %% (id+1 is the foreach_start action) ?LOGF("Add foreach_end action in session ~p as id ~p, Jump to:~p", [CurS#session.id,NewId+1,Id+2],?INFO), ets:insert(Tab, {{CurS#session.id,NewId+1},EndAction}), NewConf#config{curid=NewId+1}; %%%% Parsing the 'repeat' element %%%% Last child element must be either 'while' or 'until' parse(_Element = #xmlElement{name=repeat,attributes=Attrs,content=Content}, Conf = #config{session_tab = Tab, sessions=[CurS|_], curid=Id}) -> MaxRepeat = getAttr(integer,Attrs,max_repeat,20), RepeatName = get_dynvar_name(getAttr(string,Attrs,name)), [LastElement|_] = lists:reverse([E || E=#xmlElement{} <- Content]), case LastElement of #xmlElement{name=While,attributes=WhileAttrs} when (While == 'while') or (While == 'until')-> {Rel,Value} = case getAttr(string,WhileAttrs,eq,none) of none -> {neq,getAttr(string,WhileAttrs,neq)}; X -> {eq,X} end, %either or Var = getAttr(atom,WhileAttrs,var), NewConf = lists:foldl(fun parse/2, Conf#config{curid=Id}, Content), NewId = NewConf#config.curid, EndAction = {ctrl_struct,{repeat,RepeatName, While,Rel,Var,list_to_binary(Value),Id+1, MaxRepeat}}, %Id+1 -> id of the first action inside the loop ?LOGF("Add repeat action in session ~p as id ~p, Jump to: ~p", [CurS#session.id,NewId+1,Id+1],?INFO), ets:insert(Tab,{{CurS#session.id,NewId+1},EndAction}), NewConf#config{curid=NewId+1}; _ -> exit({invalid_xml,"Last element inside a loop must be " " or "}) end; %%% Parsing the dyn_variable element parse(#xmlElement{name=dyn_variable, attributes=Attrs}, Conf=#config{sessions=[CurS|_],dynvar=DynVars}) -> StrName = ts_utils:clean_str(getAttr(Attrs, name)), {ok, [{atom,1,Name}],1} = erl_scan:string("'"++StrName++"'"), {Type,Expr} = case {getAttr(string,Attrs,regexp,none), getAttr(string,Attrs,re,none), getAttr(string,Attrs,pgsql_expr,none), getAttr(string,Attrs,xpath,none), getAttr(string,Attrs,jsonpath,none)} of {none,none,none,none,none} -> DefaultRegExp = ?DEF_RE_DYNVAR_BEGIN ++ StrName ++?DEF_RE_DYNVAR_END, {re,DefaultRegExp}; {none,none,none,XPath,none} -> {xpath,XPath}; {none,none,none,none,JSONPath} -> {jsonpath,JSONPath}; {none,none,PG,none,none} -> {pgsql_expr,PG}; {none,RE,none,none,none} -> {re,RE}; {RegExp,_,_,_,_} -> {regexp,RegExp} end, FlattenExpr =lists:flatten(Expr), %% precompilation of the exp DynVar = case Type of regexp -> exit({error, regexp_obsolete_use_re}); re -> ?LOGF("Add new re: ~s ~n", [Expr],?INFO), {ok, CompiledRegExp} = re:compile(FlattenExpr), {re,Name,CompiledRegExp}; xpath -> ?LOGF("Add new xpath: ~s ~n", [Expr],?INFO), CompiledXPathExp = mochiweb_xpath:compile_xpath(FlattenExpr), {xpath,Name,CompiledXPathExp}; _Other -> ?LOGF("Add ~s ~s ~p ~n", [Type,Name,Expr],?INFO), {Type,Name,Expr} end, NewDynVar = [DynVar|DynVars], ?LOGF("Add new dyn variable=~p in session ~p~n", [NewDynVar,CurS#session.id],?INFO), Conf#config{ dynvar= NewDynVar }; parse( #xmlElement{name=change_type, attributes=Attrs}, Conf = #config{sessions=[CurS|Other], curid=Id,session_tab = Tab}) -> CType = getAttr(atom, Attrs, new_type), Server = getAttr(string, Attrs, host), Port = getAttr(integer, Attrs, port), Store = getAttr(atom, Attrs, store, false), Restore = getAttr(atom, Attrs, restore, false), PType = set_net_type(getAttr(Attrs, server_type)), SessType=case Conf#config.main_sess_type == CType of false -> CurS#session.type; true -> CType % back to the main type end, ets:insert(Tab,{{CurS#session.id, Id+1}, {change_type, CType, Server, Port, PType, Store, Restore}}), ?LOGF("Parse change_type (~p) ~p:~p:~p:~p ~n",[CType, Server,Port,PType,Id],?NOTICE), Conf#config{main_sess_type=SessType, curid=Id+1, sessions=[CurS#session{type=CType}|Other] }; parse( Element = #xmlElement{name=set_option, attributes=Attrs}, Conf = #config{sessions=[CurS|_Other], curid=Id,session_tab = Tab}) -> case getAttr(atom, Attrs, type) of "" -> {Type,Name,Args} = case getAttr(string, Attrs, name) of "rate_limit" -> Rate = getAttr(integer, Attrs, value), Max = getAttr(integer, Attrs, max, Rate), {undefined, rate_limit, {1024*Rate div 1000, 1024 * Max}} end, ets:insert(Tab,{{CurS#session.id, Id+1}, {set_option,Type,Name,Args}}), Conf#config{curid=Id+1}; Mod -> Mod:parse_config(Element, Conf) end; %%% Parsing the request element parse(Element = #xmlElement{name=request, attributes=Attrs}, Conf = #config{sessions=[CurSess|_], curid=Id}) -> Type = CurSess#session.type, SubstitutionFlag = getAttr(atom, Attrs, subst, false), lists:foldl( fun(A,B) ->Type:parse_config(A,B) end, Conf#config{curid=Id+1, cur_req_id=Id+1, subst=SubstitutionFlag, match=[] }, Element#xmlElement.content); %%% Match parse(Element=#xmlElement{name=match,attributes=Attrs}, Conf=#config{match=Match})-> Do = getAttr(atom, Attrs, do, continue), When = getAttr(atom, Attrs, 'when', match), Subst = getAttr(atom, Attrs, subst, false), MaxLoop = getAttr(integer, Attrs, max_loop, 20), LoopBack = getAttr(integer, Attrs, loop_back, 0), MaxRestart = getAttr(integer, Attrs, max_restart, 3), SleepLoop = getAttr(integer, Attrs, sleep_loop, 5), ValRaw = getText(Element#xmlElement.content), RegExp = ts_utils:clean_str(ValRaw), SkipHeaders = getAttr(atom, Attrs, skip_headers, no), ApplyTo = case getAttr(string, Attrs, apply_to_content, undefined) of undefined -> undefined; Data -> {Mod, Fun} = ts_utils:split2(Data,$:), {list_to_atom(Mod), list_to_atom(Fun)} end, NewMatch = #match{regexp=RegExp,subst=Subst, do=Do,'when'=When, sleep_loop=SleepLoop * 1000, skip_headers=SkipHeaders, loop_back=LoopBack, max_restart=MaxRestart, max_loop=MaxLoop, apply_to_content=ApplyTo}, lists:foldl(fun parse/2, Conf#config{ match=lists:append(Match, [NewMatch]) }, Element#xmlElement.content); %%% Parsing the option element parse(Element = #xmlElement{name=option, attributes=Attrs}, Conf = #config{session_tab = Tab}) -> case getAttr(atom, Attrs, type) of "" -> case getAttr(Attrs, name) of "thinktime" -> Val = getAttr(float_or_integer,Attrs, value), ets:insert(Tab,{{thinktime, value}, Val}), Random = case { getAttr(integer, Attrs, min), getAttr(integer, Attrs, max)} of {Min, Max } when is_integer(Min), is_integer(Max), Max > 0, Max >= Min -> {"range", Min, Max}; {"",""} -> getAttr(string,Attrs, random, ?config(thinktime_random)) end, ets:insert(Tab,{{thinktime, random}, Random}), Override = getAttr(string, Attrs, override, ?config(thinktime_override)), ets:insert(Tab,{{thinktime, override}, Override}), lists:foldl( fun parse/2, Conf, Element#xmlElement.content); "ssl_ciphers" -> Cipher = getAttr(string,Attrs, value, negociate), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{ssl_ciphers=Cipher}, lists:foldl( fun parse/2, Conf#config{proto_opts=NewProto}, Element#xmlElement.content); "seed" -> Seed = getAttr(integer,Attrs, value, now), lists:foldl( fun parse/2, Conf#config{seed=Seed}, Element#xmlElement.content); "rate_limit" -> Rate = getAttr(integer,Attrs, value, 512), % 512KB/sec MaxBurst = getAttr(integer,Attrs, max, Rate), TokenBucket= #token_bucket{rate=1024*Rate div 1000, burst= 1024*MaxBurst}, lists:foldl( fun parse/2, Conf#config{rate_limit=TokenBucket}, Element#xmlElement.content); "hibernate" -> Hibernate = case getAttr(integer_or_string,Attrs, value, 10000 ) of "infinity" -> infinity; Seconds -> Seconds * 1000 end, lists:foldl( fun parse/2, Conf#config{hibernate=Hibernate}, Element#xmlElement.content); "ports_range" -> Min = getAttr(integer,Attrs, min, 1025), Max = getAttr(integer,Attrs, max, 65534), case getAttr(atom,Attrs, value, on) of off -> lists:foldl( fun parse/2, Conf,Element#xmlElement.content); on -> %%TODO: check if min > 1024 and max < 65536 lists:foldl( fun parse/2, Conf#config{ports_range={Min,Max}}, Element#xmlElement.content) end; "job_notify_port" -> Port = getAttr(integer,Attrs, value, ?config(job_notify_port)), lists:foldl( fun parse/2, Conf#config{job_notify_port=Port}, Element#xmlElement.content); "tcp_rcv_buffer" -> Size = getAttr(integer,Attrs, value, ?config(rcv_size)), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{tcp_rcv_size=Size}, lists:foldl( fun parse/2, Conf#config{proto_opts=NewProto}, Element#xmlElement.content); "udp_rcv_buffer" -> Size = getAttr(integer,Attrs, value, ?config(rcv_size)), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{udp_rcv_size=Size}, lists:foldl( fun parse/2, Conf#config{proto_opts=NewProto}, Element#xmlElement.content); "tcp_snd_buffer" -> Size = getAttr(integer,Attrs, value, ?config(snd_size)), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{tcp_snd_size=Size}, lists:foldl( fun parse/2, Conf#config{proto_opts=NewProto}, Element#xmlElement.content); "udp_snd_buffer" -> Size = getAttr(integer,Attrs, value, ?config(snd_size)), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{udp_snd_size=Size}, lists:foldl( fun parse/2, Conf#config{proto_opts=NewProto}, Element#xmlElement.content); "tcp_timeout" -> Size = getAttr(integer,Attrs, value, ?config(tcp_timeout)), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{idle_timeout=Size}, lists:foldl( fun parse/2, Conf#config{proto_opts=NewProto}, Element#xmlElement.content); "retry_timeout" -> Size = getAttr(integer,Attrs, value, ?config(client_retry_timeout)), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{retry_timeout=Size}, lists:foldl( fun parse/2, Conf#config{proto_opts=NewProto}, Element#xmlElement.content); "file_server" -> FileName = getAttr(Attrs, value), case file:read_file_info(FileName) of {ok, _} -> ok; {error, _Reason} -> exit({error, bad_filename, FileName}) end, Id = getAttr(atom, Attrs, id,default), lists:foldl( fun parse/2, Conf#config{file_server=[{Id, FileName} | Conf#config.file_server]}, Element#xmlElement.content); Other -> ?LOGF("Unknown option ~p !~n",[Other], ?WARN), lists:foldl( fun parse/2, Conf, Element#xmlElement.content) end; Module -> Module:parse_config(Element, Conf) end; %%% Parsing the thinktime element parse(Element = #xmlElement{name=thinktime, attributes=Attrs}, Conf = #config{curid=Id, session_tab = Tab, sessions = [CurS |_]}) -> {RT,T} = case getAttr(Attrs, value) of "wait_global" -> {wait_global,infinity}; "%%"++Tail -> % dynamic thinktime {"%%"++Tail,"%%"++Tail}; _ -> DefThink = get_default(Tab,{thinktime, value},thinktime_value), DefRandom = get_default(Tab,{thinktime, random},thinktime_random), {Think, Randomize} = case get_default(Tab,{thinktime, override},thinktime_override) of "true" -> {DefThink, DefRandom}; "false" -> case { getAttr(integer, Attrs, min), getAttr(integer, Attrs, max), getAttr(float_or_integer, Attrs, value)} of {Min, Max, "" } when is_integer(Min), is_integer(Max), Max > 0, Min >0, Max > Min -> {"", {"range", Min, Max} }; {"","",""} -> CurRandom = getAttr(string, Attrs,random,DefRandom), {DefThink, CurRandom}; {"","",CurThink} when CurThink > 0 -> CurRandom = getAttr(string, Attrs,random,DefRandom), {CurThink, CurRandom}; _ -> exit({error, bad_thinktime}) end end, Val=case Randomize of "true" -> {random, Think * 1000}; {"range", Min2, Max2} -> {range, Min2 * 1000, Max2 * 1000}; "false" -> round(Think * 1000) end, {Val, Think} end, ?LOGF("New thinktime ~p for id (~p:~p)~n",[RT, CurS#session.id, Id+1], ?INFO), ets:insert(Tab,{{CurS#session.id, Id+1}, {thinktime, RT}}), lists:foldl( fun parse/2, Conf#config{curthink=T,curid=Id+1}, Element#xmlElement.content); %% Parsing the setdynvars element parse(Element = #xmlElement{name=setdynvars, attributes=Attrs}, Conf = #config{session_tab = Tab, sessions=[CurS|_], curid=Id}) -> Vars = [ getAttr(atom,Attr,name,none) || #xmlElement{name=var,attributes=Attr} <- Element#xmlElement.content], Action = case getAttr(string,Attrs,sourcetype,"erlang") of "erlang" -> [Module,Callback] = string:tokens(getAttr(string,Attrs,callback,none),":"), {setdynvars,erlang,{list_to_atom(Module),list_to_atom(Callback)},Vars}; "eval" -> Snippet = getAttr(string,Attrs,code,""), Fun= ts_utils:eval(Snippet), true = is_function(Fun, 1), {setdynvars,code,Fun,Vars}; "file" -> Order = getAttr(atom,Attrs,order,iter), FileId = getAttr(atom,Attrs,fileid,none), case lists:keysearch(FileId,1,Conf#config.file_server) of {value,_Val} -> Delimiter = getAttr(string,Attrs,delimiter,";"), {setdynvars,file,{Order,FileId,Delimiter},Vars}; false -> io:format(standard_error, "Unknown_file_id ~p in file setdynvars declaration: you forgot to add a file_server option~n",[FileId]), exit({error, unknown_file_id}) end; "random_string" -> Length = getAttr(integer,Attrs,length,20), {setdynvars,random,{string,Length},Vars}; "urandom_string" -> Length = getAttr(integer,Attrs,length,20), {setdynvars,urandom,{string,Length},Vars}; "random_number" -> Start = getAttr(integer,Attrs,start,1), End = getAttr(integer,Attrs,'end',10), {setdynvars,random,{number,Start,End},Vars}; "jsonpath" -> From = getAttr(atom, Attrs,from), JSONPath = getAttr(Attrs,jsonpath), {setdynvars,jsonpath,{JSONPath, From},Vars} end, ?LOGF("Add setdynvars in session ~p as id ~p",[CurS#session.id,Id+1],?INFO), ets:insert(Tab, {{CurS#session.id, Id+1}, Action}), Conf#config{curid=Id+1}; %% Parsing other elements parse(Element = #xmlElement{}, Conf = #config{}) -> lists:foldl(fun parse/2, Conf, Element#xmlElement.content); %% Parsing non #xmlElement elements parse(_Element, Conf = #config{}) -> Conf. getAttr(Attr, Name) -> getAttr(string, Attr, Name, ""). %%%---------------------------------------------------------------------- %%% @spec getAttr(Type:: 'string'|'list'|'float_or_integer', Attr::list(), %%% Name::string()) -> term() %%% @doc search the attribute list for the given one %%% @end %%%---------------------------------------------------------------------- getAttr(Type, Attr, Name) -> getAttr(Type, Attr, Name, ""). getAttr(Type, [Attr = #xmlAttribute{name=Name}|_], Name, _Default) -> case { Attr#xmlAttribute.value, Type} of {[], string } -> "" ; {[], list } -> [] ; {[], float_or_integer } -> 0 ; {A,_} -> getTypeAttr(Type,A) end; getAttr(Type, [_H|T], Name, Default) -> getAttr(Type, T, Name, Default); getAttr(_Type, [], _Name, Default) -> Default. getTypeAttr(string, String)-> String; getTypeAttr(list, String)-> String; getTypeAttr(float_or_integer, String)-> case erl_scan:string(String) of {ok, [{integer,1,I}],1} -> I; {ok, [{float,1,F}],1} -> F end; getTypeAttr(integer_or_string, String)-> case erl_scan:string(String) of {ok, [{integer,1,I}],1} -> I; _ -> String end; getTypeAttr(Type, String) -> {ok, [{Type,1,Val}],1} = erl_scan:string(String), Val. %%%---------------------------------------------------------------------- %%% Function: getText/1 %%% Purpose: get the text of the XML node %%%---------------------------------------------------------------------- getText([#xmlText{value=Value}|_]) -> string:strip(Value, both); getText(_Other) -> "". %%%---------------------------------------------------------------------- %%% Function: to_seconds/2 %%% Purpose: get the real duration in seconds %%%---------------------------------------------------------------------- to_seconds("second", Val)-> Val; to_seconds("minute", Val)-> Val*60; to_seconds("hour", Val)-> Val*3600; to_seconds("millisecond", Val)-> Val/1000. to_milliseconds("second", Val)-> Val*1000; to_milliseconds("minute", Val)-> Val*60000; to_milliseconds("hour", Val)-> Val*3600000; to_milliseconds("millisecond", Val)-> Val. %%%---------------------------------------------------------------------- %%% Function: get_default/2 %%%---------------------------------------------------------------------- get_default(Tab, Key,ConfigName) when not is_tuple(Key) -> get_default(Tab, {Key, value},ConfigName); get_default(Tab, Key,ConfigName) -> case ets:lookup(Tab,Key) of [] -> ?config(ConfigName); [{_, SName}] -> SName end. %%%---------------------------------------------------------------------- %%% Function: mark_prev_req/3 %%% Purpose: use to set page marks in requests during parsing ; by %%% default, a new request is mark as an endpage; if a new request is %%% parse, then the previous one must be set to false, unless there is %%% a thinktime between them %%%---------------------------------------------------------------------- mark_prev_req(0, _, _) -> ok; mark_prev_req(Id, Tab, CurS) -> %% if the previous msg is a #ts_request request, set endpage to %% false, we are the current last request of the page case ets:lookup(Tab,{CurS#session.id, Id}) of [{Key, Msg=#ts_request{}}] -> ets:insert(Tab,{Key, Msg#ts_request{endpage=false}}); [{_, {transaction,_,_}}] ->% transaction, continue to search back mark_prev_req(Id-1, Tab, CurS); _ -> ok end. get_batch_nodes(pbs) -> get_batch_nodes(torque); get_batch_nodes(lsf)-> case os:getenv("LSB_HOSTS") of false -> []; Nodes -> lists:map(fun shortnames/1, string:tokens(Nodes, " ")) end; get_batch_nodes(oar) -> get_batch_nodes2("OAR_NODEFILE"); get_batch_nodes(torque) -> get_batch_nodes2("PBS_NODEFILE"). get_batch_nodes2(Env) -> case os:getenv(Env) of false -> []; NodeFile -> {ok, Nodes} = ts_utils:file_to_list(NodeFile), lists:map(fun shortnames/1, Nodes) end. shortnames(Hostname)-> [S | _]= string:tokens(Hostname,"."), S. %%---------------------------------------------------------------------- %% @spec: backup_config(Dir::string(), Name::string(), Config::tuple()) -> %% ok | {error, Reason::term()} %% @doc: create a backup copy of the config file in the log directory %% This is useful to have an history of all parameters of a test. %% Use parsed config file to expand all ENTITY %% @end %%---------------------------------------------------------------------- backup_config(Dir,standard_io, Config) -> backup_config(Dir, "tsung_stdin.xml", Config); backup_config(Dir, Name, Config) -> BaseName = filename:basename(Name), {ok,IOF}=file:open(filename:join(Dir,BaseName),[write]), Export=xmerl:export_simple([Config],xmerl_xml), case catch io:format(IOF,"~s~n",[lists:flatten(Export)]) of {'EXIT', _Error} -> % weird characters in the XML ? io:format(IOF,"~p~n",[lists:flatten(Export)]); _ -> ok end, file:close(IOF). %% @spec read_stdio()-> string() %% @doc Read config from standard input %% @end read_stdio()-> read_stdio(io:get_line(""),[]). read_stdio(eof, Data)-> lists:flatten(Data); read_stdio(Data,Acc) -> read_stdio(io:get_line(""),[Acc,Data]). set_net_type("tcp") -> gen_tcp; set_net_type("tcp6") -> gen_tcp6; set_net_type("udp") -> gen_udp; set_net_type("udp6") -> gen_udp6; set_net_type("ssl") -> ssl; set_net_type("ssl6") -> ssl6. get_dynvar_name(VarNameStr) -> %% check if the var name is for an array (myvar[N]) case re:run(VarNameStr,"(.+)\[(\d+)\]",[{capture,all_but_first,list},dotall]) of {match,[Name,Index]} -> {list_to_atom(Name),Index}; _ -> list_to_atom(VarNameStr) end. tsung-1.4.2/src/tsung_controller/ts_config_server.erl0000644000201100017670000011050211701017117022575 0ustar nniclausdream%%% %%% Copyright IDEALX S.A.S. 2003 %%% %%% Author : Nicolas Niclausse %%% Created: 04 Dec 2003 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%%------------------------------------------------------------------- %%% File : ts_config_server.erl %%% Author : Nicolas Niclausse %%% Description : %%% %%% Created : 4 Dec 2003 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_config_server). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -behaviour(gen_server). %%-------------------------------------------------------------------- %% Include files %%-------------------------------------------------------------------- -include("ts_profile.hrl"). -include("ts_config.hrl"). %%-------------------------------------------------------------------- %% External exports -export([start_link/1, read_config/1, read_config/2, get_req/2, get_next_session/1, get_client_config/1, newbeams/1, newbeam/2, get_monitor_hosts/0, encode_filename/1, decode_filename/1, endlaunching/1, status/0, start_file_server/1, get_user_agents/0, get_client_config/2, get_user_param/1, get_jobs_state/0 ]). %%debug -export([choose_client_ip/1, choose_session/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, {config, logdir, curcfg = 0, % number of configured launchers client_static_users = 0, % number of clients that already have their static users static_users = 0, % static users not yet given to a client ports, % dict, used if we need to choose the client port users=1, % userid (incremental counter) start_date, % hostname, % controller hostname last_beam_id = 0, % last tsung beam id (used to set nodenames) ending_beams = 0, % number of beams with no new users to start lastips, % store next ip to choose for each client host total_weight % total weight of client machines }). %%==================================================================== %% External functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: start_link/0 %% Description: Starts the server %%-------------------------------------------------------------------- start_link(LogDir) -> gen_server:start_link({global, ?MODULE}, ?MODULE, [LogDir], []). status() -> gen_server:call({global, ?MODULE}, {status}). %%-------------------------------------------------------------------- %% Function: newbeam/1 %% Description: start a new beam %%-------------------------------------------------------------------- newbeams(HostList)-> gen_server:cast({global, ?MODULE},{newbeams, HostList }). %%-------------------------------------------------------------------- %% Function: newbeam/2 %% Description: start a new beam with given config. Use by launcher %% when maxclient is reached. In this case, the arrival rate is known %%-------------------------------------------------------------------- newbeam(Host, {Arrivals, MaxUsers})-> gen_server:cast({global, ?MODULE},{newbeam, Host, {Arrivals, MaxUsers} }). %%-------------------------------------------------------------------- %% Function: get_req/2 %% Description: get Nth request from given session Id %% Returns: #message | {error, Reason} %%-------------------------------------------------------------------- get_req(Id, Count)-> gen_server:call({global, ?MODULE},{get_req, Id, Count}). %%-------------------------------------------------------------------- %% Function: get_user_agents/0 %% Description: %% Returns: List %%-------------------------------------------------------------------- get_user_agents()-> gen_server:call({global, ?MODULE},{get_user_agents}). %%-------------------------------------------------------------------- %% Function: read_config/1 %% Description: Read Config file %% Returns: ok | {error, Reason} %%-------------------------------------------------------------------- read_config(ConfigFile)-> read_config(ConfigFile,?config_timeout). read_config(ConfigFile,Timeout)-> gen_server:call({global,?MODULE},{read_config, ConfigFile},Timeout). %%-------------------------------------------------------------------- %% Function: get_client_config/1 %% Description: get client machine setup (for the launcher) %% Returns: {ok, {ArrivalList, StartDate, MaxUsers}} | {error, notfound} %%-------------------------------------------------------------------- get_client_config(Host)-> gen_server:call({global,?MODULE},{get_client_config, Host}, ?config_timeout). get_client_config(Type, Host)-> gen_server:call({global,?MODULE},{get_client_config, Type, Host}, ?config_timeout). %%-------------------------------------------------------------------- %% Function: get_monitor_hosts/0 %% Returns: [Hosts] %%-------------------------------------------------------------------- get_monitor_hosts()-> gen_server:call({global,?MODULE},{get_monitor_hosts}). %%-------------------------------------------------------------------- %% @spec get_next_session(Host::string())-> {ok, SessionId::integer(), %% SessionSize::integer(),IP::tuple(), UserId::integer()} %% @doc Choose randomly a session %% @end %%-------------------------------------------------------------------- get_next_session(Host)-> gen_server:call({global, ?MODULE},{get_next_session, Host}). get_user_param(Host)-> gen_server:call({global, ?MODULE},{get_user_param, Host}). endlaunching(Node) -> gen_server:cast({global, ?MODULE},{end_launching, Node}). get_jobs_state() -> gen_server:call({global, ?MODULE},{get_jobs_state}). %%==================================================================== %% Server functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: init/1 %% Description: Initiates the server %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%-------------------------------------------------------------------- init([LogDir]) -> process_flag(trap_exit,true), {ok, MyHostName} = ts_utils:node_to_hostname(node()), ?LOGF("Config server started, logdir is ~p~n ",[LogDir],?NOTICE), {ok, #state{logdir=LogDir, hostname=list_to_atom(MyHostName)}}. %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_call({read_config, ConfigFile}, _From, State=#state{logdir=LogDir}) -> case catch ts_config:read(ConfigFile, LogDir) of {ok, Config=#config{curid=LastReqId,sessions=[LastSess| Sessions]}} -> case check_config(Config) of ok -> ts_utils:init_seed(Config#config.seed), ts_user_server:init_seed(Config#config.seed), application:set_env(tsung_controller, clients, Config#config.clients), application:set_env(tsung_controller, dump, Config#config.dump), application:set_env(tsung_controller, stats_backend, Config#config.stats_backend), application:set_env(tsung_controller, debug_level, Config#config.loglevel), SumWeights = fun(X, Sum) -> X#client.weight + Sum end, Sum = lists:foldl(SumWeights, 0, Config#config.clients), %% we only know now the size of last session from the file: add it %% in the table print_info(), NewLast=LastSess#session{size = LastReqId, type=Config#config.main_sess_type}, %% start the file server (if defined) using a separate process (it can be long) spawn(?MODULE, start_file_server, [Config]), NewConfig=loop_load(sort_static(Config#config{sessions=[NewLast]++Sessions})), set_max_duration(Config#config.duration), ts_job_notify:listen(Config#config.job_notify_port), {reply, ok, State#state{config=NewConfig, static_users=NewConfig#config.static_users,total_weight = Sum}}; {error, Reason} -> ?LOGF("Error while checking config: ~p~n",[Reason],?EMERG), {reply, {error, Reason}, State} end; {error, {{case_clause, {error, enoent}}, [{xmerl_scan, fetch_DTD, 2}|_]}} -> ?LOG("Error while parsing XML: DTD not found !~n",?EMERG), {reply, {error, dtd_not_found}, State}; {error, Reason} -> ?LOGF("Error while parsing XML config file: ~p~n",[Reason],?EMERG), {reply, {error, Reason}, State}; {'EXIT', Reason} -> ?LOGF("Error while parsing XML config file: ~p~n",[Reason],?EMERG), {reply, {error, Reason}, State} end; %% get Nth request from given session Id handle_call({get_req, Id, N}, _From, State) -> Config = State#state.config, Tab = Config#config.session_tab, ?DebugF("look for ~p th request in session ~p for ~p~n",[N,Id,_From]), case ets:lookup(Tab, {Id, N}) of [{_, Session}] -> ?DebugF("ok, found ~p for ~p~n",[Session,_From]), {reply, Session, State}; Other -> {reply, {error, Other}, State} end; handle_call({get_user_agents}, _From, State) -> Config = State#state.config, case ets:lookup(Config#config.session_tab, {http_user_agent, value}) of [] -> {reply, empty, State}; [{_Key, UserAgents}] -> {reply, UserAgents, State} end; %% get user parameters (static user: the session id is already known) handle_call({get_user_param, HostName}, _From, State=#state{users=UserId}) -> Config = State#state.config, {value, Client} = lists:keysearch(HostName, #client.host, Config#config.clients), {IPParam, Server} = get_user_param(Client,Config), ts_mon:newclient({static,now()}), {reply, {ok, { IPParam, Server, UserId,Config#config.dump,Config#config.seed}}, State#state{users=UserId+1}}; %% get a new session id and user parameters for the given node handle_call({get_next_session, HostName}, _From, State=#state{users=Users}) -> Config = State#state.config, {value, Client} = lists:keysearch(HostName, #client.host, Config#config.clients), ?DebugF("get new session for ~p~n",[_From]), case choose_session(Config#config.sessions) of {ok, Session=#session{id=Id}} -> ?LOGF("Session ~p choosen~n",[Id],?INFO), ts_mon:newclient({Id,now()}), {IPParam, Server} = get_user_param(Client,Config), {reply, {ok, Session#session{client_ip= IPParam, server=Server,userid=Users, dump=Config#config.dump, seed=Config#config.seed}}, State#state{users=Users+1} }; Other -> {reply, {error, Other}, State} end; handle_call({get_client_config, static, Host}, _From, State=#state{config=Config}) -> %% static users (eg. each user started once at fixed time) %% we must spread this list of fixed users to each beam %% If we have N users and M client beams Clients=Config#config.clients, StaticUsers=State#state.static_users, Done=State#state.client_static_users, % number of clients that already have their static users {value, Client} = lists:keysearch(Host, #client.host, Clients), StartDate = set_start_date(State#state.start_date), case Done +1 == length(Clients) of true -> % last client, give him all pending users {reply,{ok,StaticUsers,StartDate},State#state{start_date=StartDate,static_users=[]}}; false -> Weight = Client#client.weight, Number=ts_utils:ceiling(length(StaticUsers)*Weight/State#state.total_weight), {NewUsers,Tail}=lists:split(Number,StaticUsers), {reply,{ok,NewUsers,StartDate},State#state{client_static_users=Done+1,start_date=StartDate,static_users=Tail}} end; %% get randomly generated users handle_call({get_client_config, Host}, _From, State=#state{curcfg=OldCfg,total_weight=Total_Weight}) -> ?DebugF("get_client_config from ~p~n",[Host]), Config = State#state.config, Clients=Config#config.clients, %% set start date if not done yet StartDate = set_start_date(State#state.start_date), {value, Client} = lists:keysearch(Host, #client.host, Clients), IsLast = OldCfg + 1 >= length(Clients),% test is this is the last launcher to ask for it's config Get = fun(Phase,Args)-> {get_client_cfg(Phase,Args),Args} end, {Res, _Acc} = lists:mapfoldl(Get, {Total_Weight,Client,IsLast},Config#config.arrivalphases), {NewPhases,ClientParams} = lists:unzip(Res), Reply = {ok,{ClientParams,StartDate,Client#client.maxusers}}, NewConfig=Config#config{arrivalphases=NewPhases}, {reply,Reply,State#state{config=NewConfig,start_date=StartDate, curcfg = OldCfg +1}}; %% handle_call({get_monitor_hosts}, _From, State) -> Config = State#state.config, {reply, Config#config.monitor_hosts, State}; % get status: send the number of actives nodes handle_call({status}, _From, State) -> Config = State#state.config, Reply = {ok, length(Config#config.clients), State#state.ending_beams}, {reply, Reply, State}; handle_call({get_jobs_state}, _From, State) when State#state.config == undefined -> {reply, not_configured, State}; handle_call({get_jobs_state}, {Pid,Tag}, State) -> Config = State#state.config, Reply = case Config#config.job_notify_port of {Ets,Port} -> ets:give_away(Ets,Pid,Port), {Ets,Port}; Else -> Else end, {reply, Reply, State}; handle_call(Request, _From, State) -> ?LOGF("Unknown call ~p !~n",[Request],?ERR), {reply, ok, State}. %%-------------------------------------------------------------------- %% Function: handle_cast/2 %% Description: Handling cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- %% start the launcher on the current beam handle_cast({newbeams, HostList}, State=#state{logdir = LogDir, hostname = LocalHost, config = Config}) -> LocalVM = Config#config.use_controller_vm, GetLocal = fun(Host)-> is_vm_local(Host,LocalHost,LocalVM) end, {LocalBeams, RemoteBeams} = lists:partition(GetLocal,HostList), case local_launcher(LocalBeams, LogDir, Config) of {error, _Reason} -> ts_mon:abort(), {stop, normal,State}; Id0 -> Seed=Config#config.seed, Args = set_remote_args(LogDir, Config#config.ports_range), {BeamsIds, LastId} = lists:mapfoldl(fun(A,Acc) -> {{A, Acc}, Acc+1} end, Id0, RemoteBeams), Fun = fun({Host,Id}) -> remote_launcher(Host, Id, Args) end, RemoteNodes = ts_utils:pmap(Fun, BeamsIds), ?LOG("All remote beams started, sync ~n",?NOTICE), global:sync(), StartLaunchers = fun(Node) -> ts_launcher_static:launch({Node,[]}), ts_launcher:launch({Node, [], Seed}) end, case Config#config.ports_range of undefined -> ok; _ -> ?LOG("Start client port server on remote nodes ~n",?NOTICE), %% first, get a single erlang node per host, and start the cport gen_server on this node UNodes = get_one_node_per_host(RemoteNodes), SetParams = fun(Node) -> {ok, MyHostName} =ts_utils:node_to_hostname(Node), {Node, "cport-" ++ MyHostName} end, CPorts = lists:map(SetParams, UNodes), ?LOGF("Will run start_cport with arg:~p ~n",[CPorts],?DEB), lists:foreach(fun ts_sup:start_cport/1 ,CPorts) end, lists:foreach(StartLaunchers, RemoteNodes), {noreply, State#state{last_beam_id = LastId}} end; %% use_controller_vm and max number of concurrent users reached , big trouble ! handle_cast({newbeam, Host, _}, State=#state{ hostname=LocalHost,config=Config}) when Config#config.use_controller_vm and ( ( LocalHost == Host ) or ( Host == 'localhost' )) -> Msg ="Maximum number of concurrent users in a single VM reached and 'use_controller_vm' is true, can't start new beam !!! Check 'maxusers' value in configuration.~n", ?LOG(Msg, ?EMERG), erlang:display(Msg), {noreply, State}; %% start a launcher on a new beam with slave module handle_cast({newbeam, Host, Arrivals}, State=#state{last_beam_id = NodeId, config=Config, logdir = LogDir}) -> Args = set_remote_args(LogDir,Config#config.ports_range), Seed = Config#config.seed, Node = remote_launcher(Host, NodeId, Args), ts_launcher_static:stop(Node), % no need for static launcher in this case (already have one) ts_launcher:launch({Node, Arrivals, Seed}), {noreply, State#state{last_beam_id = NodeId+1}}; handle_cast({end_launching, _Node}, State=#state{ending_beams=Beams}) -> {noreply, State#state{ending_beams = Beams+1}}; handle_cast(Msg, State) -> ?LOGF("Unknown cast ~p ! ~n",[Msg],?WARN), {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info/2 %% Description: Handling all non call/cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_info({timeout, _Ref, end_tsung}, State) -> ts_mon:abort(), ?LOG("Tsung test max duration reached, exits ! ~n",?EMERG), {stop, normal, State}; handle_info({'EXIT', _Pid, {slave_failure,timeout}}, State) -> ts_mon:abort(), ?LOG("Abort ! ~n",?EMERG), {stop, normal, State}; handle_info({'EXIT', Pid, normal}, State) -> ?LOGF("spawned process termination (~p) ~n",[Pid],?INFO), {noreply, State}; handle_info({'ETS-TRANSFER',Tab,_FromPid,GiftData}, State=#state{config=Config}) -> {noreply, State#state{config=Config#config{job_notify_port={Tab,GiftData}}}}; handle_info(Info, State) -> ?LOGF("Unknown info ~p ! ~n",[Info],?WARN), {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate/2 %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- %% @spec is_vm_local(Host::atom(),Localhost::atom(),UseController::boolean()) -> boolean() is_vm_local(Host,Host,true) -> true; is_vm_local('localhost',_,true) -> true; is_vm_local(_,_,_) -> false. set_start_date(undefined)-> ts_utils:add_time(now(), ?config(warm_time)); set_start_date(Date) -> Date. get_user_param(Client,Config)-> {ok,IP} = choose_client_ip(Client), {ok, Server} = choose_server(Config#config.servers), CPort = choose_port(IP, Config#config.ports_range), { {IP, CPort}, Server}. %%---------------------------------------------------------------------- %% Func: choose_client_ip/1 %% Args: #client, Dict %% Purpose: choose an IP for a client %% Returns: {ok, IP, NewDict} IP=IP address %%---------------------------------------------------------------------- choose_client_ip(#client{ip = IPList, host=Host}) -> choose_rr(IPList, Host, {0,0,0,0}). %%---------------------------------------------------------------------- %% Func: choose_server/1 %% Args: List %% Purpose: choose a server for a new client %% Returns: {ok, #server} %%---------------------------------------------------------------------- choose_server(ServerList) -> choose_rr(ServerList, server, #server{}). %%---------------------------------------------------------------------- %% Func: choose_rr/3 %% Args: List, Key, Default %% Purpose: choose an value in list in a round robin way. Use last %% value stored in the process dictionnary % Return Default if list is empty %% Returns: {ok, Val} %%---------------------------------------------------------------------- choose_rr([],_, Def) -> % no val, return default {ok, Def}; choose_rr([Val],_,_) -> % only one value {ok, Val}; choose_rr(List, Key, _) -> I = case get({rr,Key}) of undefined -> 1 ; % first use of this key, init index to 1 Val when is_integer(Val) -> (Val rem length(List))+1 % round robin end, put({rr, Key},I), {ok, lists:nth(I, List)}. %%---------------------------------------------------------------------- %% Func: choose_session/1 %% Args: List of #session %% Purpose: choose an session randomly %% Returns: #session %%---------------------------------------------------------------------- choose_session([Session]) -> %% only one Session {ok, Session}; choose_session(Sessions) -> choose_session(Sessions, random:uniform() * 100,0). choose_session([S=#session{popularity=P} | _],Rand,Cur) when Rand =< P+Cur-> {ok, S}; choose_session([#session{popularity=P} | SList], Rand, Cur) -> choose_session(SList, Rand, Cur+P). %%---------------------------------------------------------------------- %% @spec get_client_cfg(ArrivalPhase::record(arrivalphase), %% Acc::{Total_weight::integer(),Client::record(client),IsLast::binary()}) -> %% {{UpdatedPhase::record(arrivalphase),{Intensity::number(),NUsers::integer(),Duration::integer()}}, Acc} %% @doc set parameters for given host client and phase. %% @end get_client_cfg(Arrival=#arrivalphase{duration = Duration, intensity= PhaseIntensity, curnumber= CurNumber, maxnumber= MaxNumber }, {TotalWeight,Client,IsLast} ) -> Weight = Client#client.weight, ClientIntensity = PhaseIntensity * Weight / TotalWeight, NUsers = round(case MaxNumber of infinity -> %% only use the duration to set the number of users Duration * ClientIntensity; _ -> TmpMax = case {IsLast,CurNumber == MaxNumber} of {true,_} -> MaxNumber-CurNumber; {false,true} -> 0; {false,false} -> lists:max([1,trunc(MaxNumber * Weight / TotalWeight)]) end, lists:min([TmpMax, Duration*ClientIntensity]) end), ?LOGF("New arrival phase ~p for client ~p (last ? ~p): will start ~p users~n", [Arrival#arrivalphase.phase,Client#client.host, IsLast,NUsers],?NOTICE), {Arrival#arrivalphase{curnumber=CurNumber+NUsers}, {ClientIntensity, NUsers, Duration}}. %%---------------------------------------------------------------------- %% Func: encode_filename/1 %% Purpose: kludge: the command line erl doesn't like special characters %% in strings when setting up environnement variables for application, %% so we encode these characters ! %%---------------------------------------------------------------------- encode_filename(String) when is_list(String)-> Transform=[{"\\.","_46"},{"\/","_47"},{"\-","_45"}, {"\:","_58"}, {",","_44"}], lists:foldl(fun replace_str/2, "ts_encoded" ++ String, Transform); encode_filename(Term) -> Term. %%---------------------------------------------------------------------- %% Func: decode_filename/1 %%---------------------------------------------------------------------- decode_filename("ts_encoded" ++ String)-> Transform=[{"_46","."},{"_47","\/"},{"_45","\-"}, {"_58","\:"}, {"_44",","}], lists:foldl(fun replace_str/2, String, Transform). replace_str({A,B},X) -> re:replace(X,A,B,[{return,list},global]). %%---------------------------------------------------------------------- %% Func: print_info/0 Print system info %%---------------------------------------------------------------------- print_info() -> VSN = case lists:keysearch(tsung_controller,1,application:loaded_applications()) of {value, {_,_ ,V}} -> V; _ -> "unknown" end, ?LOGF("SYSINFO:Tsung version: ~s~n",[VSN],?NOTICE), ?LOGF("SYSINFO:Erlang version: ~s~n",[erlang:system_info(system_version)],?NOTICE), ?LOGF("SYSINFO:System architecture ~s~n",[erlang:system_info(system_architecture)],?NOTICE), ?LOGF("SYSINFO:Current path: ~s~n",[code:which(tsung)],?NOTICE). %%---------------------------------------------------------------------- %% Func: start_file_server/1 %%---------------------------------------------------------------------- start_file_server(#config{file_server=[]}) -> ?LOG("No File server defined, skip~n",?DEB); start_file_server(Config=#config{file_server=Filenames}) -> ?LOG("Starting File server~n",?INFO), FileSrv = {ts_file_server, {ts_file_server, start, []}, transient, 2000, worker, [ts_msg_server]}, supervisor:start_child(ts_controller_sup, FileSrv), ts_file_server:read(Filenames), ?LOG("Starting user servers if needed~n",?INFO), setup_user_servers(Config#config.vhost_file,Config#config.user_server_maxuid). %%---------------------------------------------------------------------- %% Func: setup_user_servers/2 %%---------------------------------------------------------------------- setup_user_servers(_,none) -> ?LOG("Don't start any user server, as user_server_maxuid not defined~n",?DEB), ok; setup_user_servers(none,Val) when is_integer(Val) -> ts_user_server:reset(Val); setup_user_servers(FileId,Val) when is_atom(FileId), is_integer(Val) -> ?LOGF("Starting user servers with params ~p ~p~n",[FileId,Val],?DEB), {ok,Domains} = ts_file_server:get_all_lines(FileId), ?LOGF("Domains:~p~n",[Domains],?DEB), lists:foreach(fun(Domain) -> {ok,_} = ts_user_server_sup:start_user_server(list_to_atom("us_" ++Domain)) end, Domains), ts_user_server:reset_all(Val). %%---------------------------------------------------------------------- %% Func: check_config/1 %% Returns: ok | {error, ErrorList} %%---------------------------------------------------------------------- check_config(Config)-> Pop= ts_utils:check_sum(Config#config.sessions, #session.popularity, ?SESSION_POP_ERROR_MSG), %% FIXME: we should not depend on a protocol specific feature here Agents = ts_config_http:check_user_agent_sum(Config#config.session_tab), case lists:filter(fun(X)-> X /= ok end, [Pop, Agents]) of [] -> ok; ErrorList -> {error, ErrorList} end. load_app(Name) when is_atom(Name) -> FName = atom_to_list(Name) ++ ".app", case code:where_is_file(FName) of non_existing -> {error, {file:format_error(error_enoent), FName}}; FullName -> case file:consult(FullName) of {ok, [Application]} -> {ok, Application}; {error, Reason} -> {error, {file:format_error(Reason), FName}} end end. %%---------------------------------------------------------------------- %% Func: loop_load/1 %% Args: #config %% Returns: #config %% Purpose: duplicate phases 'load_loop' times. %%---------------------------------------------------------------------- loop_load(Config=#config{load_loop=0,arrivalphases=Arrival}) -> Sorted=lists:keysort(#arrivalphase.phase, Arrival), Config#config{arrivalphases=Sorted}; loop_load(Config=#config{load_loop=Loop,arrivalphases=Arrival}) when is_integer(Loop) -> loop_load(Config, ts_utils:keymax(#arrivalphase.phase, Arrival), Arrival ). %% We have a list of n phases: duplicate the list and increase by the %% max to get a new unique id for all phases. Here we don't care about %% the order, so we start with the last iteration (Loop* Max) loop_load(Config=#config{load_loop=0},_,Current) -> Sorted=lists:keysort(#arrivalphase.phase, Current),% is it necessary ? Config#config{arrivalphases = Sorted}; loop_load(Config=#config{load_loop=Loop, arrivalphases=Arrival},Max,Current) -> Fun= fun(Phase) -> Phase+Max*Loop end, NewArrival = lists:keymap(Fun,#arrivalphase.phase,Arrival), loop_load(Config#config{load_loop=Loop-1},Max,lists:append(Current, NewArrival)). %% @doc sort static users by start time sort_static(Config=#config{static_users=S})-> ?LOGF("sort static users: ~p ~n", [S], ?DEB), SortedL= lists:keysort(1,S), Config#config{static_users=static_name_to_session(Config#config.sessions,SortedL)}. %% %% @doc start a remote beam %% start_slave(Host, Name, Args) when is_atom(Host), is_atom(Name)-> case slave:start(Host, Name, Args) of {ok, Node} -> ?LOGF("started newbeam on node ~p ~n", [Node], ?NOTICE), Res = net_adm:ping(Node), ?LOGF("ping ~p ~p~n", [Node,Res], ?NOTICE), Node; {error, Reason} -> ?LOGF("Can't start newbeam on host ~p (reason: ~p) ! Aborting!~n",[Host, Reason],?EMERG), exit({slave_failure, Reason}) end. choose_port(_,undefined) -> 0; choose_port(_, _Range) -> -1. %% @spec static_name_to_session(Sessions::list(), Static::list() ) -> StaticUsers::list() %% @doc convert session name to session id in static users list @end static_name_to_session(Sessions, Static) -> ?LOGF("Static users with session id ~p~n",[Static],?DEB), Search = fun({Delay,Name})-> {value, Session} = lists:keysearch(Name, #session.name, Sessions), {Delay, Session} end, Res=lists:map(Search, Static), ?LOGF("Static users with session id ~p~n",[Res],?DEB), Res. %% @spec set_nodename(NodeId::integer()) -> string() %% @doc set slave node name: check if controller node name has an id, %% and put it in the slave name set_nodename(NodeId) when is_integer(NodeId)-> CId = case atom_to_list(node()) of "tsung_controller@"++_ -> ""; "tsung_controller"++Tail -> [Id|_] = string:tokens(Tail,"@"), Id++"_" end, list_to_atom("tsung"++ CId++ integer_to_list(NodeId)). %% @spec set_max_duration(integer()) -> ok %% @doc start a timer for the maximum duration of the load test. The %% maximum duration is 49 days set_max_duration(0) -> ok; % nothing to do set_max_duration(Duration) when Duration =< 4294967 -> ?LOGF("Set max duration of test: ~p s ~n",[Duration],?NOTICE), erlang:start_timer(Duration*1000, self(), end_tsung ). local_launcher([],_,_) -> 0; local_launcher([Host],LogDir,Config) -> ?LOGF("Start a launcher on the controller beam ~p~n", [Host], ?NOTICE), LogDirEnc = encode_filename(LogDir), %% set the application spec (read the app file and update some env. var.) {ok, {_,_,AppSpec}} = load_app(tsung), {value, {env, OldEnv}} = lists:keysearch(env, 1, AppSpec), NewEnv = [ {debug_level,?config(debug_level)}, {log_file,LogDirEnc}], RepKeyFun = fun(Tuple, List) -> lists:keyreplace(element(1, Tuple), 1, List, Tuple) end, Env = lists:foldl(RepKeyFun, OldEnv, NewEnv), NewAppSpec = lists:keyreplace(env, 1, AppSpec, {env, Env}), ok = application:load({application, tsung, NewAppSpec}), case application:start(tsung) of ok -> ?LOG("Application started, activate launcher, ~n", ?INFO), application:set_env(tsung, debug_level, Config#config.loglevel), ts_launcher_static:launch({node(), Host, []}), ts_launcher:launch({node(), Host, [], Config#config.seed}), 1 ; {error, Reason} -> ?LOGF("Can't start launcher application (reason: ~p) ! Aborting!~n",[Reason],?EMERG), {error, Reason} end. remote_launcher(Host, NodeId, Args) when is_list(Host)-> remote_launcher(list_to_atom(Host), NodeId, Args); remote_launcher(Host, NodeId, Args) when is_list(NodeId)-> remote_launcher(Host, list_to_integer(NodeId), Args); remote_launcher(Host, NodeId, Args)-> Name = set_nodename(NodeId), ?LOGF("starting newbeam on host ~p with Args ~p~n", [Host, Args], ?INFO), start_slave(Host, Name, Args). set_remote_args(LogDir,PortsRange)-> {ok, [[BootController]]} = init:get_argument(boot), ?DebugF("BootController ~p~n", [BootController]), {ok, [[?TSUNGPATH,PathVar]]} = init:get_argument(boot_var), ?DebugF("BootPathVar ~p~n", [PathVar]), {ok, PAList} = init:get_argument(pa), PA = lists:flatmap(fun(A) -> [" -pa "] ++A end,PAList), ?DebugF("PA list ~p ~n", [PA]), Boot = re:replace(BootController,"tsung_controller","tsung",[{return,list},global]), ?DebugF("Boot ~p~n", [Boot]), Sys_Args= ts_utils:erl_system_args(), LogDirEnc = encode_filename(LogDir), Ports = case PortsRange of {Min, Max} -> " -tsung cport_min " ++ integer_to_list(Min) ++ " -tsung cport_max " ++ integer_to_list(Max); undefined -> "" end, lists:flatten([ Sys_Args," -boot ", Boot, " -boot_var ", ?TSUNGPATH, " ",PathVar, PA, " +K true ", " -tsung debug_level ", integer_to_list(?config(debug_level)), " -tsung log_file ", LogDirEnc, Ports ]). %% @spec get_one_node_per_host(RemoteNodes::list()) -> Nodes::list() %% @doc From a list if erlang nodenames, return a list with only a %% single node per host %% @end get_one_node_per_host([]) -> %%no remote nodes, we are using a controller vm [node()]; get_one_node_per_host(RemoteNodes) -> get_one_node_per_host(RemoteNodes,dict:new()) . get_one_node_per_host([], Dict) -> {_,Nodes} = lists:unzip(dict:to_list(Dict)), Nodes; get_one_node_per_host([Node | Nodes], Dict) -> Host = ts_utils:node_to_hostname(Node), case dict:is_key(Host, Dict) of true -> get_one_node_per_host(Nodes,Dict); false -> NewDict = dict:store(Host, Node, Dict), get_one_node_per_host(Nodes,NewDict) end. tsung-1.4.2/src/tsung_controller/ts_file_server.erl0000644000201100017670000002214411701017117022253 0ustar nniclausdream%%% Copyright (C) 2005 Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%%------------------------------------------------------------------- %%% File : ts_file_server.erl %%% Author : Nicolas Niclausse %%% Description : Read a line-based file %%% %%% Created : 6 Jul 2005 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_file_server). -author('nicolas.niclausse@niclux.org'). -behaviour(gen_server). %% External exports -export([start/0, get_random_line/0, get_random_line/1, get_next_line/0, get_next_line/1, get_all_lines/0, get_all_lines/1, stop/0, read/1, read/2]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(file, {items, %% tuple of lines read from a file size, %% total number of lines current=-1 %% current line in file }). -record(state, {files}). -define(DICT, dict). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("xmerl.hrl"). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: parse_config/2 %% Args: Element, Config %% Returns: List %% Purpose: parse a request defined in the XML config file %%---------------------------------------------------------------------- read(Filenames) -> gen_server:call({global, ?MODULE}, {read, Filenames}, ?config(file_server_timeout)). read(Filenames, Timeout) -> gen_server:call({global, ?MODULE}, {read, Filenames}, Timeout). start() -> ?LOG("Starting~n",?DEB), gen_server:start_link({global, ?MODULE}, ?MODULE, [], []). get_random_line({Pid,_DynData}) when is_pid(Pid)-> %% called within a substitution (eg. file is 'default') case get_random_line(default) of {ok, Val} -> Val; Error -> Error end; get_random_line(FileID)-> gen_server:call({global, ?MODULE}, {get_random_line, FileID}). get_random_line() -> get_random_line(default). get_next_line({Pid,_DynData}) when is_pid(Pid)-> %% called within a substitution (eg. file is 'default') case get_next_line(default) of {ok, Val} -> Val; Error -> Error end; get_next_line(FileID)-> gen_server:call({global, ?MODULE}, {get_next_line, FileID}). get_next_line() -> get_next_line(default). get_all_lines({Pid,_DynData}) when is_pid(Pid)-> %% called within a substitution (eg. file is 'default') case get_all_lines(default) of {ok, Val} -> Val; Error -> Error end; get_all_lines(FileID)-> gen_server:call({global, ?MODULE}, {get_all_lines, FileID}). get_all_lines() -> get_all_lines(default). stop()-> gen_server:call({global, ?MODULE}, stop). %%%---------------------------------------------------------------------- %%% Callback functions from gen_server %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%---------------------------------------------------------------------- init([]) -> {ok, #state{files=?DICT:new()}}. %%---------------------------------------------------------------------- %% Func: handle_call/3 %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_call({get_all_lines, FileID}, _From, State) -> FileDesc = ?DICT:fetch(FileID, State#state.files), Reply = {ok, tuple_to_list(FileDesc#file.items)}, {reply, Reply, State}; handle_call({get_random_line, FileID}, _From, State) -> FileDesc = ?DICT:fetch(FileID, State#state.files), I = random:uniform(FileDesc#file.size), Reply = {ok, element(I, FileDesc#file.items)}, {reply, Reply, State}; handle_call({get_next_line, FileID}, _From, State) -> FileDesc = ?DICT:fetch(FileID, State#state.files), I = (FileDesc#file.current + 1) rem FileDesc#file.size, Reply = {ok, element(I+1, FileDesc#file.items)}, NewFileDesc = FileDesc#file{current=I}, {reply, Reply, State#state{files=?DICT:store(FileID, NewFileDesc, State#state.files)}}; handle_call({read, Filenames}, _From, State) -> lists:foldl(fun open_file/2, {reply, ok, State}, Filenames); handle_call(stop, _From, State)-> {stop, normal, ok, State}. %%---------------------------------------------------------------------- %% Func: handle_cast/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_cast(_Msg, State) -> {noreply, State}. %%---------------------------------------------------------------------- %% Func: handle_info/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%---------------------------------------------------------------------- %% Func: terminate/2 %% Purpose: Shutdown the server %% Returns: any (ignored by gen_server) %%---------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Open a file and return a new state %%---------------------------------------------------------------------- open_file({ID, Path}, {reply, Result, State}) -> case ?DICT:find(ID, State#state.files) of {ok, _} -> ?LOGF("File with id ~p already opened (path is ~p)~n",[ID, Path], ?WARN), {reply, {error, already_open}, State}; error -> ?LOGF("Opening file ~p~n",[Path], ?INFO), {Status, File} = file:open(Path, [read] ), case Status of error -> ?LOGF("Error while opening ~p file ~p~n",[File, Path], ?ERR), {reply, {error, File}, State}; _ -> List_items = read_item(File, []), file:close(File), % Close the config file FileDesc = #file{items = list_to_tuple(List_items), size=length(List_items)}, {reply, Result, State#state{files = ?DICT:store(ID, FileDesc, State#state.files)}} end end. %%---------------------------------------------------------------------- %% Treate one line of the file %% Lines starting by '#' are skipped %%---------------------------------------------------------------------- read_item(File, L)-> %% Read one line Line = io:get_line(File, ""), case Line of eof -> lists:reverse(L); _-> Tokens = string:tokens(Line, "\n"), case Tokens of [] -> read_item(File, L); ["#" | _] -> read_item(File, L); [Value] -> %% FIXME: maybe we should use an ets table instead ? read_item(File, [Value|L]) end end. tsung-1.4.2/src/tsung_controller/tsung_controller.erl0000644000201100017670000001303611701017117022643 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(tsung_controller). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -export([start/2, start_phase/3, stop/1, stop_all/1, status/1]). -behaviour(application). -include("ts_profile.hrl"). -include_lib("kernel/include/file.hrl"). %%---------------------------------------------------------------------- %% Func: start/2 %% Returns: {ok, Pid} | %% {ok, Pid, State} | %% {error, Reason} %%---------------------------------------------------------------------- start(_Type, _StartArgs) -> error_logger:tty(false), {ok, {LogDir, _Name}} = ts_utils:setsubdir(?config(log_dir)), erlang:display("Log directory is: " ++ LogDir), LogFile = filename:join(LogDir, atom_to_list(node()) ++ ".log"), case error_logger:logfile({open, LogFile }) of ok -> case ts_controller_sup:start_link(LogDir) of {ok, Pid} -> {ok, Pid}; Error -> io:format(standard_error,"Can't start ! ~p ~n",[Error]), Error end; {error, Reason} -> Msg = "Error while opening log file: " , io:format(standard_error,Msg ++ " ~p ~n",[Reason]), {error, Reason} end. start_phase(load_config, _StartType, _PhaseArgs) -> {Conf,Timeout} = case ?config(config_file) of "-" -> {standard_io, 120000}; %2mn timeout File -> T = case file:read_file_info(File) of {ok, #file_info{size=Size}} when Size > 10000000 -> % > 10MB io:format(standard_error,"Can take up to 5mn to read config ~p~n ",[Size]), 300000; % 10mn {ok, #file_info{size=Size}} when Size > 1000000 -> % > 1MB io:format(standard_error,"Can take up to 3mn to read config ~p~n ",[Size]), 180000; % 5mn {ok, #file_info{size=_}} -> 120000 % 2mn end, {File, T} end, case ts_config_server:read_config(Conf,Timeout) of {error,Reason}-> io:format(standard_error,"Config Error, aborting ! ~p~n ",[Reason]), init:stop(1); ok -> ok end; start_phase(start_os_monitoring, _StartType, _PhaseArgs) -> ts_os_mon:activate(); start_phase(start_clients, _StartType, _PhaseArgs) -> ts_mon:start_clients({?config(clients), ?config(dump), ?config(stats_backend)}). %%---------------------------------------------------------------------- %% Func: status/1 %% Returns: any %%---------------------------------------------------------------------- status([Host]) when is_atom(Host)-> _List = net_adm:world_list([Host]), global:sync(), Msg = case catch ts_mon:status() of {Clients, Count, Connected, Interval, Phase} -> S1 = io_lib:format("Tsung is running [OK]~n" ++ " Current request rate: ~.2f req/sec~n" ++ " Current users: ~p~n" ++ " Current connected users: ~p ~n", [Count/Interval, Clients, Connected]), {ok, Nodes, Ended_Beams} = ts_config_server:status(), case {Phase, Nodes == Ended_Beams} of {error, _} -> % newphase not initialised, first phase S1 ++ " Current phase: 1"; {_, true} -> S1 ++ " Current phase: last, waiting for pending clients"; {{ok,P}, _} -> NPhases = (P div Nodes) + 1, io_lib:format("~s Current phase: ~p",[S1,NPhases]) end; {'EXIT', {noproc, _}} -> "Tsung is not started" end, io:format("~s~n",[Msg]). %%---------------------------------------------------------------------- %% Func: stop/1 %% Returns: any %%---------------------------------------------------------------------- stop(_State) -> stop. %%---------------------------------------------------------------------- %% Func: stop_all/0 %% Returns: any %%---------------------------------------------------------------------- stop_all(Arg) -> ts_utils:stop_all(Arg,'ts_mon'). tsung-1.4.2/src/tsung_controller/ts_os_mon_sup.erl0000644000201100017670000000512511701017117022127 0ustar nniclausdream%%% Copyright (C) 2009 Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_os_mon_sup). -vc('$Id: ts_client_sup.erl 953 2008-11-23 16:57:05Z nniclausse $ '). -author('nicolas.niclausse@niclux.org'). -behaviour(supervisor). -include("ts_profile.hrl"). %% External exports -export([start_link/1, start_child/2]). %% supervisor callbacks -export([init/1]). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start_link(Plugin) -> Module=get_module(Plugin), supervisor:start_link({local,Module}, ?MODULE, [Plugin]). start_child(Plugin, Args) -> ?LOGF("Starting child for plugin ~p with args ~p~n",[Plugin,Args], ?DEB), Module=get_module(Plugin), supervisor:start_child(Module,[Args]). %%%---------------------------------------------------------------------- %%% Callback functions from supervisor %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, {SupFlags, [ChildSpec]}} | %% ignore | %% {error, Reason} %%---------------------------------------------------------------------- init([Plugin]) -> ?LOGF("Starting with args ~p~n",[Plugin], ?INFO), SupFlags = {simple_one_for_one, 20, 20}, {ok, {SupFlags, get_spec(Plugin)}}. %% internal funs get_spec(Plugin) -> Module=get_module(Plugin), [{Module,{Module, start, []}, permanent, 2000, worker,[Module]}]. get_module(Plugin) when is_atom(Plugin)-> ModuleStr="ts_os_mon_" ++ atom_to_list(Plugin), list_to_atom(ModuleStr). tsung-1.4.2/src/tsung_controller/tsung_controller.app.src.in0000644000201100017670000000437011701017117024035 0ustar nniclausdream{application, tsung_controller, [{description, "tsung, a bench tool for TCP/UDP servers"}, {vsn, "%VSN%"}, {modules, [ tsung_controller, ts_controller_sup, ts_stats_mon, ts_mon, ts_timer, ts_user_server, ts_config_server, ts_msg_server, ts_file_server, ts_os_mon ]}, {registered, [ ts_stats, ts_mon, ts_config_server, ts_os_mon ]}, {env, [ {debug_level, 6}, {smp_disable, true}, % disable smp on clients {ts_cookie, "humhum"}, {clients_timeout, 60000}, % timeout for global synchro {file_server_timeout, 30000},% timeout for reading file {warm_time, 1}, % (seconds) initial waiting time when launching clients {thinktime_value, "5"}, % default value = 5sec {thinktime_override, "false"}, {thinktime_random, "false"}, {munin_port, 4949}, {snmp_port, 161}, {snmp_version, v2}, {snmp_community, "public"}, {dumpstats_interval, 10000}, {dump, none}, %% full or light or none {stats_backend, none}, %% text|rrdtool {nclients, 10}, %% number of clients {nclients_deb, 1}, %% beginning of interval {nclients_fin, 2000}, %% end of interval {config_file, "./tsung.xml"}, {log_file, "./tsung.log"}, {match_log_file, "./match.log"} ]}, {applications, [ @ERLANG_APPLICATIONS@ ]}, {start_phases, [{load_config, []},{start_os_monitoring,[{timeout,30000}]}, {start_clients,[]}]}, {mod, {tsung_controller, []}} ]}. tsung-1.4.2/src/tsung_controller/ts_mon.erl0000644000201100017670000004762211701017117020547 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2004 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%---------------------------------------------------------------------- %% @copyright 2001 IDEALX %% @author Nicolas Niclausse %% @since 8 Feb 2001 %% %% @doc monitor and log events: arrival and departure of users, new %% connections, os_mon and send/rcv message (when dump is set to true) %%---------------------------------------------------------------------- -module(ts_mon). -author('nicolas@niclux.org'). -vc('$Id$ '). -behaviour(gen_server). -include("ts_profile.hrl"). -include("ts_config.hrl"). %% External exports -export([start/1, stop/0, newclient/1, endclient/1, sendmes/1, add/2, start_clients/1, abort/0, status/0, rcvmes/1, add/1, dumpstats/0, add_match/2, dump/1, launcher_is_alive/0 ]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define(DUMP_FILENAME,"tsung.dump"). -define(FULLSTATS_FILENAME,"tsung-fullstats.log"). -define(DELAYED_WRITE_SIZE,524288). % 512KB -define(DELAYED_WRITE_DELAY,5000). % 5 sec -record(state, {log, % log fd backend, % type of backend: text|... log_dir, % log directory fullstats, % fullstats fd dump_interval,% dumpfile, % file used when dumptrafic is set light or full client=0, % number of clients currently running maxclient=0, % max of simultaneous clients stats, % record keeping stats info stop = false, % true if we should stop laststats, % values of last printed stats lastdate, % date of last printed stats type, % type of logging (none, light, full) launchers=0 % number of launchers started }). -record(stats, { users_count = 0, finish_users_count = 0, os_mon, session = [] }). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% @spec start(LogDir::string())-> {ok, Pid::pid()} | ignore | {error, Error::term()} %% @doc Start the monitoring process %% @end %%---------------------------------------------------------------------- start(LogDir) -> ?LOG("starting monitor, global ~n",?NOTICE), gen_server:start_link({global, ?MODULE}, ?MODULE, [LogDir], []). %% @spec start_clients({Machines::term(), Dump::string(), BackEnd::atom()}) -> ok start_clients({Machines, Dump, BackEnd}) -> gen_server:call({global, ?MODULE}, {start_logger, Machines, Dump, BackEnd}, infinity). stop() -> gen_server:cast({global, ?MODULE}, {stop}). add(nocache,Data) -> gen_server:cast({global, ?MODULE}, {add, Data}). add(Data) -> ts_mon_cache:add(Data). add_match(Data,{UserId,SessionId,RequestId}) -> add_match(Data,{UserId,SessionId,RequestId,[]}); add_match(Data=[Head|_],{UserId,SessionId,RequestId,Bin}) -> TimeStamp=now(), put(last_match,Head), ts_mon_cache:add_match(Data,{UserId,SessionId,RequestId,TimeStamp, Bin}). status() -> gen_server:call({global, ?MODULE}, {status}). abort() -> gen_server:cast({global, ?MODULE}, {abort}). dumpstats() -> gen_server:cast({global, ?MODULE}, {dumpstats}). newclient({Who, When}) -> gen_server:cast({global, ?MODULE}, {newclient, Who, When}). endclient({Who, When, Elapsed}) -> gen_server:cast({global, ?MODULE}, {endclient, Who, When, Elapsed}). sendmes({none, _, _}) -> skip; sendmes({protocol, _, _}) -> skip; sendmes({_Type, Who, What}) -> gen_server:cast({global, ?MODULE}, {sendmsg, Who, now(), What}). rcvmes({none, _, _}) -> skip; rcvmes({protocol, _, _})-> skip; rcvmes({_, _, closed}) -> skip; rcvmes({_Type, Who, What}) -> gen_server:cast({global, ?MODULE}, {rcvmsg, Who, now(), What}). dump({none, _, _})-> skip; dump({_Type, Who, What}) -> gen_server:cast({global, ?MODULE}, {dump, Who, now(), What}). launcher_is_alive() -> gen_server:cast({global, ?MODULE}, {launcher_is_alive}). %%%---------------------------------------------------------------------- %%% Callback functions from gen_server %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%---------------------------------------------------------------------- init([LogDir]) -> ?LOGF("Init, log dir is ~p~n",[LogDir],?NOTICE), Stats = #stats{os_mon = dict:new()}, State=#state{ dump_interval = ?config(dumpstats_interval), log_dir = LogDir, stats = Stats, lastdate = now(), laststats = Stats }, case ?config(mon_file) of "-" -> {ok, State#state{log=standard_io}}; Name -> Filename = filename:join(LogDir, Name), case file:open(Filename,[write]) of {ok, Stream} -> ?LOG("starting monitor~n",?NOTICE), {ok, State#state{log=Stream}}; {error, Reason} -> ?LOGF("Can't open mon log file! ~p~n",[Reason], ?ERR), {stop, Reason} end end. %%---------------------------------------------------------------------- %% Func: handle_call/3 %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_call({start_logger, Machines, DumpType, Backend}, From, State) -> start_logger({Machines, DumpType, Backend}, From, State); %%% get status handle_call({status}, _From, State) -> Request = ts_stats_mon:status(request), Interval = ts_utils:elapsed(State#state.lastdate, now()) / 1000, Phase = ts_stats_mon:status(newphase,sum), Connected = case ts_stats_mon:status(connected,sum) of {ok, Val} -> Val; _ -> 0 end, Reply = { State#state.client, Request,Connected, Interval, Phase}, {reply, Reply, State}; handle_call(Request, _From, State) -> ?LOGF("Unknown call ~p !~n",[Request],?ERR), Reply = ok, {reply, Reply, State}. %%---------------------------------------------------------------------- %% Func: handle_cast/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_cast({add, Data}, State=#state{stats=Stats}) when is_list(Data) -> case State#state.backend of fullstats -> io:format(State#state.fullstats,"~p~n",[Data]); _Other -> ok end, New = lists:foldl(fun ts_stats_mon:add_stats_data/2, Stats#stats.os_mon, Data), NewStats = Stats#stats{os_mon=New}, {noreply,State#state{stats=NewStats}}; handle_cast({add, Data}, State=#state{stats=Stats}) when is_tuple(Data) -> case State#state.backend of fullstats -> io:format(State#state.fullstats,"~p~n",[Data]); _Other -> ok end, New = ts_stats_mon:add_stats_data(Data, Stats#stats.os_mon), NewStats = Stats#stats{os_mon=New}, {noreply,State#state{stats=NewStats}}; handle_cast({newclient, Who, When}, State=#state{stats=Stats}) -> Clients = State#state.client+1, OldCount = Stats#stats.users_count, NewStats = Stats#stats{users_count=OldCount+1}, case State#state.type of none -> ok; protocol -> ok; _ -> io:format(State#state.dumpfile,"NewClient:~w:~p~n",[ts_utils:time2sec_hires(When), Who]), io:format(State#state.dumpfile,"load:~w~n",[Clients]) end, case Clients > State#state.maxclient of true -> {noreply, State#state{client = Clients, maxclient=Clients, stats=NewStats}}; false -> {noreply, State#state{client = Clients, stats=NewStats}} end; handle_cast({endclient, Who, When, Elapsed}, State=#state{stats=Stats}) -> Clients = State#state.client-1, OldSession = Stats#stats.session, %% update session sample NewSession = ts_stats_mon:update_stats(sample, OldSession, Elapsed), OldCount = Stats#stats.finish_users_count, NewStats = Stats#stats{finish_users_count=OldCount+1,session= NewSession}, case State#state.type of none -> skip; protocol -> skip; _Type -> io:format(State#state.dumpfile,"EndClient:~w:~p~n",[ts_utils:time2sec_hires(When), Who]), io:format(State#state.dumpfile,"load:~w~n",[Clients]) end, case {Clients, State#state.stop} of {0, true} -> ?LOG("No more users and stop is true, stop~n", ?INFO), {stop, normal, State}; _ -> {noreply, State#state{client = Clients, stats=NewStats}} end; handle_cast({dumpstats}, State=#state{stats=Stats}) -> export_stats(State), NewSessions = ts_stats_mon:reset_all_stats(Stats#stats.session), NewOSmon = ts_stats_mon:reset_all_stats(Stats#stats.os_mon), NewStats = Stats#stats{session=NewSessions, os_mon=NewOSmon}, {noreply, State#state{laststats = Stats, stats=NewStats,lastdate=now()}}; handle_cast({sendmsg, _, _, _}, State = #state{type = none}) -> {noreply, State}; handle_cast({sendmsg, Who, When, What}, State = #state{type=light,dumpfile=Log}) -> io:format(Log,"Send:~w:~w:~-44s~n",[ts_utils:time2sec_hires(When),Who, binary_to_list(What)]), {noreply, State}; handle_cast({sendmsg, Who, When, What}, State=#state{type=full,dumpfile=Log}) when is_binary(What)-> io:format(Log,"Send:~w:~w:~s~n",[ts_utils:time2sec_hires(When),Who,binary_to_list(What)]), {noreply, State}; handle_cast({sendmsg, Who, When, What}, State=#state{type=full,dumpfile=Log}) -> io:format(Log,"Send:~w:~w:~p~n",[ts_utils:time2sec_hires(When),Who,What]), {noreply, State}; handle_cast({dump, Who, When, What}, State=#state{type=protocol,dumpfile=Log}) -> io:format(Log,"~w;~w;~s~n",[ts_utils:time2sec_hires(When),Who,What]), {noreply, State}; handle_cast({rcvmsg, _, _, _}, State = #state{type=none}) -> {noreply, State}; handle_cast({rcvmsg, Who, When, What}, State = #state{type=light, dumpfile=Log}) when is_binary(What)-> io:format(Log,"Recv:~w:~w:~-44s~n",[ts_utils:time2sec_hires(When),Who, binary_to_list(What)]), {noreply, State}; handle_cast({rcvmsg, Who, When, What}, State = #state{type=light, dumpfile=Log}) -> io:format(Log,"Recv:~w:~w:~-44p~n",[ts_utils:time2sec_hires(When),Who, What]), {noreply, State}; handle_cast({rcvmsg, Who, When, What}, State=#state{type=full,dumpfile=Log}) when is_binary(What)-> io:format(Log, "Recv:~w:~w:~s~n",[ts_utils:time2sec_hires(When),Who,binary_to_list(What)]), {noreply, State}; handle_cast({rcvmsg, Who, When, What}, State=#state{type=full,dumpfile=Log}) -> io:format(Log, "Recv:~w:~w:~p~n",[ts_utils:time2sec_hires(When),Who,What]), {noreply, State}; handle_cast({stop}, State = #state{client = 0, launchers=1}) -> ?LOG("Stop asked, no more users, last launcher stopped, OK to stop~n", ?INFO), {stop, normal, State}; handle_cast({stop}, State=#state{launchers=L}) -> % we should stop, wait until no more clients are alive ?LOG("A launcher has finished, but not all users have finished, wait before stopping~n", ?NOTICE), {noreply, State#state{stop = true, launchers=L-1}}; handle_cast({launcher_is_alive}, State=#state{launchers=L}) -> ?LOG("A launcher has started~n", ?NOTICE), {noreply, State#state{launchers=L+1}}; handle_cast({abort}, State) -> % stop now ! ?LOG("Aborting by request !~n", ?EMERG), ts_stats_mon:add({ count, error_abort }), {stop, abort, State}; handle_cast(Msg, State) -> ?LOGF("Unknown msg ~p !~n",[Msg], ?WARN), {noreply, State}. %%---------------------------------------------------------------------- %% Func: handle_info/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%---------------------------------------------------------------------- %% Func: terminate/2 %% Purpose: Shutdown the server %% Returns: any (ignored by gen_server) %%---------------------------------------------------------------------- terminate(Reason, State) -> ?LOGF("stopping monitor (~p)~n",[Reason],?NOTICE), export_stats(State), ts_stats_mon:status(ts_stats_mon), % blocking call to ts_stats_mon; this way, we are % sure the last call to dumpstats is finished case State#state.backend of json -> io:format(State#state.log,"]}]}~n",[]); _ -> io:format(State#state.log,"EndMonitor:~w~n",[now()]) end, case State#state.log of standard_io -> ok; Dev -> file:close(Dev) end, file:close(State#state.fullstats), slave:stop(node()), ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState, NewStateData} %%-------------------------------------------------------------------- code_change(_OldVsn, StateData, _Extra) -> {ok, StateData}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: start_logger/3 %% Purpose: open log files and start timer %% Returns: {reply, ok, State} | {stop, Reason, State} %%---------------------------------------------------------------------- %% fulltext backend: open log file with compression enable and delayed_write start_logger({Machines, DumpType, fullstats}, From, State=#state{fullstats=undefined}) -> Filename = filename:join(State#state.log_dir,?FULLSTATS_FILENAME), ?LOG("Open file with delayed_write for fullstats backend~n",?NOTICE), case file:open(Filename,[write, {delayed_write, ?DELAYED_WRITE_SIZE, ?DELAYED_WRITE_DELAY}]) of {ok, Stream} -> start_logger({Machines, DumpType, fullstats}, From, State#state{fullstats=Stream}); {error, Reason} -> ?LOGF("Can't open mon log file ~p! ~p~n",[Filename,Reason], ?ERR), {stop, Reason, State} end; start_logger({Machines, DumpType, Backend}, _From, State=#state{log=Log,fullstats=FS}) -> ?LOGF("Activate clients with ~p backend~n",[Backend],?NOTICE), print_headline(Log,Backend), timer:apply_interval(State#state.dump_interval, ?MODULE, dumpstats, [] ), start_launchers(Machines), ts_stats_mon:set_output(Backend,{Log,FS}), ts_stats_mon:set_output(Backend,{Log,FS}, transaction), ts_stats_mon:set_output(Backend,{Log,FS}, request), ts_stats_mon:set_output(Backend,{Log,FS}, connect), ts_stats_mon:set_output(Backend,{Log,FS}, page), start_dump(State#state{type=DumpType, backend=Backend}). print_headline(Log,json)-> DateStr = ts_utils:now_sec(), io:format(Log,"{~n \"stats\": [~n {\"timestamp\": ~p, \"samples\": [",[DateStr]); print_headline(_Log,_Backend)-> ok. %% @spec start_dump(State::record(state)) -> {reply, Reply, State} %% @doc open file for dumping traffic start_dump(State=#state{type=none}) -> {reply, ok, State}; start_dump(State=#state{type=Type}) -> Filename = filename:join(State#state.log_dir,?DUMP_FILENAME), case file:open(Filename,[write, {delayed_write, ?DELAYED_WRITE_SIZE, ?DELAYED_WRITE_DELAY}]) of {ok, Stream} -> ?LOG("dump file opened, starting monitor~n",?INFO), case Type of protocol -> io:format(Stream,"#date;pid;id;http method;host;URL;HTTP status;size;match;error~n",[]); _ -> ok end, {reply, ok, State#state{dumpfile=Stream}}; {error, Reason} -> ?LOGF("Can't open mon dump file! ~p~n",[Reason], ?ERR), {reply, ok, State#state{type=none}} end. %%---------------------------------------------------------------------- %% Func: export_stats/1 %%---------------------------------------------------------------------- export_stats(State=#state{log=Log,stats=Stats,laststats=LastStats, backend=json}) -> DateStr = ts_utils:now_sec(), io:format(Log,"]},~n {\"timestamp\": ~w, \"samples\": [",[DateStr]), %% print number of simultaneous users io:format(Log," {\"name\": \"users\", \"value\": ~p, \"max\": ~p}",[State#state.client,State#state.maxclient]), export_stats_common(json, Stats,LastStats,Log); export_stats(State=#state{log=Log,stats=Stats,laststats=LastStats, backend=BackEnd}) -> DateStr = ts_utils:now_sec(), io:format(Log,"# stats: dump at ~w~n",[DateStr]), %% print number of simultaneous users io:format(Log,"stats: ~p ~p ~p~n",[users,State#state.client,State#state.maxclient]), export_stats_common(BackEnd, Stats,LastStats,Log). export_stats_common(BackEnd, Stats,LastStats,Log)-> Param = {BackEnd,LastStats#stats.os_mon,Log}, dict:fold(fun ts_stats_mon:print_stats/3, Param, Stats#stats.os_mon), ts_stats_mon:print_stats({session, sample}, Stats#stats.session,{BackEnd,[],Log}), ts_stats_mon:print_stats({users_count, count}, Stats#stats.users_count, {BackEnd,LastStats#stats.users_count,Log}), ts_stats_mon:print_stats({finish_users_count, count}, Stats#stats.finish_users_count, {BackEnd,LastStats#stats.finish_users_count,Log}), ts_stats_mon:dumpstats(request), ts_stats_mon:dumpstats(page), ts_stats_mon:dumpstats(connect), ts_stats_mon:dumpstats(transaction), ts_stats_mon:dumpstats(). %%---------------------------------------------------------------------- %% Func: start_launchers/2 %% @doc start the launcher on clients nodes %%---------------------------------------------------------------------- start_launchers(Machines) -> ?DebugF("Need to start tsung client on ~p~n",[Machines]), GetHost = fun(A) -> list_to_atom(A#client.host) end, HostList = lists:map(GetHost, Machines), ?DebugF("Hostlist is ~p~n",[HostList]), %% starts beam on all client hosts ts_config_server:newbeams(HostList). tsung-1.4.2/src/tsung_controller/ts_config_mysql.erl0000644000201100017670000000743411701017117022445 0ustar nniclausdream%%% Created: July 2008 by Grgoire Reboul %%% From : ts_config_pgsql.erl by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_config_mysql). -author('gregoire.reboul@laposte.net'). -export([parse_config/2]). -include("ts_profile.hrl"). -include("ts_mysql.hrl"). -include("ts_config.hrl"). -include("xmerl.hrl"). %%---------------------------------------------------------------------- %% Func: parse_config/2 %% Args: Element, Config %% Returns: List %% Purpose: parse a request defined in the XML config file %%---------------------------------------------------------------------- %% Parsing other elements parse_config(Element = #xmlElement{name=dyn_variable}, Conf = #config{}) -> ts_config:parse(Element,Conf); parse_config(Element = #xmlElement{name=mysql}, Config=#config{curid = Id, session_tab = Tab, sessions = [CurS | _], dynvar=DynVar, subst = SubstFlag, match=MatchRegExp}) -> Request = case ts_config:getAttr(atom, Element#xmlElement.attributes, type) of sql -> ValRaw = ts_config:getText(Element#xmlElement.content), SQL = ts_utils:clean_str(ValRaw), ?LOGF("Got SQL query: ~p~n",[SQL], ?NOTICE), #mysql_request{sql=SQL, type= sql}; close -> ?LOGF("Got Close queryp~n",[], ?NOTICE), #mysql_request{type= close}; authenticate -> Database = ts_config:getAttr(Element#xmlElement.attributes, database), User = ts_config:getAttr(Element#xmlElement.attributes, username), Passwd = ts_config:getAttr(Element#xmlElement.attributes, password), ?LOGF("Got Auth datas: database->~p user->~p password->~p~n",[Database,User,Passwd], ?NOTICE), #mysql_request{username=User, database=Database, passwd=Passwd, type=authenticate}; connect -> ?LOGF("Got Connect ~n",[], ?NOTICE), #mysql_request{type=connect} end, Msg= #ts_request{ack = parse, endpage = true, dynvar_specs = DynVar, subst = SubstFlag, match = MatchRegExp, param = Request}, ts_config:mark_prev_req(Id-1, Tab, CurS), ets:insert(Tab,{{CurS#session.id, Id}, Msg }), lists:foldl( fun(A,B)->ts_config:parse(A,B) end, Config#config{dynvar=[]}, Element#xmlElement.content); %% Parsing other elements parse_config(Element = #xmlElement{}, Conf = #config{}) -> ts_config:parse(Element,Conf); %% Parsing non #xmlElement elements parse_config(_, Conf = #config{}) -> Conf. tsung-1.4.2/src/tsung_controller/ts_timer.erl0000644000201100017670000001552611701017117021074 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_timer). -vc('$Id$ '). -author('jflecomte@IDEALX.com'). -modifiedby('nicolas@niclux.org'). -include("ts_profile.hrl"). -behaviour(gen_fsm). %% Puropose: %% gen_fsm with 3 states: receiver, ack, initialize %% External events: connected %% External exports -export([start/1, connected/1, config/1]). %% gen_fsm callbacks -export([init/1, initialize/2, receiver/2, ack/2, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). -record(state, {nclient=0, pidlist = []}). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start(NClients) -> ?LOG("starting fsm timer",?INFO), gen_fsm:start_link({global, ?MODULE}, ?MODULE, [NClients], []). config(NClients) -> ?LOG("Configure fsm timer",?INFO), gen_fsm:send_event({global, ?MODULE}, {config, NClients}). connected(Pid) -> gen_fsm:send_event({global, ?MODULE}, {connected, Pid}). %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, StateName, StateData} | %% {ok, StateName, StateData, Timeout} | %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- init(_Args) -> ?LOG("starting timer",?INFO), {ok, initialize, #state{}}. %%---------------------------------------------------------------------- %% Func: StateName/2 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- initialize({config, Val}, State) -> {next_state, receiver, State#state{nclient=Val}}. %% now all the clients are connected, let's start to ack them receiver({connected, Pid}, #state{pidlist=List, nclient=1}) -> ?LOG("All connected, global ack!",?NOTICE), {next_state, ack, #state{pidlist=[Pid|List],nclient=0}, 1}; %% receive a new connected mes receiver({connected, Pid}, #state{pidlist=List, nclient=N}) -> ?LOGF("New connected ~p (nclient=~p)",[Pid, N],?DEB), {next_state, receiver, #state{pidlist=List ++ [Pid], nclient=N-1}, ?config(clients_timeout)}; %% timeout event, now we start to send ack, by sending a timeout event immediatly receiver(timeout, StateData) -> {next_state, ack, StateData,1}. %% no more ack to send, stop ack(timeout, #state{pidlist=[]}) -> {stop, normal, #state{}}; %% ack all pids ack(timeout, #state{pidlist=L}) -> lists:foreach(fun(A)->ts_client:next({A}) end, L), {next_state, receiver, #state{pidlist=[]}}. %%---------------------------------------------------------------------- %% Func: StateName/3 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {reply, Reply, NextStateName, NextStateData} | %% {reply, Reply, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} | %% {stop, Reason, Reply, NewStateData} %%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: handle_event/3 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. %%---------------------------------------------------------------------- %% Func: handle_sync_event/4 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {reply, Reply, NextStateName, NextStateData} | %% {reply, Reply, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} | %% {stop, Reason, Reply, NewStateData} %%---------------------------------------------------------------------- handle_sync_event(_Event, _From, StateName, StateData) -> Reply = ok, {reply, Reply, StateName, StateData}. %%---------------------------------------------------------------------- %% Func: handle_info/3 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_info(_Info, StateName, StateData) -> {next_state, StateName, StateData}. %%---------------------------------------------------------------------- %% Func: terminate/3 %% Purpose: Shutdown the fsm %% Returns: any %%---------------------------------------------------------------------- terminate(_Reason, _StateName, _StateData) -> ?LOG("terminate timer",?INFO), ok. %%-------------------------------------------------------------------- %% Func: code_change/4 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState, NewStateData} %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- tsung-1.4.2/src/tsung_controller/ts_config_shell.erl0000644000201100017670000000535511701017117022407 0ustar nniclausdream%%% %%% Copyright 2010 INRIA %%% %%% Author : Nicolas Niclausse %%% Created: 18 aot 2010 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_config_shell). -vc('$Id$ '). -author('nicolas.niclausse@sophia.inria.fr'). -export([parse_config/2]). -include("ts_profile.hrl"). -include("ts_http.hrl"). -include("ts_config.hrl"). -include("xmerl.hrl"). -include("ts_shell.hrl"). %% @spec parse_config(#xmlElement{}, Config::term()) -> NewConfig::term() %% @doc Parses a tsung.xml configuration file xml element for this %% protocol and updates the Config term. %% @end parse_config(Element = #xmlElement{name=dyn_variable}, Conf = #config{}) -> ts_config:parse(Element,Conf); parse_config(Element = #xmlElement{name=shell}, Config=#config{curid = Id, session_tab = Tab, sessions = [CurS | _], dynvar=DynVar, subst = SubstFlag, match=MatchRegExp}) -> Cmd = ts_config:getAttr(string,Element#xmlElement.attributes, cmd), Args = ts_config:getAttr(string,Element#xmlElement.attributes, args, ""), Request = #shell{command=Cmd,args=Args}, Msg= #ts_request{ack = parse, endpage = true, dynvar_specs = DynVar, subst = SubstFlag, match = MatchRegExp, param = Request}, ts_config:mark_prev_req(Id-1, Tab, CurS), ets:insert(Tab,{{CurS#session.id, Id},Msg}), lists:foldl( fun(A,B)->ts_config:parse(A,B) end, Config#config{dynvar=[]}, Element#xmlElement.content); %% Parsing other elements parse_config(Element = #xmlElement{}, Conf = #config{}) -> ts_config:parse(Element,Conf); %% Parsing non #xmlElement elements parse_config(_, Conf = #config{}) -> Conf. tsung-1.4.2/src/tsung_controller/ts_os_mon_snmp.erl0000644000201100017670000002517211701017117022301 0ustar nniclausdream%%% %%% Copyright 2008 © Nicolas Niclausse %%% %%% Author : Nicolas Niclausse %%% Created: 21 oct 2008 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_os_mon_snmp). -vc('$Id: ts_os_mon_snmp.erl,v 0.0 2008/10/21 12:57:49 nniclaus Exp $ '). -author('nicolas.niclausse@niclux.org'). -behaviour(gen_server). -include("ts_profile.hrl"). -include("ts_os_mon.hrl"). -include_lib("snmp/include/snmp_types.hrl"). -export([start/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state,{ mon_server, % pid of mon server dnscache=[], interval, pid, % pid of snmp_mgr uri, % use as an identifier for the snmp manager host, oids = [], % custom OIDS funs, % store fun to be applied in a dict version, port, community, addr }). %% SNMP definitions %% FIXME: make this customizable in the XML config file ? -define(SNMP_CPU_RAW_USER, [1,3,6,1,4,1,2021,11,50,0]). -define(SNMP_CPU_RAW_SYSTEM, [1,3,6,1,4,1,2021,11,52,0]). -define(SNMP_CPU_RAW_IDLE, [1,3,6,1,4,1,2021,11,53,0]). -define(SNMP_CPU_LOAD1, [1,3,6,1,4,1,2021,10,1,5,1]). -define(SNMP_MEM_BUFFER, [1,3,6,1,4,1,2021,4,14,0]). -define(SNMP_MEM_CACHED, [1,3,6,1,4,1,2021,4,15,0]). -define(SNMP_MEM_AVAIL, [1,3,6,1,4,1,2021,4,6,0]). -define(SNMP_MEM_TOTAL, [1,3,6,1,4,1,2021,4,5,0]). -define(SNMP_TIMEOUT,5000). start(Args) -> ?LOGF("starting os_mon_snmp with args ~p",[Args],?NOTICE), gen_server:start_link(?MODULE, Args, []). %%-------------------------------------------------------------------- %% Function: init/1 %% Description: Initiates the server %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%-------------------------------------------------------------------- init({HostStr, {Port, Community, Version, DynOIDS}, Interval, MonServer}) -> {ok, IP} = inet:getaddr(HostStr, inet), Apps = application:loaded_applications(), case proplists:is_defined(snmp,Apps) of true -> ?LOG("SNMP manager already started~n", ?NOTICE); _ -> ?LOG("Initialize SNMP application~n", ?NOTICE), Res1= application:start(snmp), ?LOGF("Initialize SNMP manager: ~p~n", [Res1],?NOTICE), Res2=snmpm:start(), ?LOGF("Register SNMP manager: ~p~n",[Res2], ?NOTICE), Res3=snmpm:register_user("tsung",snmpm_user_default,undefined), ?LOGF("SNMP initialization: ~p~n", [Res3],?NOTICE) end, erlang:start_timer(5, self(), connect ), FunsL= [{?SNMP_CPU_RAW_SYSTEM,cpu_system,sample_counter,fun(X)-> X/(Interval/1000) end}, {?SNMP_CPU_RAW_USER,cpu_user,sample_counter,fun(X)-> X/(Interval/1000) end}, {?SNMP_MEM_AVAIL,freemem,sample,fun(X)-> X/1000 end}, {?SNMP_CPU_LOAD1,load,sample,fun(X)-> X*100 end}] ++ DynOIDS, OIDS=lists:map(fun({Oid,_Name,_Type,_Fun})-> Oid end,FunsL), Funs=lists:foldl(fun({Oid,Name,Type,Fun},Acc)->dict:store(Oid,{Name,Type,Fun},Acc) end,dict:new(),FunsL), ?LOGF("Starting SNMP mgr on ~p~n", [IP], ?DEB), {ok, #state{ mon_server = MonServer, host = HostStr, uri = "snmp://"++HostStr++":" ++ integer_to_list(Port), port = Port, addr = IP, oids = OIDS, funs = Funs, community = Community, version = Version, interval = Interval}}. %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. %%-------------------------------------------------------------------- %% Function: handle_cast/2 %% Description: Handling cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_cast(Msg, State) -> {stop, {unknown_message, Msg}, State}. %%-------------------------------------------------------------------- %% Function: handle_info/2 %% Description: Handling all non call/cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_info({timeout,_Ref,connect},State=#state{uri=URI, addr=IP,port=Port,community=Community,version=Version}) -> ok = snmpm:register_agent("tsung",URI, [{engine_id,"myengine"}, {address,IP}, {port,Port}, {version,Version}, {community,Community}]), ?LOGF("SNMP mgr started; remote node is ~p~n", [URI],?INFO), erlang:start_timer(State#state.interval, self(), send_request ), {noreply, State}; handle_info({timeout,_Ref,send_request},State=#state{uri=URI,oids=OIDS}) -> ?LOGF("SNMP mgr; get data from host ~p~n", [URI],?DEB), snmp_get(URI, OIDS, State), erlang:start_timer(State#state.interval, self(), send_request ), {noreply,State}; handle_info(Message, State) -> {stop, {unknown_message, Message} , State}. %%-------------------------------------------------------------------- %% Function: terminate/2 %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- %%-------------------------------------------------------------------- %% Function: analyse_snmp_data/3 %% Returns: any (send msg to ts_mon) %%-------------------------------------------------------------------- analyse_snmp_data(Args, Host, State) -> analyse_snmp_data(Args, Host, [], State). %% Function: analyse_snmp_data/4 analyse_snmp_data([], _Host, Resp, State) -> ts_os_mon:send(State#state.mon_server,Resp); analyse_snmp_data([Val=#varbind{value='NULL'}| Tail], Host, Stats, State) -> ?LOGF("SNMP: Skip void result (~p) ~n", [Val],?DEB), analyse_snmp_data(Tail, Host, Stats, State); %% FIXME: this may not be accurate: if we lost packets (the server is %% overloaded), the value will be inconsistent, since we assume a %% constant time across samples ($INTERVAL) analyse_snmp_data([#varbind{variabletype='NULL'}| Tail], Host, Stats, State) -> %% skip bad values analyse_snmp_data(Tail, Host, Stats, State); analyse_snmp_data([#varbind{oid=?SNMP_CPU_RAW_SYSTEM, value=Val}| Tail], Host, Stats, State) -> {value, User} = lists:keysearch(?SNMP_CPU_RAW_USER, #varbind.oid, Tail), Value = Val + User#varbind.value, CountName = {cpu , Host}, NewValue = Value/(State#state.interval/1000), NewTail = lists:keydelete(?SNMP_CPU_RAW_USER, #varbind.oid, Tail), analyse_snmp_data(NewTail, Host, [{sample_counter, CountName, NewValue}| Stats], State); analyse_snmp_data([User=#varbind{oid=?SNMP_CPU_RAW_USER}| Tail], Host, Stats, State) -> %%put this entry at the end, this will be used when SYSTEM match analyse_snmp_data(Tail ++ [User], Host, Stats, State); analyse_snmp_data([#varbind{oid=OID, value=Val}| Tail], Host, Stats, State=#state{funs=F}) -> {DataName, Type, Fun} = dict:fetch(OID,F), Value = Fun(Val), Name = {DataName,Host}, ?LOGF("Analyse SNMP: ~p:~p:~p ~n", [Type, Name, Value],?DEB), analyse_snmp_data(Tail, Host, [{Type, Name, Value}| Stats], State). %%-------------------------------------------------------------------- %% Function: snmp_get/3 %% Description: ask a list of OIDs to the given snmp_mgr %%-------------------------------------------------------------------- snmp_get(Agent,OIDs,State)-> snmp_get(Agent,[OIDs],State,?SNMP_TIMEOUT,[]). snmp_get("snmp://"++Host, [], State, _TimeOut, Results )-> [Agent|_]=string:tokens(Host,":"), analyse_snmp_data(Results,Agent,State); snmp_get(URI, [OIDs|Tail], State, TimeOut,PrevRes)-> ?LOGF("Running snmp get ~p ~p~n", [URI,OIDs], ?DEB), Res = snmpm:sync_get("tsung",URI,OIDs,TimeOut), ?LOGF("Res ~p ~n", [Res], ?DEB), case Res of {ok,{noError,_,Results},_Remaining} -> snmp_get(URI, Tail, State, TimeOut, Results++PrevRes); {error, {send_failed,_,tooBig}} -> %% split the OID list in two, and retry ?LOGF("SNMP: too big packet, split and retry (~p)~n", [URI], ?INFO), snmp_get(URI, tuple_to_list(lists:split(length(OIDs) div 2, OIDs)), State, TimeOut, PrevRes); Other -> ?LOGF("SNMP Error:~p for ~p~n", [Other, URI], ?WARN), {error, Other} end. tsung-1.4.2/src/tsung_controller/ts_user_server.erl0000644000201100017670000004164011701017117022314 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_user_server). -author('jflecomte@IDEALX.com'). -vc('$Id$ '). -include("ts_profile.hrl"). %%-compile(export_all). -export([reset/1, init_seed/1, get_unique_id/1, get_really_unique_id/1, get_id/0, get_idle/0, get_offline/0, get_online/1, add_to_online/1, remove_from_online/1, remove_connected/1, add_to_connected/1, get_first/0]). %% for multiple user_server process, one per virtual host -export([reset/2, get_id/1, get_idle/1, get_offline/1, get_online/2, add_to_online/2, remove_from_online/2, remove_connected/2, get_first/1, reset_all/1]). -behaviour(gen_server). %% External exports -export([start/0,start/1, stop/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, { offline, %ets table last_offline, connected, %ets table last_connected, online, %ets table last_online, first_client, % id (integer) userid_max % max number of ids (starts at 1) }). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start() -> ?LOGF("Starting default user_server ~n",[],?INFO), gen_server:start_link({global, ?MODULE}, ?MODULE, [], []). start(Name) -> ?LOGF("Starting user_server with name ~p ~n",[Name],?INFO), gen_server:start_link({global, Name}, ?MODULE, [], []). reset(default,NFin) -> reset(NFin); reset(UserServer,NFin) -> gen_server:call(UserServer,{reset,NFin}). reset(NFin)-> gen_server:call({global, ?MODULE}, {reset, NFin}). reset_all(NFin) -> lists:foreach(fun(Pid) -> reset(Pid,NFin) end, ts_user_server_sup:all_children()). get_id(default) -> get_id(); get_id(UserServer) -> gen_server:call(UserServer, get_id). get_id()-> gen_server:call({global, ?MODULE }, get_id). %% return a unique id. deprecated since tsung_userid dyn var exists get_unique_id({_Pid, DynVar})-> case ts_dynvars:lookup(tsung_userid,DynVar) of {ok, Val} -> ts_utils:term_to_list(Val); false -> ?LOG("tsung_userid not found ! Can't create unique id~n", ?ERR), "0" end. %% return a really unique id, one that is unique across runs. get_really_unique_id({Pid, DynVars}) -> Sec = ts_utils:now_sec(), ?DebugF("Sec=~p",[Sec]), [[integer_to_list(Sec),"-",get_unique_id({Pid, DynVars})]]. %% get an idle id (offline), and add it to the connected table get_idle(default) -> get_idle(); get_idle(UserServer) -> gen_server:call(UserServer,get_idle). get_idle()-> gen_server:call({global, ?MODULE}, get_idle). %% FIXME: handle vhost add_to_connected(Id)-> gen_server:cast({global, ?MODULE}, {add_to_connected, Id}). get_online(default,Id) -> get_online(Id); get_online(UserServer,Id) when is_list(Id)-> get_online(UserServer,list_to_integer(Id)); get_online(UserServer,Id) when Id-> gen_server:call(UserServer, {get_online, Id}). get_online(Id) when is_list(Id) -> get_online(list_to_integer(Id)); get_online(Id) -> gen_server:call({global, ?MODULE}, {get_online, Id}). %% get an offline id, don't change the connected table. get_offline(default) -> get_offline(); get_offline(UserServer) -> gen_server:call(UserServer, get_offline). get_offline()-> gen_server:call({global, ?MODULE}, get_offline). get_first(default)-> get_first(); get_first(UserServer)-> gen_server:call(UserServer, get_first). get_first()-> gen_server:call({global, ?MODULE}, get_first). remove_connected(default,ID) -> remove_connected(ID); remove_connected(UserServer,Id) when is_list(Id) -> remove_connected(UserServer,list_to_integer(Id)); remove_connected(UserServer,Id) -> gen_server:cast(UserServer, {remove_connected, Id}). remove_connected(Id) when is_list(Id) -> remove_connected(list_to_integer(Id)); remove_connected(Id) -> gen_server:cast({global, ?MODULE}, {remove_connected, Id}). add_to_online(default,Id) -> add_to_online(Id); add_to_online(UserServer,Id) when is_list(Id) -> add_to_online(UserServer,list_to_integer(Id)); add_to_online(UserServer,Id) -> gen_server:cast(UserServer, {add_to_online, Id}). add_to_online(Id) when is_list(Id) -> add_to_online(list_to_integer(Id)); add_to_online(Id) -> gen_server:cast({global, ?MODULE}, {add_to_online, Id}). remove_from_online(default,Id) -> remove_from_online(Id); remove_from_online(UserServer,Id) when is_list(Id) -> remove_from_online(UserServer,list_to_integer(Id)); remove_from_online(UserServer,Id) -> gen_server:cast(UserServer, {remove_from_online, Id}). remove_from_online(Id) when is_list(Id) -> remove_from_online(list_to_integer(Id)); remove_from_online(Id) -> gen_server:cast({global, ?MODULE}, {remove_from_online, Id}). stop()-> lists:foreach(fun(Pid) -> gen_server:call(Pid, stop) end,ts_user_server_sup:all_children()). init_seed(A) -> gen_server:cast({global, ?MODULE}, {init_seed, A}). %%%---------------------------------------------------------------------- %%% Callback functions from gen_server %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%---------------------------------------------------------------------- init(_Args) -> ?LOG("ok, started unconfigured~n", ?INFO), {ok, #state{}}. %%---------------------------------------------------------------------- %% Func: handle_call/3 %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- %%Get one id in the full list of potential users handle_call(get_id, _From, State) -> Key = random:uniform( State#state.userid_max ) +1, {reply, Key, State}; %%Get one id in the users whos have to be connected handle_call(get_idle, _From, State=#state{offline=Offline,connected=Connected}) -> case State#state.last_offline of undefined -> ?LOG("No more free users !~n", ?WARN), {reply, {error, no_free_userid}, State}; Key when is_integer(Key) -> {ok,NextOffline} = ets_iterator_del(Offline,Key,State#state.last_offline), ?DebugF("New nextoffline is ~p~n",[NextOffline]), ets:insert(Connected, {Key,1}), case State#state.first_client of undefined -> {reply, Key, State#state{first_client=Key,last_connected=Key, last_offline=NextOffline}}; _Id -> {reply, Key, State#state{last_connected=Key, last_offline=NextOffline}} end; Error -> ?LOGF("Error when get idle ~p~n",[Error],?ERR), {reply, {error, no_free_userid}, State} end; %%Get one offline id handle_call(get_offline, _From, State=#state{offline=Offline,last_offline=Prev}) -> case ets_iterator_next(Offline, Prev) of {error, _Reason} -> {reply, {error, no_offline}, State}; {ok, {Next,Pwd}} -> ?DebugF("Choose (next is user defined) offline user ~p~n",[Prev]), {reply, {ok, Prev}, State#state{last_offline={Next,Pwd}}}; {ok, Next} -> ?DebugF("Choose offline user ~p~n",[Prev]), {reply, {ok, Prev}, State#state{last_offline=Next}} end; handle_call(get_first, _From, State) -> {reply, State#state.first_client, State}; handle_call({reset, NFin}, _From, _State) -> Offline = ets:new(offline,[ordered_set, private]), Online = ets:new(online, [set, private]), Connected = ets:new(connected, [set, private]), ?LOGF("Reset offline and online lists (maxid=~p)~n",[NFin],?NOTICE), fill_offline(NFin, Offline), First = ets:first(Offline), State2 = #state{offline=Offline, first_client = undefined, last_offline=First, connected = Connected, last_connected = undefined, last_online = undefined, online =Online, userid_max=NFin}, {reply, ok, State2}; %%% Get a online id different from 'Id' handle_call( {get_online, Id}, _From, State=#state{ online = Online, last_online = Prev}) -> case ets_iterator_next(Online, Prev, Id) of {error, _Reason} -> ?DebugF("No online users (~p,~p), ets table was ~p ~n",[Id, Prev,ets:info(Online)]), {reply, {error, no_online}, State}; {ok, {User,Pwd}} -> ?DebugF("Choose user defined online user ~p for ~p ~n",[User, Id]), {reply, {ok, User}, State#state{last_online={User,Pwd}}}; {ok, Next} -> ?DebugF("Choose online user ~p for ~p ~n",[Next, Id]), {reply, {ok, Next}, State#state{last_online=Next}} end; handle_call(stop, _From, State)-> {stop, normal, ok, State}. %%---------------------------------------------------------------------- %% Func: handle_cast/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- %%Get one id in the full list of potential users handle_cast({init_seed, Val}, State) -> ts_utils:init_seed(Val), {noreply, State}; handle_cast({remove_connected, Id}, State=#state{online=Online,offline=Offline,connected=Connected}) -> %% session config may not include presence:final, so we need to check/delete from Online to be safe {noreply, LastOnline} = ets_delete_online(Online,Id,State), ets:delete(Connected,Id), ets:insert(Offline, {Id,2}), case State#state.last_offline of undefined -> % Offline table was empty {noreply, State#state{last_online=LastOnline, last_offline=Id}}; _ -> {noreply, State#state{last_online=LastOnline}} end; %% user_defined user case handle_cast({add_to_connected, Id}, State=#state{connected=Connected, first_client=First}) -> ?LOGF("Add ~p to connected list~n",[Id],?DEB), ets:insert(Connected, {Id,1}), case First of undefined -> {noreply, State#state{last_connected=Id, first_client=Id}}; _ -> {noreply, State#state{last_connected=Id}} end; handle_cast({add_to_online, Id}, State=#state{online=Online, connected=Connected}) -> case ets:member(Connected,Id) of true -> ets:delete(Connected,Id), ets:insert(Online, {Id,1}), {noreply, State#state{last_online=Id}}; false -> ?LOGF("add_to_online: warn, id ~p is not connected,do not add to online~n",[Id],?NOTICE), {noreply, State} end; handle_cast({remove_from_online, Id}, State=#state{online=Online,connected=Connected}) -> {noreply, LastOnline} = ets_delete_online(Online,Id,State), ets:insert(Connected, {Id,1}), {noreply, State#state{last_online=LastOnline}}. %%---------------------------------------------------------------------- %% Func: handle_info/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%---------------------------------------------------------------------- %% Func: terminate/2 %% Purpose: Shutdown the server %% Returns: any (ignored by gen_server) %%---------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%---------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%---------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- fill_offline(0, _)-> ok; fill_offline(N, Tab) when is_integer(N) -> ets:insert(Tab,{N, 0}), fill_offline(N-1, Tab). %%%---------------------------------------------------------------------- %%% Func: ets_iterator_del/3 %%% Args: Ets, Key, Iterator %%% Purpose: delete entry Key from Ets, update iterator if needed %%% Returns: {ok, Key} or {ok, undefined} %%%---------------------------------------------------------------------- % iterator equal key:it will no longer be valid ets_iterator_del(Ets, Key, Key) -> Next = ets:next(Ets,Key), ets:delete(Ets,Key), case Next of '$end_of_table' -> case ets:first(Ets) of '$end_of_table' -> {ok, undefined}; NewIter -> {ok, NewIter} end; NewIter -> {ok, NewIter} end; ets_iterator_del(Ets, Key, Iterator) -> ets:delete(Ets,Key), {ok, Iterator}. %%%---------------------------------------------------------------------- %%% Func: ets_iterator_next/2 %%% Args: Ets, Iterator %%% Purpose: get next key; no requirements on value %%% Returns: {ok, NextKey} or {error, empty_ets} %%%---------------------------------------------------------------------- ets_iterator_next(Ets, Iterator) -> ets_iterator_next(Ets, Iterator, undefined). %%%---------------------------------------------------------------------- %%% Func: ets_iterator_next/3 %%% Args: Ets, Iterator, Key %%% Purpose: get next key, must be different from 'Key' %%%---------------------------------------------------------------------- ets_iterator_next(Ets, undefined, Key) -> case ets:first(Ets) of '$end_of_table' -> {error, empty_ets}; Key -> case ets:next(Ets,Key) of '$end_of_table' -> {error, empty_ets}; Iter -> {ok, Iter} end; NewIter -> {ok, NewIter} end; ets_iterator_next(Ets, Iterator, Key) -> case ets:next(Ets,Iterator) of '$end_of_table' -> %% start again from the beginnig ets_iterator_next(Ets, undefined, Key); Key -> % not this one, try again ets_iterator_next(Ets, Key, Key); Next -> {ok, Next} end. %%%---------------------------------------------------------------------- %%% Func: ets_delete_online/3 %%% Purpose: verify user is in Online table, delete, and update last_online if necessary %%%---------------------------------------------------------------------- ets_delete_online(Online,Id,State) -> case ets:lookup(Online,Id) of [] -> {noreply, State#state.last_online}; [_|_] -> {ok, LastOnline} = ets_iterator_del(Online,Id,State#state.last_online), %% reset the last_online entries if it's equal to Id case State#state.last_online of Id -> ?LOGF("Reset last id (~p) because its offline ~n",[Id],?INFO), {noreply, LastOnline}; _ -> {noreply, State#state.last_online} end end. tsung-1.4.2/src/tsung_controller/ts_config_pgsql.erl0000644000201100017670000001661211701017117022424 0ustar nniclausdream%%% %%% Copyright Nicolas Niclausse 2005 %%% %%% Author : Nicolas Niclausse %%% Created: 6 Nov 2005 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% common functions used by pgsql clients to parse config -module(ts_config_pgsql). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -export([parse_config/2]). -include("ts_profile.hrl"). -include("ts_pgsql.hrl"). -include("ts_config.hrl"). -include("xmerl.hrl"). %%---------------------------------------------------------------------- %% Func: parse_config/2 %% Args: Element, Config %% Returns: List %% Purpose: parse a request defined in the XML config file %%---------------------------------------------------------------------- %% Parsing other elements parse_config(Element = #xmlElement{name=dyn_variable}, Conf = #config{}) -> ts_config:parse(Element,Conf); parse_config(Element = #xmlElement{name=pgsql}, Config=#config{curid = Id, session_tab = Tab, sessions = [CurS | _], dynvar=DynVar, subst = SubstFlag, match=MatchRegExp}) -> {Ack,Request} = case ts_config:getAttr(atom, Element#xmlElement.attributes, type) of sql -> ValRaw = ts_config:getText(Element#xmlElement.content), SQL = list_to_binary(ts_utils:clean_str(ValRaw)), ?LOGF("Got SQL query: ~p~n",[SQL], ?NOTICE), {parse,#pgsql_request{sql=SQL, type= sql}}; close -> {parse,#pgsql_request{type=close}}; sync -> {parse,#pgsql_request{type=sync}}; flush -> {parse,#pgsql_request{type=flush}}; copydone -> {parse,#pgsql_request{type=copydone}}; execute -> Portal = ts_config:getAttr(Element#xmlElement.attributes, name_portal), Limit = ts_config:getAttr(integer,Element#xmlElement.attributes, max_rows,0), {no_ack,#pgsql_request{type=execute,name_portal=Portal,max_rows=Limit}}; parse -> Name = ts_config:getAttr(Element#xmlElement.attributes, name_prepared), Query = list_to_binary(ts_config:getText(Element#xmlElement.content)), Params=case ts_config:getAttr(Element#xmlElement.attributes, parameters) of "" -> ""; P -> lists:map(fun(S)-> list_to_integer(S) end, ts_utils:splitchar(P,$,)) end, {no_ack,#pgsql_request{type=parse,name_prepared=Name,equery=Query,parameters=Params}}; bind -> Portal = ts_config:getAttr(Element#xmlElement.attributes, name_portal), Prep = ts_config:getAttr(Element#xmlElement.attributes, name_prepared), Formats = case ts_config:getAttr(Element#xmlElement.attributes, formats_results) of "" -> ""; FR -> lists:map(fun(A)->list_to_atom(A) end,ts_utils:splitchar(FR,$,)) end, Params=case ts_config:getAttr(Element#xmlElement.attributes, parameters) of "" -> ""; P -> lists:map(fun("null")-> null; (A) -> A end, ts_utils:split(P,",")) end, ParamsFormat = ts_config:getAttr(atom,Element#xmlElement.attributes, formats, none), {no_ack,#pgsql_request{type=bind,name_portal=Portal,name_prepared=Prep, formats=ParamsFormat,formats_results=Formats,parameters=Params}}; copy -> Contents = case ts_config:getAttr(string, Element#xmlElement.attributes, contents_from_file) of [] -> P=ts_config:getText(Element#xmlElement.content), list_to_binary(lists:map(fun(S)-> list_to_integer(S) end, ts_utils:splitchar(P,$,))); FileName -> {ok, FileContent} = file:read_file(FileName), FileContent end, {no_ack,#pgsql_request{type=copy,equery=Contents}}; copyfail -> Str = ts_config:getAttr(string,Element#xmlElement.attributes, equery,undefined), {parse,#pgsql_request{type=copyfail,equery=list_to_binary(Str)}}; describe -> Portal=ts_config:getAttr(string,Element#xmlElement.attributes, name_portal,undefined), Prep = ts_config:getAttr(string,Element#xmlElement.attributes, name_prepared,undefined), {no_ack,#pgsql_request{type=describe,name_portal=Portal,name_prepared=Prep}}; authenticate -> Passwd = ts_config:getAttr(Element#xmlElement.attributes, password), {parse,#pgsql_request{passwd=Passwd, type= authenticate}}; connect -> Database = ts_config:getAttr(Element#xmlElement.attributes, database), User = ts_config:getAttr(Element#xmlElement.attributes, username), {parse,#pgsql_request{username=User, database=Database, type=connect}} end, Msg= #ts_request{ack = Ack, endpage = true, dynvar_specs = DynVar, subst = SubstFlag, match = MatchRegExp, param = Request}, ts_config:mark_prev_req(Id-1, Tab, CurS), ets:insert(Tab,{{CurS#session.id, Id}, Msg }), lists:foldl( fun(A,B)->ts_config:parse(A,B) end, Config#config{dynvar=[]}, Element#xmlElement.content); %% Parsing options %% parse_config(Element = #xmlElement{name=options}, Conf = #config{session_tab = Tab}) -> %% case ts_config:getAttr(Element#xmlElement.attributes, name) of %% "todo" -> %% Val = ts_config:getAttr(Element#xmlElement.attributes, value), %% ets:insert(Tab,{{http_use_server_as_proxy}, Val}) %% end, %% lists:foldl( fun(A,B)->ts_config:parse(A,B) end, Conf, Element#xmlElement.content); %% Parsing other elements parse_config(Element = #xmlElement{}, Conf = #config{}) -> ts_config:parse(Element,Conf); %% Parsing non #xmlElement elements parse_config(_, Conf = #config{}) -> Conf. tsung-1.4.2/src/tsung_controller/tsung_controller.rel.src0000644000201100017670000000024011701017117023422 0ustar nniclausdream{release, {"tsung_controller", "&tsung_controller_vsn&"}, {erts, "&erts_vsn&"}, [ {kernel,"&kernel_vsn&"}, {ssl,"&ssl_vsn&"}, {stdlib,"&stdlib_vsn&"}]}. tsung-1.4.2/src/tsung_controller/ts_config_jabber.erl0000644000201100017670000002173111701017117022521 0ustar nniclausdream%%% %%% Copyright IDEALX S.A.S. 2004 %%% %%% Author : Nicolas Niclausse %%% Created: 20 Apr 2004 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_config_jabber). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -export([parse_config/2 ]). -include("ts_profile.hrl"). -include("ts_jabber.hrl"). -include("ts_config.hrl"). -include("xmerl.hrl"). %%---------------------------------------------------------------------- %% Func: parse_config/2 %% Args: Element, Config %% Returns: List %% Purpose: parse a request defined in the XML config file %%---------------------------------------------------------------------- %% TODO: Dynamic content substitution is not yet supported for Jabber parse_config(Element = #xmlElement{name=dyn_variable}, Conf = #config{}) -> ts_config:parse(Element,Conf); parse_config(Element = #xmlElement{name=jabber}, Config=#config{curid= Id, session_tab = Tab, match=MatchRegExp, dynvar=DynVar, subst= SubstFlag, sessions = [CurS |_]}) -> TypeStr = ts_config:getAttr(string,Element#xmlElement.attributes, type, "chat"), Ack = ts_config:getAttr(atom,Element#xmlElement.attributes, ack, no_ack), Dest= ts_config:getAttr(atom,Element#xmlElement.attributes, destination,random), Size= ts_config:getAttr(integer,Element#xmlElement.attributes, size,0), Data= ts_config:getAttr(string,Element#xmlElement.attributes, data,undefined), Show= ts_config:getAttr(string,Element#xmlElement.attributes, show, "chat"), Status= ts_config:getAttr(string,Element#xmlElement.attributes, status, "Available"), Type= list_to_atom(TypeStr), Room = ts_config:getAttr(string,Element#xmlElement.attributes, room, undefined), Nick = ts_config:getAttr(string,Element#xmlElement.attributes, nick, undefined), Group = ts_config:getAttr(string,Element#xmlElement.attributes, group, "Tsung Group"), RE = ts_config:getAttr(string,Element#xmlElement.attributes, regexp, undefined), Node = case ts_config:getAttr(string, Element#xmlElement.attributes, 'node', undefined) of "" -> user_root; X -> X end, NodeType = ts_config:getAttr(string, Element#xmlElement.attributes, 'node_type', undefined), %% This specify where the node identified in the 'node' attribute is located. %% If node is undefined (no node attribute) %% -> we don't specify the node, let the server choose one for us. %% else %% If node is absolute (starts with "/") %% use that absolute address %% else %% the address is relative. Composed of two variables: user and node %% if node is "" (attribute node="") %% we want the "root" node for that user (/home/domain/user) %% else %% we want a specific child node for that user (/home/domain/user/node) %% in both cases, the user is obtained as: %% if dest == "random" %% random_user() %% if dest == "online" %% online_user() %% if dest == "offline" %% offline_user() %% Otherwise: (any other string) %% The specified string Domain =ts_config:get_default(Tab, jabber_domain_name, jabber_domain), MUC_service = ts_config:get_default(Tab, muc_service, muc_service), PubSub_service =ts_config:get_default(Tab, pubsub_service, pubsub_service), %% Authentication {XMPPId, UserName, Passwd} = case lists:keysearch(xmpp_authenticate, #xmlElement.name, Element#xmlElement.content) of {value, AuthEl=#xmlElement{} } -> User= ts_config:getAttr(string,AuthEl#xmlElement.attributes, username, undefined), PWD= ts_config:getAttr(string,AuthEl#xmlElement.attributes, passwd, undefined), {user_defined,User,PWD}; _ -> GUserName=ts_config:get_default(Tab, jabber_username, jabber_username), GPasswd =ts_config:get_default(Tab, jabber_passwd, jabber_passwd), {0,GUserName,GPasswd} end, Msg=#ts_request{ack = Ack, dynvar_specs= DynVar, endpage = true, subst = SubstFlag, match = MatchRegExp, param = #jabber{domain = Domain, username = UserName, passwd = Passwd, id = XMPPId, data = Data, type = Type, regexp = RE, dest = Dest, size = Size, show = Show, status = Status, room = Room, nick = Nick, group = Group, muc_service = MUC_service, pubsub_service = PubSub_service, node = Node, node_type = NodeType } }, ts_config:mark_prev_req(Id-1, Tab, CurS), ets:insert(Tab,{{CurS#session.id, Id}, Msg}), ?LOGF("Insert new request ~p, id is ~p~n",[Msg,Id],?INFO), lists:foldl( fun(A,B) -> ts_config:parse(A,B) end, Config#config{dynvar=[]}, Element#xmlElement.content); %% Parsing options parse_config(Element = #xmlElement{name=option}, Conf = #config{session_tab = Tab}) -> NewConf = case ts_config:getAttr(Element#xmlElement.attributes, name) of "username" -> Val = ts_config:getAttr(string,Element#xmlElement.attributes, value,"tsunguser"), ets:insert(Tab,{{jabber_username,value}, Val}), Conf; "passwd" -> Val = ts_config:getAttr(string,Element#xmlElement.attributes, value,"sesame"), ets:insert(Tab,{{jabber_passwd,value}, Val}), Conf; "domain" -> Val = ts_config:getAttr(string,Element#xmlElement.attributes, value,"erlang-projects.org"), ets:insert(Tab,{{jabber_domain_name,value}, {domain,Val}}), Conf; "vhost_file" -> Val = ts_config:getAttr(atom,Element#xmlElement.attributes, value,"vhostfile"), ets:insert_new(Tab,{{jabber_domain_name,value}, {vhost,Val}}), Conf#config{vhost_file = Val}; "global_number" -> N = ts_config:getAttr(integer,Element#xmlElement.attributes, value, 100), ts_timer:config(N), ets:insert(Tab,{{jabber_global_number, value}, N}), Conf; "userid_max" -> N = ts_config:getAttr(integer,Element#xmlElement.attributes, value, 10000), ts_user_server:reset(N), ets:insert(Tab,{{jabber_userid_max,value}, N}), Conf#config{user_server_maxuid = N}; "muc_service" -> N = ts_config:getAttr(string,Element#xmlElement.attributes, value, "conference.localhost"), ets:insert(Tab,{{muc_service,value}, N}), Conf; "pubsub_service" -> N = ts_config:getAttr(string,Element#xmlElement.attributes, value, "pubsub.localhost"), ets:insert(Tab,{{pubsub_service,value}, N}), Conf end, lists:foldl( fun(A,B) -> ts_config:parse(A,B) end, NewConf, Element#xmlElement.content); %% Parsing other elements parse_config(Element = #xmlElement{}, Conf = #config{}) -> ts_config:parse(Element,Conf); %% Parsing non #xmlElement elements parse_config(_, Conf = #config{}) -> Conf. tsung-1.4.2/src/tsung_controller/ts_match_logger.erl0000644000201100017670000001637111701017117022406 0ustar nniclausdream%%% %%% Copyright (C) 2008 Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%---------------------------------------------------------------------- %% @copyright 2008 Nicolas Niclausse %% @author Nicolas Niclausse %% @since 1.3.1 , 19 Nov 2008 %% @doc log match entries @end %% ---------------------------------------------------------------------- -module(ts_match_logger). -author('nicolas@niclux.org'). -vc('$Id: ts_mon.erl 774 2007-11-20 09:36:13Z nniclausse $ '). -behaviour(gen_server). -include("ts_profile.hrl"). -include("ts_config.hrl"). -define(DELAYED_WRITE_SIZE,524288). % 512KB -define(DELAYED_WRITE_DELAY,5000). % 5 sec %% External exports, API -export([start/1, stop/0, add/1 ]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, {filename, % log filename level, % type of backend: text|rrdtool|fullstats dumpid=1, % current dump id logdir, fd % file descriptor }). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% @spec start(LogDir::string()) -> ok | throw({error, Reason}) %% @doc Start the monitoring process %% @end %%---------------------------------------------------------------------- start(LogDir) -> ?LOG("starting match logger, global ~n",?NOTICE), gen_server:start_link({global, ?MODULE}, ?MODULE, [LogDir], []). stop() -> gen_server:cast({global, ?MODULE}, {stop}). %%---------------------------------------------------------------------- %% @spec add(Data::list()| {UserId::integer(),SessionId::integer(), %% RequestId::integer(),TimeStamp::tuple(),{count, Val::atom()}}) -> ok %% @doc log match entries %% @end %%---------------------------------------------------------------------- add(Data) -> gen_server:cast({global, ?MODULE}, {add, Data}). %%%---------------------------------------------------------------------- %%% Callback functions from gen_server %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%---------------------------------------------------------------------- init([LogDir]) -> ?LOG("starting match logger~n",?NOTICE), Base = filename:basename(?config(match_log_file)), Filename = filename:join(LogDir, Base), case file:open(Filename,[write, {delayed_write, ?DELAYED_WRITE_SIZE, ?DELAYED_WRITE_DELAY}]) of {ok, Fd} -> ?LOG("starting match logger~n",?NOTICE), io:format(Fd,"# timestamp userid sessionid requestid event~n",[]), {ok, #state{ fd = Fd, filename = Filename, logdir = LogDir }}; {error, Reason} -> ?LOGF("Can't open match log file! ~p~n",[Reason], ?ERR), {stop, Reason} end. %%---------------------------------------------------------------------- %% Func: handle_call/3 %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_call(Request, _From, State) -> ?LOGF("Unknown call ~p !~n",[Request],?ERR), Reply = ok, {reply, Reply, State}. %%---------------------------------------------------------------------- %% Func: handle_cast/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_cast({add, List}, State) when is_list(List)-> NewState=lists:foldl(fun(X,Acc)-> log(X,Acc) end,State, List), {noreply,NewState}; handle_cast({add, Data}, State) when is_tuple(Data)-> NewState=log(Data,State), {noreply,NewState}; handle_cast({stop}, State) -> {stop, normal, State}; handle_cast(Msg, State) -> ?LOGF("Unknown msg ~p !~n",[Msg], ?WARN), {noreply, State}. %%---------------------------------------------------------------------- %% Func: handle_info/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%---------------------------------------------------------------------- %% Func: terminate/2 %% Purpose: Shutdown the server %% Returns: any (ignored by gen_server) %%---------------------------------------------------------------------- terminate(Reason, State) -> ?LOGF("stoping match logger (~p)~n",[Reason],?NOTICE), file:close(State#state.fd), ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState, NewStateData} %%-------------------------------------------------------------------- code_change(_OldVsn, StateData, _Extra) -> {ok, StateData}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- log({UserId,SessionId,RequestId,TimeStamp,{count, Val},[]},State=#state{fd=File}) -> TS=ts_utils:time2sec_hires(TimeStamp), io:format(File,"~f ~B ~B ~B ~p~n",[TS,UserId,SessionId,RequestId,Val]), State; log({UserId,SessionId,RequestId,TimeStamp,{count, Val},Bin}, State=#state{logdir=LogDir, dumpid=Id}) -> log({UserId,SessionId,RequestId,TimeStamp,{count, Val},[]}, State), Name=ts_utils:join("-",lists:map(fun integer_to_list/1,[UserId,SessionId,RequestId,Id])), Filename=filename:join(LogDir, "match-"++ Name ++".dump"), file:write_file(Filename,Bin), State#state{dumpid=Id+1}. tsung-1.4.2/src/tsung_controller/ts_stats_mon.erl0000644000201100017670000004117311701017117021760 0ustar nniclausdream%%% %%% Copyright (C) 2007 Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%---------------------------------------------------------------------- %% @copyright 2007-2008 Nicolas Niclausse %% @author Nicolas Niclausse %% @since 20 Nov 2007 %% @doc computes statistics for request, page, connect, transactions, %% data size, errors, ... and other stats specific to plugins %% ---------------------------------------------------------------------- -module(ts_stats_mon). -author('nicolas@niclux.org'). -vc('$Id: ts_mon.erl 774 2007-11-20 09:36:13Z nniclausse $ '). -behaviour(gen_server). -include("ts_profile.hrl"). -include("ts_config.hrl"). %% External exports, API -export([start/0, start/1, stop/0, stop/1, add/1, add/2, dumpstats/0, dumpstats/1, set_output/2, set_output/3, status/1, status/2 ]). %% More external exports for ts_mon -export([update_stats/3, add_stats_data/2, reset_all_stats/1]). -export([print_stats/3]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, {log, % log fd backend, % type of backend: text|rrdtool|fullstats dump_interval,% type = ts_stats_mon, % type of stats fullstats, % fullstats fd stats, % dict keeping stats info laststats % values of last printed stats }). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% @spec start(Id::term()) -> ok | throw({error, Reason}) %% @doc Start the monitoring process %%---------------------------------------------------------------------- start(Id) -> ?LOGF("starting ~p stats server, global ~n",[Id],?NOTICE), gen_server:start_link({global, Id}, ?MODULE, [Id], []). start() -> ?LOG("starting stats server, global ~n",?NOTICE), gen_server:start_link({global, ?MODULE}, ?MODULE, [?MODULE], []). stop(Id) -> gen_server:cast({global, Id}, {stop}). stop() -> gen_server:cast({global, ?MODULE}, {stop}). add([]) -> ok; add(Data) -> gen_server:cast({global, ?MODULE}, {add, Data}). add([], _Id) -> ok; add(Data, Id) -> gen_server:cast({global, Id}, {add, Data}). status(Name,Type) -> gen_server:call({global, ?MODULE}, {status, Name, Type}). status(Id) -> gen_server:call({global, Id}, {status}). dumpstats() -> gen_server:cast({global, ?MODULE}, {dumpstats}). dumpstats(Id) -> gen_server:cast({global, Id}, {dumpstats}). set_output(BackEnd,Stream) -> set_output(BackEnd,Stream,?MODULE). set_output(BackEnd,Stream,Id) -> gen_server:cast({global, Id}, {set_output, BackEnd, Stream}). %%%---------------------------------------------------------------------- %%% Callback functions from gen_server %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%---------------------------------------------------------------------- %% single type of data: don't need a dict, a simple list can store the data init([Type]) when Type == 'connect'; Type == 'page'; Type == 'request' -> ?LOGF("starting dedicated stats server for ~p ~n",[Type],?NOTICE), Stats = [0,0,0,0,0,0,0,0], {ok, #state{ dump_interval = ?config(dumpstats_interval), stats = Stats, type = Type, laststats = Stats }}; %% id = transaction or ?MODULE: it can handle several types of stats, must use a dict. init([Id]) -> ?LOGF("starting ~p stats server~n",[Id],?NOTICE), Tab = dict:new(), {ok, #state{ dump_interval = ?config(dumpstats_interval), stats = Tab, type = Id, laststats = Tab }}. %%---------------------------------------------------------------------- %% Func: handle_call/3 %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_call({status}, _From, State=#state{stats=Stats} ) when is_list(Stats) -> [_Esp, _Var, _Max, _Min, Count, _MeanFB,_CountFB,_Last] = Stats, {reply, Count, State}; handle_call({status}, _From, State=#state{stats=Stats} ) -> {reply, Stats, State}; handle_call({status, Name, Type}, _From, State ) -> Value = dict:find({Name,Type}, State#state.stats), {reply, Value, State}; handle_call(Request, _From, State) -> ?LOGF("Unknown call ~p !~n",[Request],?ERR), Reply = ok, {reply, Reply, State}. %%---------------------------------------------------------------------- %% Func: handle_cast/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_cast({add, Data}, State=#state{type=Type}) when (( Type == 'connect') or (Type == 'page') or (Type == 'request')) -> case State#state.backend of fullstats -> io:format(State#state.fullstats,"~p~n",[{sample, Type, Data}]); _Other -> ok end, [Esp, Var, Max, Min, I, MeanFB,CountFB,Last] = State#state.stats, {NewEsp,NewVar,NewMin,NewMax,NewI} = ts_stats:meanvar_minmax(Esp,Var,Min,Max,Data,I), {noreply,State#state{stats=[NewEsp,NewVar,NewMax,NewMin,NewI,MeanFB,CountFB,Last]}}; handle_cast({add, Data}, State) when is_list(Data) -> case State#state.backend of fullstats -> io:format(State#state.fullstats,"~p~n",[Data]); _Other -> ok end, NewStats = lists:foldl(fun add_stats_data/2, State#state.stats, Data ), {noreply,State#state{stats=NewStats}}; handle_cast({add, Data}, State) when is_tuple(Data) -> case State#state.backend of fullstats -> io:format(State#state.fullstats,"~p~n",[Data]); _Other -> ok end, NewStats = add_stats_data(Data, State#state.stats), {noreply,State#state{stats=NewStats}}; handle_cast({set_output, BackEnd, {Stream, StreamFull}}, State) -> {noreply,State#state{backend=BackEnd, log=Stream, fullstats=StreamFull}}; handle_cast({dumpstats}, State) -> export_stats(State), NewStats = reset_all_stats(State#state.stats), {noreply, State#state{laststats = NewStats, stats=NewStats}}; handle_cast({stop}, State) -> {stop, normal, State}; handle_cast(Msg, State) -> ?LOGF("Unknown msg ~p !~n",[Msg], ?WARN), {noreply, State}. %%---------------------------------------------------------------------- %% Func: handle_info/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%---------------------------------------------------------------------- %% Func: terminate/2 %% Purpose: Shutdown the server %% Returns: any (ignored by gen_server) %%---------------------------------------------------------------------- terminate(Reason, State) -> ?LOGF("stoping stats monitor (~p)~n",[Reason],?NOTICE), export_stats(State), ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState, NewStateData} %%-------------------------------------------------------------------- code_change(_OldVsn, StateData, _Extra) -> {ok, StateData}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: add_stats_data/2 %% Purpose: update or add value in dictionnary %% Returns: Dict %%---------------------------------------------------------------------- %% continuous incrementing counters add_stats_data({Type, Name, Value},Stats) when Type==sample; Type==sample_counter -> MyFun = fun (OldVal) -> update_stats(Type, OldVal, Value) end, dict:update({Name,Type}, MyFun, update_stats(Type, [], Value), Stats); %% increase by one when called add_stats_data({count, Name}, Stats) -> dict:update_counter({Name, count}, 1, Stats); %% cumulative counter add_stats_data({sum, Name, Val}, Stats) -> dict:update_counter({Name, sum}, Val, Stats). %%---------------------------------------------------------------------- %% Func: export_stats/2 %%---------------------------------------------------------------------- export_stats(State=#state{type=Type,backend=Backend}) when Type == 'connect'; Type == 'page'; Type == 'request' -> Param = {Backend,State#state.laststats,State#state.log}, print_stats({Type,sample}, State#state.stats, Param); export_stats(State=#state{backend=Backend}) -> Param = {Backend,State#state.laststats,State#state.log}, dict:fold(fun print_stats/3, Param, State#state.stats). %%---------------------------------------------------------------------- %% @spec print_stats({Backend::tuple(),Name::tuple(), %% Type::sample|count|sum|sample_counter}, Value::list(), %% {Last, Logfile} ) -> {Last, Logfile} %% @doc print statistics in text format in Logfile @end %%---------------------------------------------------------------------- print_stats({_,_}, [], {Backend,LastRes,Logfile})-> {Backend,LastRes, Logfile}; print_stats({_,_}, [0,0,0,0,0,0,0|_], {Backend,LastRes,Logfile})-> {Backend,LastRes, Logfile}; print_stats({_,_,_}, 0, {Backend,0, Logfile})-> % no data yet {Backend,0, Logfile}; print_stats({{Name,Node},Type},Value,{json,Res,Log}) when (Type =:= sample) orelse (Type =:= sample_counter) -> [_,Host] = string:tokens(Node,"@"), print_stats_txt({Name,Type,", {\"name\": \"~p\", \"hostname\": \"" ++ Host ++"\", \"value\": ~p, \"mean\": ~p,\"stdvar\": ~p,\"max\": ~p,\"min\": ~p ,\"global_mean\": ~p ,\"global_count\": ~p}"},Value,{json,Res,Log}); print_stats({Name,Type},Value,{json,Res,Log}) when (Type =:= sample) orelse (Type =:= sample_counter) -> print_stats_txt({Name,Type,", {\"name\": \"~p\", \"value\": ~p, \"mean\": ~p,\"stdvar\": ~p,\"max\": ~p,\"min\": ~p ,\"global_mean\": ~p ,\"global_count\": ~p}"},Value,{json,Res,Log}); print_stats({Name,Type},Value,Other) when Type =:= sample orelse Type =:= sample_counter -> print_stats_txt({Name,Type,"stats: ~p ~p ~p ~p ~p ~p ~p ~p~n"},Value,Other); print_stats({Name,Type},Value,{json,Res,Log}) when is_integer(Name) -> % http return code print_stats_txt({"http_"++integer_to_list(Name),Type, ", {\"name\": \"~s\", \"value\": ~p, \"total\": ~p}"},Value,{json,Res,Log}); print_stats({Name=connected,Type},Value,{json,Res,Log}) -> print_stats_txt({Name,Type,", {\"name\": \"~p\", \"value\": ~p, \"max\": ~p}"},Value,{json,Res,Log}); print_stats({Name,Type},Value,{json,Res,Log}) -> print_stats_txt({Name,Type,", {\"name\": \"~p\", \"value\": ~p, \"total\": ~p}"},Value,{json,Res,Log}); print_stats({Name,Type},Value,Other) -> print_stats_txt({Name,Type,"stats: ~p ~p ~p~n"},Value,Other). %% @spec print_stats_txt(tuple(),Data::list(),tuple()) -> {Backend::atom(),LastRest::term(), LogFile::term()} print_stats_txt({Name,_,Format}, [Mean,0,Max,Min,Count,MeanFB,CountFB|_], {Backend,LastRes,Logfile})-> io:format(Logfile, Format, [Name, Count, Mean, 0, Max, Min,MeanFB,CountFB ]), {Backend,LastRes, Logfile}; print_stats_txt({Name,_,Format},[Mean,Var,Max,Min,Count,MeanFB,CountFB|_],{Backend,LastRes,Logfile})-> StdVar = math:sqrt(Var/Count), io:format(Logfile, Format, [Name, Count, Mean, StdVar, Max, Min, MeanFB,CountFB]), {Backend,LastRes, Logfile}; print_stats_txt({Name, _,Format}, [Value,Last], {Backend,LastRes, Logfile}) -> io:format(Logfile, Format, [Name, Value, Last ]), {Backend,LastRes, Logfile}; print_stats_txt({Name, _,Format}, Value, {Backend,LastRes, Logfile}) when is_number(LastRes)-> io:format(Logfile, Format, [Name, Value-LastRes, Value]), {Backend,LastRes, Logfile}; print_stats_txt({Name, Type, Format}, Value, {Backend,LastRes, Logfile}) when is_number(Value)-> PrevVal = case dict:find({Name, Type}, LastRes) of {ok, OldVal} -> OldVal; error -> 0 end, io:format(Logfile, Format, [Name, Value-PrevVal, Value]), {Backend,LastRes, Logfile}. %%---------------------------------------------------------------------- %% update_stats/3 %% @spec (Type::atom, List, Value::[integer() | float()]) -> List %% @doc update the mean and variance for the given sample %%---------------------------------------------------------------------- update_stats(sample, [], New) -> [New, 0, New, New, 1, 0, 0, 0]; update_stats(sample, Data, Value) -> %% we don't use lastvalue for 'sample', set it to zero update_stats2(Data, Value, 0); update_stats(sample_counter,[], New) -> %% first call, store the initial value [0, 0, 0, 0, 0, 0, 0, New]; update_stats(sample_counter, Current, 0) -> % skip 0 values Current; update_stats(sample_counter,[Mean,Var,Max,Min,Count,MeanFB,CountFB,Last],Value) when Value < Last-> %% maybe the counter has been restarted, use the new value, but don't update other data [Mean,Var,Max,Min,Count,MeanFB,CountFB,Value]; update_stats(sample_counter, [0, 0, 0, 0, 0, MeanFB,CountFB,Last], Value) -> New = Value-Last, [New, 0, New, New, 1, MeanFB,CountFB,Value]; update_stats(sample_counter,Data, Value) -> update_stats2(Data, Value, Value). update_stats2([Mean, Var, Max, Min, Count, MeanFB,CountFB,Last], Value, NewLast) when is_number(Value), is_number(NewLast), is_number(Last), is_number(Count)-> New = Value-Last, {NewMean, NewVar, _} = ts_stats:meanvar(Mean, Var, [New], Count), if New > Max -> % new max, min unchanged [NewMean, NewVar, New, Min, Count+1, MeanFB,CountFB,NewLast]; New < Min -> [NewMean, NewVar, Max, New, Count+1, MeanFB,CountFB,NewLast]; true -> [NewMean, NewVar, Max, Min, Count+1, MeanFB,CountFB,NewLast] end. %%---------------------------------------------------------------------- %% Func: reset_all_stats/1 %%---------------------------------------------------------------------- reset_all_stats(Data) when is_list(Data)-> reset_stats(Data); reset_all_stats(Dict)-> MyFun = fun (_Key, OldVal) -> reset_stats(OldVal) end, dict:map(MyFun, Dict). %%---------------------------------------------------------------------- %% @spec reset_stats(list()) -> list() %% @doc reset all stats except min and max and lastvalue. Compute the %% global mean here %% @end %%---------------------------------------------------------------------- reset_stats([]) -> []; reset_stats([_Mean, _Var, Max, Min, 0, _MeanFB,0,Last]) -> [0, 0, Max, Min, 0, 0, 0,Last]; reset_stats([Mean, _Var, Max, Min, Count, MeanFB,CountFB,Last]) -> NewCount=CountFB+Count, NewMean=(CountFB*MeanFB+Count*Mean)/NewCount, [0, 0, Max, Min, 0, NewMean,NewCount,Last]; reset_stats([_Sample, LastValue]) -> [0, LastValue]; reset_stats(LastValue) -> LastValue. tsung-1.4.2/src/tsung_controller/ts_user_server_sup.erl0000644000201100017670000000347111701017117023203 0ustar nniclausdream%%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% %%% ts_user_server_sup.erl %%% @author Pablo Polvorin %%% @doc %%% created on 2008-09-09 -module(ts_user_server_sup). -export([start_link/0,init/1,start_user_server/1,all_children/0]). -behaviour(supervisor). start_link() -> {ok,Pid} = supervisor:start_link({global,?MODULE},?MODULE,[]), start_default_user_server(), %default user_server is always started {ok,Pid}. init([]) -> SupFlags = {simple_one_for_one,1,1 }, ChildSpec = [ {ts_user_server,{ts_user_server, start, []}, temporary,2000,worker,[ts_user_server]} ], {ok, {SupFlags, ChildSpec}}. start_user_server(Name) -> supervisor:start_child({global,?MODULE},[Name]). start_default_user_server() -> supervisor:start_child({global,?MODULE},[]). all_children() -> [ Pid ||{_,Pid,_,_} <- supervisor:which_children({global,?MODULE})]. tsung-1.4.2/src/tsung_controller/ts_job_notify.erl0000644000201100017670000003057111701017117022113 0ustar nniclausdream%%% %%% Copyright 2011 INRIA %%% %%% Author : Nicolas Niclausse %%% Created: 04 mai 2011 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% %%% @doc %%% %%% @end -module(ts_job_notify). -vc('$Id: ts_notify.erl,v 0.0 2011/05/04 11:18:48 nniclaus Exp $ '). -author('nicolas.niclausse@inria.fr'). -behaviour(gen_server). -include("ts_profile.hrl"). -include("ts_job.hrl"). %% API -export([start_link/0]). -export([listen/1, monitor/1, demonitor/1, wait_jobs/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define(SERVER, ?MODULE). -record(state, {port, % listen port acceptsock, % The socket we are accept()ing at acceptloop_pid, % The PID of the companion process that blocks jobs}). %%%=================================================================== %%% API %%%=================================================================== %%-------------------------------------------------------------------- %% @doc %% Starts the server %% %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} %% @end %%-------------------------------------------------------------------- start_link() -> gen_server:start_link({global, ?MODULE}, ?MODULE, [], []). listen(Port) -> gen_server:cast({global, ?MODULE}, {listen, Port}). monitor({JobID, OwnerPid, StartTime, QueuedTime, Dump}) -> gen_server:cast({global, ?MODULE}, {monitor, {JobID, OwnerPid, StartTime, QueuedTime,Dump}}). demonitor({JobID}) -> gen_server:cast({global, ?MODULE}, {monitor, {JobID}}). wait_jobs(Pid) -> gen_server:cast({global, ?MODULE}, {wait_jobs, Pid}). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== %%-------------------------------------------------------------------- %% @private %% @doc %% Initializes the server %% %% @spec init(Args) -> {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %% @end %%-------------------------------------------------------------------- init([]) -> ?LOG("Starting~n",?DEB), case global:whereis_name(ts_config_server) of undefined -> {ok, #state{jobs=ets:new(jobs,[{keypos, #job_session.jobid}])}}; _Pid -> ?LOG("Config server is alive !~n",?DEB), case ts_config_server:get_jobs_state() of {Jobs,Port} -> ?LOG("Got backup of node state~n",?DEB), {noreply,NewState} = handle_cast({listen,Port}, #state{jobs=Jobs,port=Port}), {ok, NewState}; Else -> ?LOGF("Got this from config server:~p~n",[Else],?DEB), {ok, #state{jobs=ets:new(jobs,[{keypos, #job_session.jobid}])}} end end. %%-------------------------------------------------------------------- %% @private %% @doc %% Handling call messages %% %% @spec handle_call(Request, From, State) -> %% {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- handle_call({accepted, _Tag, Sock}, _From, State) -> ?LOGF("New socket:~p~n", [Sock],?DEB), {reply, continue, State#state{}}; handle_call({accept_error, _Tag, Error}, _From, State) -> ?LOGF("accept() failed ~p~n",[Error],?ERR), {stop, Error, stop, State}; handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% Handling cast messages %% %% @spec handle_cast(Msg, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- handle_cast({monitor, {JobID, OwnerPid, SubmitTS, QueuedTS,Dump}}, State=#state{jobs=Jobs}) -> ?LOGF("monitoring job ~p from pid ~p~n",[JobID,OwnerPid],?DEB), ets:insert(Jobs,#job_session{jobid=JobID,owner=OwnerPid, submission_time=SubmitTS, queue_time=QueuedTS,dump=Dump}), SubmitTime=ts_utils:elapsed(SubmitTS,QueuedTS), ts_mon:add([{sum,job_queued,1},{sample,tr_job_submit,SubmitTime}]), {noreply, State}; handle_cast({demonitor, {JobID}}, State=#state{jobs=Jobs}) -> ets:delete(Jobs,JobID), {noreply, State}; handle_cast({wait_jobs, Pid}, State=#state{jobs=Jobs}) -> %% look for all jobs started by this pid ?LOGF("look for job of ~p~n",[Pid],?DEB), check_jobs(Jobs,Pid), {noreply, State}; handle_cast({listen, undefined}, State) -> ?LOG("No listen port defined, can't open listening socket ~n",?NOTICE), {noreply, State}; handle_cast({listen,Port}, State) -> Opts = [{reuseaddr, true}, {active, once}], case gen_tcp:listen(Port, Opts) of {ok, ListenSock} -> ?LOGF("Listening on port ~p done, start accepting loop~n",[Port],?INFO), {noreply, State#state {acceptsock=ListenSock, port=Port, acceptloop_pid = spawn_link(ts_utils, accept_loop, [self(), unused, ListenSock])}}; {error, Reason} -> ?LOGF("Error when trying to listen to socket: ~p~n",[Reason],?ERR), {noreply, State} end; handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% Handling all non call/cast messages %% %% @spec handle_info(Info, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- handle_info({tcp, Socket, Data}, State=#state{jobs=Jobs}) -> %% OAR: %% args are job_id,job_name,TAG,comment %% TAG can be: %% - RUNNING : when the job is launched %% - END : when the job is finished normally %% - ERROR : when the job is finished abnormally %% - INFO : used when oardel is called on the job %% - SUSPENDED : when the job is suspended %% - RESUMING : when the job is resumed ?LOGF("received ~p from socket ~p",[Data,Socket],?DEB), case string:tokens(Data," ") of [Id, _Name, "RUNNING"|_] -> ?LOGF("look for job ~p in table",[Id],?DEB), case ets:lookup(Jobs,Id) of [] -> ?LOGF("Job owner of ~p is unknown",[Id],?NOTICE); [Job] -> Now=now(), Queued=ts_utils:elapsed(Job#job_session.queue_time,Now), ts_mon:add([{sample,tr_job_wait,Queued},{sum,job_running,1}, {sum,job_queued,-1}]), ets:update_element(Jobs,Id,{#job_session.start_time,Now}) end; [Id, Name, "END"|_] -> case ets:lookup(Jobs,Id) of [] -> ?LOGF("Job owner of ~p is unknown",[Id],?NOTICE); [Job=#job_session{start_time=undefined}] -> ?LOGF("ERROR: Start time of job ~p is unknown",[Id],?ERR), ts_mon:add([{sum,job_running,-1}, {sum,ok_job ,1}]), ets:delete_object(Jobs,Job), check_jobs(Jobs,Job#job_session.owner); [Job]-> Now=now(), Duration=ts_utils:elapsed(Job#job_session.start_time,Now), ts_mon:add([{sample,tr_job_duration,Duration},{sum,job_running,-1}, {sum,ok_job ,1}]), ts_job:dump(Job#job_session.dump,{none,Job#job_session{end_time=Now,status="ok"},Name,undefined,undefined}), ets:delete_object(Jobs,Job), check_jobs(Jobs,Job#job_session.owner) end; [Id, Name, "ERROR"|_] -> case ets:lookup(Jobs,Id) of [] -> ?LOGF("Job owner of ~p is unknown",[Id],?NOTICE); [Job=#job_session{start_time=undefined}] -> ?LOGF("ERROR: start time of job ~p is unknown",[Id],?ERR), ts_mon:add([{sum,job_running,-1}, {sum,error_job,1}]), ets:delete_object(Jobs,Job), check_jobs(Jobs,Job#job_session.owner); [Job]-> Now=now(), Duration=ts_utils:elapsed(Job#job_session.start_time,Now), ts_mon:add([{sample,tr_job_duration,Duration},{sum,job_running,-1}, {sum,error_job,1}]), ts_job:dump(Job#job_session.dump,{none,Job#job_session{end_time=Now,status="error"},Name,undefined,undefined}), ets:delete_object(Jobs,Job), check_jobs(Jobs,Job#job_session.owner) end; [_Id, _Name, "INFO"|_] -> ok; [_Id, _Name, "SUSPENDED"|_] -> ok; [_Id, _Name, "RESUMING"|_] -> ok end, inet:setopts(Socket,[{active,once}]), {noreply, State}; handle_info({tcp_closed, _Socket}, State) -> {noreply, State}; handle_info({'ETS-TRANSFER',_Tab,_FromPid,_GiftData}, State=#state{}) -> ?LOG("Got ownership on job state table", ?NOTICE), {noreply, State}; handle_info(Info, State) -> ?LOGF("Unexpected message received: ~p", [Info], ?WARN), {noreply, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% This function is called by a gen_server when it is about to %% terminate. It should be the opposite of Module:init/1 and do any %% necessary cleaning up. When it returns, the gen_server terminates %% with Reason. The return value is ignored. %% %% @spec terminate(Reason, State) -> void() %% @end %%-------------------------------------------------------------------- terminate(normal, _State) -> ?LOG("Terminating for normal reason", ?WARN), ok; terminate(Reason, State) when is_integer(State#state.port)-> ?LOGF("Terminating for reason ~p", [Reason], ?WARN), Pid=global:whereis_name(ts_config_server), ?LOGF("Config server pid is ~p", [Pid], ?DEB), ets:give_away(State#state.jobs,Pid,State#state.port), ok; terminate(Reason, State) -> ?LOGF("Terminating for reason ~p ~p", [Reason,State], ?WARN), ok. %%-------------------------------------------------------------------- %% @private %% @doc %% Convert process state when code is changed %% %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} %% @end %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== check_jobs(Jobs,Pid)-> case ets:match_object(Jobs, #job_session{owner=Pid, _='_'}) of [] -> ?LOGF("no jobs for pid ~p~n",[Pid],?DEB), Pid ! {erlang, ok, nojobs}; PidJobs-> ?LOGF("still ~p jobs for pid ~p~n",[length(PidJobs),Pid],?INFO) end. tsung-1.4.2/src/tsung_controller/ts_controller_sup.erl0000644000201100017670000001054611701017117023023 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2003 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_controller_sup). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -include("ts_profile.hrl"). -behaviour(supervisor). %% External exports -export([start_link/1]). %% supervisor callbacks -export([init/1]). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start_link(LogDir) -> ?LOG("starting supervisor ...~n",?INFO), supervisor:start_link({local, ?MODULE}, ?MODULE, [LogDir]). %%%---------------------------------------------------------------------- %%% Callback functions from supervisor %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, {SupFlags, [ChildSpec]}} | %% ignore | %% {error, Reason} %%---------------------------------------------------------------------- init([LogDir]) -> ?LOG("starting",?INFO), Config = {ts_config_server, {ts_config_server, start_link, [LogDir]}, transient, 2000, worker, [ts_config_server]}, Mon = {ts_mon, {ts_mon, start, [LogDir]}, transient, 2000, worker, [ts_mon]}, Stats_Mon = {ts_stats_mon, {ts_stats_mon, start, []}, transient, 2000, worker, [ts_stats_mon]}, Request_Mon = {request, {ts_stats_mon, start, [request]}, transient, 2000, worker, [ts_stats_mon]}, Page_Mon = {page, {ts_stats_mon, start, [page]}, transient, 2000, worker, [ts_stats_mon]}, Connect_Mon = {connect, {ts_stats_mon, start, [connect]}, transient, 2000, worker, [ts_stats_mon]}, Transaction_Mon = {transaction, {ts_stats_mon, start, [transaction]}, transient, 2000, worker, [ts_stats_mon]}, Match_Log = {ts_match_logger, {ts_match_logger, start, [LogDir]}, transient, 2000, worker, [ts_match_logger]}, ErlangSup = {ts_erlang_mon_sup, {ts_os_mon_sup, start_link, [erlang]}, permanent, 2000, supervisor, [ts_os_mon_sup]}, MuninSup = {ts_munin_mon_sup, {ts_os_mon_sup, start_link, [munin]}, permanent, 2000, supervisor, [ts_os_mon_sup]}, SNMPSup = {ts_snmp_mon_sup, {ts_os_mon_sup, start_link, [snmp]}, permanent, 2000, supervisor, [ts_os_mon_sup]}, Timer = {ts_timer, {ts_timer, start, [?config(nclients)]}, transient, 2000, worker, [ts_timer]}, Msg = {ts_msg_server, {ts_msg_server, start, []}, transient, 2000, worker, [ts_msg_server]}, UserSup = {ts_user_server_sup,{ts_user_server_sup,start_link,[]},transient,2000, supervisor,[ts_user_server_sup]}, Notify = {ts_job_notify, {ts_job_notify, start_link, []}, transient, 2000, worker, [ts_job_notify]}, {ok,{{one_for_one,?retries,10}, [Config, Mon, Stats_Mon, Request_Mon, Page_Mon, Connect_Mon, Transaction_Mon, Match_Log, Timer, Msg, Notify,UserSup, ErlangSup, MuninSup,SNMPSup]}}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- tsung-1.4.2/src/tsung_controller/ts_os_mon_munin.erl0000644000201100017670000002671311701017117022454 0ustar nniclausdream%%% %%% Copyright 2008 © Nicolas Niclausse %%% %%% Author : Nicolas Niclausse %%% Created: 21 oct 2008 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_os_mon_munin). -vc('$Id: ts_os_mon_snmp.erl,v 0.0 2008/10/21 12:57:49 nniclaus Exp $ '). -author('nicolas.niclausse@niclux.org'). -behaviour(gen_server). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% @doc munin plugin for ts_os_mon %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -include("ts_profile.hrl"). -include("ts_os_mon.hrl"). -define(READ_TIMEOUT,2500). % 2.5 sec -define(SEND_TIMEOUT,5000). -define(RETRY_SLEEP,30000). %% if interval is more than this, we must send ping to avoid closed %% connection from munin node server (default timeout is 10s in recent %% version of munin-node): -define(MAX_INTERVAL,8000). -define(PING_INTERVAL,5000). -export([start/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state,{ mon, % pid of mon server interval, % interval in msec between gathering of data socket, % tcp socket port, % tcp port of munin-node server host, % remote munin-node hostname addr, % remote munin-node IP addr ncpus % number of cpus of remote server }). start(Args) -> ?LOGF("starting os_mon_munin with args ~p",[Args],?NOTICE), gen_server:start_link(?MODULE, Args, []). %%-------------------------------------------------------------------- %% Function: init/1 %% Description: Initiates the server %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%-------------------------------------------------------------------- init({HostStr, {Port}, Interval, MonServer}) -> ?LOGF("Starting munin mgr on ~p:~p~n", [HostStr,Port], ?DEB), {ok, IP} = inet:getaddr(HostStr, inet), erlang:start_timer(?INIT_WAIT, self(), connect ), {ok, #state{mon=MonServer, host=HostStr, interval=Interval, addr=IP, port=Port}}. %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. %%-------------------------------------------------------------------- %% Function: handle_cast/2 %% Description: Handling cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_cast(Msg, State) -> {stop, {unknown_message, Msg}, State}. %%-------------------------------------------------------------------- %% Function: handle_info/2 %% Description: Handling all non call/cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_info({timeout,_Ref,connect},State=#state{addr=IP,port=Port,host=HostStr}) -> Opts=[list, {active, false}, {packet, line}, {send_timeout, ?SEND_TIMEOUT}, {keepalive, true} ], case gen_tcp:connect(IP, Port, Opts) of {ok, Socket} -> case gen_tcp:recv(Socket,0, ?READ_TIMEOUT) of {ok, "# munin node at "++ Str} -> MuninHost = ts_utils:chop(Str), ?LOGF("Connected to ~p~n", [MuninHost], ?INFO), %% We want CPU value ranging from 0 to 100, so we need the max value : gen_tcp:send(Socket,"config cpu\n"), ConfigCPU=read_munin_data(Socket), NCPUs = case proplists:get_value('user.max',ConfigCPU) of Num when is_number(Num) -> Num/100 ; _ -> ?LOG("can't find the number of CPU, assume one~n",?NOTICE), 1 end, ?LOGF("first fetch succesful to ~p~n", [MuninHost], ?INFO), case (State#state.interval > ?MAX_INTERVAL) of true -> erlang:start_timer(?PING_INTERVAL, self(), ping ); _ -> ok end, erlang:start_timer(State#state.interval, self(), send_request ), {noreply, State#state{socket=Socket,host=MuninHost,ncpus=NCPUs}}; {error, Reason} -> ?LOGF("Error while connecting to munin server: ~p~n", [Reason], ?ERR), {stop, Reason, State} end; {error, Reason} -> ?LOGF("Can't connect to munin server on ~p, reason:~p~n", [HostStr, Reason], ?ERR), {stop, Reason, State} end; handle_info({timeout, _Ref, ping}, State=#state{socket=Socket} ) -> gen_tcp:send(Socket,"\n"), gen_tcp:recv(Socket,0,?READ_TIMEOUT), erlang:start_timer(?PING_INTERVAL, self(), ping ), {noreply, State}; handle_info({timeout, _Ref, send_request}, State=#state{socket=Socket,host=Hostname} ) -> %% Currenly, fetch only cpu and memory %% FIXME: should be customizable in XML config file ?LOGF("Fetching munin for cpu on host ~p~n", [Hostname], ?DEB), gen_tcp:send(Socket,"fetch cpu\n"), AllCPU=read_munin_data(Socket), ?LOGF("Fetching munin for memory on host ~p~n", [Hostname], ?DEB), gen_tcp:send(Socket,"fetch memory\n"), AllMem=read_munin_data(Socket), ?LOGF("Fetching munin for load on host ~p~n", [Hostname], ?DEB), gen_tcp:send(Socket,"fetch load\n"), AllLoad=read_munin_data(Socket), %% sum all cpu types, except idle. NonIdle=lists:keydelete('idle.value',1,AllCPU), RawCpu = lists:foldl(fun({_Key,Val},Acc) when is_integer(Val)-> Acc+Val end,0,NonIdle) / (State#state.interval div 1000), Cpu=check_value(RawCpu,{Hostname,"cpu"})/State#state.ncpus, ?LOGF(" munin cpu on host ~p is ~p~n", [Hostname,Cpu], ?DEB), %% returns free + buffer + cache FunFree = fun({Key,Val},Acc) when ((Key=='buffers.value') or (Key=='free.value') or (Key=='cached.value') ) -> Acc+Val; (_, Acc) -> Acc end, FreeMem=check_value(lists:foldl(FunFree,0,AllMem),{Hostname,"memory"})/1048576,%MBytes ?LOGF(" munin memory on host ~p is ~p~n", [Hostname,FreeMem], ?DEB), %% load only has one value at present Load = lists:foldl(fun({_Key,Val},Acc) -> Acc+Val end,0,AllLoad), ?LOGF(" munin load on host ~p is ~p~n", [Hostname,Load], ?DEB), ts_os_mon:send(State#state.mon,[{sample_counter, {cpu, Hostname}, Cpu}, {sample, {freemem, Hostname}, FreeMem}, {sample, {load, Hostname}, Load}]), erlang:start_timer(State#state.interval, self(), send_request ), {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate/2 %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- terminate(_Reason, #state{socket=undefined}) -> ok; terminate(_Reason, #state{socket=Socket}) -> gen_tcp:close(Socket). %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- read_munin_data(Socket)-> read_munin_data(Socket,gen_tcp:recv(Socket,0,?READ_TIMEOUT),[]). read_munin_data(_Socket,{ok,".\n"}, Acc)-> Acc; read_munin_data(Socket,{ok, "graph_args --base "++ Data}, Acc) when is_list(Acc)-> %% special case for getting the number of cpus NewAcc = case re:run(Data,"--upper-limit (\\d+)",[{capture,all_but_first,list}]) of {match,[Val]} when length(Val) > 0 -> ?LOGF("the munin node has ~p CPUs ~n",[Val],?INFO), [{'user.max',list_to_integer(Val)}| Acc]; _ -> ?LOGF("upper-limit don't match ~p~n",[Data],?WARN), Acc end, read_munin_data(Socket,gen_tcp:recv(Socket,0,?READ_TIMEOUT), NewAcc); read_munin_data(Socket,{ok, Data}, Acc) when is_list(Acc)-> ?DebugF("Parse munin data: ~p~n",[Data]), NewAcc = case string:tokens(Data," \n") of [Key, Value] -> try ts_utils:list_to_number(Value) of Num when is_number(Num) -> [{list_to_atom(Key), Num }|Acc] catch _Type:_Exp -> Acc end; [_Key| _Rest] -> Acc; _ -> ?LOGF("Unknown data received from munin server: ~p~n",[Data],?WARN), Acc end, read_munin_data(Socket,gen_tcp:recv(Socket,0,?READ_TIMEOUT), NewAcc); read_munin_data(Socket,{error, timeout}, Acc) when is_list(Acc)-> %% the remote server may be overloaded, wait a bit before retrying ?LOG("munin: timeout error, server must be overloaded, sleep for 30 sec~n", ?WARN), gen_tcp:close(Socket), timer:sleep(?RETRY_SLEEP), erlang:error(server_timeout). %% check is this a valid value (positive at least) check_value(Val,_) when Val > 0 -> Val; check_value(Val,{Host, Type}) -> ?LOGF("munin: bad ~s value on host ~p: ~p~n", [Type, Host, Val],?WARN), 0. tsung-1.4.2/src/tsung_controller/ts_config_fs.erl0000644000201100017670000000613611701017117021706 0ustar nniclausdream%%% %%% Copyright 2009 INRIA %%% %%% Author : Nicolas Niclausse %%% Created: 20 aot 2009 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_config_fs). -vc('$Id$ '). -author('nicolas.niclausse@sophia.inria.fr'). -export([parse_config/2]). -include("ts_profile.hrl"). -include("ts_http.hrl"). -include("ts_config.hrl"). -include("xmerl.hrl"). -include("ts_fs.hrl"). %% @spec parse_config(#xmlElement{}, Config::term()) -> NewConfig::term() %% @doc Parses a tsung.xml configuration file xml element for this %% protocol and updates the Config term. %% @end parse_config(Element = #xmlElement{name=dyn_variable}, Conf = #config{}) -> ts_config:parse(Element,Conf); parse_config(Element = #xmlElement{name=fs}, Config=#config{curid = Id, session_tab = Tab, sessions = [CurS | _], dynvar=DynVar, subst = SubstFlag, match=MatchRegExp}) -> Cmd = ts_config:getAttr(atom,Element#xmlElement.attributes, cmd, write), Size = ts_config:getAttr(integer,Element#xmlElement.attributes, size, 1024), Path = ts_config:getAttr(string,Element#xmlElement.attributes, path), Mode = ts_config:getAttr(atom,Element#xmlElement.attributes, mode, write), Dest = ts_config:getAttr(string,Element#xmlElement.attributes, dest), Position = ts_config:getAttr(integer,Element#xmlElement.attributes, position, undefined), Request = #fs{command=Cmd,size=Size,mode=Mode,path=Path,position=Position, dest=Dest}, Msg= #ts_request{ack = parse, endpage = true, dynvar_specs = DynVar, subst = SubstFlag, match = MatchRegExp, param = Request}, ts_config:mark_prev_req(Id-1, Tab, CurS), ets:insert(Tab,{{CurS#session.id, Id},Msg}), lists:foldl( fun(A,B)->ts_config:parse(A,B) end, Config#config{dynvar=[]}, Element#xmlElement.content); %% Parsing other elements parse_config(Element = #xmlElement{}, Conf = #config{}) -> ts_config:parse(Element,Conf); %% Parsing non #xmlElement elements parse_config(_, Conf = #config{}) -> Conf. tsung-1.4.2/src/tsung_controller/ts_msg_server.erl0000644000201100017670000001133211701017117022117 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_msg_server). -author('jflecomte@IDEALX.com'). -vc('$Id$ '). -export([get_id/0, get_id/1, reset/0]). -include("ts_profile.hrl"). -behaviour(gen_server). %% External exports -export([start/0, stop/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, {number}). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start() -> ?LOG("Starting ~n",?INFO), gen_server:start_link({global,?MODULE}, ?MODULE, [], []). get_id()-> gen_server:call({global, ?MODULE}, get_id). get_id(list)-> integer_to_list(get_id()). reset()-> gen_server:call({global, ?MODULE}, reset). stop()-> gen_server:call({global, ?MODULE}, stop). %%%---------------------------------------------------------------------- %%% Callback functions from gen_server %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%---------------------------------------------------------------------- init([]) -> {ok, #state{number = 0}}. %%---------------------------------------------------------------------- %% Func: handle_call/3 %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_call(get_id, _From, State) -> Element = State#state.number + 1, State2 = State#state{number = Element}, {reply, Element, State2}; handle_call(reset, _From, State) -> {reply, ok, State#state{number = 0}}; handle_call(stop, _From, State)-> {stop, normal, ok, State}. %%---------------------------------------------------------------------- %% Func: handle_cast/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_cast(_Msg, State) -> {noreply, State}. %%---------------------------------------------------------------------- %% Func: handle_info/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%---------------------------------------------------------------------- %% Func: terminate/2 %% Purpose: Shutdown the server %% Returns: any (ignored by gen_server) %%---------------------------------------------------------------------- terminate(Reason, _State) -> ?LOGF("terminate ~n (reason ~p)",[Reason],?INFO), ok. %%---------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%---------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- tsung-1.4.2/src/tsung_controller/ts_config_job.erl0000644000201100017670000000757411701017117022057 0ustar nniclausdream%%% %%% Copyright 2011 INRIA %%% %%% Author : Nicolas Niclausse %%% Created: 4 mai 2011 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_config_job). -vc('$Id$ '). -author('nicolas.niclausse@inria.fr'). -export([parse_config/2]). -include("ts_profile.hrl"). -include("ts_http.hrl"). -include("ts_config.hrl"). -include("xmerl.hrl"). -include("ts_job.hrl"). %% @spec parse_config(#xmlElement{}, Config::term()) -> NewConfig::term() %% @doc Parses a tsung.xml configuration file xml element for this %% protocol and updates the Config term. %% @end parse_config(Element = #xmlElement{name=dyn_variable}, Conf = #config{}) -> ts_config:parse(Element,Conf); parse_config(Element = #xmlElement{name=job}, Config=#config{curid = Id, session_tab = Tab, sessions = [CurS | _], dynvar=DynVar, subst = SubstFlag, match=MatchRegExp}) -> Request = #job{req = ts_config:getAttr(atom,Element#xmlElement.attributes, req, submit), type = ts_config:getAttr(atom,Element#xmlElement.attributes, type, oar), script = ts_config:getAttr(string,Element#xmlElement.attributes, script), notify_script = ts_config:getAttr(string,Element#xmlElement.attributes, notify_script), walltime = ts_config:getAttr(string,Element#xmlElement.attributes, walltime, "1:00:00"), resources = ts_config:getAttr(string,Element#xmlElement.attributes, resources, ""), queue = ts_config:getAttr(string,Element#xmlElement.attributes, queue), notify_port = ts_config:getAttr(integer_or_string,Element#xmlElement.attributes, notify_port), jobid = ts_config:getAttr(integer_or_string,Element#xmlElement.attributes, jobid, undefined), name = ts_config:getAttr(string,Element#xmlElement.attributes, name, "tsung"), user = ts_config:getAttr(string,Element#xmlElement.attributes, user, undefined), options = ts_config:getAttr(string,Element#xmlElement.attributes, options), duration = ts_config:getAttr(integer_or_string,Element#xmlElement.attributes, duration, 3600) }, Msg= #ts_request{ack = parse, endpage = true, dynvar_specs = DynVar, subst = SubstFlag, match = MatchRegExp, param = Request}, ts_config:mark_prev_req(Id-1, Tab, CurS), ets:insert(Tab,{{CurS#session.id, Id},Msg}), lists:foldl( fun(A,B)->ts_config:parse(A,B) end, Config#config{dynvar=[]}, Element#xmlElement.content); %% Parsing other elements parse_config(Element = #xmlElement{}, Conf = #config{}) -> ts_config:parse(Element,Conf); %% Parsing non #xmlElement elements parse_config(_, Conf = #config{}) -> Conf. tsung-1.4.2/src/tsung_controller/ts_config_ldap.erl0000644000201100017670000002061511701017117022214 0ustar nniclausdream%%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% File : ts_ldap.erl %%% Author : Pablo Polvorin %%% Purpose : LDAP plugin -module(ts_config_ldap). -export([ parse_config/2 ]). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("xmerl.hrl"). -include("ts_ldap.hrl"). %%---------------------------------------------------------------- %%-----Configuration parsing %%---------------------------------------------------------------- parse_config(Element = #xmlElement{name=dyn_variable}, Conf = #config{}) -> ts_config:parse(Element,Conf); parse_config(Element = #xmlElement{name=ldap}, Config=#config{curid = Id, session_tab = Tab, sessions = [CurS | _], dynvar=DynVar, subst = SubstFlag, match=MatchRegExp}) -> Request = case ts_config:getAttr(atom, Element#xmlElement.attributes, type) of start_tls -> Cacert = ts_config:getAttr(string,Element#xmlElement.attributes,cacertfile), KeyFile = ts_config:getAttr(string,Element#xmlElement.attributes,keyfile), CertFile = ts_config:getAttr(string,Element#xmlElement.attributes,certfile), #ts_request{ack = parse, endpage = true, dynvar_specs= DynVar, subst = SubstFlag, match= MatchRegExp, param = #ldap_request{type=start_tls,cacertfile=Cacert,keyfile=KeyFile,certfile=CertFile}}; bind -> User = ts_config:getAttr(string,Element#xmlElement.attributes,user), Password = ts_config:getAttr(string,Element#xmlElement.attributes,password), #ts_request{ack = parse, endpage = true, dynvar_specs = DynVar, subst = SubstFlag, match = MatchRegExp, param = #ldap_request{type=bind,user=User,password=Password}}; unbind -> #ts_request{ack = no_ack, endpage = true, dynvar_specs = DynVar, subst = SubstFlag, match = MatchRegExp, param = #ldap_request{type=unbind}}; search -> Base = ts_config:getAttr(string,Element#xmlElement.attributes,base), Scope = ts_config:getAttr(atom,Element#xmlElement.attributes,scope), Filter = ts_config:getAttr(string,Element#xmlElement.attributes,filter), ResultVar = case ts_config:getAttr(string,Element#xmlElement.attributes,result_var,none) of none -> none; VarName -> {ok,list_to_atom(VarName)} end, Attributes=[], {ParsedFilter,[]} = rfc4515_parser:filter(rfc4515_parser:tokenize(Filter)), #ts_request{ack = parse, endpage = true, dynvar_specs = DynVar, subst = SubstFlag, match = MatchRegExp, param = #ldap_request{type=search, result_var = ResultVar, base=Base, scope=Scope, filter=ParsedFilter, attributes=Attributes}}; add -> DN = ts_config:getAttr(string,Element#xmlElement.attributes,dn), XMLAttrs = [El || El <- Element#xmlElement.content, is_record(El,xmlElement)], Attrs = lists:map(fun(#xmlElement{name=attr,attributes=Attr,content=Content}) -> Vals = lists:foldl(fun(#xmlElement{name=value,content=[#xmlText{value=Value}]},Values) -> [binary_to_list(iolist_to_binary(Value))|Values] end,[] ,[E || E=#xmlElement{name=value} <- Content]), {ts_config:getAttr(string,Attr,type),Vals} end, XMLAttrs), #ts_request{ack = parse, endpage = true, dynvar_specs = DynVar, subst = SubstFlag, match = MatchRegExp, param = #ldap_request{type=add, dn=DN, attrs=Attrs }}; modify -> DN = ts_config:getAttr(string,Element#xmlElement.attributes,dn), Modifications = [{list_to_atom(ts_config:getAttr(string,Attrs,type)),parse_xml_attr_type_and_value(Content)} || #xmlElement{name=modification,attributes=Attrs,content=Content} <- Element#xmlElement.content], ExpandedModifications = lists:foldl( fun({Operation,Attrs},L) -> lists:foldl(fun({Type,Values},L2) -> [{Operation,Type,Values}|L2] end,L,Attrs) end,[],Modifications), #ts_request{ack = parse, endpage = true, dynvar_specs = DynVar, subst = SubstFlag, match = MatchRegExp, param = #ldap_request{type=modify, modifications = ExpandedModifications, dn=DN }} end, ts_config:mark_prev_req(Id-1, Tab, CurS), ets:insert(Tab,{{CurS#session.id, Id}, Request }), lists:foldl( fun(A,B)->ts_config:parse(A,B) end, Config#config{dynvar=[]}, Element#xmlElement.content); %% Parsing other elements parse_config(Element = #xmlElement{}, Conf = #config{}) -> ts_config:parse(Element,Conf); %% Parsing non #xmlElement elements parse_config(_, Conf = #config{}) -> Conf. parse_xml_attr_values(Elements) -> [binary_to_list(iolist_to_binary(Value)) || #xmlElement{name=value,content=[#xmlText{value=Value}]} <- Elements]. parse_xml_attr_type_and_value(Elements) -> [ {ts_config:getAttr(string,Attr,type),parse_xml_attr_values(Content)} || #xmlElement{name=attr,attributes=Attr,content=Content} <-Elements]. tsung-1.4.2/src/tsung-rrd.pl.in0000755000201100017670000000603011701017117016017 0ustar nniclausdream#!/usr/bin/perl # # Copyright (C) 2009 Bearstech http://www.bearstech.com/ # Copyright (C) 2009 Rodolphe Quiédeville # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Auteur: Rodolphe Quiédeville (rodolphe@quiedeville.org) # Version: $Id$ # purpose: create rrd files with tsung log datas use strict; use RRDs; use Getopt::Long; use vars qw ($help *verbose $version $log_file $output); $log_file = "tsung.log"; $output = "."; GetOptions( "help",\$help, "verbose",\$verbose, "log=s",\$log_file, "output=s",\$output, "version",\$version ); # check options printf "dir %s doesn't exists\n",$output if (!-d $output); printf "file %s doesn't exists\n",$log_file if (!-f $log_file); # do the job open (FILE, $log_file ) or die "Cannot open : $!"; while () { my $date = $1 if (/stats: dump at (\d+)/); users($date, $1, "users") if (/^stats: users (\d+) (\d+)$/); users($date, $1, "connected") if (/^stats: connected (\d+) (\d+)$/); codes($date, $1, $2) if (/^stats: (\d+) (\d+) (\d+)$/); generic($date, $1, $2) if (/^stats: (page) (\d+)/); generic($date, $1, $2) if (/^stats: (session) (\d+)/); generic($date, $1, $2) if (/^stats: (request) (\d+)/); generic($date, $1, $2) if (/^stats: (connect) (\d+)/); generic($date, $1, $2) if (/^stats: (size_\w+) (\d+)/); generic($date, $1, $2) if (/^stats: (users_count) (\d+)/); generic($date, $1, $2) if (/^stats: (finish_users_count) (\d+)/); } close FILE; # users data sub users { my ($date,$value, $stat) = @_; RRDs::update ("users.rrd", "--template", $stat, "$date:$value"); } # # HTTP return code # sub codes { my ($date, $code, $value) = @_; my $file = "$output/code-$code.rrd"; create_rrd ($file) if (! -f $file); RRDs::update ($file, "--template", "value", "$date:$value"); } # # Some generic datas # sub generic { my ($date, $data, $value) = @_; my $file = "$output/$data.rrd"; create_rrd ($file) if (! -f $file); RRDs::update ($file, "--template", "value", "$date:$value"); } # # Create RRD file # sub create_rrd { my ($file) = @_; printf "Create %s\n",$file if $verbose; my @parms = ("$file", "--start",1240488000, "--step", 10, "DS:value:GAUGE:20:0:671744", "RRA:AVERAGE:0.5:1:2400", "RRA:AVERAGE:0.5:2:1200"); RRDs::create (@parms); if (my $ERROR = RRDs::error) { die "RRDs ERROR: $ERROR\n"; }; } tsung-1.4.2/src/tsung_stats.pl.in0000644000201100017670000006536011701017117016460 0ustar nniclausdream#!/usr/bin/perl -w # -*- Mode: CPerl -*- # # This code was developped by IDEALX (http://IDEALX.org/) and # contributors (their names can be found in the CONTRIBUTORS file). # Copyright (C) 2000-2004 IDEALX # Copyright (C) 2005-2011 Nicolas Niclausse # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # Version: $Id$ # purpose: quick and dirty ugly hack to compute stats and plots graph # given a (set of) log file(s) from the tsung tool. # dygraphs fonctionnality added by Bearstech (http://bearstech.com) # using dygraphs JavaScript Visualization Library (http://danvk.org/dygraphs/) use strict; use Getopt::Long; use vars qw ($help @files $dygraph $verbose $debug $noplot $noextra $version $stats $template_dir $nohtml $template_dir $gnuplot $logy $rotate_xtics $imgfmt $oldgnuplot ); use File::Spec::Functions qw(rel2abs); use File::Basename; use File::Copy; my $tagvsn = '@PACKAGE_VERSION@'; GetOptions( "help",\$help, "verbose",\$verbose, "debug",\$debug, "stats=s",\$stats, "gnuplot=s",\$gnuplot, "version",\$version, "logy",\$logy, "dygraph",\$dygraph, "noplot",\$noplot, "tdir=s",\$template_dir, "nohtml",\$nohtml, "rotate-xtics",\$rotate_xtics, "noextra",\$noextra, "img_format=s",\$imgfmt ); &usage if $help or $Getopt::Long::error; &version if $version; my $extra = not $noextra; my $match =0; my $async =0; my $errors =0; my $maxval; my $os_mon_other; my $category; my $CPU_MAX = 3200; # cpu usage should never be higher than 3200% (32 cores at 100%) my $prefix ="@prefix@"; unless ($template_dir) { if (-d (dirname($0) . "/templates/")) { $template_dir = dirname($0)."/templates/"; } elsif (-d "$ENV{HOME}/.tsung/templates/") { $template_dir = "$ENV{HOME}/.tsung/templates/"; } elsif (-d "@datadir@/@TEMPLATES_SUBDIR@") { $template_dir = "@datadir@/@TEMPLATES_SUBDIR@"; } elsif (-d "/usr/share/tsung/templates") { $template_dir = "/usr/share/tsung/templates"; } elsif (-d "/usr/local/share/tsung/templates") { $template_dir = "/usr/local/share/tsung/templates"; } else { warn "Can't find template directory !"; } } $stats = "tsung.log" unless $stats; die "The stats file ($stats) does not exist, abort !" unless -e $stats; $imgfmt = "png" unless $imgfmt; my %imgfmt_list = ("png" => 1, "svg" => 1, "pdf" => 1, "ps" => 1); # hash of all the format we support to make search more efficient die "Image format \"$imgfmt\" not supported" unless $imgfmt_list{"$imgfmt"}; my $datadir = "data"; # all data files are created in this subdirectory my $imgdir = "images"; # all data files are created in this subdirectory my $gplotdir = "gnuplot_scripts"; # all data files are created in this subdirectory my $csvdir = "csv_data"; # all data files are created in this subdirectory my $http; # true if http. add status code graphs in the HTML output my @dydirs = ($csvdir); my @gnuplotdirs=($gplotdir, $imgdir); my @dirs = ($datadir); push @dirs, @dydirs if $dygraph; push @dirs, @gnuplotdirs if not $dygraph; foreach my $dir (@dirs) { unless (-d $dir) { print "creating subdirectory $dir \n"; mkdir "$dir" or die "can't create directory $dir"; } } $gnuplot = "gnuplot" unless $gnuplot; my $oldgnuplot = is_oldgnuplot($gnuplot); $gnuplot .= " >> gnuplot.log 2>&1"; &parse_stats_file($stats); &html_report() unless $nohtml; # returns 1 (=true) if gnuplot doesn't support the "set terminal png size x,y" specifications sub is_oldgnuplot { # args my $gnuplot = shift; my $version = `$gnuplot -V`; if ($version =~ m /gnuplot (\d+).(\d+) patchlevel (\d+)/) { my ($major, $minor,$patchlevel) = ($1,$2,$3); return 0 if $major > 5; return 1 if $major < 3; return 1 if $minor <2; # does it work with gnuplot 4.1 or 4.0 ? we assume no. return 0; } else { return 1; } } # plot stats from file with gnuplot sub plot_stats { # args: my $title = shift; # arrayref contaning data titles my $datatype = shift; # type of data (added in filenames) my $timestamp = shift; my $files = shift; # arrayref contaning data files my $ylabel = shift; my $logy = shift; # if true use logarithmic scale for Y axis # local var my $style = "linespoint"; # TODO: can be override in option my $legend_loc = "key left top"; # TODO: can be override in option my $output_type; my $filename; # temporary var my $thumbnail_size = 0.5; if (scalar @{$files} == 0 ) { warn "No data for $datatype\n"; return; } $datatype = "unknown" unless $datatype; open(GP,">$gplotdir/graphes-$datatype.gplot") or die "can't open graphes-$datatype.gplot: $!"; select GP; foreach my $output_ext ($imgfmt, "tn.png") { if (($output_ext eq "png") || ($output_ext eq "tn.png")) { $output_type = "png"; } elsif ($output_ext eq "ps") { $output_type = "postscript color "; } elsif ($output_ext eq "pdf") { $output_type = "pdf color "; } elsif ($output_ext eq "svg") { $output_type = "svg"; } # gnuplot styles and options print "set size $thumbnail_size,$thumbnail_size\n" if (($output_ext eq "tn.png") && $oldgnuplot); print "set xtics rotate by +90\n" if ((($output_ext eq "png") || ($output_ext eq "tn.png")) and ($rotate_xtics)); print "set style data $style\n"; if (($output_ext eq "tn.png") and not $oldgnuplot) { print "set terminal $output_type tiny size 320,240\n"; } elsif ((($output_type eq "svg") or (($output_type eq "png"))) and not $oldgnuplot) { print "set terminal $output_type size 1024,768\n"; } else { print "set terminal $output_type\n"; } print "set grid\n"; my $d; # temporary var (title) foreach $d (0..$#{$title}) { # gnuplot headings if ($output_ext eq "tn.png") { print "set output \"$imgdir/graphes-$datatype-@{$title}[$d]_$output_ext\"\n"; } else { print "set output \"$imgdir/graphes-$datatype-@{$title}[$d].$output_ext\"\n"; } print "set title \" @{$title}[$d]\"\n"; if ($timestamp) { print "set xlabel \"unit = $timestamp sec \"\n"; } else { print "set xlabel \"unit = sec \"\n"; } print "set ylabel \"".$ylabel->[$d]."\"\n" if $ylabel->[$d]; print "show title\n"; print "set $legend_loc\n"; print "set logscale y\n" if $logy; print "plot "; foreach $filename (@{$files}) { print " \"$datadir/$filename\" using "; # if $timestamp isn't defined, use the first column as timestamp if ($timestamp) { print $d+1 ; } else { print " 1:" .($d+2); } my $cur_title = $filename; $cur_title =~ s/\.txt$//; $cur_title =~ s/:os_mon//; print " title \"$cur_title\"" ; print "," unless ($filename eq @{$files}[$#{$files}]); # unless last occurence } print "\n"; # plot done } } close GP; system("$gnuplot $gplotdir/graphes-$datatype.gplot") and warn "Error while running gnuplot: $!"; } # plot stats from file with dygraph sub plot_stats_dygraph { # args: my $title = shift; # arrayref contaning data titles my $datatype = shift; # type of data (added in filenames) my $timestamp = shift; my $files = shift; # arrayref contaning data files my $ylabel = shift; my $logy = shift; # if true use logarithmic scale for Y axis # local var my $filename; # temporary var my $thumbnail_size = 0.5; my $row; my @rowdata; my %info; my $cur_title; #tmp var my @fields; my $time; my $value; my $count = 0; if (scalar @{$files} == 0 ) { warn "No data for $datatype\n"; return; } $datatype = "unknown" unless $datatype; my $d; # temporary var (title) foreach $d (0..$#{$title}) { #loop on all the kind of graph needed for a subject (transaction, user ...) $info{'path'} = "$csvdir/graphes-$datatype-@{$title}[$d].csv"; #if ($timestamp) { # print "set xlabel \"unit = $timestamp sec \"\n"; #} else { # print "set xlabel \"unit = sec \"\n"; #} #print "set ylabel \"".$ylabel->[$d]."\"\n" if $ylabel->[$d]; foreach $filename (@{$files}) { $cur_title = $filename; $cur_title =~ s/\.txt$//; $cur_title =~ s/:os_mon//; open FILE, "$datadir/$filename"; #we go throught the file in order to complete row $count = 1; $rowdata[0] = ["time"] if not defined $rowdata[0]; push @{$rowdata[0]}, $cur_title; #print "go for $cur_title\n"; while () { @fields = split(/ /); ($time, $value) = @fields[0,$d+1] if defined @fields[0,$d+1]; chomp($value); chomp($time); #print "$time : $value \n"; die if not defined $time ; $rowdata[$count] = [$time] if not defined $rowdata[$count]; # new line while (1) { #look for right time my $actual = @{$rowdata[$count]}[0]; chomp($actual); die unless defined $actual; if ($time == $actual) { push @{$rowdata[$count]}, $value ; $count++; last; } elsif ($time > $actual) { push @{$rowdata[$count]}, "" ; $count++; } else { last; } } } close FILE; #FIXME : ugly open and close multiple time } open CSV, ">$csvdir/graphes-$datatype-@{$title}[$d].csv" or die $!; foreach $row (@rowdata) { print CSV (join(",", @{$row})." \n"); } close CSV; } } sub max { my $value = shift; my $oldvalue= shift; return $value unless $oldvalue; return $value if $oldvalue < $value; return $oldvalue; } sub min { my $value = shift; my $oldvalue= shift; return $value unless $oldvalue; return $value if $oldvalue > $value; return $oldvalue; } sub parse_stats_file { my $file = shift; my $data; my $timestamp; my $first_timestamp =0; my $first_interval =0; my $interval; open (FILE,"<$file") or die "Can't open $file $!"; while () { if (/^stats: (\S+)\s+(.*)$/) { my $type = $1; my $values = $2; $type =~ s/page_resptime/page/g; $type =~ s/response_time/request/g; # handle new format of ts_os_mon : reformat as old one if ($type =~ m/os_mon/) { $type =~ s/\{(\S+)\,\"(\S+)\"\}/$1:$2/g; } else { $type =~ s/\{(\S+)\,\"(\S+)\"\}/$1:os_mon\@$2/g; } my ($rate,$mean,$stdvar,$max,$min,$meanfb,$countfb) = split(/\s+/,$values); if ($type =~ /^cpu:/ ) { next if $values =~ /^0/; # skip when no data is available next if $mean > $CPU_MAX; # skip bad value $category->{$type} = "os_mon_cpu"; } elsif ($type =~ /^freemem:/) { next if $values =~ /^0/; # skip when no data is available $category->{$type} = "os_mon_free"; } elsif ($type =~ /^load:/) { next if $values =~ /^0/; # skip when no data is available $category->{$type} = "os_mon_load"; } elsif ($type =~ /^\w{4}packets:/) { next if $values =~ /^0/; # skip when no data is available $category->{$type} = "os_mon_packets"; } elsif ($type =~ /^(\w+):os_mon/) { next if $values =~ /^0/; # skip when no data is available $category->{$type} = "os_mon_other"; my $osname = $1; push @{$os_mon_other}, $osname unless (grep {/^$osname$/ } @{$os_mon_other}) ; } elsif ($type =~ /^\d+$/) { $category->{$type} = "http_status"; } elsif ($type eq "request" or $type eq "page" or $type eq "session" or $type eq "connect" or $type eq "async_rcv") { $category->{$type} = "stats"; } elsif ($type =~ /^tr_/ or $type eq "page") { $category->{$type} = "transaction"; } elsif ($type =~ "^size") { $category->{$type} = "network"; } elsif ($type =~ /^error/) { $category->{$type} = "error"; } elsif ($type =~ /^bidi/ or $type eq "request_noack") { $async = 1; $category->{$type} = "count"; } elsif ($type =~ /match/) { $match = 1; $category->{$type} = "match"; } elsif ($type ne "users" and $type ne "connected" and $type =~ /^job_/) { $category->{$type} = "count"; } else { $category->{$type} = "gauge"; } if ($interval) { $rate /= $interval; $maxval->{'rate'}->{$type} = &max($rate, $maxval->{'rate'}->{$type}); $maxval->{'maxmean'}->{$type} = &max($mean, $maxval->{'maxmean'}->{$type}); $maxval->{'mean'}->{$type} = $meanfb; $maxval->{'count'}->{$type} = &max($countfb, $maxval->{'count'}->{$type}); $maxval->{'minmean'}->{$type} = &min($mean, $maxval->{'minmean'}->{$type}) if $rate; } push @{$data->{$type}}, $timestamp . " ". $values; } elsif (/^\# stats:\s+dump at\s+(\d+)/) { $first_timestamp= $1 unless $first_timestamp; $interval = ($timestamp) ? $timestamp : 0; # keep previous value $timestamp = $1 - $first_timestamp; $interval = $timestamp-$interval; $first_interval= $interval if $interval and not $first_interval; } } close FILE; if ($nohtml) { foreach my $key (sort keys %{$maxval->{'rate'}}) { if ($key =~ /\d+/ or $key =~ /^size/) { printf "Total $key = %7.2f\n", $maxval->{'mean'}->{$key}; } else { printf "Mean $key (max sample) = %7.2f\n", $maxval->{'mean'}->{$key}; } printf "Rate $key (max sample) = %7.2f\n",$maxval->{'rate'}->{$key}; } } my @time; my @errors; my @tps; my @code; my %extra_info = (); my @session; my @connect; my @size; my @match; my @users; my @users_rate; my @transactions; my @async; my $key; if ($interval != $first_interval) { print "warn, last interval ($interval) not equal to the first, use the first one ($first_interval)\n"; $interval=$first_interval; } my @col = ("rate","mean","stdvar","max_sample","min_sample"); #session, perfs et transaction my @colcount = ("rate","total"); #http_code match, event, errirn users_arrivaln size my @colusers = ("simultaneous","maximum_simultaneous"); #users my @colasync = ("rate","total"); #async foreach $key (keys %{$data}) { $key =~ s/\'//g; open (TYPE, "> $datadir/$key.txt") or die "$!"; foreach my $data (@{$data->{$key}}) { if (($key !~ /^users$/ and $key !~ /^connected$/ and $key !~ /^size_/ and $key !~ /^job_/) and $interval) { # my @tmp; my $time; ($time, $data, @tmp) = split(/\s/,$data); $data /= $interval; $data = "$time $data @tmp"; } elsif ($key =~ /^size/) { # bits instead of bytes my ($time, @tmp) = split(/\s/,$data); @tmp = map {$_*8/(1024*$interval) } @tmp; # kb/s instead of Bytes/s $data = "$time @tmp"; } elsif ($key =~ /^connected/ or $key =~ /^job_/) { my ($time,$rate, $cur) = split(/\s/,$data); $data = "$time $cur $rate"; } print TYPE $data ."\n"; } if ($key eq "session") { push @session, "$key.txt"; } elsif ($key =~ /^size/) { push @size, "$key.txt"; } elsif ($key =~ /^users$/ or $key =~ /^connected$/ or $key =~ /^job_/) { push @users, "$key.txt"; } elsif ($key =~ /users/) { push @users_rate, "$key.txt"; } elsif ($key =~ /match/) { push @match, "$key.txt"; } elsif ($key =~ /^error/) { push @errors, "$key.txt"; } elsif ($key=~ /^bidi/ or $key eq "request_noack") { push @async, "$key.txt"; } elsif ($key =~ /request$/ or $key eq "connect" or $key eq "async_rcv") { push @tps, "$key.txt"; } elsif ($key =~ /^tr_/ or $key eq "page") { push @transactions, "$key.txt"; } elsif ($key =~ /^\d+$/) { $http = 1; push @code, "$key.txt"; } elsif ($key =~ /^(\S+)?:\S+?@\S+$/) { my $key_short_name = $1; push(@{$extra_info{$key_short_name}}, "$key.txt"); } else { push @time, "$key.txt"; } close TYPE; } if (not $dygraph) { plot_stats(\@col,"Session",undef,\@session,["sessions/sec"],$logy) unless $noplot; plot_stats(\@colcount,"HTTP_CODE",undef,\@code,["number/sec","total"],$logy) if not $noplot and @code; plot_stats(\@col,"Perfs",undef,\@tps,["rate","msec"],$logy) unless $noplot; plot_stats(\@col,"Transactions",undef,\@transactions,["transactions/sec","msec"],$logy) unless $noplot; plot_stats(\@colcount,"Match",undef,\@match,["rate","rate"],$logy) unless $noplot; plot_stats(\@colcount,"Event",undef,\@time,["rate","msec"],$logy) unless $noplot; plot_stats(\@colasync,"Async",undef,\@async,["rate","rate"],$logy) unless $noplot; plot_stats(\@colcount,"Errors",undef,\@errors,["errors/sec","total"],$logy) unless $noplot; plot_stats(\@colusers,"Users",undef,\@users,["value", "total"],$logy) unless $noplot; plot_stats(\@colcount,"Users_Arrival",undef,\@users_rate,["number of users/sec", "total"],$logy) unless $noplot; plot_stats(\@colcount,"Size",undef,\@size,["Kbits/sec","total Kbits"],$logy) unless $noplot; } else { plot_stats_dygraph(\@col,"Session",undef,\@session,["sessions/sec"],$logy) unless $noplot; plot_stats_dygraph(\@colcount,"HTTP_CODE",undef,\@code,["number/sec","total"],$logy) if not $noplot and @code; plot_stats_dygraph(\@col,"Perfs",undef,\@tps,["rate","msec"],$logy) unless $noplot; plot_stats_dygraph(\@col,"Transactions",undef,\@transactions,["transactions/sec","msec"],$logy) unless $noplot; plot_stats_dygraph(\@colcount,"Match",undef,\@match,["rate","rate"],$logy) unless $noplot; plot_stats_dygraph(\@colcount,"Event",undef,\@time,["rate","msec"],$logy) unless $noplot; plot_stats_dygraph(\@colasync,"Async",undef,\@async,["rate","rate"],$logy) unless $noplot; plot_stats_dygraph(\@colcount,"Errors",undef,\@errors,["errors/sec","total"],$logy) unless $noplot; plot_stats_dygraph(\@colusers,"Users",undef,\@users,["value", "total"],$logy) unless $noplot; plot_stats_dygraph(\@colcount,"Users_Arrival",undef,\@users_rate,["number of users/sec", "total"],$logy) unless $noplot; plot_stats_dygraph(\@colcount,"Size",undef,\@size,["Kbits/sec","total Kbits"],$logy) unless $noplot; } # Generate graphes for extra indicators (os_mon for example) if (not $noplot and $extra and (scalar keys %extra_info) != 0 ) { print STDOUT "Generation os_mon graphs\n" if $verbose; foreach my $key (sort keys %extra_info) { my $pos = index($key,":"); if (not $dygraph) { plot_stats(\@col, $key, undef, \@{$extra_info{$key}}, [$key],$logy); } else { plot_stats_dygraph(\@col, $key, undef, \@{$extra_info{$key}}, [$key],$logy); } } } $extra=0 if (scalar keys %extra_info == 0 ); # no extra information available $errors=1 unless (scalar @errors == 0 ); # no extra information available } sub html_report { require Template; my $titre = 'Tsung '; my $version = $tagvsn; my $contact = '@PACKAGE_BUGREPORT@'; my $output = 'index.html'; my $tt = Template->new({ INCLUDE_PATH => $template_dir, PRE_CHOMP => 1, INTERPOLATE => 1, }) or die "Template error " . Template->error(); my $xml_conf; opendir (DIR, ".") or warn "can't open directory ."; while (my $file = readdir (DIR) ) { if ($file =~ /.xml$/) { $xml_conf= $file; } } foreach my $type ("mean", "maxmean", "minmean") { foreach my $data (keys % {$maxval->{$type}} ) { next if ($data =~ m/^size/); if ($data =~ m/os_mon/) { $maxval->{$type}->{$data} = sprintf "%.2f",$maxval->{$type}->{$data}; next; } next if not ($data eq "session" or $data eq "connect" or $data eq "request" or $data eq "page" or $data =~ m/^tr_/); $maxval->{$type}->{$data} = &formattime($maxval->{$type}->{$data}); } } foreach my $size ("size_rcv", "size_sent") { if ($maxval->{rate}->{$size}) { $maxval->{rate}->{$size} = &formatsize($maxval->{rate}->{$size}*8,"bits"); $maxval->{maxmean}->{$size} = &formatsize($maxval->{maxmean}->{$size},"B"); } else { warn "$size is equal to 0 !\n"; } } my $vars = { version => $version, os_mon => $extra, errors => $errors, title => $titre, subtitle => "Stats Report", http => $http, stats_subtitle => "Stats Report ", graph_subtitle => "Graphs Report ", contact => $contact, data => $maxval, cat_data => $category, conf => $xml_conf }; $tt->process("report.thtml", $vars, "report.html") or die $tt->error(), "\n"; $vars = { version => $version, os_mon => $extra, errors => $errors, http => $http, match => $match, async => $async, title => $titre, subtitle => "Graphs Report", stats_subtitle => "Stats Report ", graph_subtitle => "Graphs Report ", os_mon_other=> $os_mon_other, contact => $contact, conf => $xml_conf, ext => $imgfmt }; if (not $dygraph) { $tt->process("graph.thtml", $vars, "graph.html") or die $tt->error(), "\n"; } else { $tt->process("graph_dy.thtml", $vars, "graph.html") or die $tt->error(), "\n"; copy (($template_dir . "/dygraph-combined.js"), ".") or die "copy failed : $!"; } } sub usage { print "this script is part of tsung version $tagvsn, Copyright (C) 2001-2004 IDEALX (http://IDEALX.org/)\n\n"; print "tsung comes with ABSOLUTELY NO WARRANTY; This is free software, and ou are welcome to redistribute it under certain conditions type `tsung_stats.pl --version` for details.\n\n"; print "Usage: $0 []\n","Available options:\n\t", "[--help] (this help text)\n\t", "[--verbose] (print all messages)\n\t", "[--debug] (print receive without send messages)\n\t", "[--dygraph] use dygraphs (http://danvk.org/dygraphs/) to render graphs\n\t", "[--noplot] (don't make graphics)\n\t", "[--gnuplot ] (path to the gnuplot binary)\n\t", "[--nohtml] (don't create HTML reports)\n\t", "[--logy] (logarithmic scale for Y axis)\n\t", "[--tdir ] (Path to the HTML tsung templates)\n\t", "[--noextra (don't generate graphics from extra data (os monitor, etc)\n\t", "[--rotate-xtics (rotate legend of x axes)\n\t", "[--stats ] (stats file to analyse, default=tsung.log)\n\t", "[--img_format ] (output format for images, default=png available format: ps, svg, png, pdf)\n\t"; exit; } sub affiche() { my $name = shift; my $value = shift; return sprintf "#%7s = %.3f",$name,$value; } sub formattime { my $value = shift; return unless $value; $value /=1000; if ($value < 0.001) { sprintf "%.3f msec",$value*1000; } elsif ($value < 0.1) { sprintf "%.2f msec",$value*1000; } elsif ($value < 60) { sprintf "%.2f sec",$value; } elsif ($value < 3600) { my $mn= int($value / 60); $value = $value-($mn*60); sprintf "%dmn %dsec",$mn, $value; } elsif ($value > 3600) { my $h = int($value / 3600); my $mn= int(($value -$h*3600) / 60); $value = $value-($h*3600+$mn*60); sprintf "%d h %dmn %dsec",$h,$mn, $value; } else { sprintf "%.1f sec",$value; } } sub formatsize { my $value = shift; my $unit = shift; return unless $value; if ($value < 1024) { sprintf "%d $unit",$value; } elsif ($value < 1024*1024) { sprintf "%.2f K$unit",$value/1024; } elsif ($value < 1024*1024*1024) { sprintf "%.2f M$unit",$value/(1024*1024); } else { sprintf "%.2f G$unit",$value/(1024*1024*1024); } } sub version { print "this script is part of Tsung version $tagvsn Written by Nicolas Niclausse and Jean Franois Lecomte Copyright (C) 2001-2004 IDEALX (http://IDEALX.org/) Copyright (C) 2004-2011 Nicolas Niclausse This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program (see COPYING); if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA."; exit; } tsung-1.4.2/src/tsung/0000755000201100017670000000000011701017143014265 5ustar nniclausdreamtsung-1.4.2/src/tsung/ts_jabber.erl0000644000201100017670000002711611701017117016734 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2004 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% File : ts_jabber.erl %%% Author : Nicolas Niclausse %%% Purpose : Jabber/XMPP plugin %%% Created : 11 Jan 2004 by Nicolas Niclausse -module(ts_jabber). -author('nniclausse@hyperion'). -behavior(ts_plugin). -include("ts_profile.hrl"). -include("ts_jabber.hrl"). -export([init_dynparams/0, add_dynparams/4, get_message/2, session_defaults/0, subst/2, parse/2, dump/2, parse_bidi/2, parse_config/2, decode_buffer/2, new_session/0]). %%---------------------------------------------------------------------- %% Function: session_default/0 %% Purpose: default parameters for session (persistent & bidirectional) %% Returns: {ok, true|false, true|false} %%---------------------------------------------------------------------- session_defaults() -> {ok, true, false}. %% @spec decode_buffer(Buffer::binary(),Session::record(jabber)) -> NewBuffer::binary() %% @doc We need to decode buffer (remove chunks, decompress ...) for %% matching or dyn_variables %% @end decode_buffer(Buffer,#jabber{}) -> Buffer. % nothing to do for jabber %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> #jabber{}. %FIXME: we should use another record (#jabber_session for ex.) %%---------------------------------------------------------------------- %% Function: get_message/1 %% Purpose: Build a message/request %% Args: #jabber %% Returns: binary %%---------------------------------------------------------------------- get_message(Req=#jabber{},#state_rcv{session=S}) -> {ts_jabber_common:get_message(Req),S}. dump(A,B) -> ts_plugin:dump(A,B). %%---------------------------------------------------------------------- %% Function: parse/2 %% Purpose: Parse the given data and return a new state %% Args: Data (binary) %% State (record) %% Returns: {NewState, Opts, Close} %% State = #state_rcv{} %% Opts = proplist() %% Close = bool() %%---------------------------------------------------------------------- parse(closed, State) -> ?LOG("XMPP connection closed by server!",?WARN), {State#state_rcv{ack_done = true}, [], true}; parse(Data, State=#state_rcv{datasize=Size}) -> ?DebugF("RECEIVED : ~p~n",[Data]), case get(regexp) of undefined -> ?LOG("No regexp defined, skip",?WARN), {State#state_rcv{ack_done=true}, [], false}; Regexp -> case re:run(Data, Regexp) of {match,_} -> ?DebugF("XMPP parsing: Match (regexp was ~p)~n",[Regexp]), {State#state_rcv{ack_done=true, datasize=Size+size(Data)}, [], false}; nomatch -> {State#state_rcv{ack_done=false,datasize=Size+size(Data)}, [], false} end end. %%---------------------------------------------------------------------- %% Function: parse_bidi/2 %% Purpose: Parse the given data, return a response and new state %% Args: Data (binary) %% State (record) %% Returns: Data (binary) %% NewState (record) %%---------------------------------------------------------------------- parse_bidi(Data, State) -> RcvdXml = binary_to_list(Data), case re:run(RcvdXml,"]*subscribe[\"']") of {match,_} -> ?LOGF("RECEIVED : ~p~n",[RcvdXml],?DEB), {match,SubMatches} = re:run(RcvdXml,"]*subscribe[\"\'][^>]*>",[global]), bidi_resp(subscribed,RcvdXml,SubMatches,State); _Else -> {nodata,State} end. %%---------------------------------------------------------------------- %% Function: bidi_resp/4 %% Purpose: Parse XMPP packet, build client response %% Accomodates single packets w/ multiple requests %% Args: RcvdXml (list) %% Submatches (list) %% State (record) %% Returns: Data (binary) %% NewState (record) %%---------------------------------------------------------------------- %% subscribed: Complete a pending subscription request bidi_resp(subscribed,RcvdXml,SubMatches,State) -> JoinedXml=lists:foldl(fun(X,Foo) -> [{Start,Len}]=X, SubStr = string:substr(RcvdXml,Start+1,Len), case re:run(SubStr,"from=[\"']([^\s]*)[\"'][\s\/\>]",[{capture,[1],list}]) of {match,[MyId]} -> %% MyId=string:substr(SubStr,Start1 +6, Length1 -8), ?LOGF("Subscription request from : ~p~n",[MyId],?DEB), MyXml = [""], lists:append([Foo],[MyXml]); _Else -> ?LOGF("Error getting sender address: ~p~n",[SubStr],?DEB), "" end end,"",SubMatches), case lists:flatten(JoinedXml) of "" -> {nodata,State}; _ -> ?LOGF("RESPONSE TO SEND : ~s~n",[JoinedXml],?DEB), {list_to_binary(JoinedXml),State} end. %% parse_config(Element, Conf) -> ts_config_jabber:parse_config(Element, Conf). %%---------------------------------------------------------------------- %% Function: add_dynparams/4 %% Purpose: add dynamic parameters to build the message %%---------------------------------------------------------------------- %% The rest of the code expect to found a "domain" field in the #jabber request %% with the domain of the jabber server (as string). We use the step of dynvars substitution %% to choose and set the domain we want to connect, and keep that choice in the %% process dictionary so we reuse it for all request made from the same session. %% (see comments on choose_domain/1 %% %% if we are testing a single domain (the default case), we change from {domain,D}. %% to the specified domain (D). If {vhost,FileId}, we choose a domain from that file %% and set it. add_dynparams(Subst,DynData, Param =#jabber{id=OldId,username=OldUser,passwd=OldPasswd, domain={domain,Domain}}, Host) -> {Id,User,Pwd} = choose_or_cache_user_id(OldId,OldUser,OldPasswd), add_dynparams(Subst,DynData, Param#jabber{id=Id,username=User,passwd=Pwd,domain=Domain, user_server=default},Host); add_dynparams(Subst,DynData,Param =#jabber{id=OldId, username=OldUser,passwd=OldPasswd, domain={vhost,FileId}}, Host) -> {Id,User,Pwd} = choose_or_cache_user_id(OldId,OldUser,OldPasswd), {Domain,UserServer} = choose_domain(FileId), add_dynparams(Subst,DynData, Param#jabber{id=Id,username=User, passwd=Pwd, domain=Domain, user_server=UserServer},Host); add_dynparams(_Subst,[], Param, _Host) -> Param; add_dynparams(false,#dyndata{proto=_DynData}, Param, _Host) -> Param; %%ID substitution already done add_dynparams(true,#dyndata{proto=_JabDynData, dynvars=DynVars}, Param, _Host) -> ?DebugF("Subst in jabber msg (~p) with dyn vars ~p~n",[Param,DynVars]), NewParam = subst(Param, DynVars), updatejab(DynVars, NewParam). %% This isn't ideal.. but currently there is no other way %% than use side effects, as get_message/1 andn add_dynparams/4 aren't allowed %% to return a new DynData, and so they can't modify the session state. choose_domain(VHostFileId) -> case get(xmpp_vhost_domain) of undefined -> Domain = do_choose_domain(VHostFileId), UserServer = global:whereis_name(list_to_atom("us_"++Domain)), put(xmpp_vhost_domain,{Domain,UserServer}), {Domain,UserServer}; X -> X end. do_choose_domain(VHostFileId) -> {ok,D} = ts_file_server:get_random_line(VHostFileId), D. init_dynparams() -> #dyndata{proto=#jabber_dyndata{}}. %% UserID depends on which domains the user belongs. %% That can't be know at this time (no way to know if %% we are testing a single or virtual hosting server, %% no way to access the file with vh domains) %% So we delay the id selection until the first request. %% ( add_dynparams/4 gets a copy of the request, from %% the request we can check the file name, etc) choose_user_id(UserServer) -> case ts_user_server:get_idle(UserServer) of {error, no_free_userid} -> ts_mon:add({ count, error_no_free_userid }), exit(no_free_userid); Id-> Id end. choose_or_cache_user_id(user_defined,User,Pwd) -> put(xmpp_user_id,{User,Pwd}), {user_defined, User,Pwd}; choose_or_cache_user_id(_OldId,User,Pwd) -> case get(xmpp_user_id) of undefined -> Id = choose_user_id(default), put(xmpp_user_id,Id), {Id,User,Pwd}; {DefinedUser,DefinedPwd} -> {user_defined, DefinedUser,DefinedPwd} ; Id -> {Id,User,Pwd} end. %%---------------------------------------------------------------------- %% Function: subst/2 %% Purpose: Replace on the fly dynamic element %%---------------------------------------------------------------------- subst(Req=#jabber{type = Type}, DynData) when Type == 'muc:chat' ; Type == 'muc:join'; Type == 'muc:nick' ; Type == 'muc:exit' -> Req#jabber{nick = ts_search:subst(Req#jabber.nick, DynData), room = ts_search:subst(Req#jabber.room, DynData)}; subst(Req=#jabber{type = Type}, DynData) when Type == 'pubsub:create' ; Type == 'pubsub:subscribe'; Type == 'pubsub:publish'; Type == 'pubsub:delete' -> Req#jabber{node = ts_search:subst(Req#jabber.node, DynData)}; subst(Req=#jabber{id=user_defined, username=Name,passwd=Pwd}, DynData) -> NewUser=ts_search:subst(Name,DynData), NewPwd=ts_search:subst(Pwd,DynData), put(xmpp_user_id,{NewUser,NewPwd}),% we need to keep the substitution for futures requests Req#jabber{username=NewUser,passwd=NewPwd}; subst(Req=#jabber{data=Data}, DynData) -> Req#jabber{data=ts_search:subst(Data,DynData)}. %%---------------------------------------------------------------------- %% Func: updatejab/2 %% takes dyn vars and adds them to jabber record %% 'nonce' used for sip-digest auth %% 'sid' session-id used for digest auth %%---------------------------------------------------------------------- updatejab(undefined,Param) -> Param; updatejab([],Param) -> Param; updatejab([{nonce, Val}|Rest], Param)-> updatejab(Rest, Param#jabber{nonce = Val}); updatejab([{sid, Val}|Rest], Param)-> updatejab(Rest, Param#jabber{sid = Val}); updatejab([_|Rest], Param)-> updatejab(Rest, Param). tsung-1.4.2/src/tsung/ts_ldap_common.erl0000644000201100017670000001306011701017117017770 0ustar nniclausdream%%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% File : ts_ldap_common.erl %%% Author : Pablo Polvorin %%% Purpose : LDAP plugin -module(ts_ldap_common). -export([encode_filter/1, bind_msg/3, unbind_msg/1, search_msg/5, start_tls_msg/1, add_msg/3, modify_msg/3 ]). -export([push/2,get_packet/1,empty_packet_state/0]). -define(LDAP_VERSION, 3). -define(START_TLS_OID,"1.3.6.1.4.1.1466.20037"). -define(MAX_HEADER, 8). %% mm... -include("ELDAPv3.hrl"). encode_filter({'and',L}) -> {'and',lists:map(fun encode_filter/1,L)}; encode_filter({'or',L}) -> {'or',lists:map(fun encode_filter/1,L)}; encode_filter({'not',I}) -> {'not',encode_filter(I)}; encode_filter(I = {'present',_}) -> I; encode_filter({'substring',Attr,Subs}) -> eldap:substrings(Attr,Subs); encode_filter({eq,Attr,Value}) -> eldap:equalityMatch(Attr,Value); encode_filter({'let',Attr,Value}) -> eldap:lessOrEqual(Attr,Value); encode_filter({get,Attr,Value}) -> eldap:greaterOrEqual(Attr,Value); encode_filter({aproxq,Attr,Value}) -> eldap:approxMatch(Attr,Value). bind_msg(Id,User,Password) -> Req = {bindRequest,#'BindRequest'{version=?LDAP_VERSION, name=User, authentication = {simple, Password}}}, Message = #'LDAPMessage'{messageID = Id, protocolOp = Req}, {ok,Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), Bytes. search_msg(Id,Base,Scope,Filter,Attributes) -> Req = #'SearchRequest'{baseObject = Base, scope = Scope, derefAliases = neverDerefAliases, sizeLimit = 0, % no size limit timeLimit = 0, typesOnly = false, filter = Filter, attributes = Attributes}, Message = #'LDAPMessage'{messageID = Id, protocolOp = {searchRequest,Req}}, {ok,Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), Bytes. start_tls_msg(Id) -> Req = #'ExtendedRequest'{requestName = ?START_TLS_OID}, Message = #'LDAPMessage'{messageID = Id, protocolOp = {extendedReq,Req}}, {ok,Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), Bytes. unbind_msg(Id) -> Message = #'LDAPMessage'{messageID = Id, protocolOp = {unbindRequest,[]}}, {ok,Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), Bytes. add_msg(Id,DN,Attrs) -> Req = #'AddRequest'{entry = DN, attributes = [ {'AddRequest_attributes',Type, Values} || {Type,Values} <- Attrs]}, Message = #'LDAPMessage'{messageID = Id, protocolOp = {addRequest,Req}}, {ok,Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), Bytes. modify_msg(Id,DN,Modifications) -> Mods = [ #'ModifyRequest_modification_SEQOF'{ operation = Operation, modification = #'AttributeTypeAndValues'{type=Type,vals=Values}} || {Operation,Type,Values} <- Modifications], Req = #'ModifyRequest'{object = DN, modification = Mods}, Message = #'LDAPMessage'{messageID = Id, protocolOp = {modifyRequest,Req}}, {ok,Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), Bytes. %% ------------------------------------------- %% asn1 packet buffering and delimiting %% %% Temporary fix until the new ssl module incorporate appropiate %% support for asn1 packets. %% ------------------------------------------- -record(asn1_packet_state, { length = undefined, buffer = <<>> }). empty_packet_state() -> #asn1_packet_state{}. push(<<>>,S) -> S; push(Data,S =#asn1_packet_state{buffer = B}) -> S#asn1_packet_state{buffer = <>}. get_packet(S = #asn1_packet_state{buffer= <<>>}) -> {none,S}; get_packet(S = #asn1_packet_state{length=undefined,buffer=Buffer}) -> case packet_length(Buffer) of {ok,Length} -> extract_packet(S#asn1_packet_state{length=Length}); not_enough_data -> {none,S} end; get_packet(S) -> extract_packet(S). extract_packet(#asn1_packet_state{length=N,buffer=Buffer}) when (size(Buffer) >= N) -> <> = Buffer, {packet,Packet,#asn1_packet_state{length=undefined,buffer=Rest}}; extract_packet(S) when is_record(S,asn1_packet_state) -> {none,S}. packet_length(Buffer) -> try asn1rt_ber_bin:decode_tag_and_length(Buffer) of {_Tag, Len,_Rest,RemovedBytes} -> {ok,Len+RemovedBytes} catch _Type:_Error -> case size(Buffer) > ?MAX_HEADER of true -> throw({invalid_packet,Buffer}); false -> not_enough_data end end. tsung-1.4.2/src/tsung/ts_cport.erl0000644000201100017670000001412011701017117016625 0ustar nniclausdream%%% %%% Copyright 2009 Nicolas Niclausse %%% %%% Author : Nicolas Niclausse %%% Created: 17 mar 2009 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_cport). -vc('$Id: ts_cport.erl,v 0.0 2009/03/17 10:26:56 nniclaus Exp $ '). -author('nniclausse@niclux.org'). -behaviour(gen_server). -include("ts_profile.hrl"). %% API -export([start_link/1, get_port/2]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define(EPMD_PORT,4369). -record(state, { min_port = 1025, max_port = 65535 }). %%==================================================================== %% API %%==================================================================== %%-------------------------------------------------------------------- %% Function: start_link() -> {ok,Pid} | ignore | {error,Error} %% Description: Starts the server %%-------------------------------------------------------------------- start_link(Name) -> gen_server:start_link({global, Name}, ?MODULE, [], []). get_port(CPortServer,IP)-> gen_server:call({global,CPortServer},{get,IP}). %%==================================================================== %% gen_server callbacks %%==================================================================== %%-------------------------------------------------------------------- %% Function: init(Args) -> {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %% Description: Initiates the server %%-------------------------------------------------------------------- init([]) -> %% registering can be long (global:sync needed), do it after the %% init phase (the config_server will send us a message) {Min, Max} = {?config(cport_min),?config(cport_max)}, ts_utils:init_seed(), %% set random port for the initial value. case catch Min+random:uniform(Max-Min) of Val when is_integer(Val) -> ?LOGF("Ok, starting with ~p value~n",[Val],?NOTICE), {ok, #state{min_port=Min, max_port=Max}}; Err -> ?LOGF("ERR starting: ~p~n",[Err],?ERR), {ok, #state{}} end. %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | %% {stop, Reason, State} %% Description: Handling call messages %%-------------------------------------------------------------------- handle_call({get, ClientIP}, _From, State) -> %% use the process dictionnary to store the last port of each ip %% should we use ets instead ? Reply = case get(ClientIP) of ?EPMD_PORT -> ?EPMD_PORT + 1; Val when Val > State#state.max_port -> State#state.min_port; Val -> Val end, put(ClientIP,Reply+1), ?LOGF("Give port number ~p to IP ~p~n",[Reply,ClientIP],?DEB), {reply, Reply, State}. %%-------------------------------------------------------------------- %% Function: handle_cast(Msg, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% Description: Handling cast messages %%-------------------------------------------------------------------- handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() %% Description: This function is called by a gen_server when it is about to %% terminate. It should be the opposite of Module:init/1 and do any necessary %% cleaning up. When it returns, the gen_server terminates with Reason. %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- tsung-1.4.2/src/tsung/ts_dynvars.erl0000644000201100017670000001112511701017117017166 0ustar nniclausdream%%% Copyright (C) 2008 Pablo Polvorin %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% @copyright (C) 2008 Pablo Polvorin %%% @author Pablo Polvorin %%% @author Nicolas Niclausse %%% @doc functions to manipulate dynamic variables, sort of Abstract Data Type %%% @end %%% created on 2008-08-22 %%% modified by Nicolas Niclausse: Add merge and multi keys/values (new, set) -module(ts_dynvars). -export([new/0,new/2, merge/2, lookup/2,lookup/3,set/3,entries/1,map/4]). -define(IS_DYNVARS(X),is_list(X)). %% @type dynvar() = {Key::atom(), Value::string()} | []. %% @type dynvars() = [dynvar()] %% @spec new() -> [dynvar()] new() -> []. new(Key, Val) when is_atom(Key)-> [{Key,Val}]; new(VarNames, Values) when is_list(VarNames),is_list(Values)-> %% FIXME: check if VarNames is a list of atoms case {length(VarNames), length(Values)} of {A,A} -> lists:zip(VarNames,Values); {A,B} when A > B -> % more names than values, use empty values lists:zip(VarNames,Values ++ lists:duplicate(A-B,"")); {C,D} when C < D -> % more values than names, remove unused values lists:zip(VarNames,lists:sublist(Values, C)) end. %% @spec lookup(Key::atom(), Dynvar::dynvars()) -> {ok,Value::term()} | false lookup(Key, []) -> false; lookup(Key, DynVars) when ?IS_DYNVARS(DynVars), is_atom(Key)-> case lists:keysearch(Key,1,DynVars) of {value,{Key,Value}} -> {ok,Value}; false -> false end; lookup({Key, Index}, DynVars) when ?IS_DYNVARS(DynVars), is_atom(Key), is_integer(Index)-> case lists:keysearch(Key,1,DynVars) of {value,{Key,Value}} -> {ok,lists:nth(Index,Value)}; false -> false end. %% @doc same as lookup/2, only that if the key isn't present, the default %% value is returned instead of returning false. lookup(Key, DynVars, Default) when ?IS_DYNVARS(DynVars), is_atom(Key)-> case lookup(Key, DynVars) of false -> {ok,Default}; R -> R end. %% @spec set(Key::atom(), Value::term(), DynVars::dynvars()) -> dynvars() set(Key,Value,DynVars) when ?IS_DYNVARS(DynVars),is_atom(Key) -> merge([{Key, Value}],DynVars); %% optimization: only one key and one value set([Key],[Value],DynVars) -> set(Key,Value,DynVars); set([Key],Value,DynVars) -> %% for backward compatibility set(Key,Value,DynVars); %% general case: list of keys and values set(Keys,Values,DynVars) when ?IS_DYNVARS(DynVars),is_list(Keys),is_list(Values) -> merge(new(Keys,Values),DynVars). entries(DynVars) when ?IS_DYNVARS(DynVars) -> DynVars. %% @spec map(Fun::function(),Key::atom(),Default::term(),DynVars::dynvars()) %% -> dynvars() %% @doc The value associated to key Key is replaced with %% the result of applying function Fun to its previous value. %% If there is no such previous value, Fun is applied to the default %% value Default. %% map(fun(I) -> I +1 end,b,0,[{a,5}]) => [{a,5},{b,1}] %% map(fun(I) -> I +1 end,b,0,[{a,1}]) => [{a,5},{b,2}] map(Fun,Key,Default,DynVars) when ?IS_DYNVARS(DynVars),is_atom(Key),is_function(Fun,1) -> do_map(Fun,Key,Default,DynVars,[]). do_map(Fun,Key,Default,[],Acc) -> [{Key,Fun(Default)}| Acc]; do_map(Fun,Key,_Default,[{Key,Value}|Rest],Acc) -> lists:append([[{Key,Fun(Value)}], Rest, Acc]); do_map(Fun,Key,Default,[H|Rest], Acc) -> do_map(Fun,Key,Default,Rest, [H|Acc]). %% @spec merge(DynVars::dynvars(),DynVars::dynvars()) -> dynvars() %% @doc merge two set of dynamic variables merge(DynVars1, DynVars2) when ?IS_DYNVARS(DynVars1),?IS_DYNVARS(DynVars2) -> ts_utils:keyumerge(1,DynVars1,DynVars2). tsung-1.4.2/src/tsung/ts_utils.erl0000644000201100017670000007611611701017117016653 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_utils). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -include("ts_profile.hrl"). %% to get file_info record definition -include_lib("kernel/include/file.hrl"). %% user interface -export([debug/3, debug/4, get_val/1, init_seed/0, chop/1, elapsed/2, now_sec/0, node_to_hostname/1, add_time/2, keyumerge/3, key1search/2, level2int/1, mkey1search/2, close_socket/2, datestr/0, datestr/1, erl_system_args/0, erl_system_args/1, setsubdir/1, export_text/1, foreach_parallel/2, spawn_par/3, inet_setopts/3, resolve/2, stop_all/2, stop_all/3, stop_all/4, join/2, split/2, split2/2, split2/3, make_dir_rec/1, is_ip/1, from_https/1, to_https/1, keymax/2, check_sum/3, check_sum/5, clean_str/1, file_to_list/1, term_to_list/1, decode_base64/1, encode_base64/1, to_lower/1, release_is_newer_or_eq/1, randomstr/1,urandomstr/1,urandomstr_noflat/1, eval/1, list_to_number/1, time2sec/1, time2sec_hires/1, read_file_raw/1, init_seed/1, jsonpath/2, pmap/2, concat_atoms/1, ceiling/1, accept_loop/3, append_to_filename/3, splitchar/2 ]). level2int("debug") -> ?DEB; level2int("info") -> ?INFO; level2int("notice") -> ?NOTICE; level2int("warning") -> ?WARN; level2int("error") -> ?ERR; level2int("critical") -> ?CRIT; level2int("emergency") -> ?EMERG. -define(QUOT,"""). -define(APOS,"'"). -define(AMP,"&"). -define(GT,">"). -define(LT,"<"). -define(DUPSTR_SIZE,20). -define(DUPSTR,"qxvmvtglimieyhemzlxc"). %%---------------------------------------------------------------------- %% Func: get_val/1 %% Purpose: return environnement variable value for the current application %% Returns: Value | {undef_var, Var} %%---------------------------------------------------------------------- get_val(Var) -> case application:get_env(Var) of {ok, Val} -> ensure_string(Var, Val); undefined -> % undef, application not started, try to get var from stdlib case application:get_env(stdlib,Var) of undefined -> {undef_var, Var}; {ok,Val} -> ensure_string(Var, Val) end end. %% ensure atom to string conversion of environnement variable %% This is intended to fix a problem making tsung run under Windows %% I convert parameter that are called from the command-line ensure_string(log_file, Atom) when is_atom(Atom) -> atom_to_list(Atom); ensure_string(proxy_log_file, Atom) when is_atom(Atom) -> atom_to_list(Atom); ensure_string(config_file, Atom) when is_atom(Atom) -> atom_to_list(Atom); ensure_string(_, Other) -> Other. %%---------------------------------------------------------------------- %% Func: debug/3 %% Purpose: print debug message if level is high enough %%---------------------------------------------------------------------- debug(From, Message, Level) -> debug(From, Message, [], Level). debug(From, Message, Args, Level) -> Debug_level = ?config(debug_level), if Level =< Debug_level -> error_logger:info_msg("~20s:(~p:~p) "++ Message, [From, Level, self()] ++ Args); true -> nodebug end. %%---------------------------------------------------------------------- %% Func: elapsed/2 %% Purpose: print elapsed time in milliseconds %% Returns: integer %%---------------------------------------------------------------------- elapsed({Before1, Before2, Before3}, {After1, After2, After3}) -> After = After1 * 1000000000 + After2 * 1000 + After3/1000, Before = Before1 * 1000000000 + Before2 * 1000 + Before3/1000, After - Before. %%---------------------------------------------------------------------- %% Func: chop/1 %% Purpose: remove trailing "\n" %%---------------------------------------------------------------------- chop(String) -> string:strip(String, right, 10). %%---------------------------------------------------------------------- %% Func: clean_str/1 %% Purpose: remove "\n" and space at the beginning and at that end of a string %%---------------------------------------------------------------------- clean_str(String) -> Str1 = string:strip(String, both, 10), Str2 = string:strip(Str1), Str3 = string:strip(Str2, both, 10), string:strip(Str3). %%---------------------------------------------------------------------- %% Func: init_seed/1 %%---------------------------------------------------------------------- init_seed(now)-> init_seed(); init_seed(A) when is_integer(A)-> %% in case of a distributed test, we don't want each launcher to %% have the same seed, therefore, we need to know the id of the %% node to set a reproductible but different seed for each launcher. Id=get_node_id(), ?DebugF("Seeding with ~p on node ~p~n",[Id,node()]), random:seed(1000*Id,-1000*A*Id,1000*A*A); init_seed({A,B}) when is_integer(A) and is_integer(B)-> Id=get_node_id(), ?DebugF("Seeding with ~p ~p ~p on node ~p~n",[A,B,Id,node()]), %% init_seed with 2 args is called by ts_client, with increasing %% values of A, and fixed B. If the seeds are too closed, the %% initial pseudo random values will be quite closed to each %% other. Trying to avoid this by using a multiplier big enough %% (because the algorithm use mod 30XXX , see random.erl). random:seed(1000*A*A,-1000*B*B,1000*Id*Id); init_seed({A,B,C}) -> random:seed(A,B,C). get_node_id() -> case string:tokens(atom_to_list(node()),"@") of ["tsung_control"++_,_] -> 123456; ["tsung"++I,_] -> list_to_integer(I); _ -> 654321 end. %%---------------------------------------------------------------------- %% Func: init_seed/0 %%---------------------------------------------------------------------- init_seed()-> init_seed(now()). %%---------------------------------------------------------------------- %% Func: now_sec/0 %% Purpose: returns unix like elapsed time in sec %%---------------------------------------------------------------------- now_sec() -> time2sec(now()). time2sec({MSec, Seconds, _}) -> Seconds+1000000*MSec. time2sec_hires({MSec, Seconds, MuSec}) -> Seconds+1000000*MSec+MuSec/1000000. %%---------------------------------------------------------------------- %% Func: add_time/2 %% Purpose: add given Seconds to given Time (same format as now()) %%---------------------------------------------------------------------- add_time({MSec, Seconds, MicroSec}, SecToAdd) when is_integer(SecToAdd)-> NewSec = Seconds +SecToAdd, case NewSec < 1000000 of true -> {MSec, NewSec, MicroSec}; false ->{MSec+ (NewSec div 100000), NewSec-1000000, MicroSec} end. node_to_hostname(Node) -> [_Nodename, Hostname] = string:tokens( atom_to_list(Node), "@"), {ok, Hostname}. to_lower(String)-> string:to_lower(String). encode_base64(String)-> base64:encode_to_string(String). decode_base64(Base64)-> base64:decode_to_string(Base64). % return true if current version of erlang is newer or equal release_is_newer_or_eq(Release)-> erlang:system_info(version) >= Release. %%---------------------------------------------------------------------- %% Func: key1search/2 %% Purpose: wrapper around httpd_utils module funs (maybe one day %% these functions will be added to the stdlib) %%---------------------------------------------------------------------- key1search(Tuple,String)-> proplists:get_value(String,Tuple). %%---------------------------------------------------------------------- %% Func: mkey1search/2 %% Purpose: multiple key1search: %% Take as input list of {Key, Value} tuples (length 2). %% Return the list of values corresponding to a given key %% It is assumed here that there might be several identical keys in the list %% unlike the lists:key... functions. %%---------------------------------------------------------------------- mkey1search(List, Key) -> Results = lists:foldl( fun({MatchKey, Value}, Acc) when MatchKey == Key -> [Value | Acc]; ({_OtherKey, _Value}, Acc) -> Acc end, [], List), case Results of [] -> undefined; Results -> lists:reverse(Results) end. %% close socket if it exists close_socket(_Protocol, none) -> ok; close_socket(gen_tcp, Socket) -> gen_tcp:close(Socket); close_socket(gen_tcp6, Socket)-> gen_tcp:close(Socket); close_socket(ssl, Socket) -> ssl:close(Socket); close_socket(ssl6, Socket) -> ssl:close(Socket); close_socket(gen_udp, Socket) -> gen_udp:close(Socket); close_socket(gen_udp6, Socket)-> gen_udp:close(Socket). %%---------------------------------------------------------------------- %% datestr/0 %% Purpose: print date as a string 'YYYYMMDD-HHMM' %%---------------------------------------------------------------------- datestr()-> datestr(erlang:localtime()). %%---------------------------------------------------------------------- %% datestr/1 %%---------------------------------------------------------------------- datestr({{Y,M,D},{H,Min,_S}})-> io_lib:format("~w~2.10.0b~2.10.0b-~2.10.0b~2.10.0b",[Y,M,D,H,Min]). %%---------------------------------------------------------------------- %% erl_system_args/0 %%---------------------------------------------------------------------- erl_system_args()-> erl_system_args(extended). erl_system_args(basic)-> Rsh = case init:get_argument(rsh) of {ok,[[Value]]} -> " -rsh " ++ Value; _ -> " " end, lists:append([Rsh, " -detached -setcookie ", atom_to_list(erlang:get_cookie()) ]); erl_system_args(extended)-> BasicArgs = erl_system_args(basic), SetArg = fun(A) -> case init:get_argument(A) of error -> " "; {ok,[[]]} -> " -" ++atom_to_list(A)++" "; {ok,[[Val|_]]} when is_list(Val)-> " -" ++atom_to_list(A)++" "++Val++" " end end, Shared = SetArg(shared), Hybrid = SetArg(hybrid), case ?config(smp_disable) of true -> Smp = " -smp disable "; _ -> Smp = SetArg(smp) end, Inet = case init:get_argument(kernel) of {ok,[["inetrc",InetRcFile]]} -> ?LOGF("Get inetrc= ~p~n",[InetRcFile],?NOTICE), " -kernel inetrc '"++ InetRcFile ++ "'" ; _ -> " " end, Proto = case init:get_argument(proto_dist) of {ok,[["inet6_tcp"]]}-> ?LOG("IPv6 used for erlang distribution~n",?NOTICE), " -proto_dist inet6_tcp " ; _ -> " " end, ListenMin = case application:get_env(kernel,inet_dist_listen_min) of undefined -> ""; {ok, Min} -> " -kernel inet_dist_listen_min " ++ integer_to_list(Min)++ " " end, ListenMax = case application:get_env(kernel,inet_dist_listen_max) of undefined -> ""; {ok, Max} -> " -kernel inet_dist_listen_max " ++ integer_to_list(Max)++" " end, Threads= "+A "++integer_to_list(erlang:system_info(thread_pool_size))++" ", ProcessMax="+P "++integer_to_list(erlang:system_info(process_limit))++" ", Mea = case erlang:system_info(version) of "5.3" ++ _Tail -> " +Mea r10b "; _ -> " " end, lists:append([BasicArgs, Shared, Hybrid, Smp, Mea, Inet, Proto, Threads,ProcessMax,ListenMin,ListenMax]). %%---------------------------------------------------------------------- %% setsubdir/1 %% Purpose: all log files are created in a directory whose name is the %% start date of the test. %% ---------------------------------------------------------------------- setsubdir(FileName) -> Date = datestr(), Path = filename:dirname(FileName), Base = filename:basename(FileName), Dir = filename:join(Path, Date), case file:make_dir(Dir) of ok -> {ok, {Dir, Base}}; {error, eexist} -> ?DebugF("Directory ~s already exist~n",[Dir]), {ok, {Dir, Base}}; Err -> ?LOGF("Can't create directory ~s (~p)!~n",[Dir, Err],?EMERG), {error, Err} end. %%---------------------------------------------------------------------- %% export_text/1 %% Purpose: Escape special characters `<', `&', `'' and `"' flattening %% the text. %%---------------------------------------------------------------------- export_text(T) -> export_text(T, []). export_text(Bin, Cont) when is_binary(Bin) -> export_text(binary_to_list(Bin), Cont); export_text([], Exported) -> lists:flatten(lists:reverse(Exported)); export_text([$< | T], Cont) -> export_text(T, [?LT | Cont]); export_text([$> | T], Cont) -> export_text(T, [?GT | Cont]); export_text([$& | T], Cont) -> export_text(T, [?AMP | Cont]); export_text([$' | T], Cont) -> %' export_text(T, [?APOS | Cont]); export_text([$" | T], Cont) -> %" export_text(T, [?QUOT | Cont]); export_text([C | T], Cont) -> export_text(T, [C | Cont]). %%---------------------------------------------------------------------- %% stop_all/2 %%---------------------------------------------------------------------- stop_all(Host, Name) -> stop_all(Host, Name, "Tsung"). stop_all([Host],Name,MsgName) -> VoidFun = fun(_A)-> ok end, stop_all([Host],Name,MsgName, VoidFun). stop_all([Host],Name,MsgName,Fun) when is_atom(Host) -> _List= net_adm:world_list([Host]), global:sync(), case global:whereis_name(Name) of undefined -> Msg = MsgName ++" is not running on " ++ atom_to_list(Host), erlang:display(Msg); Pid -> Controller_Node = node(Pid), Fun(Controller_Node), slave:stop(Controller_Node) end; stop_all(_,_,_,_)-> erlang:display("Bad Hostname"). %%---------------------------------------------------------------------- %% make_dir_rec/1 %% Purpose: create directory. Missing parent directories ARE created %%---------------------------------------------------------------------- make_dir_rec(DirName) when is_list(DirName) -> case file:read_file_info(DirName) of {ok, #file_info{type=directory}} -> ok; {error,enoent} -> make_dir_rec("", filename:split(DirName)); {error, Reason} -> {error,Reason} end. make_dir_rec(_Path, []) -> ok; make_dir_rec(Path, [Parent|Childs]) -> CurrentDir=filename:join([Path,Parent]), case file:read_file_info(CurrentDir) of {ok, #file_info{type=directory}} -> make_dir_rec(CurrentDir, Childs); {error,enoent} -> case file:make_dir(CurrentDir) of ok -> make_dir_rec(CurrentDir, Childs); Error -> Error end; {error, Reason} -> {error,Reason} end. %% check if a string is an IPv4 address (as "192.168.0.1") is_ip(String) when is_list(String) -> EightBit="(2[0-4][0-9]|25[0-5]|1[0-9][0-9]|[0-9][0-9]|[0-9])", RegExp = lists:append(["^",EightBit,"\.",EightBit,"\.",EightBit,"\.",EightBit,"$"]), %" case re:run(String, RegExp) of {match,_} -> true; _ -> false end; is_ip(_) -> false. %%---------------------------------------------------------------------- %% to_https/1 %% Purpose: rewrite https URL, to act as a pure non ssl proxy %%---------------------------------------------------------------------- to_https({url, "http://-"++Rest})-> "https://" ++ Rest; to_https({url, URL})-> URL; to_https({request, {body,Data}}) when is_list(Data) -> %% body request, no headers re:replace(Data,"http://-","https://",[global]); to_https({request, S="CONNECT"++Rest}) -> {ok,S}; to_https({request, []}) -> {ok, []}; to_https({request, String}) when is_list(String) -> EndOfHeader = string:str(String, "\r\n\r\n"), Header = string:substr(String, 1, EndOfHeader - 1) ++ "\r\n", Body = string:substr(String, EndOfHeader + 4), ReOpts=[global,{return,list}], TmpHeader = re:replace(Header,"http://-","https://",ReOpts), TmpHeader2 = re:replace(TmpHeader,"Accept-Encoding: [0-9,a-zA-Z_ ]+\r\n","",ReOpts), RealHeader = re:replace(TmpHeader2,"Host: -","Host: ",ReOpts), RealBody = re:replace(Body,"http://-","https://",ReOpts), RealString = RealHeader++ "\r\n" ++ RealBody, {ok, RealString}. %% @spec from_https(string()) -> {ok, string() | iodata()} %% @doc replace https links with 'http://-' %% @end from_https(String) when is_list(String)-> ReOpts=[{newline,crlf},multiline,global,caseless], %% remove Secure from Set-Cookie (TSUN-120) TmpData = re:replace(String,"(.*set-cookie:.*); *secure(.*$.*$)","\\1\\2",ReOpts), Data=re:replace(TmpData,"https://","http://-",[global]), {ok, Data}. %% concatenate a list of atoms concat_atoms(Atoms) when is_list(Atoms) -> String =lists:foldl(fun(A,Acc) -> Acc++atom_to_list(A) end, "", Atoms), list_to_atom(String). %% A Perl-style join --- concatenates all strings in Strings, %% separated by Sep. join(_Sep, []) -> []; join(Sep, List) when is_list(List)-> ToStr = fun(A) when is_integer(A) -> integer_to_list(A); (A) when is_list(A) -> A; (A) when is_float(A) -> float_to_list(A); (A) when is_atom(A) -> atom_to_list(A); (A) when is_binary(A) -> binary_to_list(A) end, string:join(lists:map(ToStr,List), Sep). %% split a string given a string (at first occurence of char) split(String,Chr) -> re:split(String,Chr,[{return,list}]). %% split a string given a char (faster) splitchar(String,Chr) -> splitchar2(String,Chr,[],[]). splitchar2([],_,[],Acc) -> lists:reverse(Acc); splitchar2([],_,AccChr,Acc) -> lists:reverse([lists:reverse(AccChr)|Acc]); splitchar2([Chr|String],Chr,AccChr,Acc) -> splitchar2(String,Chr,[],[lists:reverse(AccChr)|Acc]); splitchar2([Other|String],Chr,AccChr,Acc) -> splitchar2(String,Chr,[Other|AccChr],Acc). %% split a string in 2 (at first occurence of char) split2(String,Chr) -> split2(String,Chr,nostrip). split2(String,Chr,strip) -> % split and strip blanks {A, B} = split2(String,Chr,nostrip), {string:strip(A), string:strip(B)}; split2(String,Chr,nostrip) -> case string:chr(String, Chr) of 0 -> {String,[]}; Pos -> {string:substr(String,1,Pos-1), string:substr(String,Pos+1)} end. foreach_parallel(Fun, List)-> SpawnFun = fun(A) -> spawn(?MODULE, spawn_par, lists:append([[Fun,self()], [A]])) end, lists:foreach(SpawnFun, List), wait_pids(length(List)). wait_pids(0) -> done; wait_pids(N) -> receive {ok, _Pid, _Res } -> wait_pids(N-1) after ?TIMEOUT_PARALLEL_SPAWN -> {error, {timout, N}} % N missing answer end. spawn_par(Fun, PidFrom, Args) -> Res = Fun(Args), PidFrom ! {ok, self(), Res}. %%---------------------------------------------------------------------- %% Func: inet_setopts/3 %% Purpose: set inet options depending on the protocol (gen_tcp, gen_udp, %% ssl) %%---------------------------------------------------------------------- inet_setopts(_, none, _) -> %socket was closed before none; inet_setopts(ssl6, Socket, Opts) -> inet_setopts(ssl, Socket, Opts); inet_setopts(ssl, Socket, Opts) -> case ssl:setopts(Socket, Opts) of ok -> Socket; {error, closed} -> none; Error -> ?LOGF("Error while setting ssl options ~p ~p ~n", [Opts, Error], ?ERR), none end; inet_setopts(gen_tcp6, Socket, Opts)-> inet_setopts(gen_tcp, Socket, Opts); inet_setopts(gen_udp6, Socket, Opts)-> inet_setopts(gen_udp, Socket, Opts); inet_setopts(_Type, Socket, Opts)-> case inet:setopts(Socket, Opts) of ok -> Socket; {error, closed} -> none; Error -> ?LOGF("Error while setting inet options ~p ~p ~n", [Opts, Error], ?ERR), none end. %%---------------------------------------------------------------------- %% Func: check_sum/3 %% Purpose: check sum of int equals 100. %% Args: List of tuples, index of int in tuple, Error msg %% Returns ok | {error, {bad_sum, Msg}} %%---------------------------------------------------------------------- check_sum(RecList, Index, ErrorMsg) -> %% popularity may be a float number. 5.10-2 precision check_sum(RecList, Index, 100, 0.05, ErrorMsg). check_sum(RecList, Index, Total, Epsilon, ErrorMsg) -> %% we use the tuple representation of a record ! Sum = lists:foldl(fun(X, Sum) -> element(Index,X)+Sum end, 0, RecList), Delta = abs(Sum - Total), case Delta < Epsilon of true -> ok; false -> {error, {bad_sum, Sum ,ErrorMsg}} end. %%---------------------------------------------------------------------- %% Func: file_to_list/1 %% Purpose: read a file line by line and put them in a list %% Args: filename %% Returns {ok, List} | {error, Reason} %%---------------------------------------------------------------------- file_to_list(FileName) -> case file:open(FileName, [read]) of {error, Reason} -> {error, Reason}; {ok , File} -> Lines = read_lines(File), file:close(File), {ok, Lines} end. read_lines(FD) ->read_lines(FD,io:get_line(FD,""),[]). read_lines(_FD, eof, L) -> lists:reverse(L); read_lines(FD, Line, L) -> read_lines(FD, io:get_line(FD,""),[chop(Line)|L]). %%---------------------------------------------------------------------- %% Func: keyumerge/3 %% Purpose: Same as lists:keymerge, but remove duplicates (use items from A) %% Returns: List %%---------------------------------------------------------------------- keyumerge(_N,[],B)->B; keyumerge(N,[A|Rest],B)-> Key = element(N,A), % remove old values if it exists NewB = lists:keydelete(Key, N, B), keyumerge(N,Rest, [A|NewB]). %%---------------------------------------------------------------------- %% Func: keymax/2 %% Purpose: Return Max of Nth element of a list of tuples %% Returns: Number %%---------------------------------------------------------------------- keymax(N,[L])-> element(N,L); keymax(N,[E|Tail])-> keymax(N,Tail,element(N,E)). keymax(_N,[],Max)-> Max; keymax(N,[E|Tail],Max)-> keymax(N,Tail,lists:max([Max,element(N,E)])). %%-------------------------------------------------------------------- %% Function: resolve/2 %% Description: return cached hostname or gethostbyaddr for given ip %%-------------------------------------------------------------------- resolve(Ip, Cache) -> case lists:keysearch(Ip, 1, Cache) of {value, {Ip, ReverseHostname}} -> {ReverseHostname, Cache}; false -> case inet:gethostbyaddr(Ip) of {ok, {hostent,ReverseHostname,_,inet,_,_}} -> %% cache dns result and return it ?LOGF("Add ~p -> ~p to DNS cache ~n", [Ip, ReverseHostname],?DEB), {ReverseHostname, [{Ip, ReverseHostname} | Cache]}; {error, Reason} -> ?LOGF("DNS resolution error on ~p: ~p~n", [Ip, Reason],?WARN), %% cache dns name as IP : {ip, ip} and return Ip NewCache = lists:keymerge(1, Cache, [{Ip, Ip}]), {Ip, NewCache} end end. %%---------------------------------------------------------------------- %% @spec urandomstr_noflat(Size::integer()) ->string() %% @doc generate pseudo-random list of given size. Implemented by %% duplicating list of fixed size to be faster. unflatten version %% @end %%---------------------------------------------------------------------- urandomstr_noflat(Size) when is_integer(Size) , Size >= ?DUPSTR_SIZE -> Msg= lists:duplicate(Size div ?DUPSTR_SIZE,?DUPSTR), case Size rem ?DUPSTR_SIZE of 0-> Msg; Rest -> lists:append(Msg,urandomstr_noflat(Rest)) end; urandomstr_noflat(Size) when is_integer(Size), Size >= 0 -> lists:nthtail(?DUPSTR_SIZE-Size, ?DUPSTR). %%---------------------------------------------------------------------- %% @spec urandomstr(Size::integer()) ->string() %% @doc same as urandomstr_noflat/1, but returns a flat list. %% @end %%---------------------------------------------------------------------- urandomstr(Size) when is_integer(Size), Size >= 0 -> lists:flatten(urandomstr_noflat(Size)). %%---------------------------------------------------------------------- %% @spec randomstr(Size::integer()) ->string() %% @doc returns a random string. slow if Size is high. %% @end %%---------------------------------------------------------------------- randomstr(Size) when is_integer(Size), Size >= 0 -> lists:map(fun (_) -> random:uniform(25) + $a end, lists:seq(1,Size)). %%---------------------------------------------------------------------- %% @spec eval(string()) -> term() %% @doc evaluate strings as Erlang code at runtime %% @end %%---------------------------------------------------------------------- eval(Code) -> {ok, Scanned, _} = erl_scan:string(lists:flatten(Code)), {ok, Parsed} = erl_parse:parse_exprs(Scanned), {value, Result, _} = erl_eval:exprs(Parsed, erl_eval:new_bindings()), Result. %%---------------------------------------------------------------------- %% @spec list_to_number(string()) -> integer() | float() %% @doc convert a 'number' to either int or float %% @end %%---------------------------------------------------------------------- list_to_number(Number) -> try list_to_integer(Number) of Int -> Int catch error:_Reason -> list_to_float(Number) end. term_to_list(I) when is_integer(I)-> integer_to_list(I); term_to_list(I) when is_atom(I)-> atom_to_list(I); term_to_list(I) when is_list(I)-> I; term_to_list(I) when is_float(I)-> float_to_list(I); term_to_list(B) when is_binary(B)-> binary_to_list(B). read_file_raw(File) when is_list(File) -> case {file:open(File,[read,raw,binary]), file:read_file_info(File)} of { {ok,IODev}, {ok,#file_info{size=Size} } } -> case file:pread(IODev,0,Size) of {ok, Res} -> file:close(IODev), {ok, Res, Size}; Else -> ?LOGF("pread file ~p of size ~p: ~p~n",[File,Size,Else],?NOTICE), file:close(IODev), Else end; {{ok,IODev}, {error, Reason} } -> file:close(IODev), {error,Reason}; {{error,Reason},_} -> {error, Reason} end. %%---------------------------------------------------------------------- %% @spec jsonpath(JSONPath::string(),JSON::iolist()) -> term() %% @doc very limited implementation of JSONPath from JSON struct. %% @end %%---------------------------------------------------------------------- jsonpath("$."++JSONPath,JSON) -> jsonpath(JSONPath,JSON); jsonpath(JSONPath,JSON) -> Fun= fun(A) -> case catch list_to_integer(A) of I when is_integer(I) -> I+1; _Error -> list_to_binary(A) end end, Str=re:replace(JSONPath,"\\[(.*)\\]","\.\\1",[{return,list},global]), Keys=lists:map(Fun, string:tokens(Str,".")), json_get_bin(Keys,JSON). json_get_bin([],Val) -> Val; json_get_bin([Key|Keys],undefined) -> undefined; json_get_bin([N|Keys],L) when is_integer(N), N =< length(L) -> Val = lists:nth(N,L), json_get_bin(Keys,Val); json_get_bin([N|Keys], L) when N =:= <<"*">>, is_list(L) -> lists:map(fun(A) -> json_get_bin(Keys,A) end, L); json_get_bin([N|Keys],Val) when N =:= <<"*">> -> json_get_bin(Keys,Val); json_get_bin([<<"?",Expr/binary>> | Keys],L) when is_list(L) -> case string:tokens(binary_to_list(Expr),"=") of [Key,Val] -> Fun = fun(S) -> case json_get_bin([list_to_binary(Key)],S) of Int when is_integer(Int) -> integer_to_list(Int) =:= Val; Other when is_binary(Other)-> binary_to_list(Other) =:= Val end end, ?LOG("ok~n",?ERR), case lists:filter(Fun,L) of [] -> undefined; [Res] -> json_get_bin(Keys,Res); Res -> lists:map(fun(A) -> json_get_bin(Keys,A) end, Res) end; _ -> undefined end; json_get_bin([Key|Keys],{struct,JSON}) when is_list(JSON) -> Val = proplists:get_value(Key,JSON), json_get_bin(Keys,Val); json_get_bin(_,_) -> undefined. %% Map function F over list L in parallel. pmap(F, L) -> Parent = self(), [receive {Pid, Result} -> Result end || Pid <- [spawn(fun() -> Parent ! {self(), F(X)} end) || X <- L]]. %% ceiling(X) -> T = erlang:trunc(X), case (X - T) of Neg when Neg < 0 -> T; Pos when Pos > 0 -> T + 1; _ -> T end. %%-------------------------------------------------------------------- %% Func: accept_loop/3 %% Purpose: infinite listen/accept loop, delegating handling of accepts %% to the gen_server proper. %% Returns: only returns by throwing an exception %%-------------------------------------------------------------------- accept_loop(PPid, Tag, ServerSock)-> case case gen_tcp:accept(ServerSock) of {ok, ClientSock} -> ok = gen_tcp:controlling_process(ClientSock, PPid), gen_server:call(PPid, {accepted, Tag, ClientSock}); Error -> gen_server:call(PPid, {accept_error, Tag, Error}) end of continue -> accept_loop(PPid, Tag, ServerSock); _-> normal end. append_to_filename(Filename, From, To) -> case re:replace(Filename,From,To, [{return,list},global] ) of Filename -> Filename ++"." ++ To; RealName -> RealName end. tsung-1.4.2/src/tsung/ts_jabber_common.erl0000644000201100017670000007205711701017117020310 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_jabber_common). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -export([ get_message/1 ]). -include("ts_profile.hrl"). -include("ts_jabber.hrl"). %%---------------------------------------------------------------------- %% Func: get_message/1 %% Args: #jabber record %% Returns: binary %% Purpose: Build a message/request from a #jabber record %%---------------------------------------------------------------------- get_message(Jabber=#jabber{regexp=RegExp}) when RegExp /= undefined-> put(regexp, RegExp), get_message(Jabber#jabber{regexp=undefined}); get_message(_Jabber=#jabber{type = 'wait'}) -> << >>; get_message(Jabber=#jabber{id=user_defined, username=User,passwd=Pwd,type = 'connect'}) -> ts_user_server:add_to_connected({User,Pwd}), connect(Jabber); get_message(Jabber=#jabber{type = 'connect'}) -> connect(Jabber); get_message(#jabber{type = 'close', id=Id,username=User,passwd=Pwd,user_server=UserServer}) -> ts_user_server:remove_connected(UserServer,set_id(Id,User,Pwd)), close(); get_message(#jabber{type = 'presence'}) -> presence(); get_message(#jabber{type = 'presence:initial', id=Id,username=User,passwd=Pwd,user_server=UserServer}) -> ts_user_server:add_to_online(UserServer,set_id(Id,User,Pwd)), presence(); get_message(#jabber{type = 'presence:final', id=Id,username=User,passwd=Pwd,user_server=UserServer}) -> ts_user_server:remove_from_online(UserServer,set_id(Id,User,Pwd)), presence(unavailable); get_message(#jabber{type = 'presence:broadcast', show=Show, status=Status}) -> presence(broadcast, Show, Status); get_message(Jabber=#jabber{type = 'presence:directed', id=Id,username=User,passwd=Pwd, show=Show, status=Status,user_server=UserServer}) -> case ts_user_server:get_online(UserServer,set_id(Id,User,Pwd)) of {ok, Dest} -> presence(directed, Dest, Jabber, Show, Status); {error, no_online} -> ts_mon:add({ count, error_no_online }), << >> end; get_message(Jabber=#jabber{dest=previous}) -> Dest = get(previous), get_message(Jabber#jabber{dest=Dest}); get_message(Jabber=#jabber{type = 'presence:roster'}) -> presence(roster, Jabber); get_message(#jabber{type = 'presence:subscribe'}) -> %% must be called AFTER iq:roster:add case get(rosterjid) of undefined -> ?LOG("Warn: no jid set for presence subscribe, skip",?WARN), <<>>; RosterJid -> presence(subscribe, RosterJid) end; get_message(Jabber=#jabber{type = 'chat', id=Id, dest=online,username=User,passwd=Pwd, domain=Domain,user_server=UserServer})-> case ts_user_server:get_online(UserServer,set_id(Id,User,Pwd)) of {ok, Dest} -> message(Dest, Jabber, Domain); {error, no_online} -> ts_mon:add({ count, error_no_online }), << >> end; get_message(Jabber=#jabber{type = 'chat', domain = Domain, dest=offline,user_server=UserServer}) -> case ts_user_server:get_offline(UserServer) of {ok, Dest} -> message(Dest, Jabber, Domain); {error, no_offline} -> ts_mon:add({ count, error_no_offline }), << >> end; get_message(Jabber=#jabber{type = 'chat', dest=random, domain=Domain,user_server=UserServer}) -> Dest = ts_user_server:get_id(UserServer), message(Dest, Jabber, Domain); get_message(Jabber=#jabber{type = 'chat', dest=unique, domain=Domain,user_server=UserServer})-> {Dest, _} = ts_user_server:get_first(UserServer), message(Dest, Jabber, Domain); get_message(_Jabber=#jabber{type = 'chat', id=_Id, dest = undefined, domain=_Domain}) -> %% this can happen if previous is set but undefined, skip ts_mon:add({ count, error_no_previous }), << >>; get_message(Jabber=#jabber{type = 'chat', id=_Id, dest = Dest, domain=Domain}) -> ?DebugF("~w -> ~w ~n", [_Id, Dest]), message(Dest, Jabber, Domain); get_message(#jabber{type = 'iq:roster:add', id=Id, dest = online, username=User,passwd=Pwd, domain=Domain, group=Group,user_server=UserServer}) -> case ts_user_server:get_online(UserServer,set_id(Id,User,Pwd)) of {ok, Dest} -> request(roster_add, User, Domain, Dest, Group); {error, no_online} -> ts_mon:add({ count, error_no_online }), << >> end; get_message(#jabber{type = 'iq:roster:add',dest = offline, username=User, domain=Domain, group=Group, user_server=UserServer})-> case ts_user_server:get_offline(UserServer) of {ok, Dest} -> request(roster_add, User, Domain, Dest, Group); {error, no_offline} -> ts_mon:add({ count, error_no_offline }), << >> end; get_message(#jabber{type = 'iq:roster:rename', group=Group})-> %% must be called AFTER iq:roster:add case get(rosterjid) of undefined -> ?LOG("Warn: no jid set for iq:roster:rename msg, skip",?WARN), <<>>; RosterJid -> request(roster_rename, RosterJid, Group) end; get_message(#jabber{type = 'iq:roster:remove'})-> %% must be called AFTER iq:roster:add case get(rosterjid) of undefined -> ?LOG("Warn: no jid set for iq:roster:remove msg, skip",?WARN), <<>>; RosterJid -> request(roster_remove, RosterJid) end; get_message(#jabber{type = 'iq:roster:get', id = Id,username=User,domain=Domain}) -> request(roster_get, User, Domain, Id); get_message(Jabber=#jabber{type = 'raw'}) -> raw(Jabber); %% -- Pubsub benchmark support -- %% For node creation, data contains the pubsub nodename (relative to user %% hierarchy or absolute, optional) get_message(#jabber{type = 'pubsub:create', id=Id, username=User, node=Node, node_type=NodeType, pubsub_service = PubSubComponent, domain = Domain}) -> Username = username(User,Id), create_pubsub_node(Domain, PubSubComponent, Username, Node, NodeType); %% For node subscription, data contain the pubsub nodename (relative to user %% hierarchy or absolute) get_message(#jabber{type = 'pubsub:subscribe', id=Id, username=User, user_server=UserServer, passwd=Pwd, dest=online, node=Node, pubsub_service = PubSubComponent, domain = Domain}) -> case ts_user_server:get_online(UserServer,set_id(Id,User,Pwd)) of {ok, Dest} -> UserFrom = username(User,Id), UserTo = username(User, id_to_string(Dest)), subscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node); {error, no_online} -> ts_mon:add({ count, error_no_online }), << >> end; get_message(#jabber{type = 'pubsub:subscribe', id=Id, username=User, user_server=UserServer, dest=offline, node=Node, domain = Domain, pubsub_service = PubSubComponent}) -> case ts_user_server:get_offline(UserServer) of {ok, Dest} -> UserFrom = username(User,Id), UserTo = username(User,id_to_string(Dest)), subscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node); {error, no_offline} -> ts_mon:add({ count, error_no_offline }), << >> end; get_message(#jabber{type = 'pubsub:subscribe', id=Id, username=User, user_server=UserServer, dest=random, node=Node, domain = Domain, pubsub_service = PubSubComponent}) -> Dest = ts_user_server:get_id(UserServer), UserFrom = username(User,Id), UserTo = username(User,id_to_string(Dest)), subscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node); get_message(#jabber{type = 'pubsub:subscribe', id=Id, username=User, dest=UserTo, node=Node, domain = Domain, pubsub_service = PubSubComponent}) -> UserFrom = username(User,Id), subscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node); %% For node publication, data contain the pubsub nodename (relative to user %% hierarchy or absolute) get_message(#jabber{type = 'pubsub:publish', size=Size, id=Id, username=User, node=Node, pubsub_service = PubSubComponent, domain = Domain}) -> Username = username(User,Id), publish_pubsub_node(Domain, PubSubComponent, Username, Node, Size); %% MUC benchmark support get_message(#jabber{type = 'muc:join', room = Room, nick = Nick, muc_service = Service }) -> muc_join(Room,Nick, Service); get_message(#jabber{type = 'muc:chat', room = Room, muc_service = Service, size = Size}) -> muc_chat(Room, Service, Size); get_message(#jabber{type = 'muc:nick', room = Room, muc_service = Service, nick = Nick}) -> muc_nick(Room, Nick, Service); get_message(#jabber{type = 'muc:exit', room = Room, muc_service = Service, nick = Nick}) -> muc_exit(Room, Nick, Service); get_message(Jabber=#jabber{id=user_defined}) -> get_message2(Jabber); %% Privacy lists benchmark support get_message(#jabber{type = 'privacy:get_names', username = Name, id = Id, domain = Domain}) -> privacy_get_names(username(Name, Id), Domain); get_message(#jabber{type = 'privacy:set_active', username = Name, id = Id, domain = Domain}) -> privacy_set_active(username(Name, Id), Domain); get_message(Jabber=#jabber{username=Name, passwd=Passwd, id=Id}) -> FullName = username(Name, Id), FullPasswd = password(Passwd,Id), get_message2(Jabber#jabber{username=FullName,passwd=FullPasswd}). %%---------------------------------------------------------------------- %% Func: get_message2/1 %%---------------------------------------------------------------------- get_message2(Jabber=#jabber{type = 'register'}) -> registration(Jabber); get_message2(Jabber=#jabber{type = 'auth_get'}) -> auth_get(Jabber); get_message2(Jabber=#jabber{type = 'auth_set_plain'}) -> auth_set_plain(Jabber); get_message2(Jabber=#jabber{type = 'auth_set_digest', sid=Sid}) -> auth_set_digest(Jabber,Sid); get_message2(Jabber=#jabber{type = 'auth_set_sip', domain=Realm, nonce=Nonce}) -> auth_set_sip(Jabber,Nonce,Realm); get_message2(Jabber=#jabber{type = 'auth_sasl'}) -> auth_sasl(Jabber,"PLAIN"); get_message2(Jabber=#jabber{type = 'auth_sasl_anonymous'}) -> auth_sasl(Jabber,"ANONYMOUS"); get_message2(Jabber=#jabber{type = 'auth_sasl_bind'}) -> auth_sasl_bind(Jabber); get_message2(Jabber=#jabber{type = 'auth_sasl_session'}) -> auth_sasl_session(Jabber). %%---------------------------------------------------------------------- %% Func: connect/1 %%---------------------------------------------------------------------- connect(#jabber{domain=Domain}) -> list_to_binary([ ""]). %%---------------------------------------------------------------------- %% Func: close/0 %% Purpose: close jabber session %%---------------------------------------------------------------------- close () -> list_to_binary(""). %%---------------------------------------------------------------------- %% Func: auth_get/1 %%---------------------------------------------------------------------- auth_get(#jabber{username=Name,passwd=Passwd})-> auth_get(Name, Passwd, "auth"). %%---------------------------------------------------------------------- %% Func: auth_get/3 %%---------------------------------------------------------------------- auth_get(Username, _Passwd, Type) -> list_to_binary([ "", "", "", Username, ""]). %%---------------------------------------------------------------------- %% Func: auth_set_plain/1 %%---------------------------------------------------------------------- auth_set_plain(#jabber{username=Name,passwd=Passwd})-> auth_set_plain(Name, Passwd, "auth"). %%---------------------------------------------------------------------- %% Func: auth_set_plain/3 %%---------------------------------------------------------------------- auth_set_plain(Username, Passwd, Type) -> list_to_binary([ "", "", "", Username, "", "tsung", "", Passwd, ""]). %%---------------------------------------------------------------------- %% Func: auth_set_digest/2 %%---------------------------------------------------------------------- auth_set_digest(#jabber{username=Name,passwd=Passwd}, Sid)-> auth_set_digest(Name, Passwd, "auth", Sid). %%---------------------------------------------------------------------- %% Func: auth_set_digest/4 %%---------------------------------------------------------------------- auth_set_digest(Username, Passwd, Type, Sid) -> {Digest} = ts_digest:digest(Sid, Passwd), list_to_binary([ "", "", "", Username, "", "tsung", "", Digest, ""]). %%---------------------------------------------------------------------- %% Func: auth_set_sip/3 %%---------------------------------------------------------------------- auth_set_sip(#jabber{username=Name,passwd=Passwd,domain=Domain}, Nonce, Realm)-> auth_set_sip(Name, Passwd, Domain, "auth", Nonce, Realm). %%---------------------------------------------------------------------- %% Func: auth_set_sip/6 %%---------------------------------------------------------------------- auth_set_sip(Username, Passwd, Domain, Type, Nonce, Realm) -> Jid = Username ++ "@" ++ Realm, {SipDigest,Integrity} = ts_digest:sip_digest(Nonce, Jid, Realm, Passwd), list_to_binary([ "", "", "", Jid, "", "tsung", "", "", Domain, "", "", "", Jid, "", "", SipDigest, "", "", Nonce, "", "", Integrity, "", ""]). %%---------------------------------------------------------------------- %% Func: auth_sasl/1 %%---------------------------------------------------------------------- auth_sasl(_,"ANONYMOUS")-> list_to_binary([""]); auth_sasl(#jabber{username=Name,passwd=Passwd},Mechanism)-> auth_sasl(Name, Passwd, Mechanism). %%---------------------------------------------------------------------- %% Func: auth_sasl/2 %%---------------------------------------------------------------------- auth_sasl(Username, Passwd, Mechanism) -> S = <<0>>, N = list_to_binary(Username), P = list_to_binary(Passwd), list_to_binary(["", base64:encode(<>) ,""]). %%---------------------------------------------------------------------- %% Func: auth_sasl_bind/1 %%---------------------------------------------------------------------- auth_sasl_bind(#jabber{username=Name,passwd=Passwd,domain=Domain})-> auth_sasl_bind(Name, Passwd, Domain). %%---------------------------------------------------------------------- %% Func: auth_sasl_bind/3 %%---------------------------------------------------------------------- auth_sasl_bind(_Username, _Passwd, _Domain) -> list_to_binary(["tsung"]). %%---------------------------------------------------------------------- %% Func: auth_sasl_session/1 %%---------------------------------------------------------------------- auth_sasl_session(#jabber{username=Name,passwd=Passwd,domain=Domain})-> auth_sasl_session(Name, Passwd, Domain). %%---------------------------------------------------------------------- %% Func: auth_sasl_session/3 %%---------------------------------------------------------------------- auth_sasl_session(_Username, _Passwd, _Domain) -> list_to_binary([""]). %%---------------------------------------------------------------------- %% Func: registration/1 %% Purpose: register message %%---------------------------------------------------------------------- registration(#jabber{username=Name,passwd=Passwd})-> auth_set_plain(Name, Passwd, "register"). %%---------------------------------------------------------------------- %% Func: message/3 %% Purpose: send message to defined user at the Service (aim, ...) %%---------------------------------------------------------------------- message(Dest, #jabber{size=Size,data=undefined, username=User}, Service) when is_integer(Size) -> put(previous, Dest), Username = username(User,Dest), list_to_binary([ "",ts_utils:urandomstr_noflat(Size), ""]); message(Dest, #jabber{data=Data, username=User}, Service) when is_list(Data) -> put(previous, Dest), Username = username(User,Dest), list_to_binary([ "",Data, ""]). %%---------------------------------------------------------------------- %% Func: presence/0 %%---------------------------------------------------------------------- presence() -> list_to_binary([ ""]). %%---------------------------------------------------------------------- %% Func: presence/1 %%---------------------------------------------------------------------- presence(unavailable)-> list_to_binary([ ""]). %%---------------------------------------------------------------------- %% Func: presence/2 %%---------------------------------------------------------------------- presence(Type, Jabber=#jabber{dest=Dest}) when is_integer(Dest)-> presence(Type, Jabber#jabber{dest=integer_to_list(Dest)}) ; presence(roster, Jabber)-> presence(subscribed, Jabber); presence(subscribe, RosterJid)-> list_to_binary([ ""]); presence(Type, Jabber) when is_atom(Type)-> presence(atom_to_list(Type), Jabber); presence(Type, #jabber{dest=Dest, domain=Domain, username=UserName})-> DestName = username(UserName, Dest), list_to_binary([ ""]). %%---------------------------------------------------------------------- %% Func: presence/3 %%---------------------------------------------------------------------- presence(broadcast, Show, Status) -> list_to_binary([ "", "", Show, "", Status, ""]). %%---------------------------------------------------------------------- %% Func: presence/4 %%---------------------------------------------------------------------- presence(directed, Dest, Jabber, Show, Status) when is_integer(Dest) -> presence(directed, integer_to_list(Dest), Jabber, Show, Status); presence(directed, Dest, #jabber{username=UserName,domain=Domain}, Show, Status) -> DestName = username(UserName,Dest), list_to_binary([ "", "", Show, "", Status, ""]). %%---------------------------------------------------------------------- %% Func: request/3 %%---------------------------------------------------------------------- request(roster_rename, RosterJid,Group) -> list_to_binary([ "", Group, ""]). request(roster_remove, RosterJid) -> list_to_binary([ ""]). %%---------------------------------------------------------------------- %% Func: request/5 %%---------------------------------------------------------------------- request(roster_add, UserName, Domain, Id, Group)-> Name = username(UserName,Id), RosterJid = Name ++ "@" ++ Domain, _ = put(rosterjid,RosterJid), list_to_binary([ "","",Group,""]). %% Func: request/4 request(roster_get, _UserName, _Domain, _Id)-> list_to_binary([ ""]). %%%---------------------------------------------------------------------- %%% Func: raw/1 %%%---------------------------------------------------------------------- raw(#jabber{data=undefined}) -> << >>; raw(#jabber{data=Data}) when is_list(Data) -> list_to_binary(Data). %%%---------------------------------------------------------------------- %%% Func: create_pubsub_node/5 %%% Create a pubsub node: Generate XML packet %%% If node name is undefined (data attribute), we create a pubsub instant %%% node. %%% Nodenames are relative to the User pubsub hierarchy (ejabberd); they are %%% absolute with leading slash. %%%---------------------------------------------------------------------- create_pubsub_node(Domain, PubSubComponent,Username, Node, NodeType) -> list_to_binary(["" "" ""]). %% Generate pubsub node attribute pubsub_node_attr(undefined, _Domain, _Username) -> " "; pubsub_node_attr(user_root, Domain, Username) -> [" node='/home/", Domain, "/", Username,"'"]; pubsub_node_attr([$/|AbsNode], _Domain, _Username) -> [" node='/", AbsNode,"'"]; pubsub_node_attr(Node, Domain, Username) -> [" node='/home/", Domain, "/", Username, "/", Node,"'"]. pubsub_node_type(undefined) -> ""; pubsub_node_type(Type) when is_list(Type) -> [" type='", Type, "' "]. %%%---------------------------------------------------------------------- %%% Func: subscribe_pubsub_node/4 %%% Subscribe to a pubsub node: Generate XML packet %%% If node name is undefined (data attribute), we subscribe to target user %%% root node %%% Nodenames are relative to the User pubsub hierarchy (ejabberd); they are %%% absolute with leading slash. %%%---------------------------------------------------------------------- subscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, undefined) -> subscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, ""); subscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node) -> list_to_binary(["" "" "" ""]). %%%---------------------------------------------------------------------- %%% Func: publish_pubsub_node/4 %%% Publish an item to a pubsub node %%% Nodenames are relative to the User pubsub hierarchy (ejabberd); they are %%% absolute with leading slash. %%%---------------------------------------------------------------------- publish_pubsub_node(Domain, PubSubComponent, Username, Node, Size) -> Result = list_to_binary(["" "" "" "", ts_utils:urandomstr_noflat(Size),"" ""]), Result. muc_join(Room,Nick, Service) -> Result = list_to_binary(["", " "]), Result. muc_chat(Room, Service, Size) -> Result = list_to_binary(["", "", ts_utils:urandomstr_noflat(Size), "", ""]), Result. muc_nick(Room, Nick, Service) -> Result = list_to_binary([""]), Result. muc_exit(Room,Nick, Service) -> Result = list_to_binary([""]), Result. %%%---------------------------------------------------------------------- %%% Func: privacy_get_names/2 %%% Get names of all privacy lists server stores for the user %%%---------------------------------------------------------------------- privacy_get_names(User, Domain) -> Jid = [User,"@",Domain,"/tsung"], Req = ["", "", ""], list_to_binary(Req). %%%---------------------------------------------------------------------- %%% Func: privacy_set_active/2 %%% Set the list named according to pattern "@_list" %%% as active %%%---------------------------------------------------------------------- privacy_set_active(User, Domain) -> Jid = [User,"@",Domain,"/tsung"], List = [User,"@",Domain,"_list"], Req = ["", "", "", "", ""], list_to_binary(Req). %%%---------------------------------------------------------------------- %%% Func: username/2 %%% Generate the username given a prefix and id %%%---------------------------------------------------------------------- username(Prefix, DestId) when is_integer(DestId)-> Prefix ++ integer_to_list(DestId); username(_Prefix, DestUser) -> DestUser. %%% Convert Id to string %%% Change this if you want to have padding id_to_string(Id) when is_integer(Id)-> integer_to_list(Id); id_to_string(Id) -> Id. %%%---------------------------------------------------------------------- %%% Func: password/1 %%% Generate password for a given username %%%---------------------------------------------------------------------- password(Prefix,Id) when is_integer(Id)-> Prefix ++ integer_to_list(Id); password(Prefix,Id) -> Prefix ++ Id. %% set the real Id; by default use the Id; but it user and passwd is %% defined statically (using csv for example), Id is the tuple { User, Passwd } set_id(user_defined,User,Passwd) -> {User,Passwd}; set_id(Id,_User,_Passwd) -> Id. tsung-1.4.2/src/tsung/ts_launcher_mgr.erl0000644000201100017670000001667011701017117020160 0ustar nniclausdream%%% %%% Copyright 2009 Nicolas Niclausse %%% %%% Author : Nicolas Niclausse %%% Created: 09 dc. 2009 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_launcher_mgr). -vc('$Id: ts_launcher_mgr.erl,v 0.0 2009/12/09 11:54:33 nniclaus Exp $ '). -author('nicolas.niclausse@niclux.org'). -include("ts_profile.hrl"). -behaviour(gen_server). %% API -export([start/0, alive/1, die/1, check_registered/0]). -define(DIE_DELAY, 5000). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, {launchers=0, synced}). %%==================================================================== %% API %%==================================================================== %%-------------------------------------------------------------------- %% Function: start_link() -> {ok,Pid} | ignore | {error,Error} %% Description: Starts the server %%-------------------------------------------------------------------- start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). die(Type)-> gen_server:cast(?MODULE, {die, Type}). alive(Type)-> gen_server:cast(?MODULE, {alive, Type}). check_registered()-> gen_server:call(?MODULE, {check_registered}). %%==================================================================== %% gen_server callbacks %%==================================================================== %%-------------------------------------------------------------------- %% Function: init(Args) -> {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %% Description: Initiates the server %%-------------------------------------------------------------------- init([]) -> ?LOG("starting",?INFO), {ok, #state{}}. %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | %% {stop, Reason, State} %% Description: Handling call messages %%-------------------------------------------------------------------- handle_call({check_registered}, _From,State=#state{synced=undefined}) -> %% Check if global names are synced; Annoying "feature" of R10B7 and up case global:registered_names() of ["cport"++_Tail] -> ?LOG("Only cport server registered ! syncing ...~n", ?WARN), global:sync(); [] -> ?LOG("No registered processes ! syncing ...~n", ?WARN), global:sync(); _ -> ok end, ts_mon:launcher_is_alive(), {reply, ok, State#state{synced=yes}}; handle_call({check_registered}, _From,State=#state{synced=yes}) -> ?LOG("syncing already done, skip~n", ?INFO), {reply, ok, State#state{synced=yes}}; handle_call(_Msg, _From, State) -> Reply = ok, {reply, Reply, State}. %%-------------------------------------------------------------------- %% Function: handle_cast(Msg, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% Description: Handling cast messages %%-------------------------------------------------------------------- handle_cast({alive, Type}, State=#state{launchers=N}) -> ?LOGF("~p launcher is starting on node ~p ~n",[Type,node()],?DEB), {noreply, State#state{launchers=N+1}}; handle_cast({die, _Type}, State=#state{launchers=1}) -> ?LOGF("All launchers are done on node ~p, wait for active clients to finish~n",[node()],?INFO), ts_config_server:endlaunching(node()), check_clients(State#state{launchers=0}); handle_cast({die, Type}, State=#state{launchers=N}) -> ?LOGF("~p launcher is stopping on node ~p ~n",[Type, node()],?DEB), {noreply, State#state{launchers=N-1}}. %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info({timeout, _Ref, check_noclient}, State) -> check_clients(State); handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() %% Description: This function is called by a gen_server when it is about to %% terminate. It should be the opposite of Module:init/1 and do any necessary %% cleaning up. When it returns, the gen_server terminates with Reason. %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ts_mon:stop(), timer:sleep(?DIE_DELAY), % useful when using controller vm slave:stop(node()), %% commit suicide. FIXME: what about use_controller_vm ? ok. %%-------------------------------------------------------------------- %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- check_clients(State) -> case ts_sup:has_cport(node()) of true -> %%do not finish this beam ?LOGF("Beam will not be terminated because it has a cport server ~p ~p~n",[node(), os:getpid()], ?NOTICE), {noreply, State}; false -> case ts_client_sup:active_clients() of 0 -> % no users left, and no more launchers, stop ?LOGF("No more active users ~p ~p~n",[node(), os:getpid()], ?NOTICE), {stop, normal, State}; ActiveClients -> ?LOGF("Still ~p active client(s)~n", [ActiveClients],?NOTICE), erlang:start_timer(?check_noclient_timeout, self(), check_noclient ), {noreply, State} end end. tsung-1.4.2/src/tsung/ts_erlang.erl0000644000201100017670000000316011701017117016750 0ustar nniclausdream%%% %%% Copyright 2009 INRIA %%% %%% Author : Nicolas Niclausse %%% Created: 20 aot 2009 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_erlang). -vc('$Id: ts_erlang.erl,v 0.0 2009/08/20 16:31:58 nniclaus Exp $ '). -author('nniclaus@sophia.inria.fr'). -define(TIMEOUT,36000000). % 1 hour -include("ts_profile.hrl"). -export([client/4]). client(MasterPid,Server,Port,Opts)-> receive {Module, Fun, Args, Size} -> Res=apply(Module,Fun,Args), MasterPid ! {erlang,self(),{Module,Fun,Args,Res}}, client(MasterPid,Server,Port,Opts) after ?TIMEOUT -> MasterPid ! timeout end. tsung-1.4.2/src/tsung/ts_fs.erl0000644000201100017670000002437011701017117016116 0ustar nniclausdream%%% %%% Copyright 2009 © INRIA %%% %%% Author : Nicolas Niclausse %%% Created: 20 août 2009 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_fs). -vc('$Id: ts_erlang.erl,v 0.0 2009/08/20 16:31:58 nniclaus Exp $ '). -author('nniclaus@sophia.inria.fr'). -behavior(ts_plugin). -include("ts_profile.hrl"). -include("ts_fs.hrl"). -include_lib("kernel/include/file.hrl"). -export([init_dynparams/0, add_dynparams/4, get_message/2, session_defaults/0, dump/2, parse/2, parse_bidi/2, parse_config/2, decode_buffer/2, new_session/0]). %%==================================================================== %% Data Types %%==================================================================== %% @type dyndata() = #dyndata{proto=ProtoData::term(),dynvars=list()}. %% Dynamic data structure %% @end %% @type server() = {Host::tuple(),Port::integer(),Protocol::atom()}. %% Host/Port/Protocol tuple %% @end %% @type param() = {dyndata(), server()}. %% Dynamic data structure %% @end %% @type hostdata() = {Host::tuple(),Port::integer()}. %% Host/Port pair %% @end %% @type client_data() = binary() | closed. %% Data passed to a protocol implementation is either a binary or the %% atom closed indicating that the server closed the tcp connection. %% @end %%==================================================================== %% API %%==================================================================== parse_config(El,Config) -> ts_config_fs:parse_config(El, Config). %% @spec session_defaults() -> {ok, Persistent} | {ok, Persistent, Bidi} %% Persistent = bool() %% Bidi = bool() %% @doc Default parameters for sessions of this protocol. Persistent %% is true if connections are preserved after the underlying tcp %% connection closes. Bidi should be true for bidirectional protocols %% where the protocol module needs to reply to data sent from the %% server. @end session_defaults() -> {ok, true}. % not relevant for erlang type (?). %% @spec new_session() -> State::term() %% @doc Initialises the state for a new protocol session. %% @end new_session() -> #fs{}. %% @spec decode_buffer(Buffer::binary(),Session::record(fs)) -> NewBuffer::binary() %% @doc We need to decode buffer (remove chunks, decompress ...) for %% matching or dyn_variables %% @end decode_buffer(Buffer,#fs{}) -> Buffer. %% @spec init_dynparams() -> dyndata() %% @doc Creates a new record/term for storing dynamic request data. %% @end init_dynparams() -> #dyndata{proto=#fs_dyndata{}}. %% @spec add_dynparams(Subst, dyndata(), param(), hostdata()) -> {dyndata(), server()} | dyndata() %% Subst = term() %% @doc Updates the dynamic request data structure created by %% {@link ts_protocol:init_dynparams/0. init_dynparams/0}. %% @end add_dynparams(false, DynData, Param, HostData) -> add_dynparams(DynData#dyndata.proto, Param, HostData); add_dynparams(true, DynData, Param, HostData) -> NewParam = subst(Param, DynData#dyndata.dynvars), add_dynparams(DynData#dyndata.proto,NewParam, HostData). add_dynparams(#fs_dyndata{position=Pos,iodev=IODevice}, Req=#fs{}, _HostData) when is_integer(Pos)-> Req#fs{position=Pos,iodev=IODevice}; add_dynparams(#fs_dyndata{}, Param, _HostData) -> Param. %%---------------------------------------------------------------------- %% @spec subst(record(fs), dynvars:term()) -> record(fs) %% @doc Replace on the fly dynamic element of the request. %% @end %%---------------------------------------------------------------------- subst(Req=#fs{path=Path,size=Size}, DynVars) -> Req#fs{path=ts_search:subst(Path,DynVars),size=ts_search:subst(Size,DynVars)}. %% @spec parse(Data::client_data(), State) -> {NewState, Opts, Close} %% State = #state_rcv{} %% Opts = proplist() %% Close = bool() %% @doc %% Opts is a list of inet:setopts socket options. Don't change the %% active/passive mode here as tsung will set {active,once} before %% your options. %% Setting Close to true will cause tsung to close the connection to %% the server. %% @end parse({file, open, _Args, {ok,IODevice}},State=#state_rcv{dyndata=DynData}) -> NewDyn=(DynData#dyndata.proto)#fs_dyndata{iodev=IODevice,position=0}, {State#state_rcv{ack_done=true,datasize=0,dyndata=DynData#dyndata{proto=NewDyn}}, [], false}; parse({file, open, [Path,_], {error,Reason}},State) -> ?LOGF("error while opening file: ~p(~p)~n",[Path, Reason],?ERR), ts_mon:add({count,error_fs_open}), {State#state_rcv{ack_done=true,datasize=0}, [], false}; parse({file, close, [_IODevice], ok},State=#state_rcv{dyndata=DynData}) -> NewDyn=(DynData#dyndata.proto)#fs_dyndata{iodev=undefined,position=0}, {State#state_rcv{ack_done=true,datasize=0,dyndata=DynData#dyndata{proto=NewDyn}}, [], false}; parse({file, close, [_IODevice], {error,Reason}}, State) -> ?LOGF("error while closing file: ~p~n",[Reason],?ERR), ts_mon:add({count,error_fs_close}), {State#state_rcv{ack_done=true,datasize=0}, [], false}; parse({file, pread, [_IODev,Pos,Size], {ok,_Data}},State=#state_rcv{dyndata=DynData,datasize=DataSize}) -> NewDyn=(DynData#dyndata.proto)#fs_dyndata{position=Pos+Size}, {State#state_rcv{ack_done=true,datasize=DataSize+Size,dyndata=DynData#dyndata{proto=NewDyn}}, [], false}; parse({file, pread, [_IODev,_Pos,Size], eof},State=#state_rcv{dyndata=DynData,datasize=DataSize}) -> NewDyn=(DynData#dyndata.proto)#fs_dyndata{position=0}, {State#state_rcv{ack_done=true,datasize=DataSize+Size,dyndata=DynData#dyndata{proto=NewDyn}}, [], false}; parse({file, pread, [_IODev,_Pos,_Size], {error,Reason}},State) -> ?LOGF("error while reading file: ~p~n",[Reason],?ERR), ts_mon:add({count,error_fs_pread}), {State#state_rcv{ack_done=true,datasize=0}, [], false}; parse({file, write_file, _Args, ok},State) -> {State#state_rcv{ack_done=true,datasize=0}, [], false}; parse({file, write_file, [Path,_], {error,Reason}},State) -> ?LOGF("error while writing file: ~p (~p)~n",[Path, Reason],?ERR), ts_mon:add({count,error_fs_write}), {State#state_rcv{ack_done=true, datasize=0}, [], false}; parse({file, pwrite, [_IODev,Pos,Data], ok},State=#state_rcv{dyndata=DynData}) -> NewDyn=(DynData#dyndata.proto)#fs_dyndata{position=Pos+length(Data)}, {State#state_rcv{ack_done=true,datasize=0,dyndata=DynData#dyndata{proto=NewDyn}}, [], false}; parse({file, del_dir, [_Path], ok},State) -> {State#state_rcv{ack_done=true, datasize=0}, [], false}; parse({file, del_dir, [Path], {error,Reason}},State) -> ?LOGF("error while delete directory: ~p (~p)~n",[Path, Reason],?ERR), ts_mon:add({count,error_fs_del_dir}), {State#state_rcv{ack_done=true, datasize=0}, [], false}; parse({file, make_dir, [_Path], ok},State) -> {State#state_rcv{ack_done=true, datasize=0}, [], false}; parse({file, make_dir, [Path], {error,Reason}},State) -> ?LOGF("error while creating diretory: ~p (~p)~n",[Path, Reason],?ERR), ts_mon:add({count,error_fs_mkdir}), {State#state_rcv{ack_done=true, datasize=0}, [], false}; parse({file, delete, [_Path], ok},State) -> {State#state_rcv{ack_done=true, datasize=0}, [], false}; parse({file, delete, [Path], {error,Reason}},State) -> ?LOGF("error while deleting file: ~p (~p)~n",[Path, Reason],?ERR), {State#state_rcv{ack_done=true, datasize=0}, [], false}; parse({ts_utils, read_file_raw, [_Path], {ok,_Res,Size}},State) -> {State#state_rcv{ack_done=true,datasize=Size}, [], false}; parse({ts_utils, read_file_raw, [Path], {error,Reason}},State) -> ?LOGF("error while reading file: ~p(~p)~n",[Path,Reason],?ERR), ts_mon:add({count,error_fs_read}), {State#state_rcv{ack_done=true,datasize=0}, [], false}. %% @spec parse_bidi(Data, State) -> {nodata, NewState} | {Data, NewState} %% Data = client_data() %% NewState = term() %% State = term() %% @doc Parse a block of data from the server. No reply will be sent %% if the return value is nodata, otherwise the Data binary will be %% sent back to the server immediately. %% @end parse_bidi(Data, State) -> ts_plugin:parse_bidi(Data,State). dump(A,B) -> ts_plugin:dump(A,B). %% @spec get_message(record(),record(state_rcv)) -> {term(),record(state_rcv)} %% @doc Creates a new message to send to the connected server. %% @end get_message(R,#state_rcv{session=S}) -> {get_message2(R),S}. get_message2(#fs{command=read, path=Path}) -> {ts_utils,read_file_raw,[Path],0}; get_message2(#fs{command=read_chunk, iodev=IODevice,position=Loc, size=Size}) when is_integer(Loc)-> {file,pread,[IODevice,Loc,Size],0}; get_message2(#fs{command=write_chunk, iodev=IODevice,position=Loc, size=Size}) when is_integer(Loc)-> {file,pwrite,[IODevice,Loc,ts_utils:urandomstr(Size)],Size}; get_message2(#fs{command=open, mode=read,path=Path,position=Loc}) when is_integer(Loc)-> {file,open,[Path,[read,raw,binary]],0}; get_message2(#fs{command=open, mode=write,path=Path,position=Loc}) when is_integer(Loc)-> {file,open,[Path,[write,raw,binary]],0}; get_message2(#fs{command=close, iodev=IODevice}) -> {file,close,[IODevice],0}; get_message2(#fs{command=delete, path=Path}) -> {file,delete,[Path],0}; get_message2(#fs{command=del_dir, path=Path}) -> {file,del_dir,[Path],0}; get_message2(#fs{command=make_dir, path=Path}) -> {file,make_dir,[Path],0}; get_message2(#fs{command=write,path=Path, size=Size}) -> {file,write_file,[Path,ts_utils:urandomstr(Size),[raw]],Size}. tsung-1.4.2/src/tsung/tsung.rel.src0000644000201100017670000000021211701017117016713 0ustar nniclausdream{release, {"tsung", "&tsung_vsn&"}, {erts, "&erts_vsn&"}, [ {kernel,"&kernel_vsn&"}, {ssl,"&ssl_vsn&"}, {stdlib,"&stdlib_vsn&"}]}. tsung-1.4.2/src/tsung/ts_shell.erl0000644000201100017670000001354311701017117016615 0ustar nniclausdream%%% %%% Copyright 2009 © INRIA %%% %%% Author : Nicolas Niclausse %%% Created: 20 août 2009 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_shell). -vc('$Id: ts_erlang.erl,v 0.0 2009/08/20 16:31:58 nniclaus Exp $ '). -author('nniclaus@sophia.inria.fr'). -behaviour(ts_plugin). -include("ts_profile.hrl"). -include("ts_shell.hrl"). -include_lib("kernel/include/file.hrl"). -export([init_dynparams/0, add_dynparams/4, get_message/2, session_defaults/0, dump/2, parse/2, parse_bidi/2, parse_config/2, decode_buffer/2, new_session/0]). %%==================================================================== %% Data Types %%==================================================================== %% @type dyndata() = #dyndata{proto=ProtoData::term(),dynvars=list()}. %% Dynamic data structure %% @end %% @type server() = {Host::tuple(),Port::integer(),Protocol::atom()}. %% Host/Port/Protocol tuple %% @end %% @type param() = {dyndata(), server()}. %% Dynamic data structure %% @end %% @type hostdata() = {Host::tuple(),Port::integer()}. %% Host/Port pair %% @end %% @type client_data() = binary() | closed. %% Data passed to a protocol implementation is either a binary or the %% atom closed indicating that the server closed the tcp connection. %% @end %%==================================================================== %% API %%==================================================================== parse_config(El,Config) -> ts_config_shell:parse_config(El, Config). %% @spec session_defaults() -> {ok, Persistent} | {ok, Persistent, Bidi} %% Persistent = bool() %% Bidi = bool() %% @doc Default parameters for sessions of this protocol. Persistent %% is true if connections are preserved after the underlying tcp %% connection closes. Bidi should be true for bidirectional protocols %% where the protocol module needs to reply to data sent from the %% server. @end session_defaults() -> {ok, true}. % not relevant for erlang type (?). %% @spec new_session() -> State::term() %% @doc Initialises the state for a new protocol session. %% @end new_session() -> #shell{}. %% @spec decode_buffer(Buffer::binary(),Session::record(shell)) -> NewBuffer::binary() %% @doc We need to decode buffer (remove chunks, decompress ...) for %% matching or dyn_variables %% @end decode_buffer(Buffer,#shell{}) -> Buffer. % nothing to do for shell %% @spec init_dynparams() -> dyndata() %% @doc Creates a new record/term for storing dynamic request data. %% @end init_dynparams() -> #dyndata{proto=#shell_dyndata{}}. %% @spec add_dynparams(Subst, dyndata(), param(), hostdata()) -> {dyndata(), server()} | dyndata() %% Subst = term() %% @doc Updates the dynamic request data structure created by %% {@link ts_protocol:init_dynparams/0. init_dynparams/0}. %% @end add_dynparams(false, DynData, Param, HostData) -> add_dynparams(DynData#dyndata.proto, Param, HostData); add_dynparams(true, DynData, Param, HostData) -> NewParam = subst(Param, DynData#dyndata.dynvars), add_dynparams(DynData#dyndata.proto,NewParam, HostData). add_dynparams(#shell_dyndata{}, Param, _HostData) -> Param. %%---------------------------------------------------------------------- %% @spec subst(record(shell), term()) -> record(shell) %% @doc Replace on the fly dynamic element of the request. @end %%---------------------------------------------------------------------- subst(Req=#shell{command=Cmd,args=Args}, DynVars) -> Req#shell{command=ts_search:subst(Cmd,DynVars),args=ts_search:subst(Args,DynVars)}. dump(A,B) -> ts_plugin:dump(A,B). %% @spec parse(Data::client_data(), State) -> {NewState, Opts, Close} %% State = #state_rcv{} %% Opts = proplist() %% Close = bool() %% @doc %% Opts is a list of inet:setopts socket options. Don't change the %% active/passive mode here as tsung will set {active,once} before %% your options. %% Setting Close to true will cause tsung to close the connection to %% the server. %% @end parse({os, cmd, _Args, Res},State) when is_list(Res)-> {State#state_rcv{ack_done=true,datasize=length(Res)}, [], false}; parse({os, cmd, _Args, Res},State) -> {State#state_rcv{ack_done=true,datasize=size(term_to_binary(Res))}, [], false}. %% @spec parse_bidi(Data, State) -> {nodata, NewState} | {Data, NewState} %% Data = client_data() %% NewState = term() %% State = term() %% @doc Parse a block of data from the server. No reply will be sent %% if the return value is nodata, otherwise the Data binary will be %% sent back to the server immediately. %% @end parse_bidi(_Data, _State) -> erlang:error(dummy_implementation). %% @spec get_message(record(shell),record(state_rcv)) -> {Message::term(),record(state_rcv)} %% @doc Creates a new message to send to the connected server. %% @end get_message(#shell{command=Cmd, args=Args},#state_rcv{session=S}) -> Msg=Cmd++" "++Args , {{os, cmd, [Msg], length(Msg) } , S}. tsung-1.4.2/src/tsung/ts_client_sup.erl0000644000201100017670000000577411701017117017662 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_client_sup). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -behaviour(supervisor). -include("ts_profile.hrl"). %% External exports -export([start_link/0, start_child/1, active_clients/0]). %% supervisor callbacks -export([init/1]). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). start_child(Profile) -> supervisor:start_child(?MODULE,[Profile]). %%%---------------------------------------------------------------------- %%% Callback functions from supervisor %%%---------------------------------------------------------------------- %%-------------------------------------------------------------------- %% @spec active_clients() -> [tuple()] %% @doc returns the list of all active children on this beam's %% client supervisor. @end %%-------------------------------------------------------------------- active_clients()-> length(supervisor:which_children(?MODULE)). %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, {SupFlags, [ChildSpec]}} | %% ignore | %% {error, Reason} %%---------------------------------------------------------------------- init([]) -> ?LOG("Starting ~n", ?INFO), SupFlags = {simple_one_for_one,1, ?restart_sleep}, ChildSpec = [ {ts_client,{ts_client, start, []}, temporary,2000,worker,[ts_client]} ], % fprof:start(), % Res = fprof:trace(start, "/tmp/tsung.fprof"), % ?LOGF("starting profiler: ~p~n",[Res], ?WARN), {ok, {SupFlags, ChildSpec}}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- tsung-1.4.2/src/tsung/ts_raw.erl0000644000201100017670000001051711701017117016275 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2004 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% File : ts_jabber.erl %%% Author : Nicolas Niclausse %%% Purpose : %%% Created : 11 Jan 2004 by Nicolas Niclausse -module(ts_raw). -author('nniclausse@hyperion'). -behavior(ts_plugin). -include("ts_profile.hrl"). -include("ts_raw.hrl"). -export([init_dynparams/0, add_dynparams/4, get_message/2, session_defaults/0, subst/2, parse/2, parse_bidi/2, dump/2, parse_config/2, decode_buffer/2, new_session/0]). %%---------------------------------------------------------------------- %% Function: session_default/0 %% Purpose: default parameters for session (ack_type and persistent) %% Returns: {ok, true|false} %%---------------------------------------------------------------------- session_defaults() -> {ok,true}. %% @spec decode_buffer(Buffer::binary(),Session::record(raw)) -> NewBuffer::binary() %% @doc We need to decode buffer (remove chunks, decompress ...) for %% matching or dyn_variables %% @end decode_buffer(Buffer,#raw{}) -> Buffer. %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> #raw{}. %%---------------------------------------------------------------------- %% Function: get_message/1 %% Purpose: Build a message/request %% Args: #jabber %% Returns: binary %%---------------------------------------------------------------------- get_message(#raw{datasize=Size},S) when is_list(Size) -> get_message(#raw{datasize=list_to_integer(Size)},S); get_message(#raw{datasize=Size},#state_rcv{session=S}) when is_integer(Size), Size > 0 -> BitSize = Size*8, {<< 0:BitSize >>,S} ; get_message(#raw{data=Data},#state_rcv{session=S})-> {Data,S}. %%---------------------------------------------------------------------- %% Function: parse/3 %% Purpose: Parse the given data and return a new state %% Args: Data (binary) %% State (record) %% Returns: NewState (record) %%---------------------------------------------------------------------- %% no parsing . use only ack parse(_Data, State) -> State. parse_bidi(Data, State) -> ts_plugin:parse_bidi(Data,State). dump(A,B) -> ts_plugin:dump(A,B). %% parse_config(Element, Conf) -> ts_config_raw:parse_config(Element, Conf). %%---------------------------------------------------------------------- %% Function: add_dynparams/4 %% Purpose: add dynamic parameters to build the message %%---------------------------------------------------------------------- add_dynparams(_,[], Param, _Host) -> Param; add_dynparams(true, DynData, OldReq, _Host) -> subst(OldReq, DynData#dyndata.dynvars); add_dynparams(_Subst, _DynData, Param, _Host) -> Param. init_dynparams() -> #dyndata{}. %%---------------------------------------------------------------------- %% Function: subst/1 %%---------------------------------------------------------------------- subst(Req=#raw{datasize=Size,data=Data},DynData) -> Req#raw{datasize = ts_search:subst(Size, DynData), data= ts_search:subst(Data, DynData)}. tsung-1.4.2/src/tsung/ts_launcher.erl0000644000201100017670000004723211701017117017311 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% This module launch clients (ts_client module) given a number of %%% clients and the intensity of the arrival process (intensity = %%% inverse of the mean of inter arrival). The arrival process is a %%% Poisson Process (ie, inter-arrivals are independant and exponential) -module(ts_launcher). -created('Date: 2000/10/23 12:09:57 nniclausse '). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -include("ts_profile.hrl"). % wait up to 10ms after an error -define(NEXT_AFTER_FAILED_TIMEOUT, 10). -define(DIE_DELAY, 5000). -behaviour(gen_fsm). %% a primitive gen_fsm with two state: launcher and wait %% External exports -export([start/0, launch/1, set_static_users/1]). -export([set_warm_timeout/1]). %% gen_fsm callbacks -export([init/1, launcher/2, wait/2, wait_static/2, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- %%-------------------------------------------------------------------- %% Function: start/0 %%-------------------------------------------------------------------- start() -> ?LOG("starting ~n", ?INFO), gen_fsm:start_link({local, ?MODULE}, ?MODULE, [], []). %%-------------------------------------------------------------------- %% Function: launch/1 %%-------------------------------------------------------------------- %% Start clients with given interarrival (can be empty list) launch({Node, Arrivals, Seed}) -> ?LOGF("starting on node ~p~n",[[Node]], ?INFO), gen_fsm:send_event({?MODULE, Node}, {launch, Arrivals, Seed}); % same erlang beam case launch({Node, Host, Arrivals, Seed}) -> ?LOGF("starting on node ~p~n",[[Node]], ?INFO), gen_fsm:send_event({?MODULE, Node}, {launch, Arrivals, atom_to_list(Host), Seed}). %% Start clients with given interarrival (can be empty list) set_static_users({Node,Value}) -> ?LOGF("Substract static users number to max: ~p~n",[Value], ?DEB), gen_fsm:send_event({?MODULE, Node}, {static, Value}). %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, StateName, StateData} | %% {ok, StateName, StateData, Timeout} | %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- init([]) -> {ok, MyHostName} = ts_utils:node_to_hostname(node()), ts_launcher_mgr:alive(dynamic), {ok, wait, #launcher{myhostname=MyHostName}}. %%---------------------------------------------------------------------- %% Func: StateName/2 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- wait({launch, Args, Hostname, Seed}, State) -> wait({launch, Args, Seed}, State#launcher{myhostname = Hostname}); %% starting without configuration. We must ask the config server for %% the configuration of this launcher. wait({launch, [], Seed}, State=#launcher{static_done=Static_done}) -> ts_utils:init_seed(Seed), MyHostName = State#launcher.myhostname, ?LOGF("Launch msg receive (~p)~n",[MyHostName], ?NOTICE), ts_launcher_mgr:check_registered(), case ts_config_server:get_client_config(MyHostName) of {ok, {[{Intensity, Users, Duration}| Rest], StartDate, Max}} -> ?LOGF("Expected duration of first phase: ~p sec (~p users) ~n",[Duration/1000, Users], ?NOTICE), NewState = State#launcher{phases = Rest, nusers = Users, phase_nusers = Users, start_date = StartDate, phase_duration = Duration, intensity = Intensity, maxusers=Max }, case Static_done of true -> wait_static({static, 0}, NewState); false -> {next_state,wait_static,NewState} end; {ok,{[],_,_}} -> % no random users, only static. {stop, normal, State} end; %% start with a already known configuration. This case occurs when a %% beam is started by a launcher (maxclients reached) wait({launch, {[{Intensity, Users, Duration}| Rest], Max}, Seed}, State) -> ?LOGF("Starting with ~p users to do in the current phase (max is ~p)~n", [Users, Max],?DEB), ts_utils:init_seed(Seed), ?LOGF("Expected duration of phase: ~p sec ~n",[Duration/1000], ?NOTICE), ts_launcher_mgr:check_registered(), {next_state, launcher, State#launcher{phases = Rest, nusers = Users, phase_nusers = Users, phase_duration=Duration, phase_start = now(), intensity = Intensity, maxusers=Max}, State#launcher.short_timeout}; wait({static,0}, State) -> %% static launcher has no work to do, do not wait for him. ?LOG("Wow, static launcher is already sending me a msg, don't forget it ~n", ?INFO), {next_state, wait, State#launcher{static_done=true}}. wait_static({static, _Static}, State=#launcher{nusers=0}) -> %% no users in this phase, next one skip_empty_phase(State); wait_static({static, Static}, State=#launcher{maxusers=Max,intensity=Intensity, nusers=Users,start_date=StartDate}) when is_integer(Static) -> %% add ts_stats:exponential(Intensity) to start time to avoid %% simultaneous start of users when a lot of client beams is %% used. Also, avoid too long delay, so use a maximum delay WarmTimeout = set_warm_timeout(StartDate)+round(ts_stats:exponential(Intensity)), Warm = lists:min([WarmTimeout,?config(max_warm_delay)]), ?LOGF("Activate launcher (~p users) in ~p msec ~n",[Users, Warm], ?NOTICE), PhaseStart = ts_utils:add_time(now(), Warm div 1000), NewMax = case Max > Static of true -> Max-Static; false -> ?LOG("Warning: more static users than maximum users per beam !~n",?WARN), 1 % will fork a new beam as soon a one user is started end, ?LOGF("Set maximum users per beam to ~p~n",[NewMax],?DEB), {next_state,launcher,State#launcher{ phase_start = PhaseStart, maxusers = NewMax }, Warm}. launcher(_Event, State=#launcher{nusers = 0, phases = [] }) -> ?LOG("no more clients to start, stop ~n",?INFO), {stop, normal, State}; launcher(timeout, State=#launcher{nusers = Users, phase_nusers = PhaseUsers, phases = Phases, started_users = Started, intensity = Intensity}) -> BeforeLaunch = now(), case do_launch({Intensity,State#launcher.myhostname}) of {ok, Wait} -> case check_max_raised(State) of true -> {stop, normal, State}; false-> Duration = ts_utils:elapsed(State#launcher.phase_start, BeforeLaunch), case change_phase(Users-1, Phases, Duration, {State#launcher.phase_duration, PhaseUsers}) of {change, 0, _, PhaseLength,Rest} -> %% no users in the next phase skip_empty_phase(State#launcher{phases=Rest,phase_duration=PhaseLength}); {change, NewUsers, NewIntensity, PhaseLength,Rest} -> ts_mon:add({ count, newphase }), ?LOGF("Start a new arrival phase (~p users, ~p); expected duration=~p sec~n", [NewUsers, NewIntensity, PhaseLength/1000], ?NOTICE), {next_state,launcher,State#launcher{phases = Rest, nusers = NewUsers, phase_nusers = NewUsers, phase_duration=PhaseLength, phase_start = now(), intensity = NewIntensity}, round(Wait)}; {stop} -> {stop, normal, State}; {continue} -> Now=now(), LaunchDuration = ts_utils:elapsed(BeforeLaunch, Now), %% to keep the rate of new users as expected, %% remove the time to launch a client to the next %% wait. NewWait = case Wait > LaunchDuration of true -> round(Wait - LaunchDuration); false -> 0 end, ?DebugF("Real Wait =~p ~n", [NewWait]), {next_state,launcher,State#launcher{nusers = Users-1, started_users=Started+1} , NewWait} end end; error -> % retry with the next user, wait randomly a few msec RndWait = random:uniform(?NEXT_AFTER_FAILED_TIMEOUT), {next_state,launcher,State#launcher{nusers = Users-1} , RndWait} end. %%---------------------------------------------------------------------- %% Func: StateName/3 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {reply, Reply, NextStateName, NextStateData} | %% {reply, Reply, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} | %% {stop, Reason, Reply, NewStateData} %%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: handle_event/3 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. %%---------------------------------------------------------------------- %% Func: handle_sync_event/4 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {reply, Reply, NextStateName, NextStateData} | %% {reply, Reply, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} | %% {stop, Reason, Reply, NewStateData} %%---------------------------------------------------------------------- handle_sync_event(_Event, _From, StateName, StateData) -> Reply = ok, {reply, Reply, StateName, StateData}. %%---------------------------------------------------------------------- %% Func: handle_info/3 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_info(_Info, StateName, StateData) -> {next_state, StateName, StateData}. %%---------------------------------------------------------------------- %% Func: terminate/3 %% Purpose: Shutdown the fsm %% Returns: any %%---------------------------------------------------------------------- terminate(Reason, _StateName, _StateData) -> ?LOGF("launcher terminating for reason ~p~n",[Reason], ?INFO), ts_launcher_mgr:die(dynamic), ok. %%-------------------------------------------------------------------- %% Func: code_change/4 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState, NewStateData} %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- %%% @spec skip_empty_phase(record(launcher)) -> {next_state, launcher, record(launcher)} %%% @doc if a phase contains no users, sleep, before tryinh the next one @end skip_empty_phase(State=#launcher{phases=Phases,phase_duration=Duration})-> ?LOGF("No user, skip phase (~p ~p)~n",[Phases,Duration],?INFO), case change_phase(0, Phases, 0, {Duration, 0}) of {change, 0, _, PhaseLength, Rest} -> %% next phase is also empty, loop skip_empty_phase(State#launcher{phases=Rest,phase_duration=PhaseLength}); {change, NewUsers, NewIntensity, PhaseLength,Rest} -> {next_state,launcher,State#launcher{phases = Rest, nusers = NewUsers, phase_nusers = NewUsers, phase_duration=PhaseLength, phase_start = now(), intensity = NewIntensity}, 1} end. %%%---------------------------------------------------------------------- %%% Func: change_phase/4 %%% Purpose: decide if we need to change phase (if current users is %%% reached or if max duration is reached) %%% ---------------------------------------------------------------------- change_phase(N, [{NewIntensity, NewUsers,NewDuration}|Rest], CurrentDuration, {TotalDuration,_}) when N < 1 andalso CurrentDuration >= TotalDuration -> {change, NewUsers, NewIntensity, NewDuration, Rest}; change_phase(N, [{NewIntensity, NewUsers,NewDuration}|Rest], CurrentDuration, {TotalDuration,_}) when N < 1 -> %% no more users, check if we need to wait before changing phase (this can happen if maxnumber is set) ToWait=round(TotalDuration-CurrentDuration), ?LOGF("Need to wait ~p sec before changing phase, going to sleep~n", [ToWait/1000], ?WARN), timer:sleep(ToWait), ?LOG("Waking up~n", ?NOTICE), {change, NewUsers, NewIntensity, NewDuration, Rest}; change_phase(N, [], _, _) when N < 1 -> ?LOG("This was the last phase, wait for connected users to finish their session~n",?NOTICE), {stop}; change_phase(N,NewPhases,Current,{Total, PhaseUsers}) when Current>Total -> ?LOGF("Check phase: ~p ~p~n",[N,PhaseUsers],?DEB), Percent = 100*N/PhaseUsers, case {Percent > ?MAX_PHASE_EXCEED_PERCENT, N > ?MAX_PHASE_EXCEED_NUSERS} of {true,true} -> ?LOGF("Phase duration exceeded, more than ~p% (~.1f%) of users were not launched in time (~p users), tsung may be overloaded !~n", [?MAX_PHASE_EXCEED_PERCENT,Percent,N],?WARN); {_,_} -> ?LOGF("Phase duration exceeded, but not all users were launched (~p users, ~.1f% of phase)~n", [N, Percent],?NOTICE) end, case NewPhases of [{NewIntensity,NewUsers,NewDuration}|Rest] -> {change, NewUsers, NewIntensity, NewDuration,Rest}; [] -> ?LOG("This was the last phase, wait for connected users to finish their session~n",?NOTICE), {stop} end; change_phase(_N, _, _Current, {_Total, _}) -> {continue}. %%%---------------------------------------------------------------------- %%% Func: check_max_raised/1 %%%---------------------------------------------------------------------- check_max_raised(State=#launcher{phases=Phases,maxusers=Max,nusers=Users, started_users=Started, phase_start=Start, phase_duration=Duration, intensity=Intensity}) when Started >= Max -> PendingDuration = Duration - ts_utils:elapsed(Start, now()), ActiveClients = ts_client_sup:active_clients(), ?DebugF("Current active clients on beam: ~p (max is ~p)~n", [ActiveClients, State#launcher.maxusers]), case ActiveClients >= Max of true -> ?LOG("Max number of concurrent clients reached, must start a new beam~n", ?NOTICE), Args = case Users of 0 -> Phases; _ -> [{Intensity,Users-1,PendingDuration}|Phases] end, ts_config_server:newbeam(list_to_atom(State#launcher.myhostname), {Args, Max}), true; false -> ?DebugF("Current clients on beam: ~p~n", [ActiveClients]), false end; check_max_raised(_State) -> % number of started users less than max, no need to check ?DebugF("Current started clients on beam: ~p (max is ~p)~n", [_State#launcher.started_users, _State#launcher.maxusers]), false. %%%---------------------------------------------------------------------- %%% Func: do_launch/1 %%%---------------------------------------------------------------------- do_launch({Intensity, MyHostName})-> %%Get one client %%set the profile of the client case catch ts_config_server:get_next_session(MyHostName) of [{'EXIT', {timeout, _ }}] -> ?LOG("get_next_session failed (timeout), skip this session !~n", ?ERR), ts_mon:add({ count, error_next_session }), error; {ok, Session} -> ts_client_sup:start_child(Session), X = ts_stats:exponential(Intensity), ?DebugF("client launched, wait ~p ms before launching next client~n",[X]), {ok, X}; Error -> ?LOGF("get_next_session failed for unexpected reason [~p], abort !~n", [Error],?ERR), ts_mon:add({ count, error_next_session }), exit(shutdown) end. set_warm_timeout(StartDate)-> case ts_utils:elapsed(now(), StartDate) of WaitBeforeStart when WaitBeforeStart>0 -> round(WaitBeforeStart); _Neg -> ?LOG("Negative Warm timeout !!! Check if client "++ " machines are synchronized (ntp ?)~n"++ "Anyway, start launcher NOW! ~n", ?WARN), 1 end. tsung-1.4.2/src/tsung/ts_webdav.erl0000644000201100017670000000654311701017117016760 0ustar nniclausdream%%% %%% Copyright Nicolas Niclausse. 2008 %%% %%% Author : Nicolas Niclausse %%% Created: 12 mar 2008 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_webdav). -vc('$Id: ts_webdav.erl,v 0.0 2008/03/12 12:47:07 nniclaus Exp $ '). -author('nicolas.niclausse@niclux.org'). -behaviour(ts_plugin). -include("ts_profile.hrl"). -include("ts_http.hrl"). -export([init_dynparams/0, add_dynparams/4, get_message/2, session_defaults/0, parse/2, parse_bidi/2, dump/2, parse_config/2, decode_buffer/2, new_session/0]). session_defaults() -> {ok, true}. new_session() -> #http{}. %% @spec decode_buffer(Buffer::binary(),Session::record(http)) -> NewBuffer::binary() %% @doc We need to decode buffer (remove chunks, decompress ...) for %% matching or dyn_variables %% @end decode_buffer(Buffer,Session) -> ts_http:decode_buffer(Buffer,Session). %% we should implement methods defined in rfc4918 get_message(Req=#http_request{method=Method},#state_rcv{session=S}) when Method == propfind; Method == proppatch; Method == copy; Method == move; Method == lock; Method == mkactivity; Method == unlock; Method == report; Method == 'version-control' -> M = string:to_upper(atom_to_list(Method)), {ts_http_common:http_body(M, Req),S}; get_message(Req=#http_request{method=Method},#state_rcv{session=S}) when Method == mkcol-> {ts_http_common:http_no_body("MKCOL", Req), S}; get_message(Req,State) -> ts_http:get_message(Req,State). parse_bidi(Data, State) -> ts_http:parse_bidi(Data,State). dump(A,B) -> ts_http:dump(A,B). parse(Data, State) -> ts_http_common:parse(Data, State). parse_config(Element, Conf) -> ts_config_http:parse_config(Element, Conf). add_dynparams(Subst, DynData, Param, HostData) -> ts_http:add_dynparams(Subst, DynData, Param, HostData). init_dynparams() -> ts_http:init_dynparams(). %%% methode PROPFIND; entetes: Depth (optionel); body: XML %%% methode COPY; entete Destination: URL, If (optionel), Overwrite (Optionel), Depth; Body: XML (Optionel) %%% methode MOVE; entete Destination: URL, If (optionel), Overwrite (Optionel), Depth; Body: XML (Optionel) %%% methode PROPPATCH body: XML %%% methode MKCOL %%% methode LOCK; entete: Timeout (optionel ?), If (Optionel),Depth (Optionel); Body: XML (optionel ?) %%% methode UNLOCK; entete: Lock-Token; Body: XML (optionel ?) tsung-1.4.2/src/tsung/ts_plugin.erl0000644000201100017670000000402511701017117016777 0ustar nniclausdream%%% Copyright (C) 2011 Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% Created : 3 Mar 2011 by Nicolas Niclausse %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_plugin). -export([dump/2, parse_bidi/2]). -export([behaviour_info/1]). behaviour_info(callbacks) -> [{init_dynparams,0}, {add_dynparams, 4}, {get_message, 2}, {session_defaults, 0}, {dump, 2}, {parse, 2}, {parse_bidi, 2}, {parse_config, 2}, {decode_buffer, 2}, {new_session, 0}]; behaviour_info(_Other) -> undefined. %% @spec dump(protocol, {Request::term(),Session::term(), Id::integer(), %% Host::string(),DataSize::integer()}) -> ok %% @doc It can be used to send specific data to the current plugin back to ts_mon %% @end dump(_Type,_Data) -> ok. %% @spec parse_bidi(Data::binary(),State::record(state_rcv)) -> %% {NewData::binary()|nodata, NewState::record(state_rcv)} %% @doc Parse a block of data from the server. No reply will be sent %% if the return value is nodata, otherwise the Data binary will be %% sent back to the server immediately. %% @end parse_bidi(_Data, State) -> {nodata, State}. tsung-1.4.2/src/tsung/ts_ip_scan.erl0000644000201100017670000001661111701017117017121 0ustar nniclausdream%%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% Created : 9 Aug 2010 by Nicolas Niclausse %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%%------------------------------------------------------------------- %%% @author Nicolas Niclausse %%% @copyright (C) 2010, Nicolas Niclausse %%% @doc %%% %%% @end %%% Created : 9 Aug 2010 by Nicolas Niclausse <> %%%------------------------------------------------------------------- -module(ts_ip_scan). -behaviour(gen_server). -include("ts_profile.hrl"). -include("ts_config.hrl"). %% API -export([start_link/0, get_ip/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define(SERVER, ?MODULE). -record(state, {ips}). %%%=================================================================== %%% API %%%=================================================================== get_ip(Interface) -> gen_server:call(?MODULE, {get_ip, Interface}). %%-------------------------------------------------------------------- %% @doc %% Starts the server %% %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} %% @end %%-------------------------------------------------------------------- start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== %%-------------------------------------------------------------------- %% @private %% @doc %% Initializes the server %% %% @spec init(Args) -> {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %% @end %%-------------------------------------------------------------------- init([]) -> ?LOG("Starting ~n",?INFO), {ok, #state{}}. %%-------------------------------------------------------------------- %% @private %% @doc %% Handling call messages %% %% @spec handle_call(Request, From, State) -> %% {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- handle_call({get_ip, Interface}, _From, State=#state{ips=undefined}) -> [Val|Rest] = get_intf_aliases(Interface), {reply, Val, State#state{ips=Rest ++ [Val]}}; handle_call({get_ip, _}, _From, State=#state{ips=[Val|Rest]}) -> {reply, Val, State#state{ips=Rest ++ [Val]}}. %%-------------------------------------------------------------------- %% @private %% @doc %% Handling cast messages %% %% @spec handle_cast(Msg, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% Handling all non call/cast messages %% %% @spec handle_info(Info, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% This function is called by a gen_server when it is about to %% terminate. It should be the opposite of Module:init/1 and do any %% necessary cleaning up. When it returns, the gen_server terminates %% with Reason. The return value is ignored. %% %% @spec terminate(Reason, State) -> void() %% @end %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% @private %% @doc %% Convert process state when code is changed %% %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} %% @end %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== %% get_intf_aliases(Interface) -> case file:read_file_info("/sbin/ip") of {ok,_} -> Res=os:cmd("LC_ALL=C /sbin/ip -o -f inet addr show dev "++Interface), get_ip_aliases(string:tokens(Res,"\n"), []); {error,Reason} -> ?LOGF("ip command not found (~p), using ifconfig instead~n",[Reason],?NOTICE), Res=os:cmd("LC_ALL=C /sbin/ifconfig "), get_intf_aliases(string:tokens(Res,"\n"), Interface,[],[]) end. get_ip_aliases([], Res) -> Res; get_ip_aliases([Line|Tail], Res) -> [_,_,_,Net|_] =string:tokens(Line," "), [TmpIP|_] =string:tokens(Net,"/"), ?LOGF("found IP: ~p~n",[TmpIP],?DEB), {ok, IP } = inet:getaddr(TmpIP,inet), get_ip_aliases(Tail, [IP|Res]). get_intf_aliases([], _, _, Res) -> Res; get_intf_aliases([" inet addr:"++Line|Tail], Interface, Interface, Res) -> [TmpIP|_] =string:tokens(Line," "), ?LOGF("found IP: ~p~n",[TmpIP],?DEB), {ok, IP } = inet:getaddr(TmpIP,inet), get_intf_aliases(Tail, Interface, Interface, lists:append([IP],Res)); get_intf_aliases([" "++_Line|Tail], Interface, Current, Res) -> get_intf_aliases(Tail, Interface, Current, Res); get_intf_aliases([" "|Tail], Interface, Old, Res) -> get_intf_aliases(Tail, Interface, Old, Res); get_intf_aliases([Line|Tail], Interface, Old, Res) -> ?LOGF("scan line : ~p~n",[Line],?DEB), %% ?DebugF("scan line : ~p~n",[Line]), case string:str(Line,Interface) of 1 -> [Current|_] =string:tokens(Line," "), ?LOGF("found interface (old is ~p): ~p~n",[Old,Current],?DEB), case string:str(Current, Old++":") of 1 -> % subinterface, don't change current get_intf_aliases(Tail, Interface, Old, Res); _ -> get_intf_aliases(Tail, Interface, Current, Res) end; _ -> get_intf_aliases(Tail, Interface, "", Res) end. tsung-1.4.2/src/tsung/ts_pgsql.erl0000644000201100017670000003441011701017117016630 0ustar nniclausdream%%% %%% Copyright (C) Nicolas Niclausse 2005 %%% %%% Author : Nicolas Niclausse %%% Created: 6 Nov 2005 by Nicolas Niclausse %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% --------------------------------------------------------------------- %%% Purpose: plugin for postgresql %%% Dependancies: pgsql modules from jungerl (pgsql_proto and pgsql_util) %%% --------------------------------------------------------------------- -module(ts_pgsql). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -behavior(ts_plugin). -include("ts_profile.hrl"). -include("ts_pgsql.hrl"). -export([init_dynparams/0, add_dynparams/4, get_message/2, session_defaults/0, parse/2, parse_bidi/2, dump/2, parse_config/2, to_pairs/1, find_pair/2, decode_buffer/2, new_session/0]). %%---------------------------------------------------------------------- %% Function: session_default/0 %% Purpose: default parameters for session %% Returns: {ok, ack_type = parse|no_ack|local, persistent = true|false} %%---------------------------------------------------------------------- session_defaults() -> {ok, true}. %% @spec decode_buffer(Buffer::binary(),Session::record(pgsql)) -> NewBuffer::binary() %% @doc We need to decode buffer (remove chunks, decompress ...) for %% matching or dyn_variables %% @end decode_buffer(Buffer,#pgsql{}) -> Buffer. % nothing to do for pgsql %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> #pgsql{}. %%---------------------------------------------------------------------- %% Function: get_message/21 %% Purpose: Build a message/request , %% Args: record %% Returns: {binary,#pgsql} %%---------------------------------------------------------------------- get_message(#pgsql_request{type=connect, database=DB, username=UserName},#state_rcv{session=S}) -> Version = <>, User = pgsql_util:make_pair(user, UserName), Database = pgsql_util:make_pair(database, DB), StartupPacket = <>, PacketSize = 4 + size(StartupPacket), {<>,S#pgsql{username=UserName}}; get_message(#pgsql_request{type=sql,sql=Query},#state_rcv{session=S}) -> {pgsql_proto:encode_message(squery, Query),S}; get_message(#pgsql_request{type=close},#state_rcv{session=S}) -> {pgsql_proto:encode_message(terminate, ""),S}; get_message(#pgsql_request{type=authenticate, auth_method={?PG_AUTH_PASSWD, _Salt},passwd=PassString},#state_rcv{session=S}) -> ?LOGF("PGSQL: Must authenticate (passwd= ~p) ~n",[PassString],?DEB), {pgsql_proto:encode_message(pass_plain, PassString),S}; get_message(#pgsql_request{type=authenticate, auth_method= {?PG_AUTH_MD5, Salt},passwd=PassString},#state_rcv{session=S}) -> User=S#pgsql.username, ?LOGF("PGSQL: Must authenticate user ~p with md5 (passwd= ~p, salt=~p) ~n", [User,PassString,Salt],?DEB), {pgsql_proto:encode_message(pass_md5, {User,PassString,Salt}),S}; get_message(#pgsql_request{type=authenticate, auth_method=AuthType},#state_rcv{session=S}) -> ?LOGF("PGSQL: Authentication method not implemented ! [~p] ~n",[AuthType],?ERR), {<<>>, S}; get_message(#pgsql_request{type=execute,name_portal=Portal,max_rows=Max},#state_rcv{session=S}) -> {pgsql_proto:encode_message(execute,{Portal,Max}), S}; get_message(#pgsql_request{type=parse,name_prepared=Name,equery=Query, parameters=Params},#state_rcv{session=S}) -> {pgsql_proto:encode_message(parse,{Name,Query,Params}), S}; get_message(#pgsql_request{type=bind,formats=Formats, name_portal=Portal,name_prepared=NPrep, parameters=Params, formats_results=FormatsResults}, #state_rcv{session=S})-> {pgsql_proto:encode_message(bind,{Portal,NPrep,Params,Formats,FormatsResults}), S}; %% describe get_message(#pgsql_request{type=describe, name_portal=Name,name_prepared=undefined}, #state_rcv{session=S})-> {pgsql_proto:encode_message(describe,{portal,Name}), S}; get_message(#pgsql_request{type=describe, name_portal=undefined,name_prepared=Name}, #state_rcv{session=S})-> {pgsql_proto:encode_message(describe,{prepared_statement,Name}), S}; %% sync get_message(#pgsql_request{type=sync},#state_rcv{session=S}) -> {pgsql_proto:encode_message(sync,[]), S}; %% copyfail get_message(#pgsql_request{type=copyfail,equery=Msg},#state_rcv{session=S}) -> {pgsql_proto:encode_message(copyfail,Msg), S}; %% copydone get_message(#pgsql_request{type=copydone},#state_rcv{session=S}) -> {pgsql_proto:encode_message(copydone,<< >> ), S}; %% copy get_message(#pgsql_request{type=copy,equery=Data},#state_rcv{session=S}) -> {pgsql_proto:encode_message(copy,Data), S}; %% flush get_message(#pgsql_request{type=flush},#state_rcv{session=S}) -> {pgsql_proto:encode_message(flush,[]), S}. parse_bidi(Data, State) -> ts_plugin:parse_bidi(Data,State). dump(A,B) -> ts_plugin:dump(A,B). %%---------------------------------------------------------------------- %% Function: parse/2 %% Purpose: parse the response from the server and keep information %% about the response in State#state_rcv.session %% Args: Data (binary), State (#state_rcv) %% Returns: {NewState, Options for socket (list), Close = true|false} %%---------------------------------------------------------------------- parse(closed, State) -> {State#state_rcv{ack_done = true, datasize=0}, [], true}; %% new response, compute data size (for stats) parse(Data, State=#state_rcv{acc = [], datasize= 0}) -> parse(Data, State#state_rcv{datasize= size(Data)}); parse(Data, State=#state_rcv{acc = [], dyndata=DynData}) -> case process_head(Data) of {ok, {ready_for_query, idle}, _ } -> {State#state_rcv{ack_done = true},[],false}; {ok, {ready_for_query, transaction}, _ } -> ?Debug("PGSQL: Transaction ~n"), {State#state_rcv{ack_done = true},[],false}; {ok, {ready_for_query, failed_transaction}, _ } -> ?LOG("PGSQL: Failed Transaction ~n",?NOTICE), ts_mon:add({ count, pgsql_failed_transaction }), {State#state_rcv{ack_done = true},[],false}; {ok, {authenticate, {0, _Salt}}, Tail } -> % auth OK, continue to parse resp. parse(Tail, State); {ok, {error_message, ErrMsg}, Tail } -> ts_mon:add({ count, error_pgsql }), ?LOGF("PGSQL: Got Error Msg from postgresql [~p] ~n",[ErrMsg],?NOTICE), case Tail of << >> -> {State#state_rcv{ack_done = false},[],false}; _ -> parse(Tail, State) end; {ok, {authenticate, AuthType}, _ } -> NewDynData=DynData#dyndata{proto=#pgsql_dyndata{auth_method=AuthType}}, {State#state_rcv{ack_done = true, dyndata=NewDynData},[],false}; {ok, {copy_response, {_Format,_ColsFormat}},_ } -> ?LOG("PGSQL: Copy response ~n",?DEB), {State#state_rcv{ack_done = true},[],false}; {ok, _Pair, Tail } -> parse(Tail, State); more -> ?LOG("PGSQL: need more data from socket ~n",?DEB), {State#state_rcv{ack_done = false, acc=Data},[],false} end; %% more data, add this to accumulator and parse, update datasize parse(Data, State=#state_rcv{acc=Acc, datasize=DataSize}) -> NewSize= DataSize + size(Data), parse(<< Acc/binary,Data/binary >>, State#state_rcv{acc=[], datasize=NewSize}). %%---------------------------------------------------------------------- %% Function: parse_config/2 %% Purpose: parse tags in the XML config file related to the protocol %% Returns: List %%---------------------------------------------------------------------- parse_config(Element, Conf) -> ts_config_pgsql:parse_config(Element, Conf). %%---------------------------------------------------------------------- %% Function: add_dynparams/4 %% Purpose: add dynamic parameters to build the message %% (this is used for ex. for Cookies in HTTP) %% for postgres, use this to store the auth method and salt %% Args: Subst (true|false), DynData = #dyndata, Param = #myproto_request %% Host = String %% Returns: #pgsql_request %%---------------------------------------------------------------------- add_dynparams(false, DynData, Param, HostData) -> add_dynparams(DynData#dyndata.proto, Param, HostData); add_dynparams(true, DynData, Param, HostData) -> NewParam = subst(Param, DynData#dyndata.dynvars), add_dynparams(DynData#dyndata.proto,NewParam, HostData). add_dynparams(DynPgsql, Param, _HostData) -> ?DebugF("Dyndata=~p, param=~p~n",[DynPgsql, Param]), Param#pgsql_request{auth_method=DynPgsql#pgsql_dyndata.auth_method, salt=DynPgsql#pgsql_dyndata.salt}. %%---------------------------------------------------------------------- %% Function: init_dynparams/0 %% Purpose: initial dynamic parameters value %% Returns: #dyndata %%---------------------------------------------------------------------- init_dynparams() -> #dyndata{proto=#pgsql_dyndata{}}. %%---------------------------------------------------------------------- %% Function: subst/2 %% Purpose: Replace on the fly dynamic element of the request. %% Returns: #pgsql_request %%---------------------------------------------------------------------- subst(Req=#pgsql_request{sql=SQL}, DynData) -> Req#pgsql_request{sql=ts_search:subst(SQL, DynData)}. %%% -- Internal funs -------------------- %%---------------------------------------------------------------------- %% @spec process_head(Bin::binary()) -> {ok, Pair::list(), Rest::binary()} |more %% @doc parse postgresql binary, and return a tuple or more if the %% response is not complete %% ---------------------------------------------------------------------- process_head(<>) -> ?DebugF("PGSQL: received [~p] size=~p Pckt size= ~p ~n",[Code, Size, size(Tail)]), RealSize = Size-4, case RealSize =< size(Tail) of true -> << Packet:RealSize/binary, Data/binary >> = Tail, {ok, Pair} = pgsql_proto:decode_packet(Code, Packet), ?DebugF("PGSQL: data as string: ~p~n",[pgsql_util:to_string(Packet)]), ?LOGF("PGSQL: Pair=~p ~n",[Pair],?DEB), {ok, Pair, Data }; false -> more end; process_head(_) -> more. %%% -- funs related to dyn_variables %% @spec to_pairs(Bin::binary()) -> list() %% @doc transform postgres binary into list of pairs to_pairs(Bin) -> to_pairs(Bin,[]). %% internal fun, with accumulator to_pairs(<< >>, Acc) -> lists:reverse(Acc); to_pairs(<>, Acc) -> RealSize = Size-4, case RealSize =< size(Tail) of true -> << Packet:RealSize/binary, Data/binary >> = Tail, {ok, Pair} = pgsql_proto:decode_packet(Code, Packet), to_pairs(Data, [Pair| Acc] ); false -> %% partial bin, should not happen; anyway send the current accumulated pairs ?LOGF("real size too small, abort ?!~p (Tail was~p)~n",[Acc,Tail], ?NOTICE), lists:reverse(Acc) % end. %% @spec find_pair(Expr::string(), Pairs::list()) -> term() %% @doc Expr: expression like data_row[4][2], Pairs: list of pairs %% extracted by pgsql_proto:decode_packet. %% @end find_pair(Expr,Pairs)-> Fun= fun(A) -> case catch list_to_integer(A) of I when is_integer(I) -> I; _ -> list_to_atom(A) end end, Str=re:replace(Expr,"\\[(\\d+)\\]","\.\\1",[{return,list},global]), Keys=lists:map(Fun, string:tokens(Str,".")), find_pair_real(Keys,Pairs,1). find_pair_real([Key,Row,ColName],Pairs,CurRow) when is_atom(ColName)-> case get_col_id(atom_to_list(ColName),Pairs) of Col when is_integer(Col) -> find_pair_real([Key,Row,Col],Pairs,CurRow); _ -> undefined end; find_pair_real([Key,SameRow,Y,Z],[{Key,Value}|_],SameRow) when is_atom(Key), is_list(Value) -> case lists:nth(Y,Value) of L when is_list(L) -> lists:nth(Z,L); T when is_tuple(T) -> element(Z,T); _ -> undefined end; find_pair_real([Key,Row,Col],[{Key,Val}|_],Row) when is_atom(Key),is_list(Val),is_integer(Col)-> lists:nth(Col,Val); find_pair_real([Key,Row,Col|_],[{Key,Val}|_],Row) when is_atom(Key),is_tuple(Val)-> element(Col,Val); find_pair_real(A=[Key|_],[{Key,_Value}|Pairs],CurRow) -> %same key,different row find_pair_real(A,Pairs,CurRow+1); find_pair_real(Expr,[_|Pairs],Row) ->% not the same key find_pair_real(Expr,Pairs,Row); find_pair_real(_,_,_) -> undefined. get_col_id(ColName,Pairs) -> Desc=proplists:get_value(row_description,Pairs), case lists:keysearch(ColName,1,Desc) of {value,T} -> element(3,T); % column id is the third element of the tuple. false -> undefined end. tsung-1.4.2/src/tsung/ts_http_common.erl0000644000201100017670000006665711701017117020053 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2004 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% common functions used by http clients to: %%% - set HTTP requests %%% - parse HTTP response from server -module(ts_http_common). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -include("ts_profile.hrl"). -include("ts_http.hrl"). -include("ts_config.hrl"). -export([ http_get/1, http_post/1, http_body/2, http_no_body/2, parse/2, parse_req/1, parse_req/2 ]). %%---------------------------------------------------------------------- %% Func: http_get/1 %%---------------------------------------------------------------------- http_get(Args) -> http_no_body(?GET, Args). %%---------------------------------------------------------------------- %% Func: http_get/1 %% Args: #http_request %%---------------------------------------------------------------------- %% normal request http_no_body(Method,#http_request{url=URL, version=Version, cookie=Cookie, headers=Headers, user_agent=UA, get_ims_date=undefined, soap_action=SOAPAction, host_header=Host, userid=UserId, passwd=Passwd})-> ?DebugF("~p ~p~n",[Method,URL]), R = list_to_binary([Method, " ", URL," ", "HTTP/", Version, ?CRLF, set_header("Host",Host,Headers, ""), set_header("User-Agent",UA,Headers, ?USER_AGENT), authenticate(UserId,Passwd), soap_action(SOAPAction), set_cookie_header({Cookie, Host, URL}), headers(Headers), ?CRLF]), ?DebugF("Headers~n-------------~n~s~n",[R]), R; %% if modified since request http_no_body(Method,#http_request{url=URL, version=Version, cookie=Cookie, headers=Headers, user_agent=UA, get_ims_date=Date, soap_action=SOAPAction, host_header=Host, userid=UserId, passwd=Passwd}) -> ?DebugF("~p ~p~n",[Method, URL]), list_to_binary([Method, " ", URL," ", "HTTP/", Version, ?CRLF, ["If-Modified-Since: ", Date, ?CRLF], set_header("Host",Host,Headers, ""), set_header("User-Agent",UA,Headers, ?USER_AGENT), soap_action(SOAPAction), authenticate(UserId,Passwd), set_cookie_header({Cookie, Host, URL}), headers(Headers), ?CRLF]). %%---------------------------------------------------------------------- %% Func: http_post/1 %%---------------------------------------------------------------------- http_post(Args) -> http_body(?POST, Args). %%---------------------------------------------------------------------- %% Func: http_body/2 %% Args: #http_request %%---------------------------------------------------------------------- http_body(Method,#http_request{url=URL, version=Version, cookie=Cookie, headers=Headers, user_agent=UA, soap_action=SOAPAction, content_type=ContentType, body=Content, host_header=Host, userid=UserId, passwd=Passwd}) -> ContentLength=integer_to_list(size(Content)), ?DebugF("Content Length of POST: ~p~n.", [ContentLength]), H = [Method, " ", URL," ", "HTTP/", Version, ?CRLF, set_header("Host",Host,Headers, ""), set_header("User-Agent",UA,Headers, ?USER_AGENT), authenticate(UserId,Passwd), soap_action(SOAPAction), set_cookie_header({Cookie, Host, URL}), headers(Headers), "Content-Type: ", ContentType, ?CRLF, "Content-Length: ",ContentLength, ?CRLF, ?CRLF ], ?LOGF("Headers~n-------------~n~s~n",[H],?DEB), list_to_binary([H, Content ]). %%---------------------------------------------------------------------- %% some HTTP headers functions %%---------------------------------------------------------------------- authenticate(undefined,_)-> []; authenticate(_,undefined)-> []; authenticate(UserId,Passwd)-> AuthStr = ts_utils:encode_base64(lists:append([UserId,":",Passwd])), ["Authorization: Basic ",AuthStr,?CRLF]. %%---------------------------------------------------------------------- %% @spec set_header(Name::string, Val::string | undefined, Headers::List, %% Default::string) -> list() %% @doc If header Name is defined in Headers, print this one, otherwise, %% print the given Value (or the default one if undefined) %% @end %%---------------------------------------------------------------------- set_header(Name, Value, Headers, Default) when length(Headers) > 0 -> case lists:keysearch(Name, 1, Headers) of {value, {_,Val}} -> [Name, ": ", Val, ?CRLF]; false -> set_header(Name,Value,[], Default) end; set_header(Name, undefined, [], Default) -> [Name++": ", Default, ?CRLF]; set_header(Name, Value, [], _) -> [Name++": ", Value, ?CRLF]. soap_action(undefined) -> []; soap_action(SOAPAction) -> ["SOAPAction: \"", SOAPAction, "\"", ?CRLF]. % user defined headers headers([]) ->[]; headers(Headers) -> lists:foldl(fun({"Host", _}, Result) -> Result; ({"User-Agent", _}, Result) -> Result; ({Name, Value}, Result) -> [Name, ": ", Value, ?CRLF | Result] end, [], lists:reverse(Headers)). %%---------------------------------------------------------------------- %% Function: set_cookie_header/1 %% Args: Cookies (list), Hostname (string), URL %% Purpose: set Cookie: Header %%---------------------------------------------------------------------- set_cookie_header({[], _, _}) -> []; set_cookie_header({Cookies, Host, URL})-> MatchDomain = fun (A) -> matchdomain_url(A,Host,URL) end, CurCookies = lists:filter(MatchDomain, Cookies), set_cookie_header(CurCookies, Host, []). set_cookie_header([], _Host, []) -> []; set_cookie_header([], _Host, Acc) -> [lists:reverse(Acc), ?CRLF]; set_cookie_header([Cookie|Cookies], Host, []) -> set_cookie_header(Cookies, Host, [["Cookie: ", cookie_rec2str(Cookie)]]); set_cookie_header([Cookie|Cookies], Host, Acc) -> set_cookie_header(Cookies, Host, [["; ", cookie_rec2str(Cookie)]|Acc]). cookie_rec2str(#cookie{key=Key, value=Val}) -> lists:append([Key,"=",Val]). %%---------------------------------------------------------------------- %% Function: matchdomain_url/3 %% Purpose: return a cookie only if domain match %% Returns: true|false %%---------------------------------------------------------------------- matchdomain_url(Cookie, _Host, "http"++URL) -> % absolute URL, a proxy is used. %% FIXME: the domain stored is the domain of the proxy, we can't %% check the domain currently :( We assume it's OK %% FIXME: really check if it's a sub path; currently we only check %% that the path is somewhere in the URL which is obviously not %% the right thing to do. string:str(URL,Cookie#cookie.path) > 0; matchdomain_url(Cookie, Host, URL) -> SubDomain = string:str([$.|Host],Cookie#cookie.domain), SubPath = string:str(URL,Cookie#cookie.path), % FIXME:should use regexp:match case {SubDomain,SubPath} of {0,_} -> false; {_,1} -> true; {_,_} -> false end. %%---------------------------------------------------------------------- %% Func: parse/2 %% Args: Data, State %% Returns: {NewState, Options for socket (list), Close} %% Purpose: parse the response from the server and keep information %% about the response if State#state_rcv.session %%---------------------------------------------------------------------- parse(closed, State=#state_rcv{session=Http}) -> {State#state_rcv{session=reset_session(Http), ack_done = true}, [], true}; parse(Data, State=#state_rcv{session=HTTP}) when element(1,HTTP#http.status) == none; HTTP#http.partial == true -> List = binary_to_list(Data), TotalSize = size(Data), Header = State#state_rcv.acc ++ List, case parse_headers(HTTP, Header, State#state_rcv.host) of %% Partial header: {more, HTTPRec, Tail} -> ?LOGF("Partial Header: [HTTP=~p : Tail=~p]~n",[HTTPRec, Tail],?DEB), {State#state_rcv{ack_done=false,session=HTTPRec,acc=Tail},[],false}; %% Complete header, chunked encoding {ok, Http=#http{content_length=0, chunk_toread=0}, Tail} -> DynData = concat_cookies(Http#http.cookie, State#state_rcv.dyndata), case parse_chunked(Tail, State#state_rcv{session=Http, acc=[]}) of {NewState=#state_rcv{ack_done=false}, Opts} -> {NewState#state_rcv{dyndata=DynData}, Opts, false}; {NewState, Opts} -> {NewState#state_rcv{acc=[],dyndata=DynData}, Opts, Http#http.close} end; {ok, Http=#http{content_length=0, close=true}, _} -> %% no content length, close=true: the server will close the connection DynData = concat_cookies(Http#http.cookie, State#state_rcv.dyndata), {State#state_rcv{session= Http, ack_done = false, datasize = TotalSize, dyndata= DynData}, [], true}; {ok, Http=#http{status={100,_}}, _} -> % Status 100 Continue, ignore. %% FIXME: not tested {State#state_rcv{ack_done=false,session=reset_session(Http)},[],false}; {ok, Http, Tail} -> DynData = concat_cookies(Http#http.cookie, State#state_rcv.dyndata), check_resp_size(Http, length(Tail), DynData, State#state_rcv{acc=[]}, TotalSize, State#state_rcv.dump) end; %% continued chunked transfer parse(Data, State=#state_rcv{session=Http}) when Http#http.chunk_toread >=0 -> ?DebugF("Parse chunk data = [~s]~n", [Data]), case read_chunk_data(Data,State,Http#http.chunk_toread,Http#http.body_size) of {NewState=#state_rcv{ack_done=false}, NewOpts}-> {NewState, NewOpts, false}; {NewState, NewOpts}-> {NewState#state_rcv{acc=[]}, NewOpts, Http#http.close} end; %% continued normal transfer parse(Data, State) -> PreviousSize = State#state_rcv.datasize, DataSize = size(Data), ?DebugF("HTTP Body size=~p ~n",[DataSize]), Http = State#state_rcv.session, CLength = Http#http.content_length, case Http#http.body_size + DataSize of CLength -> % end of response {State#state_rcv{session=reset_session(Http), acc=[], ack_done = true, datasize = CLength}, [], Http#http.close}; Size -> NewHttp = (State#state_rcv.session)#http{body_size = Size}, {State#state_rcv{session = NewHttp, ack_done = false, datasize = DataSize+PreviousSize}, [], false} end. %%---------------------------------------------------------------------- %% Func: check_resp_size/5 %% Purpose: Check response size %% Returns: {NewState= record(state_rcv), SockOpts, Close} %%---------------------------------------------------------------------- check_resp_size(Http=#http{content_length=CLength, close=Close}, CLength, DynData, State, DataSize, _Dump) -> %% end of response {State#state_rcv{session= reset_session(Http), ack_done = true, datasize = DataSize, dyndata= DynData}, [], Close}; check_resp_size(Http=#http{content_length=CLength, close = Close}, BodySize, DynData, State, DataSize, Dump) when BodySize > CLength -> ?LOGF("Error: HTTP Body (~p)> Content-Length (~p) !~n", [BodySize, CLength], ?ERR), log_error(Dump, error_http_bad_content_length), {State#state_rcv{session= reset_session(Http), ack_done = true, datasize = DataSize, dyndata= DynData}, [], Close}; check_resp_size(Http, BodySize, DynData, State, DataSize,_Dump) -> %% need to read more data {State#state_rcv{session = Http#http{ body_size=BodySize}, ack_done = false, datasize = DataSize, dyndata = DynData},[],false}. %%---------------------------------------------------------------------- %% Func: parse_chunked/2 %% Purpose: parse 'Transfer-Encoding: chunked' for HTTP/1.1 %% Returns: {NewState= record(state_rcv), SockOpts, Close} %%---------------------------------------------------------------------- parse_chunked(Body, State)-> ?DebugF("Parse chunk data = [~s]~n", [Body]), read_chunk(list_to_binary(Body), State, 0, 0). %%---------------------------------------------------------------------- %% Func: read_chunk/4 %% Purpose: the real stuff for parsing chunks is here %% Returns: {NewState= record(state_rcv), SockOpts, Close} %%---------------------------------------------------------------------- read_chunk(<<>>, State, Int, Acc) -> ?LOGF("No data in chunk [Int=~p, Acc=~p] ~n", [Int,Acc],?INFO), AccInt = list_to_binary(httpd_util:integer_to_hexlist(Int)), { State#state_rcv{acc = AccInt, ack_done = false }, [] }; % read more data %% this code has been inspired by inets/http_lib.erl %% Extensions not implemented read_chunk(<>, State=#state_rcv{session=Http}, Int, Acc) -> case Char of <> when $0= read_chunk(Data, State, 16*Int+(C-$0), Acc+1); <> when $a= read_chunk(Data, State, 16*Int+10+(C-$a), Acc+1); <> when $A= read_chunk(Data, State, 16*Int+10+(C-$A), Acc+1); <> when Int>0 -> read_chunk_data(Data, State, Int+3, Acc+1); <> when Int==0, size(Data) == 3 -> %% should be the end of transfer ?DebugF("Finish tranfer chunk ~p~n", [binary_to_list(Data)]), {State#state_rcv{session= reset_session(Http), ack_done = true, datasize = Acc %% FIXME: is it the correct size? }, []}; <> when Int==0, size(Data) < 3 -> % lack ?CRLF, continue { State#state_rcv{acc = <<48, ?CR , Data/binary>>, ack_done=false }, [] }; <> when C==$ -> % Some servers (e.g., Apache 1.3.6) throw in % additional whitespace... read_chunk(Data, State, Int, Acc+1); _Other -> ?LOGF("Unexpected error while parsing chunk ~p~n", [_Other] ,?WARN), log_error(State#state_rcv.dump, error_http_unexpected_chunkdata), {State#state_rcv{session= reset_session(Http), ack_done = true}, []} end. %%---------------------------------------------------------------------- %% Func: read_chunk_data/4 %% Purpose: read 'Int' bytes of data %% Returns: {NewState= record(state_rcv), SockOpts} %%---------------------------------------------------------------------- read_chunk_data(Data, State=#state_rcv{acc=[]}, Int, Acc) when size(Data) > Int-> ?DebugF("Read ~p bytes of chunk with size = ~p~n", [Int, size(Data)]), <<_NewData:Int/binary, Rest/binary >> = Data, read_chunk(Rest, State, 0, Int + Acc); read_chunk_data(Data, State=#state_rcv{acc=[]}, Int, Acc) -> % not enough data in buffer BodySize = size(Data), ?DebugF("Partial chunk received (~p/~p)~n", [BodySize,Int]), NewHttp = (State#state_rcv.session)#http{chunk_toread = Int-BodySize, body_size = BodySize + Acc}, {State#state_rcv{session = NewHttp, ack_done = false, % continue to read data datasize = BodySize + Acc},[]}; read_chunk_data(Data, State=#state_rcv{acc=Acc}, _Int, AccSize) -> ?DebugF("Accumulated data = [~p]~n", [Acc]), NewData = <>, read_chunk(NewData, State#state_rcv{acc=[]}, 0, AccSize). %%---------------------------------------------------------------------- %% Func: add_new_cookie/3 %% Purpose: Separate cookie values from attributes %%---------------------------------------------------------------------- add_new_cookie(Cookie, Host, OldCookies) -> Fields = splitcookie(Cookie), %% FIXME: bad domain if we use a Proxy (the domain will be equal %% to the proxy domain instead of the server's domain New = parse_set_cookie(Fields, #cookie{domain=[$.|Host],path="/"}), concat_cookies([New],OldCookies). %%---------------------------------------------------------------------- %% Function: splitcookie/3 %% Purpose: split according to string ";". %% Not very elegant but 5x faster than the regexp:split version %%---------------------------------------------------------------------- splitcookie(Cookie) -> splitcookie(Cookie, [], []). splitcookie([], Cur, Acc) -> [lists:reverse(Cur)|Acc]; splitcookie(";"++Rest,Cur,Acc) -> splitcookie(string:strip(Rest, both),[],[lists:reverse(Cur)|Acc]); splitcookie([Char|Rest],Cur,Acc)->splitcookie(Rest, [Char|Cur], Acc). %%---------------------------------------------------------------------- %% Func: concat_cookie/2 %% Purpose: add new cookies to a list of old ones. If the keys already %% exists, replace with the new ones %%---------------------------------------------------------------------- concat_cookies(New, DynData=#dyndata{proto=HTTPDyn}) -> Cookies = HTTPDyn#http_dyndata.cookies, NewCookies = concat_cookies(New, Cookies), DynData#dyndata{proto=HTTPDyn#http_dyndata{cookies=NewCookies}}; concat_cookies([], DynData) -> DynData; concat_cookies(New, []) -> New; concat_cookies([New=#cookie{}|Rest], OldCookies)-> case lists:keysearch(New#cookie.key, #cookie.key, OldCookies) of {value, #cookie{domain=Dom}} when Dom == New#cookie.domain -> %same domain ?DebugF("Reset key ~p with new value ~p~n",[New#cookie.key, New#cookie.value]), NewList = lists:keyreplace(New#cookie.key, #cookie.key, OldCookies, New), concat_cookies(Rest, NewList); {value, _Val} -> % same key, but different domains concat_cookies(Rest, [New | OldCookies]); false -> concat_cookies(Rest, [New | OldCookies]) end. %%---------------------------------------------------------------------- %% Func: parse_set_cookie/2 %% cf. RFC 2965 %%---------------------------------------------------------------------- parse_set_cookie([], Cookie) -> Cookie; parse_set_cookie([Field| Rest], Cookie=#cookie{}) -> {Key,Val} = get_cookie_key(Field,[]), ?DebugF("Parse cookie key ~p with value ~p~n",[Key, Val]), parse_set_cookie(Rest, set_cookie_key(Key, Val, Cookie)). %%---------------------------------------------------------------------- set_cookie_key([L|"ersion"],Val,Cookie) when L == $V; L==$v -> Cookie#cookie{version=Val}; set_cookie_key([L|"omain"],Val,Cookie) when L == $D; L==$d -> Cookie#cookie{domain=Val}; set_cookie_key([L|"ath"],Val,Cookie) when L == $P; L==$p -> Cookie#cookie{path=Val}; set_cookie_key([L|"ax-Age"],Val,Cookie) when L == $M; L==$m -> Cookie#cookie{max_age=Val}; % NOT IMPLEMENTED set_cookie_key([L|"xpires"],Val,Cookie) when L == $E; L==$e -> Cookie#cookie{expires=Val}; % NOT IMPLEMENTED set_cookie_key([L|"ort"],Val,Cookie) when L == $P; L==$p -> Cookie#cookie{port=Val}; set_cookie_key([L|"iscard"],_Val,Cookie) when L == $D; L==$d -> Cookie#cookie{discard=true}; % NOT IMPLEMENTED set_cookie_key([L|"ecure"],_Val,Cookie) when L == $S; L==$s -> Cookie#cookie{secure=true}; % NOT IMPLEMENTED set_cookie_key([L|"ommenturl"],_Val,Cookie) when L == $C; L==$c -> Cookie; % don't care about comment set_cookie_key([L|"omment"],_Val,Cookie) when L == $C; L==$c -> Cookie; % don't care about comment set_cookie_key(Key,Val,Cookie) -> Cookie#cookie{key=Key,value=Val}. %%---------------------------------------------------------------------- get_cookie_key([],Acc) -> {lists:reverse(Acc), []}; get_cookie_key([$=|Rest],Acc) -> {lists:reverse(Acc), Rest}; get_cookie_key([Char|Rest],Acc)-> get_cookie_key(Rest, [Char|Acc]). %%-------------------------------------------------------------------- %% Func: parse_headers/3 %% Purpose: Parse HTTP headers line by line %% Returns: {ok, #http, Body} %%-------------------------------------------------------------------- parse_headers(H, Tail, Host) -> case get_line(Tail) of {line, Line, Tail2} -> parse_headers(parse_line(Line, H, Host), Tail2, Host); {lastline, Line, Tail2} -> {ok, parse_line(Line, H#http{partial=false}, Host), Tail2}; {more} -> %% Partial header {more, H#http{partial=true}, Tail} end. %%-------------------------------------------------------------------- %% Func: parse_req/1 %% Purpose: Parse HTTP request %% Returns: {ok, #http_request, Body} | {more, Http , Tail} %%-------------------------------------------------------------------- parse_req(Data) -> parse_req([], Data). parse_req([], Data) -> FunV = fun("http/"++V)->V;("HTTP/"++V)->V end, case get_line(Data) of {more} -> %% Partial header {more, [], Data}; {line, Line, Tail} -> [Method, RequestURI, Version] = string:tokens(Line," "), parse_req(#http_request{method=http_method(Method), url=RequestURI, version=FunV(Version)},Tail); {lastline, Line, Tail} -> [Method, RequestURI, Version] = string:tokens(Line," "), {ok, #http_request{method=http_method(Method), url=RequestURI, version=FunV(Version)},Tail} end; parse_req(Http=#http_request{headers=H}, Data) -> case get_line(Data) of {line, Line, Tail} -> NewH= [ts_utils:split2(Line,$:,strip) | H], parse_req(Http#http_request{headers=NewH}, Tail); {lastline, Line, Tail} -> NewH= [ts_utils:split2(Line,$:,strip) | H], {ok, Http#http_request{headers=NewH}, Tail}; {more} -> %% Partial header {more, Http#http_request{id=partial}, Data} end. %%-------------------------------------------------------------------- http_method("get")-> 'GET'; http_method("post")-> 'POST'; http_method("head")-> 'HEAD'; http_method("put")-> 'PUT'; http_method("delete")-> 'DELETE'; http_method("connect")-> 'CONNECT'; http_method("propfind")-> 'PROPFIND'; http_method("proppatch")-> 'PROPPATCH'; http_method("copy")-> 'COPY'; http_method("move")-> 'MOVE'; http_method("lock")-> 'LOCK'; http_method("unlock")-> 'UNLOCK'; http_method("mkcol")-> 'MKCOL'; http_method("mkactivity")-> 'MKACTIVITY'; http_method("report")-> 'REPORT'; http_method("options")-> 'OPTIONS'; http_method("checkout")-> 'CHECKOUT'; http_method("merge")-> 'MERGE'; http_method(Method) -> ?LOGF("Unknown HTTP method: ~p~n", [Method] ,?WARN), not_implemented. %%-------------------------------------------------------------------- %% Func: parse_status/2 %% Purpose: Parse HTTP status %% Returns: #http %%-------------------------------------------------------------------- parse_status([A,B,C|_], Http=#http{status={Prev,_}}) -> Status=list_to_integer([A,B,C]), ?DebugF("HTTP Status ~p~n",[Status]), ts_mon:add({ count, Status }), Http#http{status={Status,Prev}}. %%-------------------------------------------------------------------- %% Func: parse_line/3 %% Purpose: Parse a HTTP header %% Returns: #http %%-------------------------------------------------------------------- parse_line("http/1.1 " ++ TailLine, Http, _Host )-> parse_status(TailLine, Http); parse_line("http/1.0 " ++ TailLine, Http, _Host)-> parse_status(TailLine, Http#http{close=true}); parse_line("content-length: "++Tail, Http, _Host)-> CL=list_to_integer(Tail), ?DebugF("HTTP Content-Length ~p~n",[CL]), Http#http{content_length=CL}; parse_line("connection: close"++_Tail, Http, _Host)-> ?Debug("Connection Closed in Header ~n"), Http#http{close=true}; parse_line("content-encoding: "++Tail, Http=#http{compressed={Prev,_}}, _Host)-> ?DebugF("content encoding:~p ~n",[Tail]), Http#http{compressed={list_to_atom(Tail),Prev}}; parse_line("transfer-encoding:"++[H|Tail], Http, _Host)-> ?DebugF("~p transfer encoding~n",[[H]++Tail]), case Tail of [C|"hunked"++_] when C == $C; C == $c -> Http#http{chunk_toread=0}; "hunked"++_ when H == $C; H == $c-> Http#http{chunk_toread=0}; _ -> ?LOGF("Unknown transfer encoding ~p~n",[[H]++Tail],?NOTICE), Http end; parse_line("set-cookie: "++Tail, Http=#http{cookie=PrevCookies}, Host)-> Cookie = add_new_cookie(Tail, Host, PrevCookies), ?DebugF("HTTP New cookie val ~p~n",[Cookie]), Http#http{cookie=Cookie}; parse_line("proxy-connection: keep-alive"++_Tail, Http, _Host)-> Http#http{close=false}; parse_line("connection: Keep-Alive"++_Tail, Http, _Host)-> Http#http{close=false}; parse_line(_Line, Http, _Host) -> ?DebugF("Skip header ~p (Http record is ~p)~n", [_Line, Http]), Http. %% code taken from yaws is_nb_space(X) -> lists:member(X, [$\s, $\t]). % ret: {line, Line, Trail} | {lastline, Line, Trail} get_line(L) -> get_line(L, true, []). get_line("\r\n\r\n" ++ Tail, _Cap, Cur) -> {lastline, lists:reverse(Cur), Tail}; get_line("\r\n", _, _) -> {more}; get_line("\r\n" ++ Tail, Cap, Cur) -> case is_nb_space(hd(Tail)) of true -> %% multiline ... continue get_line(Tail, Cap,[$\n, $\r | Cur]); false -> {line, lists:reverse(Cur), Tail} end; get_line([$:|T], true, Cur) -> % ':' separator get_line(T, false, [$:|Cur]);%the rest of the header isn't set to lower char get_line([H|T], false, Cur) -> get_line(T, false, [H|Cur]); get_line([Char|T], true, Cur) when Char >= $A, Char =< $Z -> get_line(T, true, [Char + 32|Cur]); get_line([H|T], true, Cur) -> get_line(T, true, [H|Cur]); get_line([], _, _) -> %% Headers are fragmented ... We need more data {more}. %% we need to keep the compressed value of the current request reset_session(#http{status={Status,_},compressed={Current,_},chunk_toread=Val}) when Val > -1 -> #http{compressed={false,Current}, chunk_toread=-2, status={none,Status}} ; reset_session(#http{compressed={Current,_}, status={Status,_}} ) -> #http{compressed={false,Current}, status={none,Status}}. log_error(protocol,Error) -> put(protocol_error,Error), log_error2(protocol,Error); log_error(Type,Error) -> log_error2(Type,Error). log_error2(_,Error)-> ts_mon:add({count, Error}). tsung-1.4.2/src/tsung/ts_stats.erl0000644000201100017670000001262311701017117016642 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% Random Generators for several probability distributions -module(ts_stats). -created('Date: 2000/10/20 13:58:56 nniclausse Exp '). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -export([exponential/1, exponential/2, pareto/2, normal/0, normal/1, normal/2, uniform/2, invgaussian/2, mean/1, mean/3, variance/1, meanvar/4, meanvar_minmax/6, stdvar/1]). -import(math, [log/1, pi/0, sqrt/1, pow/2]). -record(pareto, {a = 1 , beta}). -record(normal, {mean = 0 , stddev= 1 }). -record(invgaussian, {mu , lambda}). %% get n samples from a function F with parameter Param sample (F, Param, N) -> sample(F, [], Param, N-1). sample (F, X, Param, 0) -> [F(Param) | X] ; sample (F, X, Param, N) -> sample(F, [F(Param)|X], Param, N-1 ). uniform(Min,Max)-> Min+random:uniform(Max-Min+1)-1. %% random sample from an exponential distribution exponential(Param) -> -math:log(random:uniform())/Param. %% N samples from an exponential distribution exponential(Param, N) -> sample(fun(X) -> exponential(X) end , Param, N). %% random sample from a Pareto distribution pareto(#pareto{a=A, beta=Beta}) -> A/(math:pow(random:uniform(), 1/Beta)). %% if a list is given, construct a record for the parameters pareto([A, Beta], N) -> pareto(#pareto{a = A , beta = Beta }, N); %% N samples from a Pareto distribution pareto(Param, N) -> sample(fun(X) -> pareto(X) end , Param, N). invgaussian([Mu,Lambda],N) -> invgaussian(#invgaussian{mu=Mu,lambda=Lambda},N); invgaussian(Param,N) -> sample(fun(X) -> invgaussian(X) end , Param, N). %% random sample from a Inverse Gaussian distribution invgaussian(#invgaussian{mu=Mu, lambda=Lambda}) -> Y = Mu*pow(normal(), 2), X1 = Mu+Mu*Y/(2*Lambda)-Mu*sqrt(4*Lambda*Y+pow(Y,2))/(2*Lambda), U = random:uniform(), X = (Mu/(Mu+X1))-U, case X >=0 of true -> X1; false -> Mu*Mu/X1 end. normal() -> [Val] = normal(#normal{},1), Val. normal([Mean,StdDev],N) -> normal(#normal{mean=Mean,stddev=StdDev},N); normal(Param,N) -> sample(fun(X) -> normal(X) end , Param, N). normal(N) when is_integer(N)-> normal(#normal{},N); normal(#normal{mean=M,stddev=S}) -> normal_boxm(M,S,0,0,1). %%% use the polar form of the Box-Muller transformation normal_boxm(M,S,X1,_X2,W) when W < 1-> W2 = sqrt( (-2.0 * log( W ) ) / W ), Y1 = X1 * W2, M + Y1 * S; normal_boxm(M,S,_,_,_W) -> X1 = 2.0 * random:uniform() - 1.0, X2 = 2.0 * random:uniform() - 1.0, normal_boxm(M,S,X1,X2,X1 * X1 + X2 * X2). %%% %% incremental computation of the mean mean(Esp, [], _) -> Esp; mean(Esp, [X|H], I) -> Next = I+1, mean((Esp+(X-Esp)/(Next)), H, Next). %% compute the mean of a list mean([]) -> 0; mean(H) -> mean(0, H, 0). %% @spec meanvar(Esp::number(),Var::number(),X::list() | number(),I::integer()) -> %% {NewEsp::number(), NewVar::number(), Next::integer()} %% @doc incremental computation of the mean and variance together. The %% algorithm should limit the round-off errors %% @end %% single value meanvar(Esp, Var, X, I) when is_number(X) -> Next = I+1, C = X - Esp, NewEsp = (X+Esp*I)/(Next), NewVar = Var+C*(X-NewEsp), { NewEsp, NewVar, Next }; %% list of samples meanvar(Esp, Var,[], I) -> {Esp, Var, I}; meanvar(Esp, Var, [X|H], I) -> {NewEsp, NewVar, Next} = meanvar(Esp,Var,X,I), meanvar(NewEsp, NewVar, H, Next). %% compute min and max also meanvar_minmax(Esp, Var, Min, Max, X, I) when is_number(X)-> meanvar_minmax(Esp, Var, Min, Max, [X], I); meanvar_minmax(Esp, Var, Min, Max, [], I) -> {Esp, Var, Min, Max, I}; meanvar_minmax(Esp, Var, 0, 0, [X|H], I) -> % first data, set min and max meanvar_minmax(Esp, Var, X, X, [X|H], I); meanvar_minmax(Esp, Var, Min, Max, [X|H], I) -> {NewEsp, NewVar, Next} = meanvar(Esp,Var,X,I), if X > Max -> % new max, min unchanged meanvar_minmax(NewEsp, NewVar, Min, X, H, Next); X < Min -> % new min, max unchanged meanvar_minmax(NewEsp, NewVar, X, Max, H, Next); true -> meanvar_minmax(NewEsp, NewVar, Min, Max, H, Next) end. %% compute the variance of a list variance([]) -> 0; variance(H) -> {_Mean, Var, I} = meanvar(0, 0, H, 0), Var/I. stdvar(H) -> math:sqrt(variance(H)). tsung-1.4.2/src/tsung/ts_mysql.erl0000644000201100017670000002661111701017117016653 0ustar nniclausdream%%% Created : July 2008 by Grgoire Reboul %%% From : ts_pgsql.erl by Nicolas Niclausse %%% Note : Based on erlang-mysql by Magnus Ahltorp & Fredrik Thulin %% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% --------------------------------------------------------------------- %%% Purpose: plugin for mysql >= 4.1 %%% Dependancies: none %%% Note: Packet fragmentation isnt implemented yet %%% --------------------------------------------------------------------- -module(ts_mysql). -vc('$Id:$ '). -author('gregoire.reboul@laposte.net'). -behavior(ts_plugin). -include("ts_profile.hrl"). -include("ts_mysql.hrl"). -export([init_dynparams/0, add_dynparams/4, get_message/2, session_defaults/0, parse/2, parse_bidi/2, dump/2, parse_config/2, decode_buffer/2, new_session/0]). %%---------------------------------------------------------------------- %% Function: session_default/0 %% Purpose: default parameters for session %% Returns: {ok, ack_type = parse|no_ack|local, persistent = true|false} %%---------------------------------------------------------------------- session_defaults() -> {ok, true}. %% @spec decode_buffer(Buffer::binary(),Session::record(mysql)) -> NewBuffer::binary() %% @doc We need to decode buffer (remove chunks, decompress ...) for %% matching or dyn_variables %% @end decode_buffer(Buffer,#mysql{}) -> Buffer. % FIXME ? %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> #mysql{}. parse_bidi(Data, State) -> ts_plugin:parse_bidi(Data,State). dump(A,B) -> ts_plugin:dump(A,B). %%---------------------------------------------------------------------- %% Function: get_message/21 %% Purpose: Build a message/request , %% Args: record %% Returns: binary %%---------------------------------------------------------------------- get_message(#mysql_request{type=connect},#state_rcv{session=S}) -> Packet=list_to_binary([]), ?LOGF("Opening socket. ~p ~n",[Packet], ?DEB), {Packet,S}; get_message(#mysql_request{type=authenticate, database=Database, username=Username, passwd=Password, salt=Salt},#state_rcv{session=S}) -> Packet=add_header(make_auth(Username, Password, Database, Salt),1), ?LOGF("Auth packet: ~p (~s)~n",[Packet,Packet], ?DEB), {Packet,S}; get_message(#mysql_request{type=sql,sql=Query},#state_rcv{session=S}) -> Packet=add_header([?MYSQL_QUERY_OP, Query],0), ?LOGF("Query packet: ~p (~s)~n",[Packet,Packet], ?DEB), {Packet,S}; get_message(#mysql_request{type=close},#state_rcv{session=S}) -> Packet=add_header([?MYSQL_CLOSE_OP],0), ?LOGF("Close packet: ~p (~s)~n",[Packet,Packet], ?DEB), {Packet,S}. %%---------------------------------------------------------------------- %% Function: parse/2 %% Purpose: parse the response from the server and keep information %% about the response in State#state_rcv.session %% Args: Data (binary), State (#state_rcv) %% Returns: {NewState, Options for socket (list), Close = true|false} %%---------------------------------------------------------------------- parse(closed, State) -> ?LOG("Parsing> socket closed ~n", ?WARN), {State#state_rcv{ack_done = true, datasize=0}, [], true}; parse(Data, State)-> <> = Data, case PacketSize =< size(PacketBody) of true -> ?LOG("Parsing> full packet ~n",?DEB), Request = State#state_rcv.request, Param = Request#ts_request.param, case Param#mysql_request.type of connect -> parse_greeting(PacketBody,State); authenticate -> parse_result(PacketBody,State); sql -> parse_result(PacketBody,State); close -> {State#state_rcv{ack_done = true, datasize=size(Data)},[],false} end; false -> ?LOGF("Parsing> incomplete packet: size->~p body->~p ~n",[PacketSize,size(PacketBody)], ?WARN), {State#state_rcv{ack_done = false, datasize=size(Data), acc=PacketBody},[],false} end. parse_greeting(Data, State=#state_rcv{acc = [],dyndata=DynData, datasize= 0}) -> ?LOGF("Parsing greeting ~p ~n",[Data], ?DEB), Salt= get_salt(Data), NewDynData=DynData#dyndata{proto=#mysql_dyndata{salt=Salt}}, {State#state_rcv{ack_done = true, datasize=size(Data), dyndata=NewDynData},[],false}. parse_result(Data,State)-> case Data of <> -> case Fieldcount of 0 -> %% No Tabular data <> = Rest2, ?LOGF("OK, No Data, Row affected: ~p (~s)~n", [AffectedRows,Data], ?DEB); 255 -> <> = Rest2, ?LOGF("Error: ~p ~s ~s ~n", [Errno,SQLState, Message], ?WARN), %% FIXME: should we stop if an error occurs ? ts_mon:add({ count, list_to_atom("error_mysql_"++integer_to_list(Errno))}); 254 when size(Rest2) < 9 -> ?LOGF("EOF: (~p) ~n", [Rest2], ?DEB); _ -> ?LOGF("OK, Tabular Data, Columns count: ~p (~s)~n", [Fieldcount,Data], ?DEB) end, {State#state_rcv{ack_done = true,datasize=size(Data)},[],false}; _ -> ?LOG("Bad packet ", ?ERR), ts_mon:add({ count, error_mysql_badpacket}), {State#state_rcv{ack_done = true,datasize=size(Data)},[],false} end. %%---------------------------------------------------------------------- %% Function: parse_config/2 %% Purpose: parse tags in the XML config file related to the protocol %% Returns: List %%---------------------------------------------------------------------- parse_config(Element, Conf) -> ts_config_mysql:parse_config(Element, Conf). %%---------------------------------------------------------------------- %% Function: add_dynparams/4 %% Purpose: add dynamic parameters to build the message %% (this is used for ex. for Cookies in HTTP) %% for postgres, use this to store the auth method and salt %% Args: Subst (true|false), DynData = #dyndata, Param = #myproto_request %% Host = String %% Returns: #mysql_request %%---------------------------------------------------------------------- add_dynparams(false, DynData, Param, HostData) -> add_dynparams(DynData#dyndata.proto, Param, HostData); add_dynparams(true, DynData, Param, HostData) -> NewParam = subst(Param, DynData#dyndata.dynvars), add_dynparams(DynData#dyndata.proto,NewParam, HostData). add_dynparams(DynMysql, Param, _HostData) -> Param#mysql_request{salt=DynMysql#mysql_dyndata.salt}. %%---------------------------------------------------------------------- %% Function: init_dynparams/0 %% Purpose: initial dynamic parameters value %% Returns: #dyndata %%---------------------------------------------------------------------- init_dynparams() -> #dyndata{proto=#mysql_dyndata{}}. %%---------------------------------------------------------------------- %% Function: subst/2 %% Purpose: Replace on the fly dynamic element of the request. %% Returns: #mysql_request %%---------------------------------------------------------------------- subst(Req=#mysql_request{sql=SQL}, DynData) -> Req#mysql_request{sql=ts_search:subst(SQL, DynData)}. %%% -- Internal funs -------------------- add_header(Packet,SeqNum) -> BPacket=list_to_binary(Packet), <<(size(BPacket)):24/little, SeqNum:8, BPacket/binary>>. get_salt(PacketBody) -> << _Protocol:8/little, Rest/binary>> = PacketBody, {_Version, Rest2} = asciz_binary(Rest,[]), <<_TreadID:32/little, Rest3/binary>> = Rest2, {Salt, Rest4} = asciz_binary(Rest3,[]), <<_Caps:16/little, Rest5/binary>> = Rest4, <<_ServerChar:16/binary-unit:8, Rest6/binary>> = Rest5, {Salt2, _Rest7} = asciz_binary(Rest6,[]), Salt ++ Salt2. make_auth(User, "", Database, _Salt) -> Caps = ?LONG_PASSWORD bor ?LONG_FLAG bor ?PROTOCOL_41 bor ?TRANSACTIONS bor ?SECURE_CONNECTION bor ?CONNECT_WITH_DB, Maxsize = ?MAX_PACKET_SIZE, UserB = list_to_binary(User), DatabaseB = list_to_binary(Database), binary_to_list(<>); make_auth(User, Password, Database, Salt) -> EncryptedPassword = encrypt_password(Password, Salt), Caps = ?LONG_PASSWORD bor ?LONG_FLAG bor ?PROTOCOL_41 bor ?TRANSACTIONS bor ?SECURE_CONNECTION bor ?CONNECT_WITH_DB, Maxsize = ?MAX_PACKET_SIZE, UserB = list_to_binary(User), PasswordL = size(EncryptedPassword), DatabaseB = list_to_binary(Database), binary_to_list(<>). encrypt_password(Password, Salt) -> Stage1= case catch crypto:sha(Password) of {'EXIT',_} -> crypto:start(), crypto:sha(Password); Sha -> Sha end, Stage2 = crypto:sha(Stage1), Res = crypto:sha_final( crypto:sha_update( crypto:sha_update(crypto:sha_init(), Salt), Stage2) ), bxor_binary(Res, Stage1). %% @doc Find the first zero-byte in Data and add everything before it %% to Acc, as a string. %% %% @spec asciz_binary(Data::binary(), Acc::list()) -> %% {NewList::list(), Rest::binary()} asciz_binary(<<>>, Acc) -> {lists:reverse(Acc)}; asciz_binary(<<0:8, Rest/binary>>, Acc) -> {lists:reverse(Acc), Rest}; asciz_binary(<>, Acc) -> asciz_binary(Rest, [C | Acc]). dualmap(_F, [], []) -> []; dualmap(F, [E1 | R1], [E2 | R2]) -> [F(E1, E2) | dualmap(F, R1, R2)]. bxor_binary(B1, B2) -> list_to_binary(dualmap(fun (E1, E2) -> E1 bxor E2 end, binary_to_list(B1), binary_to_list(B2))). tsung-1.4.2/src/tsung/ts_search.erl0000644000201100017670000003722211701017117016753 0ustar nniclausdream%%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% File : ts_search.erl %%% Author : Mickael Remond %%% Description : Add dynamic / Differenciated parameters in tsung %%% request and response %%% The function subst is intended to be called for each %%% relevant field in ts_protocol implementation. %%% Created : 22 Mar 2004 by Mickael Remond %%% Nicolas Niclausse: add dynamic variable and matching -module(ts_search). -vc('$Id$ '). -export([subst/2, match/4, parse_dynvar/2]). -include("ts_profile.hrl"). %% ---------------------------------------------------------------------- %% Function: subst/2 %% Purpose: search into a given string and replace %%Mod:Fun%% strings %% by the result of the call to Mod:Fun(Pid) where Pid is the %% Pid of the client The substitution tag are intended to %% be used in tsung.xml scenarii files. %% Returns: new string %% ---------------------------------------------------------------------- subst(Int, _DynVar) when is_integer(Int) -> Int; subst(Atom, _DynVar) when is_atom(Atom) -> Atom; subst(Binary, DynVar) when is_binary(Binary) -> list_to_binary(subst(binary_to_list(Binary), DynVar)); subst(String, DynVar) -> subst(String, DynVar, []). subst([], _DynVar, Acc) -> lists:reverse(Acc); subst([$%,$%,$_|Rest], DynVar, Acc) -> extract_variable(Rest, DynVar, Acc, []); subst([$%,$%|Rest], DynVar, Acc) -> extract_module(Rest, DynVar, Acc, []); subst([H|Tail], DynVar, Acc) -> subst(Tail, DynVar, [H|Acc]). %% Search for the module string in the subst markup extract_module([],_DynVar, Acc,_) -> lists:reverse(Acc); extract_module([$:|Tail],DynVar, Acc, Mod) -> ?DebugF("found module name: ~p~n",[lists:reverse(Mod)]), extract_function(Tail,DynVar, Acc,lists:reverse(Mod),[]); extract_module([H|Tail],DynVar, Acc, Mod) -> extract_module(Tail,DynVar, Acc,[H|Mod]). %% Search for the module string in the subst markup extract_variable([],_DynVar,Acc,_) -> lists:reverse(Acc); extract_variable([$%,$%|Tail], DynVar, Acc, Var) -> VarName = list_to_atom(lists:reverse(Var)), case ts_dynvars:lookup(VarName,DynVar) of {ok, ResultTmp} -> Result=ts_utils:term_to_list(ResultTmp), ?DebugF("found value ~p for name ~p~n",[Result,VarName]), subst(Tail, DynVar,lists:reverse(Result) ++ Acc); false -> ?LOGF("DynVar: no value found for var ~p~n",[VarName],?WARN), subst(Tail, DynVar,lists:reverse("undefined") ++ Acc) end; extract_variable([H|Tail],DynVar,Acc,Mod) -> extract_variable(Tail,DynVar,Acc,[H|Mod]). %% Search for the function string and do the real substitution before %% keeping on the parsing extract_function([], _DynVar, Acc, _Mod, _Fun) -> lists:reverse(Acc); extract_function([$%,$%|Tail], DynVar, Acc, Mod, Fun) -> ?DebugF("found function name: ~p~n",[lists:reverse(Fun)]), Module = list_to_atom(Mod), Function = list_to_atom(lists:reverse(Fun)), Result = case Module:Function({self(), DynVar }) of Int when is_integer(Int) -> lists:reverse(integer_to_list(Int)); Str when is_list(Str) -> Str; _Val -> ?LOGF("extract fun:bad result ~p~n",[_Val],?WARN), [] end, subst(Tail, DynVar, lists:reverse(Result) ++ Acc); extract_function([H|Tail],DynVar, Acc, Mod, Fun) -> extract_function(Tail, DynVar, Acc, Mod, [H|Fun]). %%---------------------------------------------------------------------- %% @spec match(Match::#match{}, Data::binary() | list, {Counts::integer(), %% Max::integer(), SessionId::integer(),UserId::integer()}, Dynvars::term() %% ) -> Count::integer() %% @doc search for regexp in Data; send result to ts_mon %% @end %%---------------------------------------------------------------------- match([], _Data, {Count, _MaxC, _SessionId, _UserId}, _DynVars) -> Count; match([Match=#match{'skip_headers'=http}|Tail], Data, Counts,DynVars) when is_binary(Data)-> %% keep http body only case re:run(Data,"\\r\\n\\r\\n(.*)",[{capture,all_but_first,binary},dotall]) of {match,[NewData]} -> match([Match#match{'skip_headers'=no}|Tail], NewData, Counts, DynVars); _ -> ?LOGF("Skip http headers failure, data was: ~p ~n",[Data], ?ERR), match([Match#match{'skip_headers'=no}|Tail], Data, Counts, DynVars) end; match([Match=#match{'apply_to_content'=undefined}|Tail], Data, Counts,DynVars) -> ?DebugF("Matching Data size ~p; apply undefined~n",[size(Data)]), match([Match|Tail], Data, Counts, [],DynVars); match([Match=#match{'apply_to_content'={Module,Fun}}|Tail], Data, Counts,DynVars) -> ?DebugF("Matching Data size ~p; apply ~p:~p~n",[size(Data),Module,Fun]), NewData = Module:Fun(Data), ?DebugF("Match: apply result =~p~n",[NewData]), match([Match|Tail], NewData, Counts, [],DynVars). %% @spec match(Match::#match{}, Data::binary() | list(), Count::tuple(), %% Stats::list(), DynVars::term()) -> Count::integer() match([], _Data, {Count,_, _,_}, Stats, _) -> %% all matches done, add stats, and return Count unchanged (continue) ts_mon:add(Stats), Count; match([Match=#match{regexp=RawRegExp,subst=Subst, do=Action, 'when'=When} |Tail], Data,Counts,Stats,DynVars)-> RegExp = case Subst of true -> subst(RawRegExp, DynVars); _ -> RawRegExp end, ?DebugF("RegExp was ~p and now is ~p after substitution (~p)~n",[RawRegExp,RegExp,Subst]), case re:run(Data, RegExp) of {When,_} -> ?LOGF("Ok Match (regexp=~p) do=~p~n",[RegExp,Action], ?INFO), setcount(Match, Counts, [{count, match}| Stats], Data); When -> % nomatch ?LOGF("Bad Match (regexp=~p) do=~p~n",[RegExp, Action], ?INFO), setcount(Match, Counts, [{count, nomatch} | Stats],Data); {match,_} -> % match but when=nomatch ?LOGF("Ok Match (regexp=~p)~n",[RegExp], ?INFO), case Action of loop -> put(loop_count, 0); restart -> put(restart_count, 0); _ -> ok end, match(Tail, Data, Counts, [{count, match} | Stats],DynVars); nomatch -> % nomatch but when=match ?LOGF("Bad Match (regexp=~p)~n",[RegExp], ?INFO), case Action of loop -> put(loop_count, 0); restart -> put(restart_count, 0); _ -> ok end, match(Tail, Data, Counts,[{count, nomatch} | Stats],DynVars); {error,_Error} -> ?LOGF("Error while matching: bad REGEXP (~p)~n", [RegExp], ?ERR), match(Tail, Data, Counts,[{count, badregexp} | Stats],DynVars) end. %%---------------------------------------------------------------------- %% Func: setcount/3 %% Args: #match, Counts, Stats %% Update the request counter after a match: %% - if loop is true, we must start again the same request, so add 1 to count %% - if restart is true, we must start again the whole session, set count to MaxCount %% - if stop is true, set count to 0 %%---------------------------------------------------------------------- setcount(#match{do=continue}, {Count, _MaxC, _SessionId, _UserId}, Stats,_)-> ts_mon:add(Stats), Count; setcount(#match{do=log}, {Count, MaxC, SessionId, UserId}, Stats,_)-> ts_mon:add_match(Stats,{UserId,SessionId,MaxC-Count}), Count; setcount(#match{do=dump}, {Count, MaxC, SessionId, UserId}, Stats,Data)-> ts_mon:add_match(Stats,{UserId,SessionId,MaxC-Count, Data}), Count; setcount(#match{do=restart, max_restart=MaxRestart}, {Count, MaxC,SessionId,UserId}, Stats,_)-> CurRestart = get(restart_count), Ids={UserId,SessionId,MaxC-Count}, ?LOGF("Restart on (no)match ~p~n",[CurRestart], ?INFO), case CurRestart of undefined -> put(restart_count,1), ts_mon:add_match([{count, match_restart} | Stats],Ids), MaxC ; Val when Val > MaxRestart -> ?LOG("Max restart reached, abort ! ~n", ?WARN), ts_mon:add_match([{count, match_restart_abort} | Stats],Ids), 0; Val -> put(restart_count, Val +1), ts_mon:add_match([{count, match_restart} | Stats],Ids), MaxC end; setcount(#match{do=loop,loop_back=Back,max_loop=MaxLoop,sleep_loop=Sleep},{Count,_MaxC,_SessionId,_UserId},Stats,_)-> CurLoop = get(loop_count), ?LOGF("Loop on (no)match ~p~n",[CurLoop], ?INFO), ts_mon:add([{count, match_loop} | Stats]), case CurLoop of undefined -> put(loop_count,1), timer:sleep(Sleep), Count +1 + Back ; Val when Val >= MaxLoop -> ?LOG("Max Loop reached, abort loop on request! ~n", ?WARN), put(loop_count, 0), Count; Val -> put(loop_count, Val +1), timer:sleep(Sleep), Count + 1 + Back end; setcount(#match{do=abort}, {Count,MaxC,SessionId,UserId}, Stats,_) -> ts_mon:add_match([{count, match_stop} | Stats],{UserId,SessionId,MaxC-Count}), 0. %%---------------------------------------------------------------------- %% Func: parse_dynvar/2 %% Args: DynVarSpecs, Data %% Purpose: look for dynamic variables in Data %% Returns: DynVars (List) %%---------------------------------------------------------------------- parse_dynvar([], _Data) -> ts_dynvars:new(); parse_dynvar(DynVarSpecs, Data) when is_binary(Data) -> ?DebugF("Parsing Dyn Variable (specs=~p); data is ~p~n",[DynVarSpecs,Data]), parse_dynvar(DynVarSpecs,Data, undefined,undefined,[]); parse_dynvar(DynVarSpecs, {_,_,_,Data}) when is_binary(Data) -> ?DebugF("Parsing Dyn Variable (specs=~p); data is ~p~n",[DynVarSpecs,Data]), parse_dynvar(DynVarSpecs,Data, undefined,undefined,[]); parse_dynvar(DynVarSpecs, {_,_,_,Data}) when is_list(Data) -> ?DebugF("Parsing Dyn Variable (specs=~p); data is ~p~n",[DynVarSpecs,Data]), parse_dynvar(DynVarSpecs,list_to_binary(Data), undefined,undefined,[]); parse_dynvar(DynVarSpecs, _Data) -> ?LOGF("Error while Parsing dyn Variable(~p)~n",[DynVarSpecs],?WARN), ts_dynvars:new(). % parse_dynvar(DynVars,BinaryData,ListData,TreeData,Accum) % ListData and TreeData are lazy computed when needed by % regexp or xpath variables respectively parse_dynvar([],_Binary , _String,_Tree, DynVars) -> DynVars; parse_dynvar(D=[{re,_, _}| _],Binary,undefined,Tree,DynVars) -> parse_dynvar(D,Binary,Binary,Tree,DynVars); parse_dynvar([{re,VarName, RegExp}| DynVarsSpecs],Binary,Data,Tree,DynVars) -> case re:run(Data, RegExp,[{capture,[1],binary}]) of {match,[Value]} -> ?LOGF("DynVar (RE): Match (~p=~p) ~n",[VarName, Value], ?INFO), parse_dynvar(DynVarsSpecs, Binary,Data,Tree, ts_dynvars:set(VarName,Value,DynVars)); nomatch -> ?LOGF("Dyn Var (RE): no Match (varname=~p), ~n",[VarName], ?WARN), ?LOGF("Regexp was: ~p ~n",[RegExp], ?INFO), parse_dynvar(DynVarsSpecs, Binary,Data,Tree, ts_dynvars:set(VarName,"",DynVars)) end; parse_dynvar(D=[{xpath,_VarName, _Expr}| _DynVarsSpecs], Binary,String,undefined,DynVars) -> Body = extract_body(Binary), ToParse = case bit_size(Body) of 0 -> Binary; _ -> Body end, try mochiweb_html:parse(ToParse) of Tree -> parse_dynvar(D,Binary,String,Tree,DynVars) catch Type:Exp -> ?LOGF("Page couldn't be parsed:(~p:~p) ~n Page:~p~n", [Type,Exp,Binary],?ERR), parse_dynvar(D,Binary,String,xpath_error,DynVars) end; parse_dynvar(D=[{jsonpath,_VarName, _Expr}| _DynVarsSpecs], Binary,String,undefined,DynVars) -> Body = extract_body(Binary), try mochijson2:decode(Body) of JSON -> ?LOGF("JSON decode: ~p~n", [JSON],?DEB), parse_dynvar(D,Binary,String,JSON,DynVars) catch Type:Exp -> ?LOGF("JSON couldn't be parsed:(~p:~p) ~n Page:~p~n", [Type,Exp,Binary],?ERR), parse_dynvar(D,Binary,String,json_error,DynVars) end; parse_dynvar(D=[{pgsql_expr,_VarName, _Expr}| _DynVarsSpecs], Binary,String,undefined,DynVars) -> Pairs=ts_pgsql:to_pairs(Binary), parse_dynvar(D,Binary,String,Pairs,DynVars); parse_dynvar([{xpath,VarName,_Expr}|DynVarsSpecs],Binary,String,xpath_error,DynVars)-> ?LOGF("Couldn't execute XPath: page not parsed (varname=~p)~n", [VarName],?ERR), parse_dynvar(DynVarsSpecs, Binary,String,xpath_error,DynVars); parse_dynvar([{jsonpath,VarName,_Expr}|DynVarsSpecs],Binary,String,json_error,DynVars)-> ?LOGF("Couldn't execute JSONPath: page not parsed (varname=~p)~n", [VarName],?ERR), parse_dynvar(DynVarsSpecs, Binary,String,json_error,DynVars); parse_dynvar([{pgsql_expr,VarName,_Expr}|DynVarsSpecs],Binary,String,pgsql_error,DynVars)-> ?LOGF("Couldn't decode pgsql expr from PGSQL binary (varname=~p)~n", [VarName],?ERR), parse_dynvar(DynVarsSpecs, Binary,String,json_error,DynVars); parse_dynvar([{xpath,VarName, Expr}| DynVarsSpecs],Binary,String,Tree,DynVars)-> Value = mochiweb_xpath:execute(Expr,Tree), ?DebugF("Xpath result: ~p~n", [Value]), case Value of [] -> ?LOGF("Dyn Var: no Match (varname=~p), ~n",[VarName],?WARN); _ -> ?LOGF("Dyn Var: Match (~p=~p), ~n",[VarName,Value],?INFO) end, parse_dynvar(DynVarsSpecs, Binary,String,Tree,ts_dynvars:set(VarName,Value,DynVars)); parse_dynvar([{jsonpath,VarName, Expr}| DynVarsSpecs],Binary,String,JSON,DynVars)-> Values = ts_utils:jsonpath(Expr,JSON), case Values of undefined -> ?LOGF("Dyn Var: no Match (varname=~p), ~n",[VarName],?WARN); _ -> ?LOGF("Dyn Var: Match (~p=~p), ~n",[VarName,Values],?INFO) end, parse_dynvar(DynVarsSpecs, Binary,String,JSON,ts_dynvars:set(VarName,Values,DynVars)); parse_dynvar([{pgsql_expr,VarName, Expr}| DynVarsSpecs],Binary,String,PGSQL,DynVars)-> Values = ts_pgsql:find_pair(Expr,PGSQL), case Values of undefined -> ?LOGF("Dyn Var: no Match (varname=~p), ~n",[VarName],?WARN); _ -> ?LOGF("Dyn Var: Match (~p=~p), ~n",[VarName,Values],?INFO) end, parse_dynvar(DynVarsSpecs, Binary,String,PGSQL,ts_dynvars:set(VarName,Values,DynVars)); parse_dynvar(Args, _Binary,_String,_Tree, _DynVars) -> ?LOGF("Bad args while parsing Dyn Var (~p)~n", [Args], ?ERR), []. extract_body(Data) -> case re:run(Data,"\r\n\r\n(.*)$",[{capture,all_but_first,binary},dotall]) of nomatch -> Data; {match, [Val]} -> Val; _ -> Data end. tsung-1.4.2/src/tsung/tsung.erl0000644000201100017670000000440611701017117016136 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(tsung). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -export([start/2, stop/1]). -behaviour(application). -include("ts_profile.hrl"). %%---------------------------------------------------------------------- %% Func: start/2 %% Returns: {ok, Pid} | %% {ok, Pid, State} | %% {error, Reason} %%---------------------------------------------------------------------- start(_Type, _StartArgs) -> % error_logger:tty(false), ?LOG("open logfile ~n",?DEB), LogFileEnc = ts_config_server:decode_filename(?config(log_file)), LogFile = filename:join(LogFileEnc, atom_to_list(node()) ++ ".log"), LogDir = filename:dirname(LogFile), ok = ts_utils:make_dir_rec(LogDir), error_logger:logfile({open, LogFile}), ?LOG("ok~n",?DEB), case ts_sup:start_link() of {ok, Pid} -> {ok, Pid}; Error -> ?LOGF("Can't start supervisor ! ~p ~n",[Error],?ERR), Error end. %%---------------------------------------------------------------------- %% Func: stop/1 %% Returns: any %%---------------------------------------------------------------------- stop(_State) -> stop. tsung-1.4.2/src/tsung/ts_job.erl0000644000201100017670000002025411701017117016255 0ustar nniclausdream%%% %%% Copyright 2011 © INRIA %%% %%% Author : Nicolas Niclausse %%% Created: 4 mai 2011 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_job). -author('nicolas.niclausse@inria.fr'). -behaviour(ts_plugin). -include("ts_profile.hrl"). -include("ts_job.hrl"). -include_lib("kernel/include/file.hrl"). -export([init_dynparams/0, add_dynparams/4, get_message/2, session_defaults/0, dump/2, parse/2, parse_bidi/2, parse_config/2, decode_buffer/2, new_session/0]). %%==================================================================== %% Data Types %%==================================================================== %% @type dyndata() = #dyndata{proto=ProtoData::term(),dynvars=list()}. %% Dynamic data structure %% @end %% @type server() = {Host::tuple(),Port::integer(),Protocol::atom()}. %% Host/Port/Protocol tuple %% @end %% @type param() = {dyndata(), server()}. %% Dynamic data structure %% @end %% @type hostdata() = {Host::tuple(),Port::integer()}. %% Host/Port pair %% @end %% @type client_data() = binary() | closed. %% Data passed to a protocol implementation is either a binary or the %% atom closed indicating that the server closed the tcp connection. %% @end %%==================================================================== %% API %%==================================================================== parse_config(El,Config) -> ts_config_job:parse_config(El, Config). %% @spec session_defaults() -> {ok, Persistent} | {ok, Persistent, Bidi} %% Persistent = bool() %% Bidi = bool() %% @doc Default parameters for sessions of this protocol. Persistent %% is true if connections are preserved after the underlying tcp %% connection closes. Bidi should be true for bidirectional protocols %% where the protocol module needs to reply to data sent from the %% server. @end session_defaults() -> {ok, true}. % not relevant for erlang type (?). %% @spec new_session() -> State::term() %% @doc Initialises the state for a new protocol session. %% @end new_session() -> #job_session{}. %% @spec decode_buffer(Buffer::binary(),Session::record(job)) -> NewBuffer::binary() %% @doc We need to decode buffer (remove chunks, decompress ...) for %% matching or dyn_variables %% @end decode_buffer(Buffer,#job_session{}) -> Buffer. %% @spec init_dynparams() -> dyndata() %% @doc Creates a new record/term for storing dynamic request data. %% @end init_dynparams() -> #dyndata{proto=#job_dyndata{}}. %% @spec add_dynparams(Subst, dyndata(), param(), hostdata()) -> {dyndata(), server()} | dyndata() %% Subst = term() %% @doc Updates the dynamic request data structure created by %% {@link ts_protocol:init_dynparams/0. init_dynparams/0}. %% @end add_dynparams(false, DynData, Param, HostData) -> add_dynparams(DynData#dyndata.proto, Param, HostData); add_dynparams(true, DynData, Param, HostData) -> NewParam = subst(Param, DynData#dyndata.dynvars), add_dynparams(DynData#dyndata.proto,NewParam, HostData). add_dynparams(#job_dyndata{}, Param, _HostData) -> Param. %%---------------------------------------------------------------------- %% @spec subst(record(job), term()) -> record(job) %% @doc Replace on the fly dynamic element of the request. %% @end %%---------------------------------------------------------------------- subst(Job=#job{duration=D,req=Req,walltime=WT,resources=Res,options=Opts,jobid=Id}, DynVars) -> Job#job{duration=ts_search:subst(D,DynVars), req=ts_search:subst(Req,DynVars), resources=ts_search:subst(Res,DynVars), walltime=ts_search:subst(WT,DynVars), options=ts_search:subst(Opts,DynVars), jobid=ts_search:subst(Id,DynVars)}. dump(protocol,{none,#job_session{jobid=JobId,owner=Owner,submission_time=Sub,queue_time=Q, start_time=Start,end_time=E,status=Status},Name,_,_})-> {R,_}=lists:mapfoldl(fun(A,Acc) -> {integer_to_list(round(ts_utils:elapsed(Acc,A))),A} end,Sub,[Q,Start,E]), Date=integer_to_list(round(ts_utils:time2sec_hires(Sub))), Data=ts_utils:join(";",[JobId,Name,Date]++R++[Status]), ts_mon:dump({protocol, Owner, Data }); dump(_P,_Args) -> ok. %% @spec parse(Data::client_data(), State) -> {NewState, Opts, Close} %% State = #state_rcv{} %% Opts = proplist() %% Close = bool() %% @doc %% Opts is a list of inet:setopts socket options. Don't change the %% active/passive mode here as tsung will set {active,once} before %% your options. %% Setting Close to true will cause tsung to close the connection to %% the server. %% @end parse({os, cmd, _Args, Res},State=#state_rcv{session=S,dump=Dump}) when is_list(Res)-> ?LOGF("os:cmd result: ~p",[Res],?DEB), %% oarsub output: %% [ADMISSION RULE] Modify resource description with type constraints %% Generate a job key... %% OAR_JOB_ID=468822 Lines = string:tokens(Res,"\n"), case lists:last(Lines) of "OAR_JOB_ID="++ID -> ?LOGF("OK,job id is ~p",[ID],?INFO), ts_job_notify:monitor({ID,self(),S#job_session.submission_time, now(),Dump}), {State#state_rcv{ack_done=true,datasize=length(Res)}, [], false}; _ -> {State#state_rcv{ack_done=true,datasize=length(Res)}, [], false} end; parse(nojobs,State) -> ?LOGF(" no jobs in queue for ~p, stop waiting",[self()],?DEB), {State#state_rcv{ack_done=true}, [], false}; parse({Mod, Fun, Args, Res},State) -> ?LOGF(" result: ~p",[{Mod, Fun, Args, Res}],?DEB), {State#state_rcv{ack_done=false}, [], false}. %% @spec parse_bidi(Data, State) -> {nodata, NewState} | {Data, NewState} %% Data = client_data() %% NewState = term() %% State = term() %% @doc Parse a block of data from the server. No reply will be sent %% if the return value is nodata, otherwise the Data binary will be %% sent back to the server immediately. %% @end parse_bidi(Data, State) -> ts_plugin:parse_bidi(Data,State). %% @spec get_message(record(job),record(state_rcv)) -> {Message::term(),record(state_rcv)} %% @doc Creates a new message to send to the connected server. %% @end get_message(#job{type=oar,req=wait_jobs},#state_rcv{session=Session}) -> ts_job_notify:wait_jobs(self()), {{erlang, now,[], 0},Session}; % we could use any function call, the result is not used get_message(Job=#job{duration=D},State) when is_integer(D)-> get_message(Job#job{duration=integer_to_list(D)},State); get_message(Job=#job{notify_port=P},State) when is_integer(P)-> get_message(Job#job{notify_port=integer_to_list(P)},State); get_message(#job{type=oar,user=U,req=submit, name=N,script=S, resources=R, queue=Q, walltime=W,notify_port=P, notify_script=NS,duration=D,options=Opts},#state_rcv{session=Session}) -> Submit = case U of undefined -> "oarsub "; User -> "sudo -u "++User++" oarsub " end, Queue = case Q of "" -> ""; _ -> "-q "++ Q end, Cmd=Submit++Queue++" -l "++R++ ",walltime="++W ++" -n " ++N ++" " ++ Opts ++ " " ++" --notify \"exec:" ++NS++" "++P++"\" " ++"\""++S++" "++D++"\"", ?LOGF("Will run ~p",[Cmd],?INFO), Message = {os, cmd, [Cmd], length(Cmd) }, {Message, Session#job_session{submission_time=now()}}. tsung-1.4.2/src/tsung/ts_mon_cache.erl0000644000201100017670000001776111701017117017430 0ustar nniclausdream%%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%%------------------------------------------------------------------- %%% File : ts_session_cache.erl %%% Author : Nicolas Niclausse %%% Description : cache sessions request from ts_config_server %%% %%% Created : 2 Dec 2003 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_mon_cache). -behaviour(gen_server). %%-------------------------------------------------------------------- %% Include files %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- %% External exports -export([start/0, add/1, add_match/2]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, { stats=[], % cache stats msgs transactions=[], % cache transaction stats msgs pages=[], % cache pages stats msgs requests=[], % cache requests stats msgs connections=[], % cache connect stats msgs match=[], % cache match logs sum % cache sum stats msgs }). -define(DUMP_STATS_INTERVAL, 500). % in milliseconds -include("ts_profile.hrl"). %%==================================================================== %% External functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: start_link/0 %% Description: Starts the server %%-------------------------------------------------------------------- start() -> ?LOG("Starting~n",?INFO), gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). %%-------------------------------------------------------------------- %% Function: add/1 %% Description: Add stats data. Will be accumulated sent periodically %% to ts_mon %%-------------------------------------------------------------------- add(Data) -> gen_server:cast(?MODULE, {add, Data}). %% @spec add_match(Data::list(),{UserId::integer(),SessionId::integer(),RequestId::integer(), %% TimeStamp::tuple()}) -> ok add_match(Data,{UserId,SessionId,RequestId,TimeStamp,Bin}) -> gen_server:cast(?MODULE, {add_match, Data, {UserId,SessionId,RequestId,TimeStamp,Bin}}). %%==================================================================== %% Server functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: init/1 %% Description: Initiates the server %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%-------------------------------------------------------------------- init([]) -> erlang:start_timer(?DUMP_STATS_INTERVAL, self(), dump_stats ), {ok, #state{sum=dict:new()}}. %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. %%-------------------------------------------------------------------- %% Function: handle_cast/2 %% Description: Handling cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_cast({add, Data}, State) when is_list(Data) -> LastState = lists:foldl(fun(NewData,NewState)-> update_stats(NewData,NewState) end, State, Data), {noreply, LastState }; handle_cast({add, Data}, State) when is_tuple(Data) -> {noreply,update_stats(Data, State)}; handle_cast({add_match, Data=[First|_Tail],{UserId,SessionId,RequestId,TimeStamp,Bin}}, State=#state{stats=List, match=MatchList})-> NewMatchList=lists:append([{UserId,SessionId,RequestId,TimeStamp,First, Bin}], MatchList), {noreply, State#state{stats = lists:append(Data, List), match = NewMatchList}}; handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info/2 %% Description: Handling all non call/cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_info({timeout, _Ref, dump_stats}, State =#state{stats= Stats, match=MatchList}) -> Fun = fun(Key,Val, Acc) -> [{sum,Key,Val}| Acc] end, NewStats=dict:fold(Fun, Stats, State#state.sum), ts_stats_mon:add(NewStats), ts_stats_mon:add(State#state.requests,request), ts_stats_mon:add(State#state.connections,connect), ts_stats_mon:add(State#state.transactions,transaction), ts_stats_mon:add(State#state.pages,page), ts_match_logger:add(MatchList), erlang:start_timer(?DUMP_STATS_INTERVAL, self(), dump_stats ), {noreply, State#state{stats=[],match=[],pages=[],requests=[],transactions=[],connections=[],sum=dict:new()}}; handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate/2 %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- terminate(Reason, _State) -> ?LOGF("Die ! (~p)~n",[Reason],?ERR), ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. update_stats({sample, request, Val}, State=#state{requests=L}) -> State#state{requests=lists:append([Val],L)}; update_stats({sample, page, Val}, State=#state{pages=L}) -> State#state{pages=lists:append([Val],L)}; update_stats({sample, connect, Val}, State=#state{connections=L}) -> State#state{connections=lists:append([Val],L)}; update_stats(S={sample, _Type, _}, State=#state{transactions=L}) -> State#state{transactions=lists:append([S],L)}; update_stats({sum, Type, Val}, State=#state{sum=Sum}) -> NewSum=dict:update_counter(Type,Val,Sum), State#state{sum=NewSum}; update_stats({count, Type}, State=#state{sum=Sum}) -> NewSum=dict:update_counter(Type,1,Sum), State#state{sum=NewSum}; update_stats(Data, State=#state{stats=L}) when is_tuple(Data)-> State#state{stats=lists:append([Data],L)}. tsung-1.4.2/src/tsung/tsung.app.src.in0000644000201100017670000000366211701017117017332 0ustar nniclausdream{application, tsung, [{description, "tsung, a load testing tool for TCP/UDP servers"}, {vsn, "%VSN%"}, {modules, [ tsung, ts_launcher, ts_session_cache, ts_client, ts_client_rcv, ts_client_sup, ts_sup, ts_stats, ts_utils, ts_profile ]}, {registered, [ ts_launcher, ts_session_cache ]}, {env, [ {debug_level, 2}, {snd_size, 32768}, % send buffer size {rcv_size, 32768}, % receive buffer size {tcp_timeout, 600000}, % 10min timeout {connect_timeout, 30000}, {max_warm_delay, 15000}, {dump, full}, % full or light {parse_type, noparse}, {persistent, true}, % persistent connection: true or false {mes_type, dynamic}, % dynamic or static {nclients, 10}, % number of client to connect {log_file, "./tsung.log"}, % log file name %% use for IMS GET : {http_modified_since_date, "Fri, 14 Nov 2003 02:43:31 GMT"}, {client_retry_timeout, 10}, % retry sending (in microsec.) {ssl_ciphers, negociate}, %%% -------- JABBER OPTIONS {jabber_users, 2000000}, {jabber_username, "c"}, {jabber_password, "pas"}, {jabber_domain, "mydomain.com"} ]}, {applications, [ @ERLANG_APPLICATIONS@]}, {mod, {tsung, []}} ]}. tsung-1.4.2/src/tsung/ts_session_cache.erl0000644000201100017670000002015111701017117020305 0ustar nniclausdream%%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%%------------------------------------------------------------------- %%% File : ts_session_cache.erl %%% Author : Nicolas Niclausse %%% Description : cache sessions request from ts_config_server %%% %%% Created : 2 Dec 2003 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_session_cache). -behaviour(gen_server). %%-------------------------------------------------------------------- %% Include files %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- %% External exports -export([start/0, get_req/2, get_user_agent/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, { table, % ets table hit =0.0, % number of hits total=0.0 % total number of requests }). -define(DUMP_STATS_INTERVAL, 500). % in milliseconds -include("ts_profile.hrl"). %%==================================================================== %% External functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: start_link/0 %% Description: Starts the server %%-------------------------------------------------------------------- start() -> ?LOG("Starting~n",?INFO), gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). %%-------------------------------------------------------------------- %% Function: get_req/2 %% Description: get next request from session 'Id' %%-------------------------------------------------------------------- get_req(Id, Count)-> gen_server:call(?MODULE,{get_req, Id, Count}). %%-------------------------------------------------------------------- %% Function: get_user_agent/0 %%-------------------------------------------------------------------- get_user_agent()-> gen_server:call(?MODULE,{get_user_agent}). %%==================================================================== %% Server functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: init/1 %% Description: Initiates the server %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%-------------------------------------------------------------------- init([]) -> Table = ets:new(sessiontable, [set, private]), {ok, #state{table=Table}}. %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- %% get Nth request from given session Id handle_call({get_req, Id, N}, _From, State) -> Tab = State#state.table, Total = State#state.total+1, ?DebugF("look for ~p th request in session ~p for ~p~n",[N,Id,_From]), case ets:lookup(Tab, {Id, N}) of [{_Key, Session}] -> Hit = State#state.hit+1, ?DebugF("ok, found in cache for ~p~n",[_From]), ?DebugF("hitrate is ~.3f~n",[100.0*Hit/Total]), {reply, Session, State#state{hit= Hit, total = Total}}; [] -> %% no match, ask the config_server ?DebugF("not found in cache (~p th request in session ~p for ~p)~n",[N,Id,_From]), case catch ts_config_server:get_req(Id, N) of {'EXIT',Reason} -> {reply, {error, Reason}, State}; Reply -> %% cache the response FIXME: handle bad response ? ets:insert(Tab, {{Id, N}, Reply}), {reply, Reply, State#state{total = Total}} end; Other -> %% ?LOGF("error ! (~p)~n",[Other],?WARN), {reply, {error, Other}, State} end; handle_call({get_user_agent}, _From, State) -> Tab = State#state.table, case ets:lookup(Tab, {http_user_agent, value}) of [] -> %% no match, ask the config_server ?Debug("user agents not found in cache~n"), UserAgents = ts_config_server:get_user_agents(), %% cache the response FIXME: handle bad response ? ?DebugF("Useragents: got from config_server~p~n",[UserAgents]), ets:insert(Tab, {{http_user_agent, value}, UserAgents}), {ok, Reply} = choose_user_agent(UserAgents), {reply, Reply, State}; [{_, [{_Freq, Value}]}] -> %single user agent defined {reply, Value, State}; [{_, empty }] -> {reply, "tsung", State}; [{_, UserAgents }] when is_list(UserAgents)-> {ok, Reply} = choose_user_agent(UserAgents), {reply, Reply, State} end; handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. %%-------------------------------------------------------------------- %% Function: handle_cast/2 %% Description: Handling cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info/2 %% Description: Handling all non call/cast messages %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate/2 %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- terminate(Reason, _State) -> ?LOGF("Die ! (~p)~n",[Reason],?ERR), ok. %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState} %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- choose_user_agent(empty) -> {ok, "tsung"}; choose_user_agent([{_P, Val}]) -> {ok, Val}; choose_user_agent(UserAgents) -> choose_user_agent(UserAgents, random:uniform(100),0). choose_user_agent([{P, Val} | _],Rand, Cur) when Rand =< P+Cur-> {ok, Val}; choose_user_agent([{P, _Val} | SList], Rand, Cur) -> choose_user_agent(SList, Rand, Cur+P). tsung-1.4.2/src/tsung/ts_http.erl0000644000201100017670000002744711701017117016475 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_http). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -behavior(ts_plugin). -include("ts_profile.hrl"). -include("ts_http.hrl"). -export([init_dynparams/0, add_dynparams/4, get_message/2, session_defaults/0, dump/2, parse/2, parse_bidi/2, parse_config/2, decode_buffer/2, new_session/0]). %%---------------------------------------------------------------------- %% Function: session_default/0 %% Purpose: default parameters for session (ack_type and persistent) %% Returns: {ok, "parse"|"no_ack"|"local", "true"|"false"} %%---------------------------------------------------------------------- session_defaults() -> %% we parse the server response, and continue if the tcp %% connection is closed {ok, true}. %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> #http{}. %% @spec decode_buffer(Buffer::binary(),Session::record(http)) -> NewBuffer::binary() %% @doc We need to decode buffer (remove chunks, decompress ...) for %% matching or dyn_variables %% @end decode_buffer(Buffer,#http{chunk_toread = -1, compressed={_,false}}) -> Buffer; decode_buffer(Buffer,#http{chunk_toread = -1, compressed={_,Val}}) -> {Headers, CompressedBody} = split_body(Buffer), Body = decompress(CompressedBody, Val), << Headers/binary, "\r\n\r\n", Body/binary >>; decode_buffer(Buffer,#http{compressed={_,Comp}})-> {Headers, Body} = decode_chunk(Buffer), ?DebugF("body is ~p~n",[Body]), RealBody = decompress(Body, Comp), ?DebugF("decoded buffer: ~p",[RealBody]), <>. %% @spec dump(protocol, {Request::ts_request(),Session::term(), Id::integer(), %% Host::string(),DataSize::integer()}) -> ok %% @doc log request and response summary %% @end dump(protocol,{#ts_request{param=HttpReq},HttpResp,UserId,Server,Size})-> Status = case element(2,HttpResp#http.status) of none -> "error_no_http_status"; % something is really wrong here ... http 0.9 response ? Int when is_integer(Int) -> integer_to_list(Int) end, Match = case erase(last_match) of undefined -> ""; {count, Val} -> atom_to_list(Val) end, Error = case erase(protocol_error) of undefined -> ""; Err -> atom_to_list(Err) end, Data=ts_utils:join(";",[integer_to_list(UserId), atom_to_list(HttpReq#http_request.method), Server, get(last_url), Status,integer_to_list(Size),Match, Error]), ts_mon:dump({protocol, self(), Data }); dump(_,_) -> ok. %%---------------------------------------------------------------------- %% Function: get_message/21 %% Purpose: Build a message/request , %% Args: #http_request %% Returns: binary %%---------------------------------------------------------------------- get_message(Req=#http_request{url=URL},#state_rcv{session=S}) -> put(last_url,URL), {get_message2(Req),S}. get_message2(Req=#http_request{method=get}) -> ts_http_common:http_no_body(?GET, Req); get_message2(Req=#http_request{method=head}) -> ts_http_common:http_no_body(?HEAD, Req); get_message2(Req=#http_request{method=delete}) -> ts_http_common:http_no_body(?DELETE, Req); get_message2(Req=#http_request{method=post}) -> ts_http_common:http_body(?POST, Req); get_message2(Req=#http_request{method=options}) -> ts_http_common:http_no_body(?OPTIONS, Req); get_message2(Req=#http_request{method=put}) -> ts_http_common:http_body(?PUT, Req). %%---------------------------------------------------------------------- %% Function: parse/2 %% Purpose: Parse the given data and return a new state %% Args: Data (binary) %% State (record) %% Returns: {NewState, Options for socket (list), Close} %%---------------------------------------------------------------------- parse(Data, State) -> ts_http_common:parse(Data, State). parse_bidi(Data, State) -> ts_plugin:parse_bidi(Data, State). %%---------------------------------------------------------------------- %% Function: parse_config/2 %%---------------------------------------------------------------------- parse_config(Element, Conf) -> ts_config_http:parse_config(Element, Conf). %%---------------------------------------------------------------------- %% Function: add_dynparams/4 %% Purpose: add dynamic parameters to build the message %% this is used for ex. for Cookies in HTTP %% Args: Subst (true|false), DynData = #dyndata, Param = #http_request, %% HostData = {Hostname, Port} %% Returns: #http_request or { #http_request, {Host, Port, Scheme}} %%---------------------------------------------------------------------- add_dynparams(false, DynData, Param, HostData) -> add_dynparams(DynData#dyndata.proto, Param, HostData); add_dynparams(true, DynData, OldReq=#http_request{url=OldUrl}, HostData={PrevHost, PrevPort, PrevProto}) -> Req = subst(OldReq, DynData#dyndata.dynvars), case Req#http_request.url of OldUrl -> add_dynparams(DynData#dyndata.proto,Req, HostData); "http" ++ Rest -> % URL has changed and is absolute URL=ts_config_http:parse_URL(Req#http_request.url), ?DebugF("URL dynamic subst: ~p~n",[URL]), NewPort = ts_config_http:set_port(URL), NewReq = add_dynparams(DynData#dyndata.proto, Req#http_request{host_header=undefined}, {URL#url.host, NewPort, PrevProto, URL#url.scheme}), % add scheme case OldUrl of "http"++_ -> % old url absolute: useproxy must be true NewReq#http_request{url="http"++Rest}; _ -> NewUrl=ts_config_http:set_query(URL), {NewReq#http_request{url=NewUrl}, {URL#url.host, NewPort,ts_config_http:set_scheme({URL#url.scheme,PrevProto})}} end; _ -> % Same host:port add_dynparams(DynData#dyndata.proto, Req, HostData) end. %% Function: add_dynparams/3 add_dynparams(DynData,Param=#http_request{host_header=undefined}, HostData )-> Header = case HostData of {Host,80, _,http}-> Host; {Host,443,_,https}-> Host; {Host,Port,_,_} -> Host++":"++ integer_to_list(Port); {Host,Port,_Proto} -> Host++":"++ integer_to_list(Port) end, ?DebugF("set host header dynamically: ~s~n",[Header]), add_dynparams(DynData, Param#http_request{host_header=Header},HostData); %% no cookies add_dynparams(#http_dyndata{cookies=[],user_agent=UA},Param, _) -> Param#http_request{user_agent=UA}; %% cookies add_dynparams(#http_dyndata{cookies=DynCookie,user_agent=UA}, Req, _) -> %% FIXME: should we use the Port value in the Cookie ? Cookie=DynCookie++Req#http_request.cookie, Req#http_request{cookie=Cookie,user_agent=UA}. init_dynparams() -> %% FIXME: optimization: suppress this call if we don't need %% customised users agents UserAgent = ts_session_cache:get_user_agent(), #dyndata{proto=#http_dyndata{user_agent=UserAgent}}. %%---------------------------------------------------------------------- %% @spec subst(Req::#http_request{}, DynData::#dynvars{} ) -> #http_request{} %% @doc Replace on the fly dynamic element of the HTTP request For %% the moment, we only do dynamic substitution in URL, body, %% userid, passwd, because we see no need for the other HTTP %% request parameters. %% @end %%---------------------------------------------------------------------- subst(Req=#http_request{url=URL, body=Body, headers = Headers, userid=UserId, passwd=Passwd}, DynData) -> Req#http_request{url = escape_url(ts_search:subst(URL, DynData)), body = ts_search:subst(Body, DynData), headers = lists:foldl(fun ({Name, Value}, Result) -> [{Name, ts_search:subst(Value, DynData)} | Result] end, [], Headers), userid = ts_search:subst(UserId, DynData), passwd = ts_search:subst(Passwd, DynData)}. %% URL substitution, we must escape some characters %% currently, we only handle space conversion to %20 escape_url(URL)-> re:replace(URL," ","%20",[{return,list},global]). decompress(Buffer,gzip)-> zlib:gunzip(Buffer); decompress(Buffer,uncompress)-> zlib:uncompress(Buffer); decompress(Buffer,deflate)-> zlib:unzip(Buffer); decompress(Buffer,false)-> Buffer; decompress(Buffer,Else)-> ?LOGF("Unknown compression method, skip decompression ~p",[Else],?WARN), Buffer. decode_chunk(Data)-> decode_chunk_header(Data,<<>>). decode_chunk_header(<>,Headers) when CRLF == << "\r\n\r\n">> -> decode_chunk_size(Data,Headers,<< >>, << >>); decode_chunk_header(<>, Head) -> decode_chunk_header(Data, <> ). decode_chunk_size(<< >>, Headers, Body, _Digits) -> {Headers, Body}; decode_chunk_size(<>, Headers, Body, <<>>) when Head == << "\r\n" >> -> %last CRLF, remove {Headers, Body}; decode_chunk_size(<>, Headers, Body, <<>>) when Head == << "\r\n" >> -> % CRLF but no digits, end of chunk ?Debug("decode chunk: crlf, no digit"), decode_chunk_size(Data, Headers, Body, <<>>); decode_chunk_size(<>, Headers, Body,Digits) when Head == << "\r\n" >> -> case httpd_util:hexlist_to_integer(binary_to_list(Digits)) of 0 -> decode_chunk_size(Data, Headers, Body ,<<>>); Size -> ?DebugF("decode chunk size ~p~n",[Size]), << Chunk:Size/binary, Tail/binary >> = Data, decode_chunk_size(Tail, Headers, << Body/binary, Chunk/binary>> ,<<>>) end; decode_chunk_size(<>, Headers, Body, PrevDigit) -> ?DebugF("chunk one digit ~p~n",[Digit]), decode_chunk_size(Data, Headers, Body, <>). split_body(Data) -> case re:run(Data,"(.*)\r\n\r\n(.*)$",[{capture,all_but_first,binary},ungreedy,dotall]) of nomatch -> Data; {match, [Header,Body]} -> {Header,<< Body/binary,"\n" >>}; _ -> Data end. tsung-1.4.2/src/tsung/ts_digest.erl0000644000201100017670000000650011701017117016760 0ustar nniclausdream%%% %%% Created: Apr 2006 by Jason Tucker %%% %%% Modified by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_digest). -author('jasonwtucker@gmail.com'). -export([ digest/2, sip_digest/4, md5hex/1, shahex/1, tohex/1 ]). %%%---------------------------------------------------------------------- %%% Func: sip_digest/4 %%%---------------------------------------------------------------------- sip_digest(Nonce, Jid, Realm, Passwd) -> HA1 = md5hex(Jid ++ ":" ++ Realm ++ ":" ++ Passwd), HA2 = md5hex("REGISTER:" ++ Jid), INTEGRITY = md5hex(Nonce ++ ":" ++ HA2), HA3 = md5hex(HA1 ++ ":" ++ INTEGRITY), {HA3,INTEGRITY}. %%%---------------------------------------------------------------------- %%% Func: digest/2 %%% Computes XMPP digest password described in JEP-0078 %%%---------------------------------------------------------------------- digest(Sid, Passwd) -> HA1 = shahex(Sid ++ Passwd), {HA1}. %%%---------------------------------------------------------------------- %%% Func: md5hex/1 %%%---------------------------------------------------------------------- md5hex(Clear) -> tohex(binary_to_list(erlang:md5(Clear))). %%%---------------------------------------------------------------------- %%% Func: shahex/1 %%%---------------------------------------------------------------------- shahex(Clear) -> ShaVal= case catch crypto:sha(Clear) of {'EXIT',_} -> crypto:start(), crypto:sha(Clear); Sha -> Sha end, tohex(binary_to_list(ShaVal)). %%%---------------------------------------------------------------------- %%% Func: tohex/1 %%% Purpose: convert list of integers to hexadecimal string %%%---------------------------------------------------------------------- tohex(A)-> Fun = fun(X)-> ts_utils:to_lower(padhex(httpd_util:integer_to_hexlist(X))) end, lists:flatten( lists:map(Fun, A) ). %%%---------------------------------------------------------------------- %%% Func: padhex/1 %%% Purpose: needed because httpd_util:integer_to_hexlist returns hex %%% values <10 as only 1 character, ie. "0F" is simply returned as %%% "F". For our digest, we need these leading zeros to be present. %%% ---------------------------------------------------------------------- padhex(S=[_Char]) -> "0" ++ S; padhex(String) -> String. tsung-1.4.2/src/tsung/ts_ldap.erl0000644000201100017670000003102711701017117016423 0ustar nniclausdream%%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. %%% File : ts_ldap.erl %%% Author : Pablo Polvorin %%% Purpose : LDAP plugin -module(ts_ldap). -behavior(ts_plugin). -export([init_dynparams/0, add_dynparams/4, get_message/2, session_defaults/0, dump/2, parse/2, parse_bidi/2, parse_config/2, decode_buffer/2, new_session/0 ]). -include("ts_profile.hrl"). -include("ts_ldap.hrl"). -include("ELDAPv3.hrl"). %%---------------------------------------------------------------- %%-----Configuration parsing %%---------------------------------------------------------------- parse_config(Element, Conf) -> ts_config_ldap:parse_config(Element,Conf). %%---------------------------------------------------------------------- %% Function: session_default/0 %% Purpose: default parameters for session %% Returns: {ok, persistent = true|false} %%---------------------------------------------------------------------- session_defaults() -> {ok, true}. %% @spec decode_buffer(Buffer::binary(),Session::record(ldap)) -> NewBuffer::binary() %% @doc We need to decode buffer (remove chunks, decompress ...) for %% matching or dyn_variables %% @end decode_buffer(Buffer,_) -> Buffer. %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> ts_ldap_common:empty_packet_state(). %%FIXME: this won't be necessary when the SSL module support the asn1 %% packet type. At this moment we are parsing the packet by %% ourselves, even over plain gen_tcp sockets, which can %% recognize asn1... dump(A,B)-> ts_plugin:dump(A,B). parse_bidi(A, B) -> ts_plugin:dump(A,B). %%---------------------------------------------------------------------- %% Function: parse/2 %% Purpose: parse the response from the server and keep information %% about the response in State#state_rcv.session %% Args: Data (binary), State (#state_rcv) %% Returns: {NewState, Options for socket (list), Close = true|false} %%---------------------------------------------------------------------- parse(closed, State) -> {State#state_rcv{ack_done = true, datasize=0}, [], true}; %% Shortcut, when using ssl i'm getting lots <<>> data. Also, next %% clause is an infinite loop if data is <<>> parse(<<>>,State) -> {State,[],false}; %% new response, compute data size (for stats) parse(Data, State=#state_rcv{acc = [], datasize= 0}) -> parse(Data, State#state_rcv{datasize= size(Data)}); parse(Data, State=#state_rcv{acc = [], dyndata=_DynData,session=Session,datasize=PrevSize}) -> St = ts_ldap_common:push(Data,Session), parse_packets(State#state_rcv{session=St,datasize =PrevSize + size(Data) },St). %% Can read more than one entire asn1 packet from the network. Read %% packets until either there are no more packets available in the %% buffer (ack_done=false), or the ack_done flag was set true by the %% appropiate parse_ldap_response parse_packets(State,Asn1St) -> case ts_ldap_common:get_packet(Asn1St) of {none,NewAsn1St} -> {State#state_rcv{ack_done=false,session=NewAsn1St},[],false}; {packet,Packet,NewAsn1St} -> {ok,Resp} = asn1rt:decode('ELDAPv3', 'LDAPMessage', Packet), parse_packet(Resp,State#state_rcv{session = NewAsn1St}) end. parse_packet(Resp,State) -> R = parse_ldap_response(Resp,State), {St,_Opts,_Close} = R, if St#state_rcv.ack_done == true -> R; St#state_rcv.ack_done == false -> parse_packets(St,St#state_rcv.session) end. %%TODO: see if its useful to count how many response records we get for each search. parse_ldap_response( #'LDAPMessage'{protocolOp = {bindResponse,Result}},State)-> case Result#'BindResponse'.resultCode of success -> ?Debug("Bind successful~n"), ts_mon:add({ count, ldap_bind_ok}), {State#state_rcv{ack_done=true},[],false}; _Error -> ts_mon:add({ count, ldap_bind_error}), %FIXME: retry,fail,etc. should be configurable ?LOG("Bind fail~n",?INFO), {State#state_rcv{ack_done=true},[],true} end; parse_ldap_response( #'LDAPMessage'{protocolOp = {'searchResDone',_R}},State) -> ?DebugF("LDAP Search response Done ~p~n",[_R]), {State#state_rcv{ack_done=true},[],false}; %%Response done, mark as acknowledged parse_ldap_response( #'LDAPMessage'{protocolOp = {'searchResEntry',R}},State) -> NewState = acumulate_result(R,State), ?DebugF("LDAP search response Entry ~p~n",[R]), {NewState#state_rcv{ack_done=false},[],false}; parse_ldap_response(#'LDAPMessage'{protocolOp = {'searchResRef',_R}},State) -> ?DebugF("LDAP search response Ref ~p~n",[_R]), {State#state_rcv{ack_done=false},[],false}; %% When get a possitive response to a startTLS command, inmediatly start ssl over that socket. parse_ldap_response(#'LDAPMessage'{protocolOp = {'extendedResp',ExtResponse }},State) -> case ExtResponse#'ExtendedResponse'.resultCode of success -> #ts_request{param = LDAPRequest} = State#state_rcv.request, %%Warnning: this won't work unless using a really recent OTP {ok,Ssl_socket} = ssl:connect(State#state_rcv.socket,[{cacertfile,LDAPRequest#ldap_request.cacertfile}, {certfile,LDAPRequest#ldap_request.certfile}, {keyfile,LDAPRequest#ldap_request.keyfile} ]), {State#state_rcv{socket=Ssl_socket,protocol=ssl,ack_done=true},[],false}; _Error -> ts_mon:add({ count, ldap_starttls_error}), ?LOG("StartTLS fail",?INFO), {State#state_rcv{ack_done=true},[],false} end; parse_ldap_response(#'LDAPMessage'{protocolOp = {'addResponse',Result}},State) -> case Result#'LDAPResult'.resultCode of success -> {State#state_rcv{ack_done=true},[],false}; _Error -> ts_mon:add({ count, ldap_add_error}), ?LOG("Add fail",?INFO), {State#state_rcv{ack_done=true},[],true} end; parse_ldap_response(#'LDAPMessage'{protocolOp = {'modifyResponse',Result}},State) -> case Result#'LDAPResult'.resultCode of success -> {State#state_rcv{ack_done=true},[],false}; _Error -> ts_mon:add({ count, ldap_modify_error}), ?LOG("Modify fail",?INFO), {State#state_rcv{ack_done=true},[],true} end; parse_ldap_response(Resp,State) -> ?LOGF("Got unexpected response: ~p~n",[Resp],?INFO), ts_mon:add({ count, ldap_unexpected_msg_resp}), {State#state_rcv{ack_done=true},[],false}. acumulate_result(R,State = #state_rcv{request = #ts_request{param=#ldap_request{result_var = ResultVar}}, dyndata=DynData}) -> case ResultVar of none -> State; {ok,VarName} -> State#state_rcv{dyndata=accumulate_dyndata(R,VarName,DynData)} end. accumulate_dyndata(R,VarName,DynData = #dyndata{dynvars=DynVars}) when is_list(DynVars)-> Prev = proplists:get_value(VarName,DynVars,[]), NewDynVars = lists:keystore(VarName,1,DynVars,{VarName,[R|Prev]}), DynData#dyndata{dynvars=NewDynVars}; accumulate_dyndata(R,VarName,DynData) -> DynData#dyndata{dynvars=[{VarName,[R]}]}. %%---------------------------------------------------------------------- %% Function: add_dynparams/4 %% Purpose: add dynamic parameters to build the message %% Args: Subst (true|false), DynData = #dyndata, Param = #myproto_request %% Host = String %% Returns: #ldap_request %% %%---------------------------------------------------------------------- add_dynparams(false, _DynData, Param, _HostData) -> Param; %% Bind message. Substitution on user and password. add_dynparams(true, DynData, Param = #ldap_request{type=bind,user=User,password=Password}, _HostData) -> Param#ldap_request{user=ts_search:subst(User,DynData#dyndata.dynvars),password=ts_search:subst(Password,DynData#dyndata.dynvars)}; %% Search message. Only perfom substitutions on the filter of the search requests. %% The filter text was already parsed into a tree-like struct, substitution %% is perfomed in the "leaf" of this tree. add_dynparams(true, DynData, Param = #ldap_request{type=search, filter = Filter}, _HostData) -> Param#ldap_request{filter = subs_filter(Filter,DynData#dyndata.dynvars)}; %% Add message. Substitution on DN and attrs values. add_dynparams(true,DynData,Param = #ldap_request{type=add,dn=DN,attrs=Attrs},_HostData) -> Param#ldap_request{dn=ts_search:subst(DN,DynData#dyndata.dynvars), attrs=subs_attrs(Attrs,DynData#dyndata.dynvars)}; %% Modification message. Substitution on DN and attrs values. add_dynparams(true,DynData,Param = #ldap_request{type=modify,dn=DN,modifications=Modifications},_HostData) -> SubsModifications = [{Operation,AttrType,[ts_search:subst(Value,DynData#dyndata.dynvars) || Value <- Values]} || {Operation,AttrType,Values}<- Modifications ], Param#ldap_request{dn=ts_search:subst(DN,DynData#dyndata.dynvars), attrs=SubsModifications}. subs_filter({Rel,Filters},DynVars) when (Rel == 'and') or (Rel == 'or') -> {Rel,lists:map(fun(F)-> subs_filter(F,DynVars) end,Filters)}; subs_filter({'not',Filter},DynVars) -> {'not',subs_filter(Filter,DynVars)}; subs_filter({BinRel,Attr,Val},DynVars) when (BinRel == 'aprox') or (BinRel == 'get') or (BinRel == 'let') or (BinRel=='eq')-> {BinRel,Attr,ts_search:subst(Val,DynVars)}; subs_filter({substring,Attr,Substrings},DynVars) -> {substring,Attr,lists:map(fun({Pos,Val}) -> {Pos,ts_search:subst(Val,DynVars)} end, Substrings)}. subs_attrs(Attrs,DynVars) -> [{Attr,[ts_search:subst(Value,DynVars) || Value <- Values]} || {Attr,Values}<-Attrs ]. %%---------------------------------------------------------------------- %% Function: init_dynparams/0 %% Purpose: initial dynamic parameters value %% Returns: #dyndata %%---------------------------------------------------------------------- init_dynparams() -> #dyndata{proto=none}. %%---------------------------------------------------------------- %%-----Messages %%---------------------------------------------------------------- get_message(Req,#state_rcv{session=S}) -> {get_message2(Req),S}. get_message2(#ldap_request{type=bind,user=User,password=Password}) -> X = ts_ldap_common:bind_msg(ts_msg_server:get_id(),User,Password), iolist_to_binary(X); %% TODO: we really need to consult the central msg_server to find a session-specific id?, any reason to prevent %% the same id to be used in different sessions? get_message2(#ldap_request{type=search,base=Base,scope=Scope,filter=Filter,attributes=Attributes}) -> EncodedFilter = ts_ldap_common:encode_filter(Filter), X = ts_ldap_common:search_msg(ts_msg_server:get_id(),Base,Scope,EncodedFilter,Attributes), iolist_to_binary(X); get_message2(#ldap_request{type=start_tls}) -> X = ts_ldap_common:start_tls_msg(ts_msg_server:get_id()), iolist_to_binary(X); get_message2(#ldap_request{type=unbind}) -> iolist_to_binary(ts_ldap_common:unbind_msg(ts_msg_server:get_id())); get_message2(#ldap_request{type=add,dn=DN,attrs=Attrs}) -> iolist_to_binary(ts_ldap_common:add_msg(ts_msg_server:get_id(),DN,Attrs)); get_message2(#ldap_request{type=modify,dn=DN,modifications=Modifications}) -> iolist_to_binary(ts_ldap_common:modify_msg(ts_msg_server:get_id(),DN,Modifications)). tsung-1.4.2/src/tsung/ts_sup.erl0000644000201100017670000000735711701017117016323 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_sup). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -include("ts_profile.hrl"). -behaviour(supervisor). %% External exports -export([start_link/0, start_cport/1, has_cport/1]). %% supervisor callbacks -export([init/1]). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start_link() -> ?LOG("starting supervisor ...~n",?INFO), supervisor:start_link({local, ?MODULE}, ?MODULE, []). start_cport({Node, CPortName}) -> ?LOGF("starting cport server ~p on node ~p ~n",[CPortName, Node],?INFO), PortServer = {CPortName, {ts_cport, start_link, [CPortName]}, transient, 2000, worker, [ts_cport]}, supervisor:start_child({?MODULE, Node}, PortServer). has_cport(Node) -> Children = supervisor:which_children({?MODULE, Node}), lists:any(fun({_,_,_,[ts_cport]}) -> true; (_) -> false end, Children). %%%---------------------------------------------------------------------- %%% Callback functions from supervisor %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, {SupFlags, [ChildSpec]}} | %% ignore | %% {error, Reason} %%---------------------------------------------------------------------- init([]) -> ?LOG("starting",?INFO), ClientsSup = {ts_client_sup, {ts_client_sup, start_link, []}, permanent, 2000, supervisor, [ts_client_sup]}, Launcher = {ts_launcher, {ts_launcher, start, []}, transient, 2000, worker, [ts_launcher]}, StaticLauncher = {ts_launcher_static, {ts_launcher_static, start, []}, transient, 2000, worker, [ts_launcher_static]}, LauncherManager = {ts_launcher_mgr, {ts_launcher_mgr, start, []}, transient, 2000, worker, [ts_launcher_mgr]}, SessionCache = {ts_session_cache, {ts_session_cache, start, []}, transient, 2000, worker, [ts_session_cache]}, MonCache = {ts_mon_cache, {ts_mon_cache, start, []}, transient, 2000, worker, [ts_mon_cache]}, IPScan = {ts_ip_scan, {ts_ip_scan, start_link, []}, transient, 2000, worker, [ts_ip_scan]}, {ok,{{one_for_one,?retries,10}, [IPScan, LauncherManager, SessionCache, MonCache,ClientsSup, StaticLauncher,Launcher ]}}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- tsung-1.4.2/src/tsung/ts_client.erl0000644000201100017670000016200411701017117016761 0ustar nniclausdream%%% This code was developped by IDEALX (http://IDEALX.org/) and %%% contributors (their names can be found in the CONTRIBUTORS file). %%% Copyright (C) 2000-2001 IDEALX %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% Created : 15 Feb 2001 by Nicolas Niclausse %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_client). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -modified_by('jflecomte@IDEALX.com'). -behaviour(gen_fsm). % two state: wait_ack | think %%% if bidi is true (for bidirectional), the server can send data %%% to the client at anytime (full bidirectional protocol, as jabber %%% for ex) -include("ts_profile.hrl"). -include("ts_config.hrl"). -define(MAX_RETRIES,3). % max number of connection retries -define(RETRY_TIMEOUT,10000). % waiting time between retries (msec) %% External exports -export([start/1, next/1]). %% gen_server callbacks -export([init/1, wait_ack/2, think/2,handle_sync_event/4, handle_event/3, handle_info/3, terminate/3, code_change/4]). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- %% @spec start(Opts::{Session::#session{},IP::tuple(),Server::#server{}, %% Id::integer()}) -> {ok, Pid::pid()} | ignore | {error, Error::term()} %% @doc Start a new session start(Opts) -> ?DebugF("Starting with opts: ~p~n",[Opts]), gen_fsm:start_link(?MODULE, Opts, []). %%---------------------------------------------------------------------- %% @spec next({pid()}) -> ok %% @doc Purpose: continue with the next request (used for global ack) %% @end %%---------------------------------------------------------------------- next({Pid}) -> gen_fsm:send_event(Pid, next_msg). %%%---------------------------------------------------------------------- %%% Callback functions from gen_server %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, StateName, State} | %% {ok, StateName, State, Timeout} | %% ignore | %% {stop, Reason} %%---------------------------------------------------------------------- init(#session{ id = SessionId, persistent = Persistent, bidi = Bidi, hibernate = Hibernate, rate_limit = RateLimit, proto_opts = ProtoOpts, size = Count, client_ip = IP, userid = Id, dump = Dump, seed = Seed, server = Server, type = CType}) -> ?DebugF("Init ... started with count = ~p~n",[Count]), case Seed of now -> ts_utils:init_seed(); SeedVal when is_integer(SeedVal) -> %% use a different but fixed seed for each client. ts_utils:init_seed({Id,SeedVal}) end, ?DebugF("Get dynparams for ~p~n",[CType]), DynData = CType:init_dynparams(), NewDynVars = ts_dynvars:set(tsung_userid,integer_to_list(Id), DynData#dyndata.dynvars), NewDynData = DynData#dyndata{dynvars=NewDynVars}, StartTime= now(), set_thinktime(?short_timeout), ?DebugF("IP param: ~p~n",[IP]), NewIP = case IP of { TmpIP, -1 } -> {ok, MyHostName} = ts_utils:node_to_hostname(node()), RealIP = case TmpIP of {scan, Interface} -> ts_ip_scan:get_ip(Interface); _ -> TmpIP end, {RealIP, "cport-" ++ MyHostName}; {{scan, Interface}, PortVal } -> ?DebugF("Must scan interface: ~p~n",[Interface]), { ts_ip_scan:get_ip(Interface), PortVal }; Val -> Val end, {RateConf,SizeThresh} = case RateLimit of Token=#token_bucket{} -> Thresh=lists:min([?size_mon_thresh,Token#token_bucket.burst]), {Token#token_bucket{last_packet_date=StartTime}, Thresh}; undefined -> {undefined, ?size_mon_thresh} end, {ok, think, #state_rcv{ port = Server#server.port, host = Server#server.host, session_id = SessionId, bidi = Bidi, protocol = Server#server.type, clienttype = CType, session = CType:new_session(), persistent = Persistent, starttime = StartTime, dump = Dump, proto_opts = ProtoOpts, size_mon = SizeThresh, size_mon_thresh = SizeThresh, count = Count, ip = NewIP, id = Id, hibernate = Hibernate, maxcount = Count, rate_limit = RateConf, dyndata = NewDynData }}. %%-------------------------------------------------------------------- %% Func: StateName/2 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%-------------------------------------------------------------------- think(next_msg,State=#state_rcv{protocol=P,socket=S}) -> ?LOG("Global ack received, continue~n", ?DEB), NewSocket = ts_utils:inet_setopts(P, S, [{active, once} ]), handle_next_action(State#state_rcv{socket=NewSocket }). wait_ack(next_msg,State=#state_rcv{request=R}) when R#ts_request.ack==global-> NewSocket = ts_utils:inet_setopts(State#state_rcv.protocol, State#state_rcv.socket, [{active, once} ]), {PageTimeStamp, _} = update_stats(State), handle_next_action(State#state_rcv{socket=NewSocket, page_timestamp=PageTimeStamp}); wait_ack(timeout,State) -> ?LOG("Error: timeout receive in state wait_ack~n", ?ERR), ts_mon:add({ count, timeout }), {stop, normal, State}. %%-------------------------------------------------------------------- %% Func: handle_event/3 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%-------------------------------------------------------------------- handle_event(Event, SName, StateData) -> ?LOGF("Unknown event (~p) received in state ~p, abort",[Event,SName],?ERR), {stop, unknown_event, StateData}. %%-------------------------------------------------------------------- %% Func: handle_sync_event/4 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {reply, Reply, NextStateName, NextStateData} | %% {reply, Reply, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} | %% {stop, Reason, Reply, NewStateData} %%-------------------------------------------------------------------- handle_sync_event(_Event, _From, StateName, StateData) -> Reply = ok, {reply, Reply, StateName, StateData}. %%---------------------------------------------------------------------- %% Func: handle_info/2 %% Returns: {next_state, StateName, State} | %% {next_state, StateName, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- %% inet data handle_info({NetEvent, _Socket, Data}, wait_ack, State=#state_rcv{rate_limit=TokenParam}) when NetEvent==tcp; NetEvent==ssl -> ?DebugF("TCP data received: size=~p ~n",[size(Data)]), NewTokenParam = case TokenParam of undefined -> undefined; #token_bucket{rate=R,burst=Burst,current_size=S0, last_packet_date=T0} -> {S1,_Wait}=token_bucket(R,Burst,S0,T0,size(Data),now(),true), TokenParam#token_bucket{current_size=S1, last_packet_date=now()} end, {NewState, Opts} = handle_data_msg(Data, State), NewSocket = ts_utils:inet_setopts(NewState#state_rcv.protocol, NewState#state_rcv.socket, [{active, once} | Opts]), case NewState#state_rcv.ack_done of true -> handle_next_action(NewState#state_rcv{socket=NewSocket,rate_limit=NewTokenParam, ack_done=false}); false -> TimeOut=(NewState#state_rcv.proto_opts)#proto_opts.idle_timeout, {next_state, wait_ack, NewState#state_rcv{socket=NewSocket,rate_limit=NewTokenParam}, TimeOut} end; handle_info({erlang, _Socket, Data}, wait_ack, State) -> ?DebugF("erlang function result received: size=~p ~n",[size(term_to_binary(Data))]), case handle_data_msg(Data, State) of {NewState=#state_rcv{ack_done=true}, _Opts} -> handle_next_action(NewState#state_rcv{ack_done=false}); {NewState, _Opts} -> TimeOut=(NewState#state_rcv.proto_opts)#proto_opts.idle_timeout, {next_state, wait_ack, NewState, TimeOut} end; handle_info({udp, Socket,_IP,_InPortNo, Data}, StateName, State) -> ?DebugF("UDP packet received: size=~p ~n",[size(Data)]), %% we don't care about IP,InPortNo, do the same as for a tcp connection: handle_info({tcp, Socket, Data}, StateName, State); %% inet close messages; persistent session, waiting for ack handle_info({NetEvent, _Socket}, wait_ack, State = #state_rcv{persistent=true}) when NetEvent==tcp_closed; NetEvent==ssl_closed -> ?LOG("connection closed while waiting for ack",?INFO), set_connected_status(false), {NewState, _Opts} = handle_data_msg(closed, State), %% socket should be closed in handle_data_msg handle_next_action(NewState#state_rcv{socket=none}); %% inet close messages; persistent session handle_info({NetEvent, Socket}, think, State = #state_rcv{persistent=true}) when NetEvent==tcp_closed; NetEvent==ssl_closed -> ?LOG("connection closed, stay alive (persistent)",?INFO), set_connected_status(false), catch ts_utils:close_socket(State#state_rcv.protocol, Socket), % mandatory for ssl {next_state, think, State#state_rcv{socket = none}}; %% inet close messages handle_info({NetEvent, Socket}, _StateName, State) when NetEvent==tcp_closed; NetEvent==ssl_closed -> ?LOG("connection closed, abort", ?WARN), %% the connexion was closed after the last msg was sent, stop quietly ts_mon:add({ count, error_closed }), set_connected_status(false), ts_utils:close_socket(State#state_rcv.protocol, Socket), % mandatory for ssl {stop, normal, State#state_rcv{socket = none}}; %% inet errors handle_info({NetError, _Socket, Reason}, wait_ack, State) when NetError==tcp_error; NetError==ssl_error -> ?LOGF("Net error (~p): ~p~n",[NetError, Reason], ?WARN), CountName="error_inet_"++atom_to_list(Reason), ts_mon:add({ count, list_to_atom(CountName) }), set_connected_status(false), {stop, normal, State}; %% timer expires, no more messages to send handle_info({timeout, _Ref, end_thinktime}, think, State= #state_rcv{ count=0 }) -> ?LOG("Session ending ~n", ?INFO), {stop, normal, State}; %% the timer expires handle_info({timeout, _Ref, end_thinktime}, think, State ) -> handle_next_action(State); handle_info(timeout, StateName, State ) -> ?LOGF("Error: timeout receive in state ~p~n",[StateName], ?ERR), ts_mon:add({ count, timeout }), {stop, normal, State}; % bidirectional protocol handle_info({NetEvent, Socket, Data}, think,State=#state_rcv{ clienttype=Type, bidi=true,host=Host,port=Port}) when ((NetEvent == tcp) or (NetEvent==ssl)) -> ts_mon:rcvmes({State#state_rcv.dump, self(), Data}), ts_mon:add({ sum, size_rcv, size(Data)}), Proto = State#state_rcv.protocol, ?LOG("Data received from socket (bidi) in state think~n",?INFO), NewState = case Type:parse_bidi(Data, State) of {nodata, State2} -> ?LOG("Bidi: no data ~n",?DEB), ts_mon:add({count, async_unknown_data_rcv}), State2; {Data2, State2} -> ts_mon:add([{ sum, size_sent, size(Data2)},{count, async_data_sent}]), ts_mon:sendmes({State#state_rcv.dump, self(), Data2}), ?LOG("Bidi: send data back to server~n",?DEB), send(Proto,Socket,Data2,Host,Port), %FIXME: handle errors ? State2 end, NewSocket = ts_utils:inet_setopts(Proto, State#state_rcv.socket, [{active, once}]), {next_state, think, NewState#state_rcv{socket=NewSocket}}; % bidi is false, but parse is also false: continue even if we get data handle_info({NetEvent, _Socket, Data}, think, State = #state_rcv{request=Req} ) when (Req#ts_request.ack /= parse) and ((NetEvent == tcp) or (NetEvent==ssl)) -> ts_mon:rcvmes({State#state_rcv.dump, self(), Data}), ts_mon:add({ sum, size_rcv, size(Data)}), ?LOGF("Data receive from socket in state think, ack=~p, skip~n", [Req#ts_request.ack],?NOTICE), ?DebugF("Data was ~p~n",[Data]), NewSocket = ts_utils:inet_setopts(State#state_rcv.protocol, State#state_rcv.socket, [{active, once}]), {next_state, think, State#state_rcv{socket=NewSocket}}; handle_info({NetEvent, _Socket, Data}, think, State) when (NetEvent == tcp) or (NetEvent==ssl) -> ts_mon:rcvmes({State#state_rcv.dump, self(), Data}), ts_mon:add({ count, error_unknown_data }), ?LOG("Data receive from socket in state think, stop~n", ?ERR), ?DebugF("Data was ~p~n",[Data]), {stop, normal, State}; handle_info({inet_reply, _Socket,ok}, StateName, State ) -> ?LOGF("inet_reply ok received in state ~p~n",[StateName],?NOTICE), {next_state, StateName, State}; handle_info(Msg, StateName, State ) -> ?LOGF("Error: Unknown msg ~p receive in state ~p, stop~n", [Msg,StateName], ?ERR), ts_mon:add({ count, error_unknown_msg }), {stop, normal, State}. %%-------------------------------------------------------------------- %% Func: terminate/3 %% Purpose: Shutdown the fsm %% Returns: any %%-------------------------------------------------------------------- terminate(normal, _StateName,State) -> finish_session(State); terminate(Reason, StateName, State) -> ?LOGF("Stop in state ~p, reason= ~p~n",[StateName,Reason],?NOTICE), ts_mon:add({ count, error_unknown }), finish_session(State). %%-------------------------------------------------------------------- %% Func: code_change/4 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState, NewStateData} %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: handle_next_action/1 %% Purpose: handle next action: thinktime, transaction or #ts_request %% Args: State %%---------------------------------------------------------------------- handle_next_action(State=#state_rcv{count=0}) -> ?LOG("Session ending ~n", ?INFO), {stop, normal, State}; handle_next_action(State) -> Count = State#state_rcv.count-1, case set_profile(State#state_rcv.maxcount,State#state_rcv.count,State#state_rcv.session_id) of {thinktime, TmpThink} -> Think = case TmpThink of "%%"++_Tail -> Raw=ts_search:subst(TmpThink,(State#state_rcv.dyndata)#dyndata.dynvars), ts_utils:list_to_number(Raw)*1000; Val -> Val end, ?DebugF("Starting new thinktime ~p~n", [Think]), case (set_thinktime(Think) >= State#state_rcv.hibernate) of true -> {next_state, think, State#state_rcv{count=Count},hibernate}; _ -> {next_state, think, State#state_rcv{count=Count}} end; {transaction, start, Tname} -> Now = now(), ?LOGF("Starting new transaction ~p (now~p)~n", [Tname,Now], ?INFO), TrList = State#state_rcv.transactions, NewState = State#state_rcv{transactions=[{Tname,Now}|TrList], count=Count}, handle_next_action(NewState); {transaction, stop, Tname} -> Now = now(), ?LOGF("Stopping transaction ~p (~p)~n", [Tname, Now], ?INFO), TrList = State#state_rcv.transactions, {value, {_, Tr}} = lists:keysearch(Tname, 1, TrList), Elapsed = ts_utils:elapsed(Tr, Now), ts_mon:add({sample, Tname, Elapsed}), NewState = State#state_rcv{transactions=lists:keydelete(Tname,1,TrList), count=Count}, handle_next_action(NewState); {setdynvars,SourceType,Args,VarNames} -> DynData=State#state_rcv.dyndata, Result = set_dynvars(SourceType,Args,VarNames,DynData), NewDynVars = ts_dynvars:set(VarNames,Result,DynData#dyndata.dynvars), NewDynData = DynData#dyndata{dynvars=NewDynVars}, ?DebugF("set dynvars: ~p ~p~n",[NewDynVars, NewDynData]), handle_next_action(State#state_rcv{dyndata = NewDynData, count=Count}); {ctrl_struct,CtrlData} -> ctrl_struct(CtrlData,State,Count); Request=#ts_request{} -> handle_next_request(Request, State); {change_type, NewCType, Server, Port, PType, Store, Restore} -> ?DebugF("Change client type, use: ~p ~p~n",[NewCType, [Server , Port, PType, Store, Restore]]), DynData=State#state_rcv.dyndata, case Store of true -> % keep state put({state, State#state_rcv.clienttype} , {State#state_rcv.socket,State#state_rcv.session,DynData#dyndata.proto}); false -> % don't keep state of old type, close connection ts_utils:close_socket(State#state_rcv.protocol, State#state_rcv.socket), set_connected_status(false) end, {Socket,Session,ProtoDynData} = case {Restore, get({state,NewCType})} of {true,{OldSocket, OldSession,OldProtoDynData}} -> % restore is true and we have something stored {OldSocket, OldSession,OldProtoDynData}; {_,_} -> % nothing to restore, or no restore asked, set new session DD=NewCType:init_dynparams(), {none,NewCType:new_session(),DD#dyndata.proto} end, NewDynData=DynData#dyndata{proto=ProtoDynData}, NewState=State#state_rcv{session=Session,socket=Socket,count=Count,clienttype=NewCType,protocol=PType,port=Port,host=Server,dyndata=NewDynData}, handle_next_action(NewState); {set_option, undefined, rate_limit, {Rate, Burst}} -> ?LOGF("Set rate limits for client: rate=~p, burst=~p~n",[Rate,Burst],?DEB), RateConf=#token_bucket{rate=Rate,burst=Burst,last_packet_date=now()}, Thresh=lists:min([Burst,State#state_rcv.size_mon_thresh]), handle_next_action(State#state_rcv{size_mon=Thresh,size_mon_thresh=Thresh,rate_limit=RateConf,count=Count}); {set_option, Type, Name, Args} -> NewState=Type:set_option(Name,Args,State), handle_next_action(NewState); Other -> ?LOGF("Error: set profile return value is ~p (count=~p)~n",[Other,Count],?ERR), {stop, set_profile_error, State} end. %%---------------------------------------------------------------------- %% @spec set_dynvars (Type::erlang|random|urandom|file, Args::tuple(), %% Variables::list(), DynData::#dyndata{}) -> list() %% @doc setting the value of several dynamic variables at once. %% @end %%---------------------------------------------------------------------- set_dynvars(erlang,{Module,Callback},_Vars,DynData) -> Module:Callback({self(),DynData#dyndata.dynvars}); set_dynvars(code,Fun,_Vars,DynData) -> Fun({self(),DynData#dyndata.dynvars}); set_dynvars(random,{number,Start,End},Vars,_DynData) -> lists:map(fun(_) -> integer_to_list(ts_stats:uniform(Start,End)) end,Vars); set_dynvars(random,{string,Length},Vars,_DynData) -> R = fun(_) -> ts_utils:randomstr(Length) end, lists:map(R,Vars); set_dynvars(urandom,{string,Length},Vars,_DynData) -> %% not random, but much faster RS= ts_utils:urandomstr(Length), N=length(Vars), lists:duplicate(N,RS); set_dynvars(file,{random,FileId,Delimiter},_Vars,_DynData) -> {ok,Line} = ts_file_server:get_random_line(FileId), ts_utils:split(Line,Delimiter); set_dynvars(file,{iter,FileId,Delimiter},_Vars,_DynData) -> {ok,Line} = ts_file_server:get_next_line(FileId), ts_utils:split(Line,Delimiter); set_dynvars(jsonpath,{JSONPath, From},_Vars,DynData) -> {ok, Val} = ts_dynvars:lookup(From,DynData#dyndata.dynvars), JSON=mochijson2:decode(Val), ts_utils:jsonpath(JSONPath, JSON). %% @spec ctrl_struct(CtrlData::term(),State::#state_rcv{},Count::integer) -> %% {next_state, NextStateName::atom(), NextState::#state_rcv{}} | %% {next_state, NextStateName::atom(), NextState::#state_rcv{}, %% Timeout::integer() | infinity} | %% {stop, Reason::term(), NewState::#state_rcv{}} %% @doc Common code for flow control actions (repeat,for) %% Count is the next action-id, if this action doesn't result %% in a jump to another place %% @end ctrl_struct(CtrlData,State,Count) -> case ctrl_struct_impl(CtrlData,State#state_rcv.dyndata) of {next,NewDynData} -> handle_next_action(State#state_rcv{dyndata=NewDynData,count=Count}); {jump,Target,NewDynData} -> %%UGLY HACK: %% because set_profile/3 works by counting down starting at maxcount, %% we need to calculate the correct value to actually make a jump to %% the desired target. %% In set_profile/3, actionId = MaxCount-Count+1 => %% Count = MaxCount-Target +1 Next = State#state_rcv.maxcount - Target + 1, handle_next_action(State#state_rcv{dyndata=NewDynData,count=Next}) end. %%---------------------------------------------------------------------- %% @spec ctrl_struct_impl(ControlStruct::term(),DynData::#dyndata{}) -> %% {next,NewDynData::#dyndata{}} | %% {jump, Target::integer(), NewDynData::#dyndata{}} %% @doc return {next,NewDynData} to continue with the sequential flow, %% {jump,Target,NewDynData} to jump to action number 'Target' %% @end %%---------------------------------------------------------------------- ctrl_struct_impl({for_start,Init="%%_"++_,VarName},DynData=#dyndata{dynvars=DynVars}) -> InitialValue = list_to_integer(ts_search:subst(Init, DynVars)), ?LOGF("Initial value of FOR loop is dynamic: ~p",[InitialValue],?DEB), ctrl_struct_impl({for_start,InitialValue,VarName},DynData); ctrl_struct_impl({for_start,InitialValue,VarName},DynData=#dyndata{dynvars=DynVars}) -> NewDynVars = ts_dynvars:set(VarName,InitialValue,DynVars), {next,DynData#dyndata{dynvars=NewDynVars}}; ctrl_struct_impl({for_end,VarName,End="%%_"++_,Increment,Target},DynData=#dyndata{dynvars=DynVars}) -> %% end value is a dynamic variable EndValue = list_to_integer(ts_search:subst(End, DynVars)), ?LOGF("End value of FOR loop is dynamic: ~p",[EndValue],?DEB), ctrl_struct_impl({for_end,VarName,EndValue,Increment,Target},DynData); ctrl_struct_impl({for_end,VarName,EndValue,Increment,Target},DynData=#dyndata{dynvars=DynVars}) -> case ts_dynvars:lookup(VarName,DynVars) of {ok,Value} when Value >= EndValue -> % Reach final value, end loop {next,DynData}; {ok,Value} -> % New iteration NewValue = Value + Increment, NewDynVars = ts_dynvars:set(VarName,NewValue,DynVars), {jump,Target,DynData#dyndata{dynvars=NewDynVars}} end; ctrl_struct_impl({if_start,Rel, VarName, Value, Target},DynData=#dyndata{dynvars=DynVars}) -> case ts_dynvars:lookup(VarName,DynVars) of {ok,VarValue} -> ?DebugF("If found ~p; value is ~p~n",[VarName,VarValue]), ?DebugF("Calling need_jump with args ~p ~p ~p~n",[Rel,Value,VarValue]), Jump = need_jump('if',rel(Rel,Value,VarValue)), jump_if(Jump,Target,DynData#dyndata{dynvars=DynVars}); false -> ts_mon:add({ count, 'error_if_undef'}), {next,DynData} end; ctrl_struct_impl({repeat,RepeatName, _,_,_,_,_,_},DynData=#dyndata{dynvars=[]}) -> Msg= list_to_atom("error_repeat_"++atom_to_list(RepeatName)++"_undef"), ts_mon:add({ count, Msg}), {next,DynData}; ctrl_struct_impl({repeat,RepeatName, While,Rel,VarName,Value,Target,Max}, DynData=#dyndata{dynvars=DynVars}) -> Iteration = case ts_dynvars:lookup(RepeatName,DynVars) of {ok,Val} -> Val; false -> 1 end, ?DebugF("Repeat (name=~p) iteration: ~p~n",[RepeatName,Iteration]), case Iteration > Max of true -> ?LOGF("Max repeat (name=~p) reached ~p~n",[VarName,Iteration],?NOTICE), ts_mon:add({ count, max_repeat}), {next,DynData}; false -> case ts_dynvars:lookup(VarName,DynVars) of {ok,VarValue} -> ?DebugF("Repeat (name=~p) found; value is ~p~n",[VarName,VarValue]), ?DebugF("Calling need_jump with args ~p ~p ~p ~p~n",[While,Rel,Value,VarValue]), Jump = need_jump(While,rel(Rel,Value,VarValue)), NewValue = 1 + Iteration, NewDynVars = ts_dynvars:set(RepeatName,NewValue,DynVars), jump_if(Jump,Target,DynData#dyndata{dynvars=NewDynVars}); false -> Msg= list_to_atom("error_repeat_"++atom_to_list(RepeatName)++"undef"), ts_mon:add({ count, Msg}), {next,DynData} end end; ctrl_struct_impl({foreach_start,ForEachName,VarName,Filter}, DynData=#dyndata{dynvars=DynVars}) -> case filter(ts_dynvars:lookup(VarName,DynVars),Filter) of false -> Msg= list_to_atom("error_foreach_"++atom_to_list(VarName)++"undef"), ts_mon:add({ count, Msg}), {next,DynData}; [First|_Tail] -> TmpDynVars = ts_dynvars:set(ForEachName,First,DynVars), NewDynVars = ts_dynvars:set(ts_utils:concat_atoms([ForEachName,'_iter']),2,TmpDynVars), {next,DynData#dyndata{dynvars=NewDynVars}}; [] -> ?LOGF("empty list for ~p (filter is ~p)",[VarName, Filter],?WARN), NewDynVars = ts_dynvars:set(ts_utils:concat_atoms([ForEachName,'_iter']),1,DynVars), {next,DynData#dyndata{dynvars=NewDynVars}}; VarValue -> ?LOGF("foreach warn:~p is not a list (~p), can't iterate",[VarName, VarValue],?WARN), NewDynVars = ts_dynvars:set(ForEachName,VarValue,DynVars), {next,DynData#dyndata{dynvars=NewDynVars}} end; ctrl_struct_impl({foreach_end,ForEachName,VarName,Filter,Target}, DynData=#dyndata{dynvars=DynVars}) -> IterName=ts_utils:concat_atoms([ForEachName,'_iter']), {ok,Iteration} = ts_dynvars:lookup(IterName,DynVars), ?DebugF("Foreach (var=~p) iteration: ~p~n",[VarName,Iteration]), case filter(ts_dynvars:lookup(VarName,DynVars),Filter) of false -> Msg= list_to_atom("error_foreach_"++atom_to_list(VarName)++"undef"), ts_mon:add({ count, Msg}), {next,DynData}; VarValue when is_list(VarValue)-> ?DebugF("Foreach list found; value is ~p~n",[VarValue]), case catch lists:nth(Iteration,VarValue) of {'EXIT',_} -> % out of bounds, exit foreach loop ?LOGF("foreach ~p: last iteration done",[ForEachName],?DEB), {next,DynData}; Val -> TmpDynVars = ts_dynvars:set(ForEachName,Val,DynVars), NewDynVars = ts_dynvars:set(IterName,Iteration+1,TmpDynVars), {jump, Target ,DynData#dyndata{dynvars=NewDynVars}} end; _ ->% not a list, don't loop {next,DynData} end. rel(R,A,B) when is_integer(B) -> %% jsonpath can output numbers instead of binaries rel(R,A,list_to_binary(integer_to_list(B))); rel('eq',A,B) -> A == B; rel('neq',A,B) -> A /= B. need_jump('while',F) -> F; need_jump('until',F) -> not F; need_jump('if',F) -> not F. jump_if(true,Target,DynData) -> {jump,Target,DynData}; jump_if(false,_Target,DynData) -> {next,DynData}. %%---------------------------------------------------------------------- %% Func: handle_next_request/2 %% Args: Request, State %%---------------------------------------------------------------------- handle_next_request(Request, State) -> Count = State#state_rcv.count-1, Type = State#state_rcv.clienttype, {PrevHost, PrevPort, PrevProto} = case Request of #ts_request{host=undefined, port=undefined, scheme=undefined} -> %% host/port/scheme not defined in request, use the current ones. {State#state_rcv.host,State#state_rcv.port, State#state_rcv.protocol}; #ts_request{host=H1, port=P1, scheme=S1} -> {H1,P1,S1} end, {Param, {Host,Port,Protocol}} = case Type:add_dynparams(Request#ts_request.subst, State#state_rcv.dyndata, Request#ts_request.param, {PrevHost, PrevPort, PrevProto}) of {Par, NewServer} -> % substitution has changed server setup ?DebugF("Dynparam, new server: ~p~n",[NewServer]), {Par, NewServer}; P -> {P, {PrevHost, PrevPort, PrevProto}} end, %% need to reconnect if the server/port/scheme has changed Socket = case {State#state_rcv.host,State#state_rcv.port,State#state_rcv.protocol} of {Host, Port, Protocol} -> % server setup unchanged State#state_rcv.socket; _ -> ?Debug("Change server configuration inside a session ~n"), ts_utils:close_socket(State#state_rcv.protocol, State#state_rcv.socket), set_connected_status(false), none end, {Message, NewSession} = Type:get_message(Param,State), Now = now(), %% reconnect if needed Proto = {Protocol,State#state_rcv.proto_opts}, case reconnect(Socket,Host,Port,Proto,State#state_rcv.ip) of {ok, NewSocket} -> case catch send(Protocol, NewSocket, Message, Host, Port) of ok -> PageTimeStamp = case State#state_rcv.page_timestamp of 0 -> Now; %first request of a page _ -> %page already started State#state_rcv.page_timestamp end, ts_mon:add({ sum, size_sent, size_msg(Message)}), ts_mon:sendmes({State#state_rcv.dump, self(), Message}), NewState = State#state_rcv{socket = NewSocket, protocol = Protocol, host = Host, request = Request, port = Port, count = Count, session = NewSession, page_timestamp= PageTimeStamp, send_timestamp= Now, timestamp= Now }, case Request#ts_request.ack of no_ack -> {PTimeStamp, _} = update_stats_noack(NewState), handle_next_action(NewState#state_rcv{ack_done=true, page_timestamp=PTimeStamp}); global -> ts_timer:connected(self()), {next_state, wait_ack, NewState}; _ -> {next_state, wait_ack, NewState} end; {error, closed} -> ?LOG("connection close while sending message !~n", ?WARN), handle_close_while_sending(State#state_rcv{socket=NewSocket, protocol=Protocol, host=Host, session=NewSession, port=Port}); {error, Reason} -> %% LOG only at INFO level since we report also an error to ts_mon ?LOGF("Error: Unable to send data, reason: ~p~n",[Reason],?INFO), CountName="error_send_"++atom_to_list(Reason), ts_mon:add({ count, list_to_atom(CountName) }), handle_timeout_while_sending(State#state_rcv{session=NewSession}); {'EXIT', {noproc, _Rest}} -> ?LOG("EXIT from ssl app while sending message !~n", ?WARN), handle_close_while_sending(State#state_rcv{socket=NewSocket, protocol=Protocol, session=NewSession, host=Host, port=Port}); Exit -> ?LOGF("EXIT Error: Unable to send data, reason: ~p~n", [Exit], ?ERR), ts_mon:add({ count, error_send }), {stop, normal, State} end; {error,_Reason} when State#state_rcv.retries < ?MAX_RETRIES -> Retries= State#state_rcv.retries +1, % simplified exponential backoff algorithm: we increase % the timeout when the number of retries increase, with a % simple rule: number of retries * retry_timeout set_thinktime(?RETRY_TIMEOUT * Retries ), {next_state, think, State#state_rcv{retries=Retries,session=NewSession}}; {error,_Reason} -> ts_mon:add({ count, error_abort_max_conn_retries }), {stop, normal, State} end. %% @spec size_msg(Data::term) -> integer() size_msg(Data) when is_binary(Data) -> size(Data); size_msg({_Mod,_Fun,_Args,Size}) -> Size. %%---------------------------------------------------------------------- %% Func: finish_session/1 %% Args: State %%---------------------------------------------------------------------- finish_session(State) -> Now = now(), set_connected_status(false), Elapsed = ts_utils:elapsed(State#state_rcv.starttime, Now), case State#state_rcv.transactions of [] -> % no pending transactions, do nothing ok; TrList -> % pending transactions (an error has probably occured) ?LOGF("Pending transactions: ~p, compute transaction time~n",[TrList],?NOTICE), lists:foreach(fun({Tname,StartTime}) -> ts_mon:add({sample,Tname,ts_utils:elapsed(StartTime,Now)}) end, TrList) end, ts_mon:endclient({State#state_rcv.id, Now, Elapsed}). %%---------------------------------------------------------------------- %% Func: handle_close_while_sending/1 %% Args: State %% Purpose: the connection has just be closed a few msec before we %% send a message, restart in a few moment (this time we will %% reconnect before sending) %%---------------------------------------------------------------------- handle_close_while_sending(State=#state_rcv{persistent = true, protocol = Proto, proto_opts = PO})-> ts_utils:close_socket(Proto, State#state_rcv.socket), set_connected_status(false), Think = PO#proto_opts.retry_timeout, %%FIXME: report the error to ts_mon ? ?LOGF("Server must have closed connection upon us, waiting ~p msec~n", [Think], ?NOTICE), set_thinktime(Think), {next_state, think, State#state_rcv{socket=none}}; handle_close_while_sending(State) -> {stop, error, State}. %%---------------------------------------------------------------------- %% Func: handle_timeout_while_sending/1 %% Args: State %% Purpose: retry if a timeout occurs during a send %%---------------------------------------------------------------------- handle_timeout_while_sending(State=#state_rcv{persistent = true, proto_opts = PO})-> Think = PO#proto_opts.retry_timeout, set_thinktime(Think), {next_state, think, State}; handle_timeout_while_sending(State) -> ?LOG("Not persistent, abort client because of send timeout~n", ?INFO), {stop, normal, State}. %%---------------------------------------------------------------------- %% Func: set_profile/2 %% Args: MaxCount, Count (integer), ProfileId (integer) %%---------------------------------------------------------------------- set_profile(MaxCount, Count, ProfileId) when is_integer(ProfileId) -> ts_session_cache:get_req(ProfileId, MaxCount-Count+1). %%---------------------------------------------------------------------- %% Func: reconnect/4 %% Returns: {Socket } | %% {stop, Reason} %% purpose: try to reconnect if this is needed (when the socket is set to none) %%---------------------------------------------------------------------- reconnect(none, ServerName, Port, {Protocol, Proto_opts}, {IP,0}) -> reconnect(none, ServerName, Port, {Protocol, Proto_opts}, {IP,0,0}); reconnect(none, ServerName, Port, {Protocol, Proto_opts}, {IP,CPort, Try}) when is_integer(CPort)-> ?DebugF("Try to (re)connect to: ~p:~p from ~p using protocol ~p~n", [ServerName,Port,IP,Protocol]), Opts = protocol_options(Protocol, Proto_opts) ++ socket_opts(IP, CPort, Protocol), Before= now(), case connect(Protocol,ServerName, Port, Opts) of {ok, Socket} -> Elapsed = ts_utils:elapsed(Before, now()), ts_mon:add({ sample, connect, Elapsed }), set_connected_status(true), ?Debug("(Re)connected~n"), {ok, Socket}; {error, Reason} -> {A,B,C,D} = IP, %% LOG only at INFO level since we report also an error to ts_mon ?LOGF("(Re)connect from ~p.~p.~p.~p:~p to ~s:~p, Error: ~p~n", [A,B,C,D, CPort, ServerName, Port , Reason],?INFO), case {Reason,CPort,Try} of {eaddrinuse, Val,CPortServer} when Val == 0; CPortServer == undefined -> %% already retry once, don't try again. ts_mon:add({ count, error_connect_eaddrinuse }); {eaddrinuse, Val,CPortServer} when Val > 0 -> %% retry once when tsung allocates port number NewCPort = case catch ts_cport:get_port(CPortServer,IP) of Data when is_integer(Data) -> Data; Error -> ?LOGF("CPort error (~p), reuse the same port ~p~n",[Error,CPort],?INFO), CPort end, ?LOGF("Connect failed with client port ~p, retry with ~p~n",[CPort, NewCPort],?INFO), reconnect(none, ServerName, Port, {Protocol, Proto_opts}, {IP,NewCPort, undefined}); _ -> CountName="error_connect_"++atom_to_list(Reason), ts_mon:add({ count, list_to_atom(CountName) }) end, {error, Reason} end; reconnect(none, ServerName, Port, {Protocol, Proto_opts}, {IP,CPortServer}) -> CPort = case catch ts_cport:get_port(CPortServer,IP) of Data when is_integer(Data) -> Data; Error -> ?LOGF("CPort error (~p), use random port~n",[Error],?INFO), 0 end, reconnect(none, ServerName, Port, {Protocol, Proto_opts}, {IP,CPort,CPortServer}); reconnect(Socket, _Server, _Port, _Protocol, _IP) -> {ok, Socket}. %% set options for local socket ip/ports socket_opts({0,0,0,0}, CPort, Proto) when Proto==gen_tcp6 orelse Proto==ssl6 orelse Proto==gen_udp6 -> %% the config server was not aware if we are using ipv6 or ipv4, %% and it set the local IP to be default one; we need to change it %% for ipv6 [{ip, {0,0,0,0,0,0,0,0}},{port,CPort}]; socket_opts(IP, CPort, _)-> [{ip, IP},{port,CPort}]. %%---------------------------------------------------------------------- %% Func: send/5 %% Purpose: wrapper function for send %% Return: ok | {error, Reason} %%---------------------------------------------------------------------- send(gen_tcp,Socket,Message,_,_) -> gen_tcp:send(Socket,Message); send(ssl,Socket,Message,_,_) -> ssl:send(Socket,Message); send(gen_udp,Socket,Message,Host,Port) ->gen_udp:send(Socket,Host,Port,Message); % ipv6 send(gen_tcp6,Socket,Message,_,_) -> gen_tcp:send(Socket,Message); send(ssl6,Socket,Message,_,_) -> ssl:send(Socket,Message); send(gen_udp6,Socket,Message,Host,Port) ->gen_udp:send(Socket,Host,Port,Message); send(erlang,Pid,Message,_,_) -> Pid ! Message, ok. %%---------------------------------------------------------------------- %% Func: connect/4 %% Return: {ok, Socket} | {error, Reason} %%---------------------------------------------------------------------- connect(gen_tcp,Server, Port, Opts) -> gen_tcp:connect(Server, Port, Opts); connect(ssl,Server, Port,Opts) -> ssl:connect(Server, Port, Opts); connect(gen_udp,_Server, _Port, Opts) -> gen_udp:open(0,Opts); connect(gen_tcp6,Server, Port, Opts) -> gen_tcp:connect(Server, Port, Opts); connect(ssl6,Server, Port,Opts) -> ssl:connect(Server, Port, Opts); connect(gen_udp6,_Server, _Port, Opts)-> gen_udp:open(0,Opts); connect(erlang,Server,Port,Opts) -> Pid=spawn_link(ts_erlang,client,[self(),Server,Port,Opts]), {ok, Pid}. %%---------------------------------------------------------------------- %% Func: protocol_options/1 %% Purpose: set connection's options for the given protocol %%---------------------------------------------------------------------- protocol_options(ssl6,Val) -> [inet6]++protocol_options(ssl,Val); protocol_options(ssl,#proto_opts{ssl_ciphers=negociate}) -> [binary, {active, once} ]; protocol_options(ssl,#proto_opts{ssl_ciphers=Ciphers}) -> ?DebugF("cipher is ~p~n",[Ciphers]), [binary, {active, once}, {ciphers, Ciphers} ]; protocol_options(gen_tcp6,Val) -> [inet6]++protocol_options(gen_tcp,Val); protocol_options(gen_tcp,#proto_opts{tcp_rcv_size=Rcv, tcp_snd_size=Snd}) -> [binary, {active, once}, {recbuf, Rcv}, {sndbuf, Snd}, {keepalive, true} %% FIXME: should be an option ]; protocol_options(gen_udp6,Val) -> [inet6]++protocol_options(gen_udp,Val); protocol_options(gen_udp,#proto_opts{udp_rcv_size=Rcv, udp_snd_size=Snd}) -> [binary, {active, once}, {recbuf, Rcv}, {sndbuf, Snd} ]; protocol_options(erlang,_) -> []. %%---------------------------------------------------------------------- %% Func: set_thinktime/1 %% Purpose: set a timer for thinktime if it is not infinite %% returns the choosen thinktime in msec %%---------------------------------------------------------------------- set_thinktime(infinity) -> infinity; set_thinktime(wait_global) -> ts_timer:connected(self()), infinity; set_thinktime({random, Think}) -> set_thinktime(round(ts_stats:exponential(1/Think))); set_thinktime({range, Min, Max}) -> set_thinktime(ts_stats:uniform(Min,Max)); set_thinktime(Think) -> %% dot not use timer:send_after because it does not scale well: %% http://www.erlang.org/ml-archive/erlang-questions/200202/msg00024.html ?DebugF("thinktime of ~p~n",[Think]), erlang:start_timer(Think, self(), end_thinktime ), Think. %%---------------------------------------------------------------------- %% Func: handle_data_msg/2 %% Args: Data (binary), State ('state_rcv' record) %% Returns: {NewState ('state_rcv' record), Socket options (list)} %% Purpose: handle data received from a socket %%---------------------------------------------------------------------- handle_data_msg(Data, State=#state_rcv{request=Req}) when Req#ts_request.ack==no_ack-> ?Debug("data received while previous msg was no_ack~n"), ts_mon:rcvmes({State#state_rcv.dump, self(), Data}), {State, []}; handle_data_msg(Data,State=#state_rcv{dump=Dump,request=Req,id=Id,clienttype=Type,maxcount=MaxCount}) when Req#ts_request.ack==parse-> ts_mon:rcvmes({Dump, self(), Data}), {NewState, Opts, Close} = Type:parse(Data, State), NewBuffer=set_new_buffer(NewState, Data), ?DebugF("Dyndata is now ~p~n",[NewState#state_rcv.dyndata]), case NewState#state_rcv.ack_done of true -> ?DebugF("Response done:~p~n", [NewState#state_rcv.datasize]), {PageTimeStamp, DynVars} = update_stats(NewState#state_rcv{buffer=NewBuffer}), MatchArgs={NewState#state_rcv.count, MaxCount, NewState#state_rcv.session_id, Id}, NewDynVars=ts_dynvars:merge(DynVars,(NewState#state_rcv.dyndata)#dyndata.dynvars), NewCount =ts_search:match(Req#ts_request.match,NewBuffer,MatchArgs,NewDynVars), NewDynData=(NewState#state_rcv.dyndata)#dyndata{dynvars=NewDynVars}, Type:dump(Dump,{Req,NewState#state_rcv.session,Id, NewState#state_rcv.host,NewState#state_rcv.datasize}), case Close of true -> ?Debug("Close connection required by protocol~n"), ts_utils:close_socket(State#state_rcv.protocol,State#state_rcv.socket), set_connected_status(false), {NewState#state_rcv{ page_timestamp = PageTimeStamp, socket = none, datasize = 0, size_mon = State#state_rcv.size_mon_thresh, count = NewCount, dyndata = NewDynData, buffer = <<>>}, Opts}; false -> {NewState#state_rcv{ page_timestamp = PageTimeStamp, count = NewCount, size_mon = State#state_rcv.size_mon_thresh, datasize = 0, dyndata = NewDynData, buffer = <<>>}, Opts} end; _ -> ?DebugF("Response: continue:~p~n",[NewState#state_rcv.datasize]), %% For size_rcv stats, we don't want to update this stats %% for every packet received (ts_mon will be overloaded), %% so we will update the stats at the end of the %% request. But this is a problem with very big response %% (several megabytes for ex.), because it will create %% artificial spikes in the stats (O B/sec for a long time %% and lot's of MB/s at the end of the req). So we update %% the stats each time a 512Ko threshold is raised. case NewState#state_rcv.datasize > NewState#state_rcv.size_mon of true -> ?Debug("Threshold raised, update size_rcv stats~n"), ts_mon:add({ sum, size_rcv, NewState#state_rcv.size_mon_thresh}), NewThresh=NewState#state_rcv.size_mon+ NewState#state_rcv.size_mon_thresh, {NewState#state_rcv{buffer=NewBuffer,size_mon=NewThresh}, Opts}; false-> {NewState#state_rcv{buffer=NewBuffer}, Opts} end end; handle_data_msg(closed,State) -> {State,[]}; %% ack = global handle_data_msg(Data,State=#state_rcv{request=Req,datasize=OldSize}) when Req#ts_request.ack==global -> %% FIXME: we do not report size now (but after receiving the %% global ack), the size stats may be not very accurate. %% FIXME: should we set buffer and parse for dynvars ? DataSize = size(Data), {State#state_rcv{ datasize = OldSize + DataSize},[]}; %% local ack, special case for jabber: skip keepalive msg (single space char) handle_data_msg(<<32>>, State=#state_rcv{clienttype=ts_jabber}) -> {State#state_rcv{ack_done = false},[]}; %% local ack, set ack_done to true handle_data_msg(Data, State=#state_rcv{request=Req, maxcount=MaxCount}) -> ts_mon:rcvmes({State#state_rcv.dump, self(), Data}), NewBuffer= set_new_buffer(State, Data), DataSize = size(Data), {PageTimeStamp, DynVars} = update_stats(State#state_rcv{datasize=DataSize, buffer=NewBuffer}), MatchArgs={State#state_rcv.count,MaxCount,State#state_rcv.session_id, State#state_rcv.id}, NewDynVars=ts_dynvars:merge(DynVars,(State#state_rcv.dyndata)#dyndata.dynvars), NewCount =ts_search:match(Req#ts_request.match, NewBuffer, MatchArgs,NewDynVars), NewDynData=(State#state_rcv.dyndata)#dyndata{dynvars=NewDynVars}, {State#state_rcv{ack_done = true, buffer= NewBuffer, dyndata = NewDynData, page_timestamp= PageTimeStamp, count=NewCount},[]}. %%---------------------------------------------------------------------- %% Func: set_new_buffer/3 %%---------------------------------------------------------------------- set_new_buffer(#state_rcv{request = #ts_request{match=[], dynvar_specs=[]}} ,_) -> << >>; set_new_buffer(#state_rcv{clienttype=Type, buffer=Buffer, session=Session},closed) -> Type:decode_buffer(Buffer,Session); set_new_buffer(#state_rcv{buffer=OldBuffer,ack_done=false},Data) -> ?Debug("Bufferize response~n"), << OldBuffer/binary, Data/binary >>; set_new_buffer(#state_rcv{clienttype=Type, buffer=OldBuffer, session=Session},Data) when is_binary(Data) -> ?Debug("decode response~n"), Type:decode_buffer(<< OldBuffer/binary, Data/binary >>, Session); set_new_buffer(#state_rcv{clienttype=Type, buffer=OldBuffer, session=Session}, {_M,_F,_A, Res}) when is_list(Res)-> %% erlang fun case Data=list_to_binary(Res), Type:decode_buffer(<< OldBuffer/binary, Data/binary >>, Session); set_new_buffer(_State, Data) -> % useful ? Data. %%---------------------------------------------------------------------- %% Func: set_connected_status/1 %% Args: true|false %% Returns: - %% Purpose: update the statistics for connected users %%---------------------------------------------------------------------- set_connected_status(S) -> set_connected_status(S,get(connected)). set_connected_status(true, true) -> ok; set_connected_status(true, Old) when Old==undefined; Old==false -> put(connected,true), ts_mon:add({sum, connected, 1}); set_connected_status(false, true) -> put(connected,false), ts_mon:add({sum, connected, -1}); set_connected_status(false, Old) when Old==undefined; Old==false -> ok. %%---------------------------------------------------------------------- %% Func: update_stats_noack/1 %% Args: State %% Returns: {TimeStamp, DynVars} %% Purpose: update the statistics for no_ack requests %%---------------------------------------------------------------------- update_stats_noack(#state_rcv{page_timestamp=PageTime,request=Request}) -> Now = now(), Stats= [{ count, request_noack}], % count and not sample because response time is not defined in this case case Request#ts_request.endpage of true -> % end of a page, compute page reponse time PageElapsed = ts_utils:elapsed(PageTime, Now), ts_mon:add(lists:append([Stats,[{sample, page, PageElapsed}]])), {0, []}; _ -> ts_mon:add(Stats), {PageTime, []} end. %%---------------------------------------------------------------------- %% Func: update_stats/1 %% Args: State %% Returns: {TimeStamp, DynVars} %% Purpose: update the statistics %%---------------------------------------------------------------------- update_stats(State=#state_rcv{size_mon_thresh=T,page_timestamp=PageTime,send_timestamp=SendTime}) -> Now = now(), Elapsed = ts_utils:elapsed(SendTime, Now), Stats = case State#state_rcv.size_mon > T of true -> LastSize=State#state_rcv.datasize-State#state_rcv.size_mon+T, [{ sample, request, Elapsed}, { sum, size_rcv, LastSize}]; false-> [{ sample, request, Elapsed}, { sum, size_rcv, State#state_rcv.datasize}] end, Request = State#state_rcv.request, DynVars = ts_search:parse_dynvar(Request#ts_request.dynvar_specs, State#state_rcv.buffer), case Request#ts_request.endpage of true -> % end of a page, compute page reponse time PageElapsed = ts_utils:elapsed(PageTime, Now), ts_mon:add(lists:append([Stats,[{sample, page, PageElapsed}]])), {0, DynVars}; _ -> ts_mon:add(Stats), {PageTime, DynVars} end. filter(false,undefined) -> false; filter({ok,List},undefined)-> List; filter({ok,List},{Include,Re}) when is_list(List)-> Filter=fun(A) -> case re:run(A,Re) of nomatch -> not Include; {match,_} -> Include end end, lists:filter(Filter,List); filter({ok,Data},{Include,Re}) -> filter({ok,[Data]},{Include,Re}). %% @spec token_bucket(R::integer(),Burst::integer(),S0::integer(),T0::tuple(),P1::integer(), %% Now::tuple(),Sleep::boolean()) -> {S1::integer(),Wait::integer()} %% @doc Implement a token bucket to rate limit the traffic: If the %% bucket is full, we wait (if asked) until we can fill the %% bucket with the incoming data %% R = limit rate in Bytes/millisec, Burst = max burst size in Bytes %% T0 arrival date of last packet, %% P1 size in bytes of the packet just received %% S1: new size of the bucket %% Wait: Time to wait %% @end token_bucket(R,Burst,S0,T0,P1,Now,Sleep) -> S1 = lists:min([S0+R*round(ts_utils:elapsed(T0, Now)),Burst]), case P1 < S1 of true -> % no need to wait {S1-P1,0}; false -> % the bucket is full, must wait Wait=(P1-S1) div R, case Sleep of true -> timer:sleep(Wait), {0,Wait}; false-> {0,Wait} end end. tsung-1.4.2/src/tsung/ts_launcher_static.erl0000644000201100017670000002146711701017117020662 0ustar nniclausdream%%% Copyright (C) 2009 Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% %%% In addition, as a special exception, you have the permission to %%% link the code of this program with any library released under %%% the EPL license and distribute linked combinations including %%% the two. -module(ts_launcher_static). -created('Date: 2009/03/10 19:09:57 nniclausse '). -vc('$Id: ts_launcher.erl 968 2008-12-16 12:51:28Z nniclausse $ '). -author('nicolas.niclausse@niclux.org'). -include("ts_profile.hrl"). -include("ts_config.hrl"). -behaviour(gen_fsm). %% a primitive gen_fsm with two state: launcher and wait %% External exports -export([start/0, launch/1, stop/1]). %% gen_fsm callbacks -export([init/1, launcher/2, wait/2, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). -record(state, { myhostname, users }). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- %%-------------------------------------------------------------------- %% Function: start/0 %%-------------------------------------------------------------------- start() -> ?LOG("starting ~n", ?INFO), gen_fsm:start_link({local, ?MODULE}, ?MODULE, [], []). %%-------------------------------------------------------------------- %% Function: launch/1 %%-------------------------------------------------------------------- %% Start clients with given session list launch({Node, Sessions}) -> ?LOGF("starting on node ~p~n",[[Node]], ?INFO), gen_fsm:send_event({?MODULE, Node}, {launch, Sessions}); % same erlang beam case launch({Node, Host, Sessions}) -> ?LOGF("starting on node ~p~n",[[Node]], ?INFO), gen_fsm:send_event({?MODULE, Node}, {launch, Sessions, atom_to_list(Host)}). %%-------------------------------------------------------------------- %% @spec stop(Node::atom()) -> ok %% @doc Start clients with given session list @end %%-------------------------------------------------------------------- stop(Node) -> ?LOGF("stopping on node ~p~n",[Node], ?INFO), gen_fsm:send_event({?MODULE, Node}, {stop}). %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, StateName, StateData} | %% {ok, StateName, StateData, Timeout} | %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- init([]) -> {ok, MyHostName} = ts_utils:node_to_hostname(node()), ts_launcher_mgr:alive(static), {ok, wait, #state{myhostname=MyHostName}}. %%---------------------------------------------------------------------- %% Func: StateName/2 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- wait({launch, Args, Hostname}, State) -> wait({launch, Args}, State#state{myhostname = Hostname}); %% starting without configuration. We must ask the config server for %% the configuration of this launcher. wait({launch, []}, State) -> MyHostName = State#state.myhostname, ?LOGF("Launch msg receive (~p)~n",[MyHostName], ?NOTICE), ts_launcher_mgr:check_registered(), {ok,Users,Start} = ts_config_server:get_client_config(static,MyHostName), case Users of [{Wait,_}|_] -> Warm = ts_launcher:set_warm_timeout(Start), ts_launcher:set_static_users({node(),length(Users)}), ?LOGF("Activate launcher (~p static users) in ~p msec, first user after ~p ms ~n",[length(Users), Warm, Wait], ?NOTICE), {next_state,launcher,State#state{users = Users}, Warm + Wait}; [] -> ?LOG("No static users, stop",?INFO), ts_launcher:set_static_users({node(),0}), {stop, normal, State} end; wait({stop}, State) -> {stop, normal, State}. launcher(timeout, State=#state{ users = [{OldWait,Session}|Users]}) -> BeforeLaunch = now(), ?LOGF("Launch static user using session ~p ~n", [Session],?DEB), do_launch({Session,State#state.myhostname}), Wait = set_waiting_time(BeforeLaunch, Users, OldWait), ?DebugF("Real Wait =~p ~n", [Wait]), case Users of [] -> ?LOG("no more clients to start ~n",?INFO), {stop, normal, State}; _ -> {next_state,launcher,State#state{users=Users},Wait} end. set_waiting_time(_Before, [] , _Previous) -> 0; % last user set_waiting_time(Before , [{Next,_}|_], Previous) -> LaunchDuration = ts_utils:elapsed(now(), Before), %% to keep the rate of new users as expected, remove the time to %% launch a client to the next wait. NewWait = Next - Previous - LaunchDuration, case NewWait > 0 of true -> round(NewWait); false -> 0 end. %%---------------------------------------------------------------------- %% Func: handle_event/3 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. %%---------------------------------------------------------------------- %% Func: handle_sync_event/4 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {reply, Reply, NextStateName, NextStateData} | %% {reply, Reply, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} | %% {stop, Reason, Reply, NewStateData} %%---------------------------------------------------------------------- handle_sync_event(_Event, _From, StateName, StateData) -> Reply = ok, {reply, Reply, StateName, StateData}. %%---------------------------------------------------------------------- %% Func: handle_info/3 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_info(_Info, StateName, StateData) -> {next_state, StateName, StateData}. %%---------------------------------------------------------------------- %% Func: terminate/3 %% Purpose: Shutdown the fsm %% Returns: any %%---------------------------------------------------------------------- terminate(Reason, _StateName, _StateData) -> ?LOGF("launcher terminating for reason ~p~n",[Reason], ?INFO), ts_launcher_mgr:die(static), ok. %%-------------------------------------------------------------------- %% Func: code_change/4 %% Purpose: Convert process state when code is changed %% Returns: {ok, NewState, NewStateData} %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- %%%---------------------------------------------------------------------- %%% Func: do_launch/1 %%%---------------------------------------------------------------------- do_launch({ Session, MyHostName})-> case catch ts_config_server:get_user_param(MyHostName) of {ok, {IPParam, Server, UserId, Dump, Seed}} -> ts_client_sup:start_child(Session#session{client_ip=IPParam,server=Server,userid=UserId, dump=Dump,seed=Seed}), ok; Error -> ?LOGF("get_next_session failed [~p], skip this session !~n", [Error],?ERR), ts_mon:add({ count, error_next_session }), error end. tsung-1.4.2/src/lib/0000755000201100017670000000000011701017143013673 5ustar nniclausdreamtsung-1.4.2/src/lib/mochiweb_xpath_utils.erl0000644000201100017670000000322211701017117020620 0ustar nniclausdream%% xpath_utils.erl %% @author Pablo Polvorin %% @doc Utility functions, mainly for type conversion %% Conversion rules taken from http://www.w3.org/TR/1999/REC-xpath-19991116 %% created on 2008-05-07 -module(mochiweb_xpath_utils). -export([string_value/1, number_value/1, node_set_value/1, boolean_value/1, convert/2]). string_value(N) when is_list(N)-> case N of [X|_] -> string_value(X); [] -> <<>> end; string_value({_,_,Contents}) -> L = lists:filter(fun ({_,_,_}) ->false; (B) when is_binary(B) -> true end,Contents), list_to_binary(L); string_value(N) when is_integer(N) -> list_to_binary(integer_to_list(N)); string_value(B) when is_binary(B) -> B; string_value(B) when is_atom(B) -> list_to_binary(atom_to_list(B)). node_set_value(N) when is_list(N) -> N; node_set_value(N) -> throw({node_set_expected,N}). number_value(N) when is_integer(N) or is_float(N) -> N; number_value(N) when is_binary(N)-> String = binary_to_list(N), case erl_scan:string(String) of {ok, [{integer,1,I}],1} -> I; {ok, [{float,1,F}],1} -> F end; number_value(N) -> number_value(string_value(N)). boolean_value([]) -> false; boolean_value([_|_]) -> true; boolean_value(N) when is_number(N) -> N /= 0; boolean_value(B) when is_binary(B) -> size(B) /= 0; boolean_value(B) when B == true; B == false -> B. convert(Value,number) -> number_value(Value); convert(Value,string) -> string_value(Value); convert(Value,node_set) -> node_set_value(Value). tsung-1.4.2/src/lib/mochiweb_xpath_functions.erl0000644000201100017670000000521211701017117021471 0ustar nniclausdream%% xpath_functions.erl %% @author Pablo Polvorin %% @doc Some core xpath functions that can be used in xpath expressions %% created on 2008-05-07 -module(mochiweb_xpath_functions). -export([default_functions/0]). %% Default functions. %% The format is: {FunctionName, fun(), FunctionSignature} %% @type FunctionName = atom() %% @type FunctionSignature = [XPathType] %% @type XPathType = node_set | string | number | boolean %% %% The engine is responsable of calling the function with %% the correct arguments, given the function signature. default_functions() -> [ {'count',fun count/2,[node_set]}, {'name',fun 'name'/2,[node_set]}, {'starts-with', fun 'starts-with'/2,[string,string]}, {'substring', fun substring/2,[string,number,number]}, {'sum', fun sum/2,[node_set]}, {'string-length', fun 'string-length'/2,[string]} ]. %% @doc Function: number count(node-set) %% The count function returns the number of nodes in the %% argument node-set. count(_Ctx,[NodeList]) -> length(NodeList). %% @doc Function: string name(node-set?) 'name'(_Ctx,[[{Tag,_,_}|_]]) -> Tag. %% @doc Function: boolean starts-with(string, string) %% The starts-with function returns true if the first argument string %% starts with the second argument string, and otherwise returns false. 'starts-with'(_Ctx,[Left,Right]) -> Size = size(Right), case Left of <> -> true; _ -> false end. %% @doc Function: string substring(string, number, number?) %% The substring function returns the substring of the first argument %% starting at the position specified in the second argument with length %% specified in the third argument substring(_Ctx,[String,Start,Length]) when is_binary(String)-> Before = Start -1, After = size(String) - Before - Length, case (Start + Length) =< size(String) of true -> <<_:Before/binary,R:Length/binary,_:After/binary>> = String, R; false -> <<>> end. %% @doc Function: number sum(node-set) %% The sum function returns the sum, for each node in the argument %% node-set, of the result of converting the string-values of the node %% to a number. sum(_Ctx,[Values]) -> lists:sum(lists:map(fun mochiweb_xpath_utils:number_value/1,Values)). %% @doc Function: number string-length(string?) %% The string-length returns the number of characters in the string %% TODO: this isn't true: currently it returns the number of bytes %% in the string, that isn't the same 'string-length'(_Ctx,[String]) -> size(String). tsung-1.4.2/src/lib/pgsql_proto.erl0000644000201100017670000005230211701017117016753 0ustar nniclausdream%%% File : pgsql_proto.erl %%% Author : Christian Sunesson %%% Description : PostgreSQL protocol driver %%% Created : 9 May 2005 %%% This is the protocol handling part of the PostgreSQL driver, it turns packages into %%% erlang term messages and back. -module(pgsql_proto). %% TODO: %% When factorizing make clear distinction between message and packet. %% Packet == binary on-wire representation %% Message = parsed Packet as erlang terms. %%% Version 3.0 of the protocol. %%% Supported in postgres from version 7.4 -define(PROTOCOL_MAJOR, 3). -define(PROTOCOL_MINOR, 0). %%% PostgreSQL protocol message codes -define(PG_BACKEND_KEY_DATA, $K). -define(PG_PARAMETER_STATUS, $S). -define(PG_ERROR_MESSAGE, $E). -define(PG_NOTICE_RESPONSE, $N). -define(PG_EMPTY_RESPONSE, $I). -define(PG_ROW_DESCRIPTION, $T). -define(PG_DATA_ROW, $D). -define(PG_READY_FOR_QUERY, $Z). -define(PG_AUTHENTICATE, $R). -define(PG_BIND, $B). -define(PG_PARSE, $P). -define(PG_COMMAND_COMPLETE, $C). -define(PG_PARSE_COMPLETE, $1). -define(PG_BIND_COMPLETE, $2). -define(PG_CLOSE_COMPLETE, $3). -define(PG_PORTAL_SUSPENDED, $s). -define(PG_NO_DATA, $n). -define(PG_COPY_RESPONSE, $G). -export([init/2, idle/2]). -export([run/1]). %% For protocol unwrapping, pgsql_tcp for example. -export([decode_packet/2]). -export([encode_message/2]). -export([encode/2]). -import(pgsql_util, [option/2]). -import(pgsql_util, [socket/1]). -import(pgsql_util, [send/2, send_int/2, send_msg/3]). -import(pgsql_util, [recv_msg/2, recv_msg/1, recv_byte/2, recv_byte/1]). -import(pgsql_util, [string/1, make_pair/2, split_pair/1]). -import(pgsql_util, [count_string/1, to_string/1]). -import(pgsql_util, [coldescs/2, datacoldescs/3]). deliver(Message) -> DriverPid = get(driver), DriverPid ! Message. run(Options) -> Db = spawn_link(pgsql_proto, init, [self(), Options]), {ok, Db}. %% TODO: We should use states instead of process dictionary init(DriverPid, Options) -> put(options, Options), % connection setup options put(driver, DriverPid), % driver's process id %%io:format("Init~n", []), %% Default values: We connect to localhost on the standard TCP/IP %% port. Host = option(host, "localhost"), Port = option(port, 5432), case socket({tcp, Host, Port}) of {ok, Sock} -> connect(Sock); Error -> Reason = {init, Error}, DriverPid ! {pgsql_error, Reason}, exit(Reason) end. connect(Sock) -> %%io:format("Connect~n", []), %% Connection settings for database-login. %% TODO: Check if the default values are relevant: UserName = option(user, "cos"), DatabaseName = option(database, "template1"), %% Make protocol startup packet. Version = <>, User = make_pair(user, UserName), Database = make_pair(database, DatabaseName), StartupPacket = <>, %% Backend will continue with authentication after the startup packet PacketSize = 4 + size(StartupPacket), ok = gen_tcp:send(Sock, <>), authenticate(Sock). authenticate(Sock) -> %% Await authentication request from backend. {ok, Code, Packet} = recv_msg(Sock, 5000), {ok, Value} = decode_packet(Code, Packet), case Value of %% Error response {error_message, Message} -> exit({authentication, Message}); {authenticate, {AuthMethod, Salt}} -> case AuthMethod of 0 -> % Auth ok setup(Sock, []); 1 -> % Kerberos 4 exit({nyi, auth_kerberos4}); 2 -> % Kerberos 5 exit({nyi, auth_kerberos5}); 3 -> % Plaintext password Password = option(password, ""), EncodedPass = encode_message(pass_plain, Password), ok = send(Sock, EncodedPass), authenticate(Sock); 4 -> % Hashed password exit({nyi, auth_crypt}); 5 -> % MD5 password Password = option(password, ""), User = option(user, ""), EncodedPass = encode_message(pass_md5, {User, Password, Salt}), ok = send(Sock, EncodedPass), authenticate(Sock); _ -> exit({authentication, {unknown, AuthMethod}}) end; %% Unknown message received Any -> exit({protocol_error, Any}) end. setup(Sock, Params) -> %% Receive startup messages until ReadyForQuery {ok, Code, Package} = recv_msg(Sock, 5000), {ok, Pair} = decode_packet(Code, Package), case Pair of %% BackendKeyData, necessary for issuing cancel requests {backend_key_data, {Pid, Secret}} -> Params1 = [{secret, {Pid, Secret}}|Params], setup(Sock, Params1); %% ParameterStatus, a key-value pair. {parameter_status, {Key, Value}} -> Params1 = [{{parameter, Key}, Value}|Params], setup(Sock, Params1); %% Error message, with a sequence of <> %% of error descriptions. Code==0 terminates the Reason. {error_message, Message} -> gen_tcp:close(Sock), exit({error_response, Message}); %% Notice Response, with a sequence of <> %% identified fields. Code==0 terminates the Notice. {notice_response, Notice} -> deliver({pgsql_notice, Notice}), setup(Sock, Params); %% Ready for Query, backend is ready for a new query cycle {ready_for_query, Status} -> deliver({pgsql_params, Params}), deliver(pgsql_connected), put(params, Params), connected(Sock); Any -> deliver({unknown_setup, Any}), connected(Sock) end. %% Connected state. Can now start to push messages %% between frontend and backend. But first some setup. connected(Sock) -> DriverPid = get(driver), %% Protocol unwrapping process. Factored out to make future %% SSL and unix domain support easier. Store process under %% 'socket' in the process dictionary. begin Unwrapper = spawn_link(pgsql_tcp, loop0, [Sock, self()]), ok = gen_tcp:controlling_process(Sock, Unwrapper), put(socket, Unwrapper) end, %% Lookup oid to type names and store them in a dictionary under %% 'oidmap' in the process dictionary. begin Packet = encode_message(squery, "SELECT oid, typname FROM pg_type"), ok = send(Sock, Packet), {ok, [{"SELECT", _ColDesc, Rows}]} = process_squery([]), Rows1 = lists:map(fun ([CodeS, NameS]) -> Code = list_to_integer(CodeS), Name = list_to_atom(NameS), {Code, Name} end, Rows), OidMap = dict:from_list(Rows1), put(oidmap, OidMap) end, %% Ready to start marshalling between frontend and backend. idle(Sock, DriverPid). %% In the idle state we should only be receiving requests from the %% frontend. Async backend messages should be forwarded to responsible %% process. idle(Sock, Pid) -> receive %% Unexpected idle messages. No request should continue to the %% idle state before a ready-for-query has been received. {message, Message} -> io:format("Unexpected message when idle: ~p~n", [Message]), idle(Sock, Pid); %% Socket closed or socket error messages. {socket, Sock, Condition} -> exit({socket, Condition}); %% Close connection {terminate, Ref, Pid} -> Packet = encode_message(terminate, []), ok = send(Sock, Packet), gen_tcp:close(Sock), Pid ! {pgsql, Ref, terminated}, unlink(Pid), exit(terminating); %% Simple query {squery, Ref, Pid, Query} -> Packet = encode_message(squery, Query), ok = send(Sock, Packet), {ok, Result} = process_squery([]), case lists:keymember(error, 1, Result) of true -> RBPacket = encode_message(squery, "ROLLBACK"), ok = send(Sock, RBPacket), {ok, RBResult} = process_squery([]); _ -> ok end, Pid ! {pgsql, Ref, Result}, idle(Sock, Pid); %% Extended query %% simplistic version using the unnammed prepared statement and portal. {equery, Ref, Pid, {Query, Params}} -> ParseP = encode_message(parse, {"", Query, []}), BindP = encode_message(bind, {"", "", Params, [binary]}), DescribeP = encode_message(describe, {portal, ""}), ExecuteP = encode_message(execute, {"", 0}), SyncP = encode_message(sync, []), ok = send(Sock, [ParseP, BindP, DescribeP, ExecuteP, SyncP]), {ok, Command, Desc, Status, Logs} = process_equery([]), OidMap = get(oidmap), NameTypes = lists:map(fun({Name, _Format, _ColNo, Oid, _, _, _}) -> {Name, dict:fetch(Oid, OidMap)} end, Desc), Pid ! {pgsql, Ref, {Command, Status, NameTypes, Logs}}, idle(Sock, Pid); %% Prepare a statement, so it can be used for queries later on. {prepare, Ref, Pid, {Name, Query}} -> send_message(Sock, parse, {Name, Query, []}), send_message(Sock, describe, {prepared_statement, Name}), send_message(Sock, sync, []), {ok, State, ParamDesc, ResultDesc} = process_prepare({[], []}), OidMap = get(oidmap), ParamTypes = lists:map(fun (Oid) -> dict:fetch(Oid, OidMap) end, ParamDesc), ResultNameTypes = lists:map(fun ({ColName, _Format, _ColNo, Oid, _, _, _}) -> {ColName, dict:fetch(Oid, OidMap)} end, ResultDesc), Pid ! {pgsql, Ref, {prepared, State, ParamTypes, ResultNameTypes}}, idle(Sock, Pid); %% Close a prepared statement. {unprepare, Ref, Pid, Name} -> send_message(Sock, close, {prepared_statement, Name}), send_message(Sock, sync, []), {ok, _Status} = process_unprepare(), Pid ! {pgsql, Ref, unprepared}, idle(Sock, Pid); %% Execute a prepared statement {execute, Ref, Pid, {Name, Params}} -> %%io:format("execute: ~p ~p ~n", [Name, Params]), begin % Issue first requests for the prepared statement. BindP = encode_message(bind, {"", Name, Params, [binary]}), DescribeP = encode_message(describe, {portal, ""}), ExecuteP = encode_message(execute, {"", 0}), FlushP = encode_message(flush, []), ok = send(Sock, [BindP, DescribeP, ExecuteP, FlushP]) end, receive {pgsql, {bind_complete, _}} -> % Bind reply first. %% Collect response to describe message, %% which gives a hint of the rest of the messages. {ok, Command, Result} = process_execute(Sock, Ref, Pid), begin % Close portal and end extended query. CloseP = encode_message(close, {portal, ""}), SyncP = encode_message(sync, []), ok = send(Sock, [CloseP, SyncP]) end, receive %% Collect response to close message. {pgsql, {close_complete, _}} -> receive %% Collect response to sync message. {pgsql, {ready_for_query, Status}} -> %%io:format("execute: ~p ~p ~p~n", %% [Status, Command, Result]), Pid ! {pgsql, Ref, {Command, Result}}, idle(Sock, Pid); {pgsql, Unknown} -> exit(Unknown) end; {pgsql, Unknown} -> exit(Unknown) end; {pgsql, Unknown} -> exit(Unknown) end; %% More requests to come. %% . %% . %% . Any -> exit({unknown_request, Any}) end. %% In the process_squery state we collect responses until the backend is %% done processing. process_squery(Log) -> receive {pgsql, {row_description, Cols}} -> {ok, Command, Rows} = process_squery_cols([]), process_squery([{Command, Cols, Rows}|Log]); {pgsql, {command_complete, Command}} -> process_squery([Command|Log]); {pgsql, {ready_for_query, Status}} -> {ok, lists:reverse(Log)}; {pgsql, {error_message, Error}} -> process_squery([{error, Error}|Log]); {pgsql, Any} -> process_squery(Log) end. process_squery_cols(Log) -> receive {pgsql, {data_row, Row}} -> process_squery_cols([lists:map(fun binary_to_list/1, Row)|Log]); {pgsql, {command_complete, Command}} -> {ok, Command, lists:reverse(Log)} end. process_equery(Log) -> receive %% Consume parse and bind complete messages when waiting for the first %% first row_description message. What happens if the equery doesnt %% return a result set? {pgsql, {parse_complete, _}} -> process_equery(Log); {pgsql, {bind_complete, _}} -> process_equery(Log); {pgsql, {row_description, Descs}} -> {ok, Descs1} = pgsql_util:decode_descs(Descs), process_equery_datarow(Descs1, Log, {undefined, Descs, undefined}); {pgsql, Any} -> process_equery([Any|Log]) end. process_equery_datarow(Types, Log, Info={Command, Desc, Status}) -> receive %% {pgsql, {command_complete, Command1}} -> process_equery_datarow(Types, Log, {Command1, Desc, Status}); {pgsql, {ready_for_query, Status1}} -> {ok, Command, Desc, Status1, lists:reverse(Log)}; {pgsql, {data_row, Row}} -> {ok, DecodedRow} = pgsql_util:decode_row(Types, Row), process_equery_datarow(Types, [DecodedRow|Log], Info); {pgsql, Any} -> process_equery_datarow(Types, [Any|Log], Info) end. process_prepare(Info={ParamDesc, ResultDesc}) -> receive {pgsql, {no_data, _}} -> process_prepare({ParamDesc, []}); {pgsql, {parse_complete, _}} -> process_prepare(Info); {pgsql, {parameter_description, Oids}} -> process_prepare({Oids, ResultDesc}); {pgsql, {row_description, Desc}} -> process_prepare({ParamDesc, Desc}); {pgsql, {ready_for_query, Status}} -> {ok, Status, ParamDesc, ResultDesc}; {pgsql, Any} -> io:format("process_prepare: ~p~n", [Any]), process_prepare(Info) end. process_unprepare() -> receive {pgsql, {ready_for_query, Status}} -> {ok, Status}; {pgsql, {close_complate, []}} -> process_unprepare(); {pgsql, Any} -> io:format("process_unprepare: ~p~n", [Any]), process_unprepare() end. process_execute(Sock, Ref, Pid) -> %% Either the response begins with a no_data or a row_description %% Needs to return {ok, Status, Result} %% where Result = {Command, ...} receive {pgsql, {no_data, _}} -> {ok, Command, Result} = process_execute_nodata(); {pgsql, {row_description, Descs}} -> {ok, Types} = pgsql_util:decode_descs(Descs), {ok, Command, Result} = process_execute_resultset(Sock, Ref, Pid, Types, []); {pgsql, Unknown} -> exit(Unknown) end. process_execute_nodata() -> receive {pgsql, {command_complete, Command}} -> case Command of "INSERT "++Rest -> {ok, [{integer, _, _Table}, {integer, _, NRows}], _} = erl_scan:string(Rest), {ok, 'INSERT', NRows}; "SELECT" -> {ok, 'SELECT', should_not_happen}; "DELETE "++Rest -> {ok, [{integer, _, NRows}], _} = erl_scan:string(Rest), {ok, 'DELETE', NRows}; Any -> {ok, nyi, Any} end; {pgsql, Unknown} -> exit(Unknown) end. process_execute_resultset(Sock, Ref, Pid, Types, Log) -> receive {pgsql, {command_complete, Command}} -> {ok, list_to_atom(Command), lists:reverse(Log)}; {pgsql, {data_row, Row}} -> {ok, DecodedRow} = pgsql_util:decode_row(Types, Row), process_execute_resultset(Sock, Ref, Pid, Types, [DecodedRow|Log]); {pgsql, {portal_suspended, _}} -> throw(portal_suspended); {pgsql, Any} -> %%process_execute_resultset(Types, [Any|Log]) exit(Any) end. %% With a message type Code and the payload Packet apropriate %% decoding procedure can proceed. decode_packet(Code, Packet) -> Ret = fun(CodeName, Values) -> {ok, {CodeName, Values}} end, case Code of ?PG_ERROR_MESSAGE -> Message = pgsql_util:errordesc(Packet), Ret(error_message, Message); ?PG_EMPTY_RESPONSE -> Ret(empty_response, []); ?PG_ROW_DESCRIPTION -> <> = Packet, Descs = coldescs(ColDescs, []), Ret(row_description, Descs); ?PG_READY_FOR_QUERY -> <> = Packet, case State of $I -> Ret(ready_for_query, idle); $T -> Ret(ready_for_query, transaction); $E -> Ret(ready_for_query, failed_transaction) end; ?PG_COMMAND_COMPLETE -> {Task, _} = to_string(Packet), Ret(command_complete, Task); ?PG_DATA_ROW -> <> = Packet, ColData = datacoldescs(NumberCol, RowData, []), Ret(data_row, ColData); ?PG_BACKEND_KEY_DATA -> <> = Packet, Ret(backend_key_data, {Pid, Secret}); ?PG_PARAMETER_STATUS -> {Key, Value} = split_pair(Packet), Ret(parameter_status, {Key, Value}); ?PG_NOTICE_RESPONSE -> Ret(notice_response, []); ?PG_AUTHENTICATE -> <> = Packet, Ret(authenticate, {AuthMethod, Salt}); ?PG_PARSE_COMPLETE -> Ret(parse_complete, []); ?PG_BIND_COMPLETE -> Ret(bind_complete, []); ?PG_PORTAL_SUSPENDED -> Ret(portal_suspended, []); ?PG_CLOSE_COMPLETE -> Ret(close_complete, []); ?PG_COPY_RESPONSE -> <> = Packet, Format = case FormatCode of 0 -> text; 1 -> binary end, Cols=pgsql_util:int16(ColFormat,[]), Ret(copy_response, {Format,Cols}); $t -> <> = Packet, Oids = pgsql_util:oids(OidsP, []), Ret(parameter_description, Oids); ?PG_NO_DATA -> Ret(no_data, []); Any -> Ret(unknown, [Code]) end. send_message(Sock, Type, Values) -> %%io:format("send_message:~p~n", [{Type, Values}]), Packet = encode_message(Type, Values), ok = send(Sock, Packet). %% Add header to a message. encode(Code, Packet) -> Len = size(Packet) + 4, <>. %% Encode a message of a given type. encode_message(pass_plain, Password) -> Pass = pgsql_util:pass_plain(Password), encode($p, Pass); encode_message(pass_md5, {User, Password, Salt}) -> Pass = pgsql_util:pass_md5(User, Password, Salt), encode($p, Pass); encode_message(terminate, _) -> encode($X, <<>>); encode_message(copydone, _) -> encode($c, <<>>); encode_message(copyfail, Msg) -> encode($f, string(Msg)); encode_message(copy, Data) -> encode($d, Data ); encode_message(squery, Query) -> % squery as in simple query. encode($Q, string(Query)); encode_message(close, {Object, Name}) -> Type = case Object of prepared_statement -> $S; portal -> $P end, String = string(Name), encode($C, <>); encode_message(describe, {Object, Name}) -> ObjectP = case Object of prepared_statement -> $S; portal -> $P end, NameP = string(Name), encode($D, <>); encode_message(flush, _) -> encode($H, <<>>); encode_message(parse, {Name, Query, Oids}) -> StringName = string(Name), StringQuery = string(Query), NOids=length(Oids), OidsBin=lists:foldl(fun(X,Acc)-> << Acc/binary ,X:32/integer>> end, << >>, Oids), encode($P, <>); encode_message(bind, Bind={NamePortal, NamePrepared, Parameters, ParamsFormats,ResultFormats}) -> %%io:format("encode bind: ~p~n", [Bind]), PortalP = string(NamePortal), PreparedP = string(NamePrepared), NParameters = length(Parameters), {NParametersFormat, ParamFormatsP} = case ParamsFormats of none -> {0,<<>>}; binary-> {1,<<1:16/integer>>}; text -> {1,<<0:16/integer>>}; auto -> ParamFormatsList = lists:map( fun (Bin) when is_binary(Bin) -> <<1:16/integer>>; (Text) -> <<0:16/integer>> end, Parameters), {NParameters, erlang:concat_binary(ParamFormatsList)} end, ParametersList = lists:map( fun (null) -> Minus = -1, <>; (Bin) when is_binary(Bin) -> Size = size(Bin), <>; (Integer) when is_integer(Integer) -> List = integer_to_list(Integer), Bin = list_to_binary(List), Size = size(Bin), <>; (Text) -> Bin = list_to_binary(Text), Size = size(Bin), <> end, Parameters), ParametersP = erlang:concat_binary(ParametersList), NResultFormats = length(ResultFormats), ResultFormatsList = lists:map( fun (binary) -> <<1:16/integer>>; (text) -> <<0:16/integer>> end, ResultFormats), ResultFormatsP = erlang:concat_binary(ResultFormatsList), %%io:format("encode bind: ~p~n", [{PortalP, PreparedP, %% NParameters, ParamFormatsP, %% NParameters, ParametersP, %% NResultFormats, ResultFormatsP}]), encode($B, <>); encode_message(execute, {Portal, Limit}) -> String = string(Portal), encode($E, <>); encode_message(sync, _) -> encode($S, <<>>). tsung-1.4.2/src/lib/pgsql_util.erl0000644000201100017670000001754611701017117016600 0ustar nniclausdream%%% File : pgsql_util.erl %%% Author : Christian Sunesson %%% Description : utility functions used in implementation of %%% postgresql driver. %%% Created : 11 May 2005 by Blah -module(pgsql_util). %% Key-Value handling -export([option/2]). %% Networking -export([socket/1]). -export([send/2, send_int/2, send_msg/3]). -export([recv_msg/2, recv_msg/1, recv_byte/2, recv_byte/1]). %% Protocol packing -export([string/1, make_pair/2, split_pair/1]). -export([split_pair_rec/1]). -export([count_string/1, to_string/1]). -export([oids/2, coldescs/2, datacoldescs/3, int16/2]). -export([decode_row/2, decode_descs/1]). -export([errordesc/1]). -export([zip/2]). %% Constructing authentication messages. -export([pass_plain/1, pass_md5/3]). -import(erlang, [md5/1]). -export([hexlist/2]). %% Lookup key in a plist stored in process dictionary under 'options'. %% Default is returned if there is no value for Key in the plist. option(Key, Default) -> Plist = get(options), case proplists:get_value(Key, Plist, Default) of Default -> Default; Value -> Value end. %% Open a TCP connection socket({tcp, Host, Port}) -> gen_tcp:connect(Host, Port, [{active, false}, binary, {packet, raw}], 5000). send(Sock, Packet) -> gen_tcp:send(Sock, Packet). send_int(Sock, Int) -> Packet = <>, gen_tcp:send(Sock, Packet). send_msg(Sock, Code, Packet) when binary(Packet) -> Len = size(Packet) + 4, Msg = <>, gen_tcp:send(Sock, Msg). recv_msg(Sock, Timeout) -> {ok, Head} = gen_tcp:recv(Sock, 5, Timeout), <> = Head, %%io:format("Code: ~p, Size: ~p~n", [Code, Size]), if Size > 4 -> {ok, Packet} = gen_tcp:recv(Sock, Size-4, Timeout), {ok, Code, Packet}; true -> {ok, Code, <<>>} end. recv_msg(Sock) -> recv_msg(Sock, infinity). recv_byte(Sock) -> recv_byte(Sock, infinity). recv_byte(Sock, Timeout) -> case gen_tcp:recv(Sock, 1, Timeout) of {ok, <>} -> {ok, Byte}; E={error, _Reason} -> throw(E) end. %% Convert String to binary string(String) when list(String) -> Bin = list_to_binary(String), <>; string(Bin) when binary(Bin) -> <>. %%% Two zero terminated strings. make_pair(Key, Value) when atom(Key) -> make_pair(atom_to_list(Key), Value); make_pair(Key, Value) when atom(Value) -> make_pair(Key, atom_to_list(Value)); make_pair(Key, Value) when list(Key), list(Value) -> BinKey = list_to_binary(Key), BinValue = list_to_binary(Value), make_pair(BinKey, BinValue); make_pair(Key, Value) when binary(Key), binary(Value) -> <>. split_pair(Bin) when binary(Bin) -> split_pair(binary_to_list(Bin)); split_pair(Str) -> split_pair_rec(Str, norec). split_pair_rec(Bin) when binary(Bin) -> split_pair_rec(binary_to_list(Bin)); split_pair_rec(Arg) -> split_pair_rec(Arg,[]). split_pair_rec([], Acc) -> lists:reverse(Acc); split_pair_rec([0], Acc) -> lists:reverse(Acc); split_pair_rec(S, Acc) -> Fun = fun(C) -> C /= 0 end, {Key, [0|S1]} = lists:splitwith(Fun, S), {Value, [0|Tail]} = lists:splitwith(Fun, S1), case Acc of norec -> {Key, Value}; _ -> split_pair_rec(Tail, [{Key, Value}| Acc]) end. count_string(Bin) when binary(Bin) -> count_string(Bin, 0). count_string(<<>>, N) -> {N, <<>>}; count_string(<<0/integer, Rest/binary>>, N) -> {N, Rest}; count_string(<<_C/integer, Rest/binary>>, N) -> count_string(Rest, N+1). to_string(Bin) when binary(Bin) -> {Count, _} = count_string(Bin, 0), <> = Bin, {binary_to_list(String), Count}. oids(<<>>, Oids) -> lists:reverse(Oids); oids(<>, Oids) -> oids(Rest, [Oid|Oids]). int16(<<>>, Vals) -> lists:reverse(Vals); int16(<>, Vals) -> int16(Rest, [Val|Vals]). coldescs(<<>>, Descs) -> lists:reverse(Descs); coldescs(Bin, Descs) -> {Name, Count} = to_string(Bin), <<_:Count/binary, 0/integer, TableOID:32/integer, ColumnNumber:16/integer, TypeId:32/integer, TypeSize:16/integer-signed, TypeMod:32/integer-signed, FormatCode:16/integer, Rest/binary>> = Bin, Format = case FormatCode of 0 -> text; 1 -> binary end, Desc = {Name, Format, ColumnNumber, TypeId, TypeSize, TypeMod, TableOID}, coldescs(Rest, [Desc|Descs]). datacoldescs(N, <>, Descs) when N >= 0 -> datacoldescs(N-1, Rest, [Data|Descs]); datacoldescs(_N, _, Descs) -> lists:reverse(Descs). decode_descs(Cols) -> decode_descs(Cols, []). decode_descs([], Descs) -> {ok, lists:reverse(Descs)}; decode_descs([Col|ColTail], Descs) -> OidMap = get(oidmap), {Name, Format, ColNumber, Oid, _, _, _} = Col, OidName = dict:fetch(Oid, OidMap), decode_descs(ColTail, [{Name, Format, ColNumber, OidName, [], [], []}|Descs]). decode_row(Types, Values) -> decode_row(Types, Values, []). decode_row([], [], Out) -> {ok, lists:reverse(Out)}; decode_row([Type|TypeTail], [Value|ValueTail], Out0) -> Out1 = decode_col(Type, Value), decode_row(TypeTail, ValueTail, [Out1|Out0]). decode_col({_, text, _, _, _, _, _}, Value) -> binary_to_list(Value); decode_col({_Name, _Format, _ColNumber, varchar, _Size, _Modifier, _TableOID}, Value) -> binary_to_list(Value); decode_col({_Name, _Format, _ColNumber, int4, _Size, _Modifier, _TableOID}, Value) -> <> = Value, Int4; decode_col({_Name, _Format, _ColNumber, Oid, _Size, _Modifier, _TableOID}, Value) -> {Oid, Value}. errordesc(Bin) -> errordesc(Bin, []). errordesc(<<0/integer, _Rest/binary>>, Lines) -> lists:reverse(Lines); errordesc(<>, Lines) -> {String, Count} = to_string(Rest), <<_:Count/binary, 0, Rest1/binary>> = Rest, Msg = case Code of $S -> {severity, list_to_atom(String)}; $C -> {code, String}; $M -> {message, String}; $D -> {detail, String}; $H -> {hint, String}; $P -> {position, list_to_integer(String)}; $p -> {internal_position, list_to_integer(String)}; $W -> {where, String}; $F -> {file, String}; $L -> {line, list_to_integer(String)}; $R -> {routine, String}; Unknown -> {Unknown, String} end, errordesc(Rest1, [Msg|Lines]). %%% Zip two lists together zip(List1, List2) -> zip(List1, List2, []). zip(List1, List2, Result) when List1 =:= []; List2 =:= [] -> lists:reverse(Result); zip([H1|List1], [H2|List2], Result) -> zip(List1, List2, [{H1, H2}|Result]). %%% Authentication utils pass_plain(Password) -> Pass = [Password, 0], list_to_binary(Pass). %% MD5 authentication patch from %% Juhani Rankimies %% (patch slightly rewritten, new bugs are mine :] /Christian Sunesson) %% %% MD5(MD5(password + user) + salt) %% pass_md5(User, Password, Salt) -> Digest = hex(md5([Password, User])), Encrypt = hex(md5([Digest, Salt])), Pass = ["md5", Encrypt, 0], list_to_binary(Pass). hex(B) when binary(B) -> hexlist(binary_to_list(B), []). hexlist([], Acc) -> lists:reverse(Acc); hexlist([N|Rest], Acc) -> HighNibble = (N band 16#f0) bsr 4, LowNibble = (N band 16#0f), hexlist(Rest, [hexdigit(LowNibble), hexdigit(HighNibble)|Acc]). hexdigit(0) -> $0; hexdigit(1) -> $1; hexdigit(2) -> $2; hexdigit(3) -> $3; hexdigit(4) -> $4; hexdigit(5) -> $5; hexdigit(6) -> $6; hexdigit(7) -> $7; hexdigit(8) -> $8; hexdigit(9) -> $9; hexdigit(10) -> $a; hexdigit(11) -> $b; hexdigit(12) -> $c; hexdigit(13) -> $d; hexdigit(14) -> $e; hexdigit(15) -> $f. tsung-1.4.2/src/lib/mochijson2.erl0000644000201100017670000006360511701017117016465 0ustar nniclausdream%% @author Bob Ippolito %% @copyright 2007 Mochi Media, Inc. %% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works %% with binaries as strings, arrays as lists (without an {array, _}) %% wrapper and it only knows how to decode UTF-8 (and ASCII). -module(mochijson2). -author('bob@mochimedia.com'). -export([encoder/1, encode/1]). -export([decoder/1, decode/1]). % This is a macro to placate syntax highlighters.. -define(Q, $\"). -define(ADV_COL(S, N), S#decoder{offset=N+S#decoder.offset, column=N+S#decoder.column}). -define(INC_COL(S), S#decoder{offset=1+S#decoder.offset, column=1+S#decoder.column}). -define(INC_LINE(S), S#decoder{offset=1+S#decoder.offset, column=1, line=1+S#decoder.line}). -define(INC_CHAR(S, C), case C of $\n -> S#decoder{column=1, line=1+S#decoder.line, offset=1+S#decoder.offset}; _ -> S#decoder{column=1+S#decoder.column, offset=1+S#decoder.offset} end). -define(IS_WHITESPACE(C), (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). %% @type iolist() = [char() | binary() | iolist()] %% @type iodata() = iolist() | binary() %% @type json_string() = atom | binary() %% @type json_number() = integer() | float() %% @type json_array() = [json_term()] %% @type json_object() = {struct, [{json_string(), json_term()}]} %% @type json_iolist() = {json, iolist()} %% @type json_term() = json_string() | json_number() | json_array() | %% json_object() | json_iolist() -record(encoder, {handler=null, utf8=false}). -record(decoder, {object_hook=null, offset=0, line=1, column=1, state=null}). %% @spec encoder([encoder_option()]) -> function() %% @doc Create an encoder/1 with the given options. %% @type encoder_option() = handler_option() | utf8_option() %% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false) encoder(Options) -> State = parse_encoder_options(Options, #encoder{}), fun (O) -> json_encode(O, State) end. %% @spec encode(json_term()) -> iolist() %% @doc Encode the given as JSON to an iolist. encode(Any) -> json_encode(Any, #encoder{}). %% @spec decoder([decoder_option()]) -> function() %% @doc Create a decoder/1 with the given options. decoder(Options) -> State = parse_decoder_options(Options, #decoder{}), fun (O) -> json_decode(O, State) end. %% @spec decode(iolist()) -> json_term() %% @doc Decode the given iolist to Erlang terms. decode(S) -> json_decode(S, #decoder{}). %% Internal API parse_encoder_options([], State) -> State; parse_encoder_options([{handler, Handler} | Rest], State) -> parse_encoder_options(Rest, State#encoder{handler=Handler}); parse_encoder_options([{utf8, Switch} | Rest], State) -> parse_encoder_options(Rest, State#encoder{utf8=Switch}). parse_decoder_options([], State) -> State; parse_decoder_options([{object_hook, Hook} | Rest], State) -> parse_decoder_options(Rest, State#decoder{object_hook=Hook}). json_encode(true, _State) -> <<"true">>; json_encode(false, _State) -> <<"false">>; json_encode(null, _State) -> <<"null">>; json_encode(I, _State) when is_integer(I) andalso I >= -2147483648 andalso I =< 2147483647 -> %% Anything outside of 32-bit integers should be encoded as a float integer_to_list(I); json_encode(I, _State) when is_integer(I) -> mochinum:digits(float(I)); json_encode(F, _State) when is_float(F) -> mochinum:digits(F); json_encode(S, State) when is_binary(S); is_atom(S) -> json_encode_string(S, State); json_encode(Array, State) when is_list(Array) -> json_encode_array(Array, State); json_encode({struct, Props}, State) when is_list(Props) -> json_encode_proplist(Props, State); json_encode({json, IoList}, _State) -> IoList; json_encode(Bad, #encoder{handler=null}) -> exit({json_encode, {bad_term, Bad}}); json_encode(Bad, State=#encoder{handler=Handler}) -> json_encode(Handler(Bad), State). json_encode_array([], _State) -> <<"[]">>; json_encode_array(L, State) -> F = fun (O, Acc) -> [$,, json_encode(O, State) | Acc] end, [$, | Acc1] = lists:foldl(F, "[", L), lists:reverse([$\] | Acc1]). json_encode_proplist([], _State) -> <<"{}">>; json_encode_proplist(Props, State) -> F = fun ({K, V}, Acc) -> KS = json_encode_string(K, State), VS = json_encode(V, State), [$,, VS, $:, KS | Acc] end, [$, | Acc1] = lists:foldl(F, "{", Props), lists:reverse([$\} | Acc1]). json_encode_string(A, State) when is_atom(A) -> L = atom_to_list(A), case json_string_is_safe(L) of true -> [?Q, L, ?Q]; false -> json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q]) end; json_encode_string(B, State) when is_binary(B) -> case json_bin_is_safe(B) of true -> [?Q, B, ?Q]; false -> json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q]) end; json_encode_string(I, _State) when is_integer(I) -> [?Q, integer_to_list(I), ?Q]; json_encode_string(L, State) when is_list(L) -> case json_string_is_safe(L) of true -> [?Q, L, ?Q]; false -> json_encode_string_unicode(L, State, [?Q]) end. json_string_is_safe([]) -> true; json_string_is_safe([C | Rest]) -> case C of ?Q -> false; $\\ -> false; $\b -> false; $\f -> false; $\n -> false; $\r -> false; $\t -> false; C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF -> false; C when C < 16#7f -> json_string_is_safe(Rest); _ -> false end. json_bin_is_safe(<<>>) -> true; json_bin_is_safe(<>) -> case C of ?Q -> false; $\\ -> false; $\b -> false; $\f -> false; $\n -> false; $\r -> false; $\t -> false; C when C >= 0, C < $\s; C >= 16#7f -> false; C when C < 16#7f -> json_bin_is_safe(Rest) end. json_encode_string_unicode([], _State, Acc) -> lists:reverse([$\" | Acc]); json_encode_string_unicode([C | Cs], State, Acc) -> Acc1 = case C of ?Q -> [?Q, $\\ | Acc]; %% Escaping solidus is only useful when trying to protect %% against "" injection attacks which are only %% possible when JSON is inserted into a HTML document %% in-line. mochijson2 does not protect you from this, so %% if you do insert directly into HTML then you need to %% uncomment the following case or escape the output of encode. %% %% $/ -> %% [$/, $\\ | Acc]; %% $\\ -> [$\\, $\\ | Acc]; $\b -> [$b, $\\ | Acc]; $\f -> [$f, $\\ | Acc]; $\n -> [$n, $\\ | Acc]; $\r -> [$r, $\\ | Acc]; $\t -> [$t, $\\ | Acc]; C when C >= 0, C < $\s -> [unihex(C) | Acc]; C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 -> [xmerl_ucs:to_utf8(C) | Acc]; C when C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 -> [unihex(C) | Acc]; C when C < 16#7f -> [C | Acc]; _ -> exit({json_encode, {bad_char, C}}) end, json_encode_string_unicode(Cs, State, Acc1). hexdigit(C) when C >= 0, C =< 9 -> C + $0; hexdigit(C) when C =< 15 -> C + $a - 10. unihex(C) when C < 16#10000 -> <> = <>, Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]], [$\\, $u | Digits]; unihex(C) when C =< 16#10FFFF -> N = C - 16#10000, S1 = 16#d800 bor ((N bsr 10) band 16#3ff), S2 = 16#dc00 bor (N band 16#3ff), [unihex(S1), unihex(S2)]. json_decode(L, S) when is_list(L) -> json_decode(iolist_to_binary(L), S); json_decode(B, S) -> {Res, S1} = decode1(B, S), {eof, _} = tokenize(B, S1#decoder{state=trim}), Res. decode1(B, S=#decoder{state=null}) -> case tokenize(B, S#decoder{state=any}) of {{const, C}, S1} -> {C, S1}; {start_array, S1} -> decode_array(B, S1); {start_object, S1} -> decode_object(B, S1) end. make_object(V, #decoder{object_hook=null}) -> V; make_object(V, #decoder{object_hook=Hook}) -> Hook(V). decode_object(B, S) -> decode_object(B, S#decoder{state=key}, []). decode_object(B, S=#decoder{state=key}, Acc) -> case tokenize(B, S) of {end_object, S1} -> V = make_object({struct, lists:reverse(Acc)}, S1), {V, S1#decoder{state=null}}; {{const, K}, S1} -> {colon, S2} = tokenize(B, S1), {V, S3} = decode1(B, S2#decoder{state=null}), decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc]) end; decode_object(B, S=#decoder{state=comma}, Acc) -> case tokenize(B, S) of {end_object, S1} -> V = make_object({struct, lists:reverse(Acc)}, S1), {V, S1#decoder{state=null}}; {comma, S1} -> decode_object(B, S1#decoder{state=key}, Acc) end. decode_array(B, S) -> decode_array(B, S#decoder{state=any}, []). decode_array(B, S=#decoder{state=any}, Acc) -> case tokenize(B, S) of {end_array, S1} -> {lists:reverse(Acc), S1#decoder{state=null}}; {start_array, S1} -> {Array, S2} = decode_array(B, S1), decode_array(B, S2#decoder{state=comma}, [Array | Acc]); {start_object, S1} -> {Array, S2} = decode_object(B, S1), decode_array(B, S2#decoder{state=comma}, [Array | Acc]); {{const, Const}, S1} -> decode_array(B, S1#decoder{state=comma}, [Const | Acc]) end; decode_array(B, S=#decoder{state=comma}, Acc) -> case tokenize(B, S) of {end_array, S1} -> {lists:reverse(Acc), S1#decoder{state=null}}; {comma, S1} -> decode_array(B, S1#decoder{state=any}, Acc) end. tokenize_string(B, S=#decoder{offset=O}) -> case tokenize_string_fast(B, O) of {escape, O1} -> Length = O1 - O, S1 = ?ADV_COL(S, Length), <<_:O/binary, Head:Length/binary, _/binary>> = B, tokenize_string(B, S1, lists:reverse(binary_to_list(Head))); O1 -> Length = O1 - O, <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B, {{const, String}, ?ADV_COL(S, Length + 1)} end. tokenize_string_fast(B, O) -> case B of <<_:O/binary, ?Q, _/binary>> -> O; <<_:O/binary, $\\, _/binary>> -> {escape, O}; <<_:O/binary, C1, _/binary>> when C1 < 128 -> tokenize_string_fast(B, 1 + O); <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223, C2 >= 128, C2 =< 191 -> tokenize_string_fast(B, 2 + O); <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239, C2 >= 128, C2 =< 191, C3 >= 128, C3 =< 191 -> tokenize_string_fast(B, 3 + O); <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244, C2 >= 128, C2 =< 191, C3 >= 128, C3 =< 191, C4 >= 128, C4 =< 191 -> tokenize_string_fast(B, 4 + O); _ -> throw(invalid_utf8) end. tokenize_string(B, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, ?Q, _/binary>> -> {{const, iolist_to_binary(lists:reverse(Acc))}, ?INC_COL(S)}; <<_:O/binary, "\\\"", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\" | Acc]); <<_:O/binary, "\\\\", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\\ | Acc]); <<_:O/binary, "\\/", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$/ | Acc]); <<_:O/binary, "\\b", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\b | Acc]); <<_:O/binary, "\\f", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\f | Acc]); <<_:O/binary, "\\n", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\n | Acc]); <<_:O/binary, "\\r", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\r | Acc]); <<_:O/binary, "\\t", _/binary>> -> tokenize_string(B, ?ADV_COL(S, 2), [$\t | Acc]); <<_:O/binary, "\\u", C3, C2, C1, C0, Rest/binary>> -> C = erlang:list_to_integer([C3, C2, C1, C0], 16), if C > 16#D7FF, C < 16#DC00 -> %% coalesce UTF-16 surrogate pair <<"\\u", D3, D2, D1, D0, _/binary>> = Rest, D = erlang:list_to_integer([D3,D2,D1,D0], 16), [CodePoint] = xmerl_ucs:from_utf16be(<>), Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc), tokenize_string(B, ?ADV_COL(S, 12), Acc1); true -> Acc1 = lists:reverse(xmerl_ucs:to_utf8(C), Acc), tokenize_string(B, ?ADV_COL(S, 6), Acc1) end; <<_:O/binary, C, _/binary>> -> tokenize_string(B, ?INC_CHAR(S, C), [C | Acc]) end. tokenize_number(B, S) -> case tokenize_number(B, sign, S, []) of {{int, Int}, S1} -> {{const, list_to_integer(Int)}, S1}; {{float, Float}, S1} -> {{const, list_to_float(Float)}, S1} end. tokenize_number(B, sign, S=#decoder{offset=O}, []) -> case B of <<_:O/binary, $-, _/binary>> -> tokenize_number(B, int, ?INC_COL(S), [$-]); _ -> tokenize_number(B, int, S, []) end; tokenize_number(B, int, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, $0, _/binary>> -> tokenize_number(B, frac, ?INC_COL(S), [$0 | Acc]); <<_:O/binary, C, _/binary>> when C >= $1 andalso C =< $9 -> tokenize_number(B, int1, ?INC_COL(S), [C | Acc]) end; tokenize_number(B, int1, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> tokenize_number(B, int1, ?INC_COL(S), [C | Acc]); _ -> tokenize_number(B, frac, S, Acc) end; tokenize_number(B, frac, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, $., C, _/binary>> when C >= $0, C =< $9 -> tokenize_number(B, frac1, ?ADV_COL(S, 2), [C, $. | Acc]); <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> tokenize_number(B, esign, ?INC_COL(S), [$e, $0, $. | Acc]); _ -> {{int, lists:reverse(Acc)}, S} end; tokenize_number(B, frac1, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> tokenize_number(B, frac1, ?INC_COL(S), [C | Acc]); <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> tokenize_number(B, esign, ?INC_COL(S), [$e | Acc]); _ -> {{float, lists:reverse(Acc)}, S} end; tokenize_number(B, esign, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, C, _/binary>> when C =:= $- orelse C=:= $+ -> tokenize_number(B, eint, ?INC_COL(S), [C | Acc]); _ -> tokenize_number(B, eint, S, Acc) end; tokenize_number(B, eint, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]) end; tokenize_number(B, eint1, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]); _ -> {{float, lists:reverse(Acc)}, S} end. tokenize(B, S=#decoder{offset=O}) -> case B of <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> tokenize(B, ?INC_CHAR(S, C)); <<_:O/binary, "{", _/binary>> -> {start_object, ?INC_COL(S)}; <<_:O/binary, "}", _/binary>> -> {end_object, ?INC_COL(S)}; <<_:O/binary, "[", _/binary>> -> {start_array, ?INC_COL(S)}; <<_:O/binary, "]", _/binary>> -> {end_array, ?INC_COL(S)}; <<_:O/binary, ",", _/binary>> -> {comma, ?INC_COL(S)}; <<_:O/binary, ":", _/binary>> -> {colon, ?INC_COL(S)}; <<_:O/binary, "null", _/binary>> -> {{const, null}, ?ADV_COL(S, 4)}; <<_:O/binary, "true", _/binary>> -> {{const, true}, ?ADV_COL(S, 4)}; <<_:O/binary, "false", _/binary>> -> {{const, false}, ?ADV_COL(S, 5)}; <<_:O/binary, "\"", _/binary>> -> tokenize_string(B, ?INC_COL(S)); <<_:O/binary, C, _/binary>> when (C >= $0 andalso C =< $9) orelse C =:= $- -> tokenize_number(B, S); <<_:O/binary>> -> trim = S#decoder.state, {eof, S} end. %% %% Tests %% -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). %% testing constructs borrowed from the Yaws JSON implementation. %% Create an object from a list of Key/Value pairs. obj_new() -> {struct, []}. is_obj({struct, Props}) -> F = fun ({K, _}) when is_binary(K) -> true end, lists:all(F, Props). obj_from_list(Props) -> Obj = {struct, Props}, ?assert(is_obj(Obj)), Obj. %% Test for equivalence of Erlang terms. %% Due to arbitrary order of construction, equivalent objects might %% compare unequal as erlang terms, so we need to carefully recurse %% through aggregates (tuples and objects). equiv({struct, Props1}, {struct, Props2}) -> equiv_object(Props1, Props2); equiv(L1, L2) when is_list(L1), is_list(L2) -> equiv_list(L1, L2); equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2; equiv(A, A) when A =:= true orelse A =:= false orelse A =:= null -> true. %% Object representation and traversal order is unknown. %% Use the sledgehammer and sort property lists. equiv_object(Props1, Props2) -> L1 = lists:keysort(1, Props1), L2 = lists:keysort(1, Props2), Pairs = lists:zip(L1, L2), true = lists:all(fun({{K1, V1}, {K2, V2}}) -> equiv(K1, K2) and equiv(V1, V2) end, Pairs). %% Recursively compare tuple elements for equivalence. equiv_list([], []) -> true; equiv_list([V1 | L1], [V2 | L2]) -> equiv(V1, V2) andalso equiv_list(L1, L2). decode_test() -> [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>), <<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]). e2j_vec_test() -> test_one(e2j_test_vec(utf8), 1). test_one([], _N) -> %% io:format("~p tests passed~n", [N-1]), ok; test_one([{E, J} | Rest], N) -> %% io:format("[~p] ~p ~p~n", [N, E, J]), true = equiv(E, decode(J)), true = equiv(E, decode(encode(E))), test_one(Rest, 1+N). e2j_test_vec(utf8) -> [ {1, "1"}, {3.1416, "3.14160"}, %% text representation may truncate, trail zeroes {-1, "-1"}, {-3.1416, "-3.14160"}, {12.0e10, "1.20000e+11"}, {1.234E+10, "1.23400e+10"}, {-1.234E-10, "-1.23400e-10"}, {10.0, "1.0e+01"}, {123.456, "1.23456E+2"}, {10.0, "1e1"}, {<<"foo">>, "\"foo\""}, {<<"foo", 5, "bar">>, "\"foo\\u0005bar\""}, {<<"">>, "\"\""}, {<<"\n\n\n">>, "\"\\n\\n\\n\""}, {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\""}, {obj_new(), "{}"}, {obj_from_list([{<<"foo">>, <<"bar">>}]), "{\"foo\":\"bar\"}"}, {obj_from_list([{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]), "{\"foo\":\"bar\",\"baz\":123}"}, {[], "[]"}, {[[]], "[[]]"}, {[1, <<"foo">>], "[1,\"foo\"]"}, %% json array in a json object {obj_from_list([{<<"foo">>, [123]}]), "{\"foo\":[123]}"}, %% json object in a json object {obj_from_list([{<<"foo">>, obj_from_list([{<<"bar">>, true}])}]), "{\"foo\":{\"bar\":true}}"}, %% fold evaluation order {obj_from_list([{<<"foo">>, []}, {<<"bar">>, obj_from_list([{<<"baz">>, true}])}, {<<"alice">>, <<"bob">>}]), "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"}, %% json object in a json array {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null], "[-123,\"foo\",{\"bar\":[]},null]"} ]. %% test utf8 encoding encoder_utf8_test() -> %% safe conversion case (default) [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] = encode(<<1,"\321\202\320\265\321\201\321\202">>), %% raw utf8 output (optional) Enc = mochijson2:encoder([{utf8, true}]), [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] = Enc(<<1,"\321\202\320\265\321\201\321\202">>). input_validation_test() -> Good = [ {16#00A3, <>}, %% pound {16#20AC, <>}, %% euro {16#10196, <>} %% denarius ], lists:foreach(fun({CodePoint, UTF8}) -> Expect = list_to_binary(xmerl_ucs:to_utf8(CodePoint)), Expect = decode(UTF8) end, Good), Bad = [ %% 2nd, 3rd, or 4th byte of a multi-byte sequence w/o leading byte <>, %% missing continuations, last byte in each should be 80-BF <>, <>, <>, %% we don't support code points > 10FFFF per RFC 3629 <> ], lists:foreach( fun(X) -> ok = try decode(X) catch invalid_utf8 -> ok end, %% could be {ucs,{bad_utf8_character_code}} or %% {json_encode,{bad_char,_}} {'EXIT', _} = (catch encode(X)) end, Bad). inline_json_test() -> ?assertEqual(<<"\"iodata iodata\"">>, iolist_to_binary( encode({json, [<<"\"iodata">>, " iodata\""]}))), ?assertEqual({struct, [{<<"key">>, <<"iodata iodata">>}]}, decode( encode({struct, [{key, {json, [<<"\"iodata">>, " iodata\""]}}]}))), ok. big_unicode_test() -> UTF8Seq = list_to_binary(xmerl_ucs:to_utf8(16#0001d120)), ?assertEqual( <<"\"\\ud834\\udd20\"">>, iolist_to_binary(encode(UTF8Seq))), ?assertEqual( UTF8Seq, decode(iolist_to_binary(encode(UTF8Seq)))), ok. custom_decoder_test() -> ?assertEqual( {struct, [{<<"key">>, <<"value">>}]}, (decoder([]))("{\"key\": \"value\"}")), F = fun ({struct, [{<<"key">>, <<"value">>}]}) -> win end, ?assertEqual( win, (decoder([{object_hook, F}]))("{\"key\": \"value\"}")), ok. atom_test() -> %% JSON native atoms [begin ?assertEqual(A, decode(atom_to_list(A))), ?assertEqual(iolist_to_binary(atom_to_list(A)), iolist_to_binary(encode(A))) end || A <- [true, false, null]], %% Atom to string ?assertEqual( <<"\"foo\"">>, iolist_to_binary(encode(foo))), ?assertEqual( <<"\"\\ud834\\udd20\"">>, iolist_to_binary(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))), ok. key_encode_test() -> %% Some forms are accepted as keys that would not be strings in other %% cases ?assertEqual( <<"{\"foo\":1}">>, iolist_to_binary(encode({struct, [{foo, 1}]}))), ?assertEqual( <<"{\"foo\":1}">>, iolist_to_binary(encode({struct, [{<<"foo">>, 1}]}))), ?assertEqual( <<"{\"foo\":1}">>, iolist_to_binary(encode({struct, [{"foo", 1}]}))), ?assertEqual( <<"{\"\\ud834\\udd20\":1}">>, iolist_to_binary( encode({struct, [{[16#0001d120], 1}]}))), ?assertEqual( <<"{\"1\":1}">>, iolist_to_binary(encode({struct, [{1, 1}]}))), ok. unsafe_chars_test() -> Chars = "\"\\\b\f\n\r\t", [begin ?assertEqual(false, json_string_is_safe([C])), ?assertEqual(false, json_bin_is_safe(<>)), ?assertEqual(<>, decode(encode(<>))) end || C <- Chars], ?assertEqual( false, json_string_is_safe([16#0001d120])), ?assertEqual( false, json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8(16#0001d120)))), ?assertEqual( [16#0001d120], xmerl_ucs:from_utf8( binary_to_list( decode(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))))), ?assertEqual( false, json_string_is_safe([16#110000])), ?assertEqual( false, json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8([16#110000])))), %% solidus can be escaped but isn't unsafe by default ?assertEqual( <<"/">>, decode(<<"\"\\/\"">>)), ok. int_test() -> ?assertEqual(0, decode("0")), ?assertEqual(1, decode("1")), ?assertEqual(11, decode("11")), ok. float_fallback_test() -> ?assertEqual(<<"-2147483649.0">>, iolist_to_binary(encode(-2147483649))), ?assertEqual(<<"2147483648.0">>, iolist_to_binary(encode(2147483648))), ok. handler_test() -> ?assertEqual( {'EXIT',{json_encode,{bad_term,{}}}}, catch encode({})), F = fun ({}) -> [] end, ?assertEqual( <<"[]">>, iolist_to_binary((encoder([{handler, F}]))({}))), ok. -endif. tsung-1.4.2/src/lib/mochinum.erl0000644000201100017670000001777011701017117016233 0ustar nniclausdream%% @copyright 2007 Mochi Media, Inc. %% @author Bob Ippolito %% @doc Useful numeric algorithms for floats that cover some deficiencies %% in the math module. More interesting is digits/1, which implements %% the algorithm from: %% http://www.cs.indiana.edu/~burger/fp/index.html %% See also "Printing Floating-Point Numbers Quickly and Accurately" %% in Proceedings of the SIGPLAN '96 Conference on Programming Language %% Design and Implementation. -module(mochinum). -author("Bob Ippolito "). -export([digits/1, frexp/1, int_pow/2, int_ceil/1, test/0]). %% IEEE 754 Float exponent bias -define(FLOAT_BIAS, 1022). -define(MIN_EXP, -1074). -define(BIG_POW, 4503599627370496). %% External API %% @spec digits(number()) -> string() %% @doc Returns a string that accurately represents the given integer or float %% using a conservative amount of digits. Great for generating %% human-readable output, or compact ASCII serializations for floats. digits(N) when is_integer(N) -> integer_to_list(N); digits(0.0) -> "0.0"; digits(Float) -> {Frac, Exp} = frexp(Float), Exp1 = Exp - 53, Frac1 = trunc(abs(Frac) * (1 bsl 53)), [Place | Digits] = digits1(Float, Exp1, Frac1), R = insert_decimal(Place, [$0 + D || D <- Digits]), case Float < 0 of true -> [$- | R]; _ -> R end. %% @spec frexp(F::float()) -> {Frac::float(), Exp::float()} %% @doc Return the fractional and exponent part of an IEEE 754 double, %% equivalent to the libc function of the same name. %% F = Frac * pow(2, Exp). frexp(F) -> frexp1(unpack(F)). %% @spec int_pow(X::integer(), N::integer()) -> Y::integer() %% @doc Moderately efficient way to exponentiate integers. %% int_pow(10, 2) = 100. int_pow(_X, 0) -> 1; int_pow(X, N) when N > 0 -> int_pow(X, N, 1). %% @spec int_ceil(F::float()) -> integer() %% @doc Return the ceiling of F as an integer. The ceiling is defined as %% F when F == trunc(F); %% trunc(F) when F < 0; %% trunc(F) + 1 when F > 0. int_ceil(X) -> T = trunc(X), case (X - T) of Neg when Neg < 0 -> T; Pos when Pos > 0 -> T + 1; _ -> T end. %% Internal API int_pow(X, N, R) when N < 2 -> R * X; int_pow(X, N, R) -> int_pow(X * X, N bsr 1, case N band 1 of 1 -> R * X; 0 -> R end). insert_decimal(0, S) -> "0." ++ S; insert_decimal(Place, S) when Place > 0 -> L = length(S), case Place - L of 0 -> S ++ ".0"; N when N < 0 -> {S0, S1} = lists:split(L + N, S), S0 ++ "." ++ S1; N when N < 6 -> %% More places than digits S ++ lists:duplicate(N, $0) ++ ".0"; _ -> insert_decimal_exp(Place, S) end; insert_decimal(Place, S) when Place > -6 -> "0." ++ lists:duplicate(abs(Place), $0) ++ S; insert_decimal(Place, S) -> insert_decimal_exp(Place, S). insert_decimal_exp(Place, S) -> [C | S0] = S, S1 = case S0 of [] -> "0"; _ -> S0 end, Exp = case Place < 0 of true -> "e-"; false -> "e+" end, [C] ++ "." ++ S1 ++ Exp ++ integer_to_list(abs(Place - 1)). digits1(Float, Exp, Frac) -> Round = ((Frac band 1) =:= 0), case Exp >= 0 of true -> BExp = 1 bsl Exp, case (Frac /= ?BIG_POW) of true -> scale((Frac * BExp * 2), 2, BExp, BExp, Round, Round, Float); false -> scale((Frac * BExp * 4), 4, (BExp * 2), BExp, Round, Round, Float) end; false -> case (Exp == ?MIN_EXP) orelse (Frac /= ?BIG_POW) of true -> scale((Frac * 2), 1 bsl (1 - Exp), 1, 1, Round, Round, Float); false -> scale((Frac * 4), 1 bsl (2 - Exp), 2, 1, Round, Round, Float) end end. scale(R, S, MPlus, MMinus, LowOk, HighOk, Float) -> Est = int_ceil(math:log10(abs(Float)) - 1.0e-10), %% Note that the scheme implementation uses a 326 element look-up table %% for int_pow(10, N) where we do not. case Est >= 0 of true -> fixup(R, S * int_pow(10, Est), MPlus, MMinus, Est, LowOk, HighOk); false -> Scale = int_pow(10, -Est), fixup(R * Scale, S, MPlus * Scale, MMinus * Scale, Est, LowOk, HighOk) end. fixup(R, S, MPlus, MMinus, K, LowOk, HighOk) -> TooLow = case HighOk of true -> (R + MPlus) >= S; false -> (R + MPlus) > S end, case TooLow of true -> [(K + 1) | generate(R, S, MPlus, MMinus, LowOk, HighOk)]; false -> [K | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)] end. generate(R0, S, MPlus, MMinus, LowOk, HighOk) -> D = R0 div S, R = R0 rem S, TC1 = case LowOk of true -> R =< MMinus; false -> R < MMinus end, TC2 = case HighOk of true -> (R + MPlus) >= S; false -> (R + MPlus) > S end, case TC1 of false -> case TC2 of false -> [D | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)]; true -> [D + 1] end; true -> case TC2 of false -> [D]; true -> case R * 2 < S of true -> [D]; false -> [D + 1] end end end. unpack(Float) -> <> = <>, {Sign, Exp, Frac}. frexp1({_Sign, 0, 0}) -> {0.0, 0}; frexp1({Sign, 0, Frac}) -> Exp = log2floor(Frac), <> = <>, {Frac1, -(?FLOAT_BIAS) - 52 + Exp}; frexp1({Sign, Exp, Frac}) -> <> = <>, {Frac1, Exp - ?FLOAT_BIAS}. log2floor(Int) -> log2floor(Int, 0). log2floor(0, N) -> N; log2floor(Int, N) -> log2floor(Int bsr 1, 1 + N). test() -> ok = test_frexp(), ok = test_int_ceil(), ok = test_int_pow(), ok = test_digits(), ok. test_int_ceil() -> 1 = int_ceil(0.0001), 0 = int_ceil(0.0), 1 = int_ceil(0.99), 1 = int_ceil(1.0), -1 = int_ceil(-1.5), -2 = int_ceil(-2.0), ok. test_int_pow() -> 1 = int_pow(1, 1), 1 = int_pow(1, 0), 1 = int_pow(10, 0), 10 = int_pow(10, 1), 100 = int_pow(10, 2), 1000 = int_pow(10, 3), ok. test_digits() -> "0" = digits(0), "0.0" = digits(0.0), "1.0" = digits(1.0), "-1.0" = digits(-1.0), "0.1" = digits(0.1), "0.01" = digits(0.01), "0.001" = digits(0.001), ok. test_frexp() -> %% zero {0.0, 0} = frexp(0.0), %% one {0.5, 1} = frexp(1.0), %% negative one {-0.5, 1} = frexp(-1.0), %% small denormalized number %% 4.94065645841246544177e-324 <> = <<0,0,0,0,0,0,0,1>>, {0.5, -1073} = frexp(SmallDenorm), %% large denormalized number %% 2.22507385850720088902e-308 <> = <<0,15,255,255,255,255,255,255>>, {0.99999999999999978, -1022} = frexp(BigDenorm), %% small normalized number %% 2.22507385850720138309e-308 <> = <<0,16,0,0,0,0,0,0>>, {0.5, -1021} = frexp(SmallNorm), %% large normalized number %% 1.79769313486231570815e+308 <> = <<127,239,255,255,255,255,255,255>>, {0.99999999999999989, 1024} = frexp(LargeNorm), ok. tsung-1.4.2/src/lib/mochiweb_charref.erl0000644000201100017670000001602211701017117017670 0ustar nniclausdream%% @author Bob Ippolito %% @copyright 2007 Mochi Media, Inc. %% @doc Converts HTML 4 charrefs and entities to codepoints. -module(mochiweb_charref). -export([charref/1, test/0]). %% External API. %% @spec charref(S) -> integer() | undefined %% @doc Convert a decimal charref, hex charref, or html entity to a unicode %% codepoint, or return undefined on failure. %% The input should not include an ampersand or semicolon. %% charref("#38") = 38, charref("#x26") = 38, charref("amp") = 38. charref(B) when is_binary(B) -> charref(binary_to_list(B)); charref([$#, C | L]) when C =:= $x orelse C =:= $X -> try erlang:list_to_integer(L, 16) catch error:badarg -> undefined end; charref([$# | L]) -> try list_to_integer(L) catch error:badarg -> undefined end; charref(L) -> entity(L). %% @spec test() -> ok %% @doc Run tests for mochiweb_charref. test() -> 1234 = charref("#1234"), 255 = charref("#xfF"), 255 = charref("#XFf"), 38 = charref("amp"), undefined = charref("not_an_entity"), ok. %% Internal API. entity("nbsp") -> 160; entity("iexcl") -> 161; entity("cent") -> 162; entity("pound") -> 163; entity("curren") -> 164; entity("yen") -> 165; entity("brvbar") -> 166; entity("sect") -> 167; entity("uml") -> 168; entity("copy") -> 169; entity("ordf") -> 170; entity("laquo") -> 171; entity("not") -> 172; entity("shy") -> 173; entity("reg") -> 174; entity("macr") -> 175; entity("deg") -> 176; entity("plusmn") -> 177; entity("sup2") -> 178; entity("sup3") -> 179; entity("acute") -> 180; entity("micro") -> 181; entity("para") -> 182; entity("middot") -> 183; entity("cedil") -> 184; entity("sup1") -> 185; entity("ordm") -> 186; entity("raquo") -> 187; entity("frac14") -> 188; entity("frac12") -> 189; entity("frac34") -> 190; entity("iquest") -> 191; entity("Agrave") -> 192; entity("Aacute") -> 193; entity("Acirc") -> 194; entity("Atilde") -> 195; entity("Auml") -> 196; entity("Aring") -> 197; entity("AElig") -> 198; entity("Ccedil") -> 199; entity("Egrave") -> 200; entity("Eacute") -> 201; entity("Ecirc") -> 202; entity("Euml") -> 203; entity("Igrave") -> 204; entity("Iacute") -> 205; entity("Icirc") -> 206; entity("Iuml") -> 207; entity("ETH") -> 208; entity("Ntilde") -> 209; entity("Ograve") -> 210; entity("Oacute") -> 211; entity("Ocirc") -> 212; entity("Otilde") -> 213; entity("Ouml") -> 214; entity("times") -> 215; entity("Oslash") -> 216; entity("Ugrave") -> 217; entity("Uacute") -> 218; entity("Ucirc") -> 219; entity("Uuml") -> 220; entity("Yacute") -> 221; entity("THORN") -> 222; entity("szlig") -> 223; entity("agrave") -> 224; entity("aacute") -> 225; entity("acirc") -> 226; entity("atilde") -> 227; entity("auml") -> 228; entity("aring") -> 229; entity("aelig") -> 230; entity("ccedil") -> 231; entity("egrave") -> 232; entity("eacute") -> 233; entity("ecirc") -> 234; entity("euml") -> 235; entity("igrave") -> 236; entity("iacute") -> 237; entity("icirc") -> 238; entity("iuml") -> 239; entity("eth") -> 240; entity("ntilde") -> 241; entity("ograve") -> 242; entity("oacute") -> 243; entity("ocirc") -> 244; entity("otilde") -> 245; entity("ouml") -> 246; entity("divide") -> 247; entity("oslash") -> 248; entity("ugrave") -> 249; entity("uacute") -> 250; entity("ucirc") -> 251; entity("uuml") -> 252; entity("yacute") -> 253; entity("thorn") -> 254; entity("yuml") -> 255; entity("fnof") -> 402; entity("Alpha") -> 913; entity("Beta") -> 914; entity("Gamma") -> 915; entity("Delta") -> 916; entity("Epsilon") -> 917; entity("Zeta") -> 918; entity("Eta") -> 919; entity("Theta") -> 920; entity("Iota") -> 921; entity("Kappa") -> 922; entity("Lambda") -> 923; entity("Mu") -> 924; entity("Nu") -> 925; entity("Xi") -> 926; entity("Omicron") -> 927; entity("Pi") -> 928; entity("Rho") -> 929; entity("Sigma") -> 931; entity("Tau") -> 932; entity("Upsilon") -> 933; entity("Phi") -> 934; entity("Chi") -> 935; entity("Psi") -> 936; entity("Omega") -> 937; entity("alpha") -> 945; entity("beta") -> 946; entity("gamma") -> 947; entity("delta") -> 948; entity("epsilon") -> 949; entity("zeta") -> 950; entity("eta") -> 951; entity("theta") -> 952; entity("iota") -> 953; entity("kappa") -> 954; entity("lambda") -> 955; entity("mu") -> 956; entity("nu") -> 957; entity("xi") -> 958; entity("omicron") -> 959; entity("pi") -> 960; entity("rho") -> 961; entity("sigmaf") -> 962; entity("sigma") -> 963; entity("tau") -> 964; entity("upsilon") -> 965; entity("phi") -> 966; entity("chi") -> 967; entity("psi") -> 968; entity("omega") -> 969; entity("thetasym") -> 977; entity("upsih") -> 978; entity("piv") -> 982; entity("bull") -> 8226; entity("hellip") -> 8230; entity("prime") -> 8242; entity("Prime") -> 8243; entity("oline") -> 8254; entity("frasl") -> 8260; entity("weierp") -> 8472; entity("image") -> 8465; entity("real") -> 8476; entity("trade") -> 8482; entity("alefsym") -> 8501; entity("larr") -> 8592; entity("uarr") -> 8593; entity("rarr") -> 8594; entity("darr") -> 8595; entity("harr") -> 8596; entity("crarr") -> 8629; entity("lArr") -> 8656; entity("uArr") -> 8657; entity("rArr") -> 8658; entity("dArr") -> 8659; entity("hArr") -> 8660; entity("forall") -> 8704; entity("part") -> 8706; entity("exist") -> 8707; entity("empty") -> 8709; entity("nabla") -> 8711; entity("isin") -> 8712; entity("notin") -> 8713; entity("ni") -> 8715; entity("prod") -> 8719; entity("sum") -> 8721; entity("minus") -> 8722; entity("lowast") -> 8727; entity("radic") -> 8730; entity("prop") -> 8733; entity("infin") -> 8734; entity("ang") -> 8736; entity("and") -> 8743; entity("or") -> 8744; entity("cap") -> 8745; entity("cup") -> 8746; entity("int") -> 8747; entity("there4") -> 8756; entity("sim") -> 8764; entity("cong") -> 8773; entity("asymp") -> 8776; entity("ne") -> 8800; entity("equiv") -> 8801; entity("le") -> 8804; entity("ge") -> 8805; entity("sub") -> 8834; entity("sup") -> 8835; entity("nsub") -> 8836; entity("sube") -> 8838; entity("supe") -> 8839; entity("oplus") -> 8853; entity("otimes") -> 8855; entity("perp") -> 8869; entity("sdot") -> 8901; entity("lceil") -> 8968; entity("rceil") -> 8969; entity("lfloor") -> 8970; entity("rfloor") -> 8971; entity("lang") -> 9001; entity("rang") -> 9002; entity("loz") -> 9674; entity("spades") -> 9824; entity("clubs") -> 9827; entity("hearts") -> 9829; entity("diams") -> 9830; entity("quot") -> 34; entity("amp") -> 38; entity("lt") -> 60; entity("gt") -> 62; entity("OElig") -> 338; entity("oelig") -> 339; entity("Scaron") -> 352; entity("scaron") -> 353; entity("Yuml") -> 376; entity("circ") -> 710; entity("tilde") -> 732; entity("ensp") -> 8194; entity("emsp") -> 8195; entity("thinsp") -> 8201; entity("zwnj") -> 8204; entity("zwj") -> 8205; entity("lrm") -> 8206; entity("rlm") -> 8207; entity("ndash") -> 8211; entity("mdash") -> 8212; entity("lsquo") -> 8216; entity("rsquo") -> 8217; entity("sbquo") -> 8218; entity("ldquo") -> 8220; entity("rdquo") -> 8221; entity("bdquo") -> 8222; entity("dagger") -> 8224; entity("Dagger") -> 8225; entity("permil") -> 8240; entity("lsaquo") -> 8249; entity("rsaquo") -> 8250; entity("euro") -> 8364; entity(_) -> undefined. tsung-1.4.2/src/lib/mochiweb_html.erl0000644000201100017670000007500211701017117017225 0ustar nniclausdream%% @author Bob Ippolito %% @copyright 2007 Mochi Media, Inc. %% @doc Loosely tokenizes and generates parse trees for HTML 4. -module(mochiweb_html). -export([tokens/1, parse/1, parse_tokens/1, to_tokens/1, escape/1, escape_attr/1, to_html/1, test/0]). % This is a macro to placate syntax highlighters.. -define(QUOTE, $\"). -define(SQUOTE, $\'). -define(ADV_COL(S, N), S#decoder{column=N+S#decoder.column, offset=N+S#decoder.offset}). -define(INC_COL(S), S#decoder{column=1+S#decoder.column, offset=1+S#decoder.offset}). -define(INC_LINE(S), S#decoder{column=1, line=1+S#decoder.line, offset=1+S#decoder.offset}). -define(INC_CHAR(S, C), case C of $\n -> S#decoder{column=1, line=1+S#decoder.line, offset=1+S#decoder.offset}; _ -> S#decoder{column=1+S#decoder.column, offset=1+S#decoder.offset} end). -define(IS_WHITESPACE(C), (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). -define(IS_LITERAL_SAFE(C), ((C >= $A andalso C =< $Z) orelse (C >= $a andalso C =< $z) orelse (C >= $0 andalso C =< $9))). -record(decoder, {line=1, column=1, offset=0}). %% @type html_node() = {string(), [html_attr()], [html_node() | string()]} %% @type html_attr() = {string(), string()} %% @type html_token() = html_data() | start_tag() | end_tag() | inline_html() | html_comment() | html_doctype() %% @type html_data() = {data, string(), Whitespace::boolean()} %% @type start_tag() = {start_tag, Name, [html_attr()], Singleton::boolean()} %% @type end_tag() = {end_tag, Name} %% @type html_comment() = {comment, Comment} %% @type html_doctype() = {doctype, [Doctype]} %% @type inline_html() = {'=', iolist()} %% External API. %% @spec parse(string() | binary()) -> html_node() %% @doc tokenize and then transform the token stream into a HTML tree. parse(Input) -> parse_tokens(tokens(Input)). %% @spec parse_tokens([html_token()]) -> html_node() %% @doc Transform the output of tokens(Doc) into a HTML tree. parse_tokens(Tokens) when is_list(Tokens) -> %% Skip over doctype, processing instructions F = fun (X) -> case X of {start_tag, _, _, false} -> false; _ -> true end end, [{start_tag, Tag, Attrs, false} | Rest] = lists:dropwhile(F, Tokens), {Tree, _} = tree(Rest, [norm({Tag, Attrs})]), Tree. %% @spec tokens(StringOrBinary) -> [html_token()] %% @doc Transform the input UTF-8 HTML into a token stream. tokens(Input) -> tokens(iolist_to_binary(Input), #decoder{}, []). %% @spec to_tokens(html_node()) -> [html_token()] %% @doc Convert a html_node() tree to a list of tokens. to_tokens({Tag0}) -> to_tokens({Tag0, [], []}); to_tokens(T={'=', _}) -> [T]; to_tokens(T={doctype, _}) -> [T]; to_tokens(T={comment, _}) -> [T]; to_tokens({Tag0, Acc}) -> to_tokens({Tag0, [], Acc}); to_tokens({Tag0, Attrs, Acc}) -> Tag = to_tag(Tag0), to_tokens([{Tag, Acc}], [{start_tag, Tag, Attrs, is_singleton(Tag)}]). %% @spec to_html([html_token()] | html_node()) -> iolist() %% @doc Convert a list of html_token() to a HTML document. to_html(Node) when is_tuple(Node) -> to_html(to_tokens(Node)); to_html(Tokens) when is_list(Tokens) -> to_html(Tokens, []). %% @spec escape(string() | atom() | binary()) -> binary() %% @doc Escape a string such that it's safe for HTML (amp; lt; gt;). escape(B) when is_binary(B) -> escape(binary_to_list(B), []); escape(A) when is_atom(A) -> escape(atom_to_list(A), []); escape(S) when is_list(S) -> escape(S, []). %% @spec escape_attr(string() | binary() | atom() | integer() | float()) -> binary() %% @doc Escape a string such that it's safe for HTML attrs %% (amp; lt; gt; quot;). escape_attr(B) when is_binary(B) -> escape_attr(binary_to_list(B), []); escape_attr(A) when is_atom(A) -> escape_attr(atom_to_list(A), []); escape_attr(S) when is_list(S) -> escape_attr(S, []); escape_attr(I) when is_integer(I) -> escape_attr(integer_to_list(I), []); escape_attr(F) when is_float(F) -> escape_attr(mochinum:digits(F), []). %% @spec test() -> ok %% @doc Run tests for mochiweb_html. test() -> test_destack(), test_tokens(), test_parse(), test_parse_tokens(), test_escape(), test_escape_attr(), test_to_html(), ok. %% Internal API test_to_html() -> Expect = <<"hey!

what's up

sucka
">>, Expect = iolist_to_binary( to_html({html, [], [{<<"head">>, [], [{title, <<"hey!">>}]}, {body, [], [{p, [{class, foo}], [<<"what's">>, <<" up">>, {br}]}, {'div', <<"sucka">>}, {comment, <<" comment! ">>}]}]})), Expect1 = <<"">>, Expect1 = iolist_to_binary( to_html({doctype, [<<"html">>, <<"PUBLIC">>, <<"-//W3C//DTD XHTML 1.0 Transitional//EN">>, <<"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>]})), ok. to_html([], Acc) -> lists:reverse(Acc); to_html([{'=', Content} | Rest], Acc) -> to_html(Rest, [Content | Acc]); to_html([{pi, Tag, Attrs} | Rest], Acc) -> Open = [<<">, Tag, attrs_to_html(Attrs, []), <<"?>">>], to_html(Rest, [Open | Acc]); to_html([{comment, Comment} | Rest], Acc) -> to_html(Rest, [[<<"">>] | Acc]); to_html([{doctype, Parts} | Rest], Acc) -> Inside = doctype_to_html(Parts, Acc), to_html(Rest, [[<<">, Inside, <<">">>] | Acc]); to_html([{data, Data, _Whitespace} | Rest], Acc) -> to_html(Rest, [escape(Data) | Acc]); to_html([{start_tag, Tag, Attrs, Singleton} | Rest], Acc) -> Open = [<<"<">>, Tag, attrs_to_html(Attrs, []), case Singleton of true -> <<" />">>; false -> <<">">> end], to_html(Rest, [Open | Acc]); to_html([{end_tag, Tag} | Rest], Acc) -> to_html(Rest, [[<<">, Tag, <<">">>] | Acc]). doctype_to_html([], Acc) -> lists:reverse(Acc); doctype_to_html([Word | Rest], Acc) -> case lists:all(fun (C) -> ?IS_LITERAL_SAFE(C) end, binary_to_list(iolist_to_binary(Word))) of true -> doctype_to_html(Rest, [[<<" ">>, Word] | Acc]); false -> doctype_to_html(Rest, [[<<" \"">>, escape_attr(Word), ?QUOTE] | Acc]) end. attrs_to_html([], Acc) -> lists:reverse(Acc); attrs_to_html([{K, V} | Rest], Acc) -> attrs_to_html(Rest, [[<<" ">>, escape(K), <<"=\"">>, escape_attr(V), <<"\"">>] | Acc]). test_escape() -> <<"&quot;\"word <<up!&quot;">> = escape(<<""\"word <>), ok. test_escape_attr() -> <<"&quot;"word <<up!&quot;">> = escape_attr(<<""\"word <>), ok. escape([], Acc) -> list_to_binary(lists:reverse(Acc)); escape("<" ++ Rest, Acc) -> escape(Rest, lists:reverse("<", Acc)); escape(">" ++ Rest, Acc) -> escape(Rest, lists:reverse(">", Acc)); escape("&" ++ Rest, Acc) -> escape(Rest, lists:reverse("&", Acc)); escape([C | Rest], Acc) -> escape(Rest, [C | Acc]). escape_attr([], Acc) -> list_to_binary(lists:reverse(Acc)); escape_attr("<" ++ Rest, Acc) -> escape_attr(Rest, lists:reverse("<", Acc)); escape_attr(">" ++ Rest, Acc) -> escape_attr(Rest, lists:reverse(">", Acc)); escape_attr("&" ++ Rest, Acc) -> escape_attr(Rest, lists:reverse("&", Acc)); escape_attr([?QUOTE | Rest], Acc) -> escape_attr(Rest, lists:reverse(""", Acc)); escape_attr([C | Rest], Acc) -> escape_attr(Rest, [C | Acc]). to_tag(A) when is_atom(A) -> norm(atom_to_list(A)); to_tag(L) -> norm(L). to_tokens([], Acc) -> lists:reverse(Acc); to_tokens([{Tag, []} | Rest], Acc) -> to_tokens(Rest, [{end_tag, to_tag(Tag)} | Acc]); to_tokens([{Tag0, [{T0} | R1]} | Rest], Acc) -> %% Allow {br} to_tokens([{Tag0, [{T0, [], []} | R1]} | Rest], Acc); to_tokens([{Tag0, [T0={'=', _C0} | R1]} | Rest], Acc) -> %% Allow {'=', iolist()} to_tokens([{Tag0, R1} | Rest], [T0 | Acc]); to_tokens([{Tag0, [T0={comment, _C0} | R1]} | Rest], Acc) -> %% Allow {comment, iolist()} to_tokens([{Tag0, R1} | Rest], [T0 | Acc]); to_tokens([{Tag0, [{T0, A0=[{_, _} | _]} | R1]} | Rest], Acc) -> %% Allow {p, [{"class", "foo"}]} to_tokens([{Tag0, [{T0, A0, []} | R1]} | Rest], Acc); to_tokens([{Tag0, [{T0, C0} | R1]} | Rest], Acc) -> %% Allow {p, "content"} and {p, <<"content">>} to_tokens([{Tag0, [{T0, [], C0} | R1]} | Rest], Acc); to_tokens([{Tag0, [{T0, A1, C0} | R1]} | Rest], Acc) when is_binary(C0) -> %% Allow {"p", [{"class", "foo"}], <<"content">>} to_tokens([{Tag0, [{T0, A1, binary_to_list(C0)} | R1]} | Rest], Acc); to_tokens([{Tag0, [{T0, A1, C0=[C | _]} | R1]} | Rest], Acc) when is_integer(C) -> %% Allow {"p", [{"class", "foo"}], "content"} to_tokens([{Tag0, [{T0, A1, [C0]} | R1]} | Rest], Acc); to_tokens([{Tag0, [{T0, A1, C1} | R1]} | Rest], Acc) -> %% Native {"p", [{"class", "foo"}], ["content"]} Tag = to_tag(Tag0), T1 = to_tag(T0), case is_singleton(norm(T1)) of true -> to_tokens([{Tag, R1} | Rest], [{start_tag, T1, A1, true} | Acc]); false -> to_tokens([{T1, C1}, {Tag, R1} | Rest], [{start_tag, T1, A1, false} | Acc]) end; to_tokens([{Tag0, [L | R1]} | Rest], Acc) when is_list(L) -> %% List text Tag = to_tag(Tag0), to_tokens([{Tag, R1} | Rest], [{data, iolist_to_binary(L), false} | Acc]); to_tokens([{Tag0, [B | R1]} | Rest], Acc) when is_binary(B) -> %% Binary text Tag = to_tag(Tag0), to_tokens([{Tag, R1} | Rest], [{data, B, false} | Acc]). test_tokens() -> [{start_tag, <<"foo">>, [{<<"bar">>, <<"baz">>}, {<<"wibble">>, <<"wibble">>}, {<<"alice">>, <<"bob">>}], true}] = tokens(<<"">>), [{start_tag, <<"foo">>, [{<<"bar">>, <<"baz">>}, {<<"wibble">>, <<"wibble">>}, {<<"alice">>, <<"bob">>}], true}] = tokens(<<"">>), [{comment, <<"[if lt IE 7]>\n\n>}] = tokens(<<"">>), [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false}, {data, <<" A= B <= C ">>, false}, {end_tag, <<"script">>}] = tokens(<<"">>), [{start_tag, <<"textarea">>, [], false}, {data, <<"">>, false}, {end_tag, <<"textarea">>}] = tokens(<<"">>), ok. tokens(B, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary>> -> lists:reverse(Acc); _ -> {Tag, S1} = tokenize(B, S), case parse_flag(Tag) of script -> {Tag2, S2} = tokenize_script(B, S1), tokens(B, S2, [Tag2, Tag | Acc]); textarea -> {Tag2, S2} = tokenize_textarea(B, S1), tokens(B, S2, [Tag2, Tag | Acc]); none -> tokens(B, S1, [Tag | Acc]) end end. parse_flag({start_tag, B, _, false}) -> case string:to_lower(binary_to_list(B)) of "script" -> script; "textarea" -> textarea; _ -> none end; parse_flag(_) -> none. tokenize(B, S=#decoder{offset=O}) -> case B of <<_:O/binary, " CDATA>>]]> ">>, Expect = {<<"html">>, [], [{<<"head">>, [], [{<<"meta">>, [{<<"http-equiv">>,<<"Content-Type">>}, {<<"content">>,<<"text/html; charset=UTF-8">>}], []}, {<<"title">>,[],[<<"Foo">>]}, {<<"link">>, [{<<"rel">>,<<"stylesheet">>}, {<<"type">>,<<"text/css">>}, {<<"href">>,<<"/static/rel/dojo/resources/dojo.css">>}, {<<"media">>,<<"screen">>}], []}, {<<"link">>, [{<<"rel">>,<<"stylesheet">>}, {<<"type">>,<<"text/css">>}, {<<"href">>,<<"/static/foo.css">>}, {<<"media">>,<<"screen">>}], []}, {comment,<<"[if lt IE 7]>\n \n >}, {<<"link">>, [{<<"rel">>,<<"icon">>}, {<<"href">>,<<"/static/images/favicon.ico">>}, {<<"type">>,<<"image/x-icon">>}], []}, {<<"link">>, [{<<"rel">>,<<"shortcut icon">>}, {<<"href">>,<<"/static/images/favicon.ico">>}, {<<"type">>,<<"image/x-icon">>}], []}]}, {<<"body">>, [{<<"id">>,<<"home">>}, {<<"class">>,<<"tundra">>}], [<<"<CDATA>>">>]}]}, Expect = parse(D0), ok. test_parse_tokens() -> D0 = [{doctype,[<<"HTML">>,<<"PUBLIC">>,<<"-//W3C//DTD HTML 4.01 Transitional//EN">>]}, {data,<<"\n">>,true}, {start_tag,<<"html">>,[],false}], {<<"html">>, [], []} = parse_tokens(D0), D1 = D0 ++ [{end_tag, <<"html">>}], {<<"html">>, [], []} = parse_tokens(D1), D2 = D0 ++ [{start_tag, <<"body">>, [], false}], {<<"html">>, [], [{<<"body">>, [], []}]} = parse_tokens(D2), D3 = D0 ++ [{start_tag, <<"head">>, [], false}, {end_tag, <<"head">>}, {start_tag, <<"body">>, [], false}], {<<"html">>, [], [{<<"head">>, [], []}, {<<"body">>, [], []}]} = parse_tokens(D3), D4 = D3 ++ [{data,<<"\n">>,true}, {start_tag,<<"div">>,[{<<"class">>,<<"a">>}],false}, {start_tag,<<"a">>,[{<<"name">>,<<"#anchor">>}],false}, {end_tag,<<"a">>}, {end_tag,<<"div">>}, {start_tag,<<"div">>,[{<<"class">>,<<"b">>}],false}, {start_tag,<<"div">>,[{<<"class">>,<<"c">>}],false}, {end_tag,<<"div">>}, {end_tag,<<"div">>}], {<<"html">>, [], [{<<"head">>, [], []}, {<<"body">>, [], [{<<"div">>, [{<<"class">>, <<"a">>}], [{<<"a">>, [{<<"name">>, <<"#anchor">>}], []}]}, {<<"div">>, [{<<"class">>, <<"b">>}], [{<<"div">>, [{<<"class">>, <<"c">>}], []}]} ]}]} = parse_tokens(D4), D5 = [{start_tag,<<"html">>,[],false}, {data,<<"\n">>,true}, {data,<<"boo">>,false}, {data,<<"hoo">>,false}, {data,<<"\n">>,true}, {end_tag,<<"html">>}], {<<"html">>, [], [<<"\nboohoo\n">>]} = parse_tokens(D5), D6 = [{start_tag,<<"html">>,[],false}, {data,<<"\n">>,true}, {data,<<"\n">>,true}, {end_tag,<<"html">>}], {<<"html">>, [], []} = parse_tokens(D6), D7 = [{start_tag,<<"html">>,[],false}, {start_tag,<<"ul">>,[],false}, {start_tag,<<"li">>,[],false}, {data,<<"word">>,false}, {start_tag,<<"li">>,[],false}, {data,<<"up">>,false}, {end_tag,<<"li">>}, {start_tag,<<"li">>,[],false}, {data,<<"fdsa">>,false}, {start_tag,<<"br">>,[],true}, {data,<<"asdf">>,false}, {end_tag,<<"ul">>}, {end_tag,<<"html">>}], {<<"html">>, [], [{<<"ul">>, [], [{<<"li">>, [], [<<"word">>]}, {<<"li">>, [], [<<"up">>]}, {<<"li">>, [], [<<"fdsa">>,{<<"br">>, [], []}, <<"asdf">>]}]}]} = parse_tokens(D7), ok. tree_data([{data, Data, Whitespace} | Rest], AllWhitespace, Acc) -> tree_data(Rest, (Whitespace andalso AllWhitespace), [Data | Acc]); tree_data(Rest, AllWhitespace, Acc) -> {iolist_to_binary(lists:reverse(Acc)), AllWhitespace, Rest}. tree([], Stack) -> {destack(Stack), []}; tree([{end_tag, Tag} | Rest], Stack) -> case destack(norm(Tag), Stack) of S when is_list(S) -> tree(Rest, S); Result -> {Result, []} end; tree([{start_tag, Tag, Attrs, true} | Rest], S) -> tree(Rest, append_stack_child(norm({Tag, Attrs}), S)); tree([{start_tag, Tag, Attrs, false} | Rest], S) -> tree(Rest, stack(norm({Tag, Attrs}), S)); tree([T={pi, _Tag, _Attrs} | Rest], S) -> tree(Rest, append_stack_child(T, S)); tree([T={comment, _Comment} | Rest], S) -> tree(Rest, append_stack_child(T, S)); tree(L=[{data, _Data, _Whitespace} | _], S) -> case tree_data(L, true, []) of {_, true, Rest} -> tree(Rest, S); {Data, false, Rest} -> tree(Rest, append_stack_child(Data, S)) end. norm({Tag, Attrs}) -> {norm(Tag), [{norm(K), iolist_to_binary(V)} || {K, V} <- Attrs], []}; norm(Tag) when is_binary(Tag) -> Tag; norm(Tag) -> list_to_binary(string:to_lower(Tag)). test_destack() -> {<<"a">>, [], []} = destack([{<<"a">>, [], []}]), {<<"a">>, [], [{<<"b">>, [], []}]} = destack([{<<"b">>, [], []}, {<<"a">>, [], []}]), {<<"a">>, [], [{<<"b">>, [], [{<<"c">>, [], []}]}]} = destack([{<<"c">>, [], []}, {<<"b">>, [], []}, {<<"a">>, [], []}]), [{<<"a">>, [], [{<<"b">>, [], [{<<"c">>, [], []}]}]}] = destack(<<"b">>, [{<<"c">>, [], []}, {<<"b">>, [], []}, {<<"a">>, [], []}]), [{<<"b">>, [], [{<<"c">>, [], []}]}, {<<"a">>, [], []}] = destack(<<"c">>, [{<<"c">>, [], []}, {<<"b">>, [], []},{<<"a">>, [], []}]), ok. stack(T1={TN, _, _}, Stack=[{TN, _, _} | _Rest]) when TN =:= <<"li">> orelse TN =:= <<"option">> -> [T1 | destack(TN, Stack)]; stack(T1={TN0, _, _}, Stack=[{TN1, _, _} | _Rest]) when (TN0 =:= <<"dd">> orelse TN0 =:= <<"dt">>) andalso (TN1 =:= <<"dd">> orelse TN1 =:= <<"dt">>) -> [T1 | destack(TN1, Stack)]; stack(T1, Stack) -> [T1 | Stack]. append_stack_child(StartTag, [{Name, Attrs, Acc} | Stack]) -> [{Name, Attrs, [StartTag | Acc]} | Stack]. destack(TagName, Stack) when is_list(Stack) -> F = fun (X) -> case X of {TagName, _, _} -> false; _ -> true end end, case lists:splitwith(F, Stack) of {_, []} -> %% No match, no state change Stack; {_Pre, [_T]} -> %% Unfurl the whole stack, we're done destack(Stack); {Pre, [T, {T0, A0, Acc0} | Post]} -> %% Unfurl up to the tag, then accumulate it [{T0, A0, [destack(Pre ++ [T]) | Acc0]} | Post] end. destack([{Tag, Attrs, Acc}]) -> {Tag, Attrs, lists:reverse(Acc)}; destack([{T1, A1, Acc1}, {T0, A0, Acc0} | Rest]) -> destack([{T0, A0, [{T1, A1, lists:reverse(Acc1)} | Acc0]} | Rest]). is_singleton(<<"br">>) -> true; is_singleton(<<"hr">>) -> true; is_singleton(<<"img">>) -> true; is_singleton(<<"input">>) -> true; is_singleton(<<"base">>) -> true; is_singleton(<<"meta">>) -> true; is_singleton(<<"link">>) -> true; is_singleton(<<"area">>) -> true; is_singleton(<<"param">>) -> true; is_singleton(<<"col">>) -> true; is_singleton(_) -> false. tokenize_data(B, S=#decoder{offset=O}) -> tokenize_data(B, S, O, true). tokenize_data(B, S=#decoder{offset=O}, Start, Whitespace) -> case B of <<_:O/binary, C, _/binary>> when (C =/= $< andalso C =/= $&) -> tokenize_data(B, ?INC_CHAR(S, C), Start, (Whitespace andalso ?IS_WHITESPACE(C))); _ -> Len = O - Start, <<_:Start/binary, Data:Len/binary, _/binary>> = B, {{data, Data, Whitespace}, S} end. tokenize_attributes(B, S) -> tokenize_attributes(B, S, []). tokenize_attributes(B, S=#decoder{offset=O}, Acc) -> case B of <<_:O/binary>> -> {lists:reverse(Acc), S}; <<_:O/binary, C, _/binary>> when (C =:= $> orelse C =:= $/) -> {lists:reverse(Acc), S}; <<_:O/binary, "?>", _/binary>> -> {lists:reverse(Acc), S}; <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> tokenize_attributes(B, ?INC_CHAR(S, C), Acc); _ -> {Attr, S1} = tokenize_literal(B, S), {Value, S2} = tokenize_attr_value(Attr, B, S1), tokenize_attributes(B, S2, [{Attr, Value} | Acc]) end. tokenize_attr_value(Attr, B, S) -> S1 = skip_whitespace(B, S), O = S1#decoder.offset, case B of <<_:O/binary, "=", _/binary>> -> tokenize_word_or_literal(B, ?INC_COL(S1)); _ -> {Attr, S1} end. skip_whitespace(B, S=#decoder{offset=O}) -> case B of <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> skip_whitespace(B, ?INC_CHAR(S, C)); _ -> S end. tokenize_literal(Bin, S) -> tokenize_literal(Bin, S, []). tokenize_literal(Bin, S=#decoder{offset=O}, Acc) -> case Bin of <<_:O/binary, $&, _/binary>> -> {{data, Data, false}, S1} = tokenize_charref(Bin, ?INC_COL(S)), tokenize_literal(Bin, S1, [Data | Acc]); <<_:O/binary, C, _/binary>> when not (?IS_WHITESPACE(C) orelse C =:= $> orelse C =:= $/ orelse C =:= $=) -> tokenize_literal(Bin, ?INC_COL(S), [C | Acc]); _ -> {iolist_to_binary(lists:reverse(Acc)), S} end. find_qgt(Bin, S=#decoder{offset=O}) -> case Bin of <<_:O/binary, "?>", _/binary>> -> ?ADV_COL(S, 2); <<_:O/binary, C, _/binary>> -> find_qgt(Bin, ?INC_CHAR(S, C)); _ -> S end. find_gt(Bin, S) -> find_gt(Bin, S, false). find_gt(Bin, S=#decoder{offset=O}, HasSlash) -> case Bin of <<_:O/binary, $/, _/binary>> -> find_gt(Bin, ?INC_COL(S), true); <<_:O/binary, $>, _/binary>> -> {?INC_COL(S), HasSlash}; <<_:O/binary, C, _/binary>> -> find_gt(Bin, ?INC_CHAR(S, C), HasSlash); _ -> {S, HasSlash} end. tokenize_charref(Bin, S=#decoder{offset=O}) -> tokenize_charref(Bin, S, O). tokenize_charref(Bin, S=#decoder{offset=O}, Start) -> case Bin of <<_:O/binary>> -> <<_:Start/binary, Raw/binary>> = Bin, {{data, Raw, false}, S}; <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) orelse C =:= ?SQUOTE orelse C =:= ?QUOTE orelse C =:= $/ orelse C =:= $> -> Len = O - Start, <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, {{data, Raw, false}, S}; <<_:O/binary, $;, _/binary>> -> Len = O - Start, <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, Data = case mochiweb_charref:charref(Raw) of undefined -> Start1 = Start - 1, Len1 = Len + 2, <<_:Start1/binary, R:Len1/binary, _/binary>> = Bin, R; Unichar -> list_to_binary(xmerl_ucs:to_utf8(Unichar)) end, {{data, Data, false}, ?INC_COL(S)}; _ -> tokenize_charref(Bin, ?INC_COL(S), Start) end. tokenize_doctype(Bin, S) -> tokenize_doctype(Bin, S, []). tokenize_doctype(Bin, S=#decoder{offset=O}, Acc) -> case Bin of <<_:O/binary>> -> {{doctype, lists:reverse(Acc)}, S}; <<_:O/binary, $>, _/binary>> -> {{doctype, lists:reverse(Acc)}, ?INC_COL(S)}; <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> tokenize_doctype(Bin, ?INC_CHAR(S, C), Acc); _ -> {Word, S1} = tokenize_word_or_literal(Bin, S), tokenize_doctype(Bin, S1, [Word | Acc]) end. tokenize_word_or_literal(Bin, S=#decoder{offset=O}) -> case Bin of <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> {error, {whitespace, [C], S}}; <<_:O/binary, C, _/binary>> when C =:= ?QUOTE orelse C =:= ?SQUOTE -> tokenize_word(Bin, ?INC_COL(S), C); _ -> tokenize_literal(Bin, S, []) end. tokenize_word(Bin, S, Quote) -> tokenize_word(Bin, S, Quote, []). tokenize_word(Bin, S=#decoder{offset=O}, Quote, Acc) -> case Bin of <<_:O/binary>> -> {iolist_to_binary(lists:reverse(Acc)), S}; <<_:O/binary, Quote, _/binary>> -> {iolist_to_binary(lists:reverse(Acc)), ?INC_COL(S)}; <<_:O/binary, $&, _/binary>> -> {{data, Data, false}, S1} = tokenize_charref(Bin, ?INC_COL(S)), tokenize_word(Bin, S1, Quote, [Data | Acc]); <<_:O/binary, C, _/binary>> -> tokenize_word(Bin, ?INC_CHAR(S, C), Quote, [C | Acc]) end. tokenize_cdata(Bin, S=#decoder{offset=O}) -> tokenize_cdata(Bin, S, O). tokenize_cdata(Bin, S=#decoder{offset=O}, Start) -> case Bin of <<_:O/binary, "]]>", _/binary>> -> Len = O - Start, <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, {{data, Raw, false}, ?ADV_COL(S, 3)}; <<_:O/binary, C, _/binary>> -> tokenize_cdata(Bin, ?INC_CHAR(S, C), Start); _ -> <<_:O/binary, Raw/binary>> = Bin, {{data, Raw, false}, S} end. tokenize_comment(Bin, S=#decoder{offset=O}) -> tokenize_comment(Bin, S, O). tokenize_comment(Bin, S=#decoder{offset=O}, Start) -> case Bin of <<_:O/binary, "-->", _/binary>> -> Len = O - Start, <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, {{comment, Raw}, ?ADV_COL(S, 3)}; <<_:O/binary, C, _/binary>> -> tokenize_comment(Bin, ?INC_CHAR(S, C), Start); <<_:Start/binary, Raw/binary>> -> {{comment, Raw}, S} end. tokenize_script(Bin, S=#decoder{offset=O}) -> tokenize_script(Bin, S, O). tokenize_script(Bin, S=#decoder{offset=O}, Start) -> case Bin of %% Just a look-ahead, we want the end_tag separately <<_:O/binary, $<, $/, SS, CC, RR, II, PP, TT, _/binary>> when (SS =:= $s orelse SS =:= $S) andalso (CC =:= $c orelse CC =:= $C) andalso (RR =:= $r orelse RR =:= $R) andalso (II =:= $i orelse II =:= $I) andalso (PP =:= $p orelse PP =:= $P) andalso (TT=:= $t orelse TT =:= $T) -> Len = O - Start, <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, {{data, Raw, false}, S}; <<_:O/binary, C, _/binary>> -> tokenize_script(Bin, ?INC_CHAR(S, C), Start); <<_:Start/binary, Raw/binary>> -> {{data, Raw, false}, S} end. tokenize_textarea(Bin, S=#decoder{offset=O}) -> tokenize_textarea(Bin, S, O). tokenize_textarea(Bin, S=#decoder{offset=O}, Start) -> case Bin of %% Just a look-ahead, we want the end_tag separately <<_:O/binary, $<, $/, TT, EE, XX, TT2, AA, RR, EE2, AA2, _/binary>> when (TT =:= $t orelse TT =:= $T) andalso (EE =:= $e orelse EE =:= $E) andalso (XX =:= $x orelse XX =:= $X) andalso (TT2 =:= $t orelse TT2 =:= $T) andalso (AA =:= $a orelse AA =:= $A) andalso (RR =:= $r orelse RR =:= $R) andalso (EE2 =:= $e orelse EE2 =:= $E) andalso (AA2 =:= $a orelse AA2 =:= $A) -> Len = O - Start, <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, {{data, Raw, false}, S}; <<_:O/binary, C, _/binary>> -> tokenize_textarea(Bin, ?INC_CHAR(S, C), Start); <<_:Start/binary, Raw/binary>> -> {{data, Raw, false}, S} end. tsung-1.4.2/src/lib/ELDAPv3.erl0000644000201100017670000035052611701017117015511 0ustar nniclausdream%% Generated by the Erlang ASN.1 BER-compiler version:1.4.5 %% Purpose: encoder and decoder to the types in mod ELDAPv3 -module('ELDAPv3'). -include("ELDAPv3.hrl"). -define('RT_BER',asn1rt_ber_bin). -asn1_info([{vsn,'1.4.5'}, {module,'ELDAPv3'}, {options,[ber,report_errors,{cwd,[47,104,111,109,101,47,112,97,98,108,111,47,108,111,99,111,115,47,106,117,110,103,101,114,108,47,108,105,98,47,101,108,100,97,112,47,115,114,99]},{outdir,[47,104,111,109,101,47,112,97,98,108,111,47,108,111,99,111,115,47,106,117,110,103,101,114,108,47,108,105,98,47,101,108,100,97,112,47,115,114,99]},{i,[46]},{i,[47,104,111,109,101,47,112,97,98,108,111,47,108,111,99,111,115,47,106,117,110,103,101,114,108,47,108,105,98,47,101,108,100,97,112,47,115,114,99]}]}]). -export([encoding_rule/0]). -export([ 'enc_LDAPMessage'/2, 'enc_MessageID'/2, 'enc_LDAPString'/2, 'enc_LDAPOID'/2, 'enc_LDAPDN'/2, 'enc_RelativeLDAPDN'/2, 'enc_AttributeType'/2, 'enc_AttributeDescription'/2, 'enc_AttributeDescriptionList'/2, 'enc_AttributeValue'/2, 'enc_AttributeValueAssertion'/2, 'enc_AssertionValue'/2, 'enc_Attribute'/2, 'enc_MatchingRuleId'/2, 'enc_LDAPResult'/2, 'enc_Referral'/2, 'enc_LDAPURL'/2, 'enc_Controls'/2, 'enc_Control'/2, 'enc_BindRequest'/2, 'enc_AuthenticationChoice'/2, 'enc_SaslCredentials'/2, 'enc_BindResponse'/2, 'enc_UnbindRequest'/2, 'enc_SearchRequest'/2, 'enc_Filter'/2, 'enc_SubstringFilter'/2, 'enc_MatchingRuleAssertion'/2, 'enc_SearchResultEntry'/2, 'enc_PartialAttributeList'/2, 'enc_SearchResultReference'/2, 'enc_SearchResultDone'/2, 'enc_ModifyRequest'/2, 'enc_AttributeTypeAndValues'/2, 'enc_ModifyResponse'/2, 'enc_AddRequest'/2, 'enc_AttributeList'/2, 'enc_AddResponse'/2, 'enc_DelRequest'/2, 'enc_DelResponse'/2, 'enc_ModifyDNRequest'/2, 'enc_ModifyDNResponse'/2, 'enc_CompareRequest'/2, 'enc_CompareResponse'/2, 'enc_AbandonRequest'/2, 'enc_ExtendedRequest'/2, 'enc_ExtendedResponse'/2 ]). -export([ 'dec_LDAPMessage'/2, 'dec_MessageID'/2, 'dec_LDAPString'/2, 'dec_LDAPOID'/2, 'dec_LDAPDN'/2, 'dec_RelativeLDAPDN'/2, 'dec_AttributeType'/2, 'dec_AttributeDescription'/2, 'dec_AttributeDescriptionList'/2, 'dec_AttributeValue'/2, 'dec_AttributeValueAssertion'/2, 'dec_AssertionValue'/2, 'dec_Attribute'/2, 'dec_MatchingRuleId'/2, 'dec_LDAPResult'/2, 'dec_Referral'/2, 'dec_LDAPURL'/2, 'dec_Controls'/2, 'dec_Control'/2, 'dec_BindRequest'/2, 'dec_AuthenticationChoice'/2, 'dec_SaslCredentials'/2, 'dec_BindResponse'/2, 'dec_UnbindRequest'/2, 'dec_SearchRequest'/2, 'dec_Filter'/2, 'dec_SubstringFilter'/2, 'dec_MatchingRuleAssertion'/2, 'dec_SearchResultEntry'/2, 'dec_PartialAttributeList'/2, 'dec_SearchResultReference'/2, 'dec_SearchResultDone'/2, 'dec_ModifyRequest'/2, 'dec_AttributeTypeAndValues'/2, 'dec_ModifyResponse'/2, 'dec_AddRequest'/2, 'dec_AttributeList'/2, 'dec_AddResponse'/2, 'dec_DelRequest'/2, 'dec_DelResponse'/2, 'dec_ModifyDNRequest'/2, 'dec_ModifyDNResponse'/2, 'dec_CompareRequest'/2, 'dec_CompareResponse'/2, 'dec_AbandonRequest'/2, 'dec_ExtendedRequest'/2, 'dec_ExtendedResponse'/2 ]). -export([ 'dec_LDAPMessage'/3, 'dec_MessageID'/3, 'dec_LDAPString'/3, 'dec_LDAPOID'/3, 'dec_LDAPDN'/3, 'dec_RelativeLDAPDN'/3, 'dec_AttributeType'/3, 'dec_AttributeDescription'/3, 'dec_AttributeDescriptionList'/3, 'dec_AttributeValue'/3, 'dec_AttributeValueAssertion'/3, 'dec_AssertionValue'/3, 'dec_Attribute'/3, 'dec_MatchingRuleId'/3, 'dec_LDAPResult'/3, 'dec_Referral'/3, 'dec_LDAPURL'/3, 'dec_Controls'/3, 'dec_Control'/3, 'dec_BindRequest'/3, 'dec_AuthenticationChoice'/3, 'dec_SaslCredentials'/3, 'dec_BindResponse'/3, 'dec_UnbindRequest'/3, 'dec_SearchRequest'/3, 'dec_Filter'/3, 'dec_SubstringFilter'/3, 'dec_MatchingRuleAssertion'/3, 'dec_SearchResultEntry'/3, 'dec_PartialAttributeList'/3, 'dec_SearchResultReference'/3, 'dec_SearchResultDone'/3, 'dec_ModifyRequest'/3, 'dec_AttributeTypeAndValues'/3, 'dec_ModifyResponse'/3, 'dec_AddRequest'/3, 'dec_AttributeList'/3, 'dec_AddResponse'/3, 'dec_DelRequest'/3, 'dec_DelResponse'/3, 'dec_ModifyDNRequest'/3, 'dec_ModifyDNResponse'/3, 'dec_CompareRequest'/3, 'dec_CompareResponse'/3, 'dec_AbandonRequest'/3, 'dec_ExtendedRequest'/3, 'dec_ExtendedResponse'/3 ]). -export([ 'maxInt'/0 ]). -export([info/0]). -export([encode/2,decode/2,encode_disp/2,decode_disp/2]). encoding_rule() -> ber. encode(Type,Data) -> case catch encode_disp(Type,Data) of {'EXIT',{error,Reason}} -> {error,Reason}; {'EXIT',Reason} -> {error,{asn1,Reason}}; {Bytes,_Len} -> {ok,wrap_encode(Bytes)}; Bytes -> {ok,wrap_encode(Bytes)} end. decode(Type,Data) -> case catch decode_disp(Type,wrap_decode(Data)) of {'EXIT',{error,Reason}} -> {error,Reason}; {'EXIT',Reason} -> {error,{asn1,Reason}}; {X,_Rest} -> {ok,X}; {X,_Rest,_Len} -> {ok,X} end. encode_disp('LDAPMessage',Data) -> 'enc_LDAPMessage'(Data,[]); encode_disp('MessageID',Data) -> 'enc_MessageID'(Data,[]); encode_disp('LDAPString',Data) -> 'enc_LDAPString'(Data,[]); encode_disp('LDAPOID',Data) -> 'enc_LDAPOID'(Data,[]); encode_disp('LDAPDN',Data) -> 'enc_LDAPDN'(Data,[]); encode_disp('RelativeLDAPDN',Data) -> 'enc_RelativeLDAPDN'(Data,[]); encode_disp('AttributeType',Data) -> 'enc_AttributeType'(Data,[]); encode_disp('AttributeDescription',Data) -> 'enc_AttributeDescription'(Data,[]); encode_disp('AttributeDescriptionList',Data) -> 'enc_AttributeDescriptionList'(Data,[]); encode_disp('AttributeValue',Data) -> 'enc_AttributeValue'(Data,[]); encode_disp('AttributeValueAssertion',Data) -> 'enc_AttributeValueAssertion'(Data,[]); encode_disp('AssertionValue',Data) -> 'enc_AssertionValue'(Data,[]); encode_disp('Attribute',Data) -> 'enc_Attribute'(Data,[]); encode_disp('MatchingRuleId',Data) -> 'enc_MatchingRuleId'(Data,[]); encode_disp('LDAPResult',Data) -> 'enc_LDAPResult'(Data,[]); encode_disp('Referral',Data) -> 'enc_Referral'(Data,[]); encode_disp('LDAPURL',Data) -> 'enc_LDAPURL'(Data,[]); encode_disp('Controls',Data) -> 'enc_Controls'(Data,[]); encode_disp('Control',Data) -> 'enc_Control'(Data,[]); encode_disp('BindRequest',Data) -> 'enc_BindRequest'(Data,[]); encode_disp('AuthenticationChoice',Data) -> 'enc_AuthenticationChoice'(Data,[]); encode_disp('SaslCredentials',Data) -> 'enc_SaslCredentials'(Data,[]); encode_disp('BindResponse',Data) -> 'enc_BindResponse'(Data,[]); encode_disp('UnbindRequest',Data) -> 'enc_UnbindRequest'(Data,[]); encode_disp('SearchRequest',Data) -> 'enc_SearchRequest'(Data,[]); encode_disp('Filter',Data) -> 'enc_Filter'(Data,[]); encode_disp('SubstringFilter',Data) -> 'enc_SubstringFilter'(Data,[]); encode_disp('MatchingRuleAssertion',Data) -> 'enc_MatchingRuleAssertion'(Data,[]); encode_disp('SearchResultEntry',Data) -> 'enc_SearchResultEntry'(Data,[]); encode_disp('PartialAttributeList',Data) -> 'enc_PartialAttributeList'(Data,[]); encode_disp('SearchResultReference',Data) -> 'enc_SearchResultReference'(Data,[]); encode_disp('SearchResultDone',Data) -> 'enc_SearchResultDone'(Data,[]); encode_disp('ModifyRequest',Data) -> 'enc_ModifyRequest'(Data,[]); encode_disp('AttributeTypeAndValues',Data) -> 'enc_AttributeTypeAndValues'(Data,[]); encode_disp('ModifyResponse',Data) -> 'enc_ModifyResponse'(Data,[]); encode_disp('AddRequest',Data) -> 'enc_AddRequest'(Data,[]); encode_disp('AttributeList',Data) -> 'enc_AttributeList'(Data,[]); encode_disp('AddResponse',Data) -> 'enc_AddResponse'(Data,[]); encode_disp('DelRequest',Data) -> 'enc_DelRequest'(Data,[]); encode_disp('DelResponse',Data) -> 'enc_DelResponse'(Data,[]); encode_disp('ModifyDNRequest',Data) -> 'enc_ModifyDNRequest'(Data,[]); encode_disp('ModifyDNResponse',Data) -> 'enc_ModifyDNResponse'(Data,[]); encode_disp('CompareRequest',Data) -> 'enc_CompareRequest'(Data,[]); encode_disp('CompareResponse',Data) -> 'enc_CompareResponse'(Data,[]); encode_disp('AbandonRequest',Data) -> 'enc_AbandonRequest'(Data,[]); encode_disp('ExtendedRequest',Data) -> 'enc_ExtendedRequest'(Data,[]); encode_disp('ExtendedResponse',Data) -> 'enc_ExtendedResponse'(Data,[]); encode_disp(Type,_Data) -> exit({error,{asn1,{undefined_type,Type}}}). decode_disp('LDAPMessage',Data) -> 'dec_LDAPMessage'(Data,mandatory); decode_disp('MessageID',Data) -> 'dec_MessageID'(Data,mandatory); decode_disp('LDAPString',Data) -> 'dec_LDAPString'(Data,mandatory); decode_disp('LDAPOID',Data) -> 'dec_LDAPOID'(Data,mandatory); decode_disp('LDAPDN',Data) -> 'dec_LDAPDN'(Data,mandatory); decode_disp('RelativeLDAPDN',Data) -> 'dec_RelativeLDAPDN'(Data,mandatory); decode_disp('AttributeType',Data) -> 'dec_AttributeType'(Data,mandatory); decode_disp('AttributeDescription',Data) -> 'dec_AttributeDescription'(Data,mandatory); decode_disp('AttributeDescriptionList',Data) -> 'dec_AttributeDescriptionList'(Data,mandatory); decode_disp('AttributeValue',Data) -> 'dec_AttributeValue'(Data,mandatory); decode_disp('AttributeValueAssertion',Data) -> 'dec_AttributeValueAssertion'(Data,mandatory); decode_disp('AssertionValue',Data) -> 'dec_AssertionValue'(Data,mandatory); decode_disp('Attribute',Data) -> 'dec_Attribute'(Data,mandatory); decode_disp('MatchingRuleId',Data) -> 'dec_MatchingRuleId'(Data,mandatory); decode_disp('LDAPResult',Data) -> 'dec_LDAPResult'(Data,mandatory); decode_disp('Referral',Data) -> 'dec_Referral'(Data,mandatory); decode_disp('LDAPURL',Data) -> 'dec_LDAPURL'(Data,mandatory); decode_disp('Controls',Data) -> 'dec_Controls'(Data,mandatory); decode_disp('Control',Data) -> 'dec_Control'(Data,mandatory); decode_disp('BindRequest',Data) -> 'dec_BindRequest'(Data,mandatory); decode_disp('AuthenticationChoice',Data) -> 'dec_AuthenticationChoice'(Data,mandatory); decode_disp('SaslCredentials',Data) -> 'dec_SaslCredentials'(Data,mandatory); decode_disp('BindResponse',Data) -> 'dec_BindResponse'(Data,mandatory); decode_disp('UnbindRequest',Data) -> 'dec_UnbindRequest'(Data,mandatory); decode_disp('SearchRequest',Data) -> 'dec_SearchRequest'(Data,mandatory); decode_disp('Filter',Data) -> 'dec_Filter'(Data,mandatory); decode_disp('SubstringFilter',Data) -> 'dec_SubstringFilter'(Data,mandatory); decode_disp('MatchingRuleAssertion',Data) -> 'dec_MatchingRuleAssertion'(Data,mandatory); decode_disp('SearchResultEntry',Data) -> 'dec_SearchResultEntry'(Data,mandatory); decode_disp('PartialAttributeList',Data) -> 'dec_PartialAttributeList'(Data,mandatory); decode_disp('SearchResultReference',Data) -> 'dec_SearchResultReference'(Data,mandatory); decode_disp('SearchResultDone',Data) -> 'dec_SearchResultDone'(Data,mandatory); decode_disp('ModifyRequest',Data) -> 'dec_ModifyRequest'(Data,mandatory); decode_disp('AttributeTypeAndValues',Data) -> 'dec_AttributeTypeAndValues'(Data,mandatory); decode_disp('ModifyResponse',Data) -> 'dec_ModifyResponse'(Data,mandatory); decode_disp('AddRequest',Data) -> 'dec_AddRequest'(Data,mandatory); decode_disp('AttributeList',Data) -> 'dec_AttributeList'(Data,mandatory); decode_disp('AddResponse',Data) -> 'dec_AddResponse'(Data,mandatory); decode_disp('DelRequest',Data) -> 'dec_DelRequest'(Data,mandatory); decode_disp('DelResponse',Data) -> 'dec_DelResponse'(Data,mandatory); decode_disp('ModifyDNRequest',Data) -> 'dec_ModifyDNRequest'(Data,mandatory); decode_disp('ModifyDNResponse',Data) -> 'dec_ModifyDNResponse'(Data,mandatory); decode_disp('CompareRequest',Data) -> 'dec_CompareRequest'(Data,mandatory); decode_disp('CompareResponse',Data) -> 'dec_CompareResponse'(Data,mandatory); decode_disp('AbandonRequest',Data) -> 'dec_AbandonRequest'(Data,mandatory); decode_disp('ExtendedRequest',Data) -> 'dec_ExtendedRequest'(Data,mandatory); decode_disp('ExtendedResponse',Data) -> 'dec_ExtendedResponse'(Data,mandatory); decode_disp(Type,_Data) -> exit({error,{asn1,{undefined_type,Type}}}). wrap_encode(Bytes) when list(Bytes) -> binary_to_list(list_to_binary(Bytes)); wrap_encode(Bytes) when binary(Bytes) -> binary_to_list(Bytes); wrap_encode(Bytes) -> Bytes. wrap_decode(Bytes) when list(Bytes) -> list_to_binary(Bytes); wrap_decode(Bytes) -> Bytes. info() -> case ?MODULE:module_info() of MI when is_list(MI) -> case lists:keysearch(attributes,1,MI) of {value,{_,Attributes}} when is_list(Attributes) -> case lists:keysearch(asn1_info,1,Attributes) of {value,{_,Info}} when is_list(Info) -> Info; _ -> [] end; _ -> [] end end. %%================================ %% LDAPMessage %%================================ 'enc_LDAPMessage'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type INTEGER %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_integer([], ?RT_BER:cindex(2,Val,messageID), []), %%------------------------------------------------- %% attribute number 2 with type CHOICE %%------------------------------------------------- {EncBytes2,EncLen2} = 'enc_LDAPMessage_protocolOp'(?RT_BER:cindex(3,Val,protocolOp), []), %%------------------------------------------------- %% attribute number 3 External ELDAPv3:Controls OPTIONAL %%------------------------------------------------- {EncBytes3,EncLen3} = case ?RT_BER:cindex(4,Val,controls) of asn1_NOVALUE -> {<<>>,0}; _ -> 'enc_Controls'(?RT_BER:cindex(4,Val,controls), [{tag,128,0,'IMPLICIT',32}]) end, BytesSoFar = [EncBytes1, EncBytes2, EncBytes3], LenSoFar = EncLen1 + EncLen2 + EncLen3, ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). %%================================ %% LDAPMessage_protocolOp %%================================ 'enc_LDAPMessage_protocolOp'({'LDAPMessage_protocolOp',Val}, TagIn) -> 'enc_LDAPMessage_protocolOp'(Val, TagIn); 'enc_LDAPMessage_protocolOp'(Val, TagIn) -> {EncBytes,EncLen} = case element(1,Val) of bindRequest -> 'enc_BindRequest'(element(2,Val), []); bindResponse -> 'enc_BindResponse'(element(2,Val), []); unbindRequest -> ?RT_BER:encode_null(element(2,Val), [{tag,64,2,'IMPLICIT',32}]); searchRequest -> 'enc_SearchRequest'(element(2,Val), []); searchResEntry -> 'enc_SearchResultEntry'(element(2,Val), []); searchResDone -> 'enc_SearchResultDone'(element(2,Val), []); searchResRef -> 'enc_SearchResultReference'(element(2,Val), []); modifyRequest -> 'enc_ModifyRequest'(element(2,Val), []); modifyResponse -> 'enc_ModifyResponse'(element(2,Val), []); addRequest -> 'enc_AddRequest'(element(2,Val), []); addResponse -> 'enc_AddResponse'(element(2,Val), []); delRequest -> ?RT_BER:encode_octet_string([], element(2,Val), [{tag,64,10,'IMPLICIT',32}]); delResponse -> 'enc_DelResponse'(element(2,Val), []); modDNRequest -> 'enc_ModifyDNRequest'(element(2,Val), []); modDNResponse -> 'enc_ModifyDNResponse'(element(2,Val), []); compareRequest -> 'enc_CompareRequest'(element(2,Val), []); compareResponse -> 'enc_CompareResponse'(element(2,Val), []); abandonRequest -> ?RT_BER:encode_integer([], element(2,Val), [{tag,64,16,'IMPLICIT',32}]); extendedReq -> 'enc_ExtendedRequest'(element(2,Val), []); extendedResp -> 'enc_ExtendedResponse'(element(2,Val), []); Else -> exit({error,{asn1,{invalid_choice_type,Else}}}) end, ?RT_BER:encode_tags(TagIn ++[], EncBytes, EncLen). 'dec_LDAPMessage_protocolOp'(Bytes, OptOrMand, TagIn) -> {{_,Len},Bytes1, RbExp} = ?RT_BER:check_tags(TagIn++[], Bytes, OptOrMand), IndefEndBytes = fun(indefinite,<<0,0,R/binary>>)-> R; (_,B)-> B end, IndefEndRb = fun(indefinite,<<0,0,_R/binary>>)-> 2; (_,_)-> 0 end, case Bytes1 of %% 'bindRequest' <<1:2,_:1,0:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_BindRequest'(Bytes1, mandatory, []), {{bindRequest, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'bindResponse' <<1:2,_:1,1:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_BindResponse'(Bytes1, mandatory, []), {{bindResponse, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'unbindRequest' <<1:2,_:1,2:5,_/binary>> -> {Dec, Rest, RbCho} = ?RT_BER:decode_null(Bytes1,[{tag,64,2,'IMPLICIT',32}], mandatory), {{unbindRequest, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'searchRequest' <<1:2,_:1,3:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_SearchRequest'(Bytes1, mandatory, []), {{searchRequest, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'searchResEntry' <<1:2,_:1,4:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_SearchResultEntry'(Bytes1, mandatory, []), {{searchResEntry, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'searchResDone' <<1:2,_:1,5:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_SearchResultDone'(Bytes1, mandatory, []), {{searchResDone, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'searchResRef' <<1:2,_:1,19:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_SearchResultReference'(Bytes1, mandatory, []), {{searchResRef, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'modifyRequest' <<1:2,_:1,6:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_ModifyRequest'(Bytes1, mandatory, []), {{modifyRequest, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'modifyResponse' <<1:2,_:1,7:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_ModifyResponse'(Bytes1, mandatory, []), {{modifyResponse, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'addRequest' <<1:2,_:1,8:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_AddRequest'(Bytes1, mandatory, []), {{addRequest, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'addResponse' <<1:2,_:1,9:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_AddResponse'(Bytes1, mandatory, []), {{addResponse, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'delRequest' <<1:2,_:1,10:5,_/binary>> -> {Dec, Rest, RbCho} = ?RT_BER:decode_octet_string(Bytes1,[],[{tag,64,10,'IMPLICIT',32}], no_length, mandatory), {{delRequest, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'delResponse' <<1:2,_:1,11:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_DelResponse'(Bytes1, mandatory, []), {{delResponse, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'modDNRequest' <<1:2,_:1,12:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_ModifyDNRequest'(Bytes1, mandatory, []), {{modDNRequest, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'modDNResponse' <<1:2,_:1,13:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_ModifyDNResponse'(Bytes1, mandatory, []), {{modDNResponse, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'compareRequest' <<1:2,_:1,14:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_CompareRequest'(Bytes1, mandatory, []), {{compareRequest, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'compareResponse' <<1:2,_:1,15:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_CompareResponse'(Bytes1, mandatory, []), {{compareResponse, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'abandonRequest' <<1:2,_:1,16:5,_/binary>> -> {Dec, Rest, RbCho} = ?RT_BER:decode_integer(Bytes1,{0,2147483647},[{tag,64,16,'IMPLICIT',32}], mandatory), {{abandonRequest, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'extendedReq' <<1:2,_:1,23:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_ExtendedRequest'(Bytes1, mandatory, []), {{extendedReq, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'extendedResp' <<1:2,_:1,24:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_ExtendedResponse'(Bytes1, mandatory, []), {{extendedResp, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; Else -> case OptOrMand of mandatory ->exit({error,{asn1,{invalid_choice_tag,Else}}}); _ ->exit({error,{asn1,{no_optional_tag,Else}}}) end end. 'dec_LDAPMessage'(Bytes, OptOrMand) -> 'dec_LDAPMessage'(Bytes, OptOrMand, []). 'dec_LDAPMessage'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type INTEGER %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_integer(Bytes2,{0,2147483647},[], mandatory), %%------------------------------------------------- %% attribute number 2 with type CHOICE %%------------------------------------------------- {Term2,Bytes4,Rb3} = 'dec_LDAPMessage_protocolOp'(Bytes3, mandatory, []), %%------------------------------------------------- %% attribute number 3 External ELDAPv3:Controls OPTIONAL %%------------------------------------------------- {Term3,Bytes5,Rb4} = case Bytes4 of <<2:2,_:1,0:5,_/binary>> -> 'dec_Controls'(Bytes4, opt_or_default, [{tag,128,0,'IMPLICIT',32}]); _ -> { asn1_NOVALUE, Bytes4, 0 } end, {Bytes6,Rb5} = ?RT_BER:restbytes2(RemBytes, Bytes5,noext), {{'LDAPMessage', Term1, Term2, Term3}, Bytes6, Rb1+Rb2+Rb3+Rb4+Rb5}. %%================================ %% MessageID %%================================ 'enc_MessageID'({'MessageID',Val}, TagIn) -> 'enc_MessageID'(Val, TagIn); 'enc_MessageID'(Val, TagIn) -> ?RT_BER:encode_integer([], Val, TagIn ++ []). 'dec_MessageID'(Bytes, OptOrMand) -> 'dec_MessageID'(Bytes, OptOrMand, []). 'dec_MessageID'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_integer(Bytes,{0,2147483647},TagIn++[], OptOrMand). %%================================ %% LDAPString %%================================ 'enc_LDAPString'({'LDAPString',Val}, TagIn) -> 'enc_LDAPString'(Val, TagIn); 'enc_LDAPString'(Val, TagIn) -> ?RT_BER:encode_octet_string([], Val, TagIn ++ []). 'dec_LDAPString'(Bytes, OptOrMand) -> 'dec_LDAPString'(Bytes, OptOrMand, []). 'dec_LDAPString'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_octet_string(Bytes,[],TagIn++[], no_length, OptOrMand). %%================================ %% LDAPOID %%================================ 'enc_LDAPOID'({'LDAPOID',Val}, TagIn) -> 'enc_LDAPOID'(Val, TagIn); 'enc_LDAPOID'(Val, TagIn) -> ?RT_BER:encode_octet_string([], Val, TagIn ++ []). 'dec_LDAPOID'(Bytes, OptOrMand) -> 'dec_LDAPOID'(Bytes, OptOrMand, []). 'dec_LDAPOID'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_octet_string(Bytes,[],TagIn++[], no_length, OptOrMand). %%================================ %% LDAPDN %%================================ 'enc_LDAPDN'({'LDAPDN',Val}, TagIn) -> 'enc_LDAPDN'(Val, TagIn); 'enc_LDAPDN'(Val, TagIn) -> ?RT_BER:encode_octet_string([], Val, TagIn ++ []). 'dec_LDAPDN'(Bytes, OptOrMand) -> 'dec_LDAPDN'(Bytes, OptOrMand, []). 'dec_LDAPDN'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_octet_string(Bytes,[],TagIn++[], no_length, OptOrMand). %%================================ %% RelativeLDAPDN %%================================ 'enc_RelativeLDAPDN'({'RelativeLDAPDN',Val}, TagIn) -> 'enc_RelativeLDAPDN'(Val, TagIn); 'enc_RelativeLDAPDN'(Val, TagIn) -> ?RT_BER:encode_octet_string([], Val, TagIn ++ []). 'dec_RelativeLDAPDN'(Bytes, OptOrMand) -> 'dec_RelativeLDAPDN'(Bytes, OptOrMand, []). 'dec_RelativeLDAPDN'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_octet_string(Bytes,[],TagIn++[], no_length, OptOrMand). %%================================ %% AttributeType %%================================ 'enc_AttributeType'({'AttributeType',Val}, TagIn) -> 'enc_AttributeType'(Val, TagIn); 'enc_AttributeType'(Val, TagIn) -> ?RT_BER:encode_octet_string([], Val, TagIn ++ []). 'dec_AttributeType'(Bytes, OptOrMand) -> 'dec_AttributeType'(Bytes, OptOrMand, []). 'dec_AttributeType'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_octet_string(Bytes,[],TagIn++[], no_length, OptOrMand). %%================================ %% AttributeDescription %%================================ 'enc_AttributeDescription'({'AttributeDescription',Val}, TagIn) -> 'enc_AttributeDescription'(Val, TagIn); 'enc_AttributeDescription'(Val, TagIn) -> ?RT_BER:encode_octet_string([], Val, TagIn ++ []). 'dec_AttributeDescription'(Bytes, OptOrMand) -> 'dec_AttributeDescription'(Bytes, OptOrMand, []). 'dec_AttributeDescription'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_octet_string(Bytes,[],TagIn++[], no_length, OptOrMand). %%================================ %% AttributeDescriptionList %%================================ 'enc_AttributeDescriptionList'({'AttributeDescriptionList',Val}, TagIn) -> 'enc_AttributeDescriptionList'(Val, TagIn); 'enc_AttributeDescriptionList'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_AttributeDescriptionList_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], EncBytes, EncLen). 'enc_AttributeDescriptionList_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_AttributeDescriptionList_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = ?RT_BER:encode_octet_string([], H, []), 'enc_AttributeDescriptionList_components'(T,[EncBytes|AccBytes], AccLen + EncLen). 'dec_AttributeDescriptionList'(Bytes, OptOrMand) -> 'dec_AttributeDescriptionList'(Bytes, OptOrMand, []). 'dec_AttributeDescriptionList'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun(FBytes,_,_)-> ?RT_BER:decode_octet_string(FBytes,[],[], no_length, mandatory) end, [], []). %%================================ %% AttributeValue %%================================ 'enc_AttributeValue'({'AttributeValue',Val}, TagIn) -> 'enc_AttributeValue'(Val, TagIn); 'enc_AttributeValue'(Val, TagIn) -> ?RT_BER:encode_octet_string([], Val, TagIn ++ []). 'dec_AttributeValue'(Bytes, OptOrMand) -> 'dec_AttributeValue'(Bytes, OptOrMand, []). 'dec_AttributeValue'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_octet_string(Bytes,[],TagIn++[], no_length, OptOrMand). %%================================ %% AttributeValueAssertion %%================================ 'enc_AttributeValueAssertion'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,attributeDesc), []), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING %%------------------------------------------------- {EncBytes2,EncLen2} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(3,Val,assertionValue), []), BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_AttributeValueAssertion'(Bytes, OptOrMand) -> 'dec_AttributeValueAssertion'(Bytes, OptOrMand, []). 'dec_AttributeValueAssertion'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING %%------------------------------------------------- {Term2,Bytes4,Rb3} = ?RT_BER:decode_octet_string(Bytes3,[],[], no_length, mandatory), {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'AttributeValueAssertion', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. %%================================ %% AssertionValue %%================================ 'enc_AssertionValue'({'AssertionValue',Val}, TagIn) -> 'enc_AssertionValue'(Val, TagIn); 'enc_AssertionValue'(Val, TagIn) -> ?RT_BER:encode_octet_string([], Val, TagIn ++ []). 'dec_AssertionValue'(Bytes, OptOrMand) -> 'dec_AssertionValue'(Bytes, OptOrMand, []). 'dec_AssertionValue'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_octet_string(Bytes,[],TagIn++[], no_length, OptOrMand). %%================================ %% Attribute %%================================ 'enc_Attribute'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,type), []), %%------------------------------------------------- %% attribute number 2 with type SET OF %%------------------------------------------------- {EncBytes2,EncLen2} = 'enc_Attribute_vals'(?RT_BER:cindex(3,Val,vals), []), BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). %%================================ %% Attribute_vals %%================================ 'enc_Attribute_vals'({'Attribute_vals',Val}, TagIn) -> 'enc_Attribute_vals'(Val, TagIn); 'enc_Attribute_vals'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_Attribute_vals_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,17,'IMPLICIT',32}], EncBytes, EncLen). 'enc_Attribute_vals_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_Attribute_vals_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = ?RT_BER:encode_octet_string([], H, []), 'enc_Attribute_vals_components'(T,[EncBytes|AccBytes], AccLen + EncLen). 'dec_Attribute_vals'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,17,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun(FBytes,_,_)-> ?RT_BER:decode_octet_string(FBytes,[],[], no_length, mandatory) end, [], []). 'dec_Attribute'(Bytes, OptOrMand) -> 'dec_Attribute'(Bytes, OptOrMand, []). 'dec_Attribute'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 with type SET OF %%------------------------------------------------- {Term2,Bytes4,Rb3} = 'dec_Attribute_vals'(Bytes3, mandatory, []), {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'Attribute', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. %%================================ %% MatchingRuleId %%================================ 'enc_MatchingRuleId'({'MatchingRuleId',Val}, TagIn) -> 'enc_MatchingRuleId'(Val, TagIn); 'enc_MatchingRuleId'(Val, TagIn) -> ?RT_BER:encode_octet_string([], Val, TagIn ++ []). 'dec_MatchingRuleId'(Bytes, OptOrMand) -> 'dec_MatchingRuleId'(Bytes, OptOrMand, []). 'dec_MatchingRuleId'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_octet_string(Bytes,[],TagIn++[], no_length, OptOrMand). %%================================ %% LDAPResult %%================================ 'enc_LDAPResult'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type ENUMERATED %%------------------------------------------------- {EncBytes1,EncLen1} = case (case ?RT_BER:cindex(2,Val,resultCode) of {_,_}->element(2,?RT_BER:cindex(2,Val,resultCode));_->?RT_BER:cindex(2,Val,resultCode) end) of success -> ?RT_BER:encode_enumerated(0,[]); operationsError -> ?RT_BER:encode_enumerated(1,[]); protocolError -> ?RT_BER:encode_enumerated(2,[]); timeLimitExceeded -> ?RT_BER:encode_enumerated(3,[]); sizeLimitExceeded -> ?RT_BER:encode_enumerated(4,[]); compareFalse -> ?RT_BER:encode_enumerated(5,[]); compareTrue -> ?RT_BER:encode_enumerated(6,[]); authMethodNotSupported -> ?RT_BER:encode_enumerated(7,[]); strongAuthRequired -> ?RT_BER:encode_enumerated(8,[]); referral -> ?RT_BER:encode_enumerated(10,[]); adminLimitExceeded -> ?RT_BER:encode_enumerated(11,[]); unavailableCriticalExtension -> ?RT_BER:encode_enumerated(12,[]); confidentialityRequired -> ?RT_BER:encode_enumerated(13,[]); saslBindInProgress -> ?RT_BER:encode_enumerated(14,[]); noSuchAttribute -> ?RT_BER:encode_enumerated(16,[]); undefinedAttributeType -> ?RT_BER:encode_enumerated(17,[]); inappropriateMatching -> ?RT_BER:encode_enumerated(18,[]); constraintViolation -> ?RT_BER:encode_enumerated(19,[]); attributeOrValueExists -> ?RT_BER:encode_enumerated(20,[]); invalidAttributeSyntax -> ?RT_BER:encode_enumerated(21,[]); noSuchObject -> ?RT_BER:encode_enumerated(32,[]); aliasProblem -> ?RT_BER:encode_enumerated(33,[]); invalidDNSyntax -> ?RT_BER:encode_enumerated(34,[]); aliasDereferencingProblem -> ?RT_BER:encode_enumerated(36,[]); inappropriateAuthentication -> ?RT_BER:encode_enumerated(48,[]); invalidCredentials -> ?RT_BER:encode_enumerated(49,[]); insufficientAccessRights -> ?RT_BER:encode_enumerated(50,[]); busy -> ?RT_BER:encode_enumerated(51,[]); unavailable -> ?RT_BER:encode_enumerated(52,[]); unwillingToPerform -> ?RT_BER:encode_enumerated(53,[]); loopDetect -> ?RT_BER:encode_enumerated(54,[]); namingViolation -> ?RT_BER:encode_enumerated(64,[]); objectClassViolation -> ?RT_BER:encode_enumerated(65,[]); notAllowedOnNonLeaf -> ?RT_BER:encode_enumerated(66,[]); notAllowedOnRDN -> ?RT_BER:encode_enumerated(67,[]); entryAlreadyExists -> ?RT_BER:encode_enumerated(68,[]); objectClassModsProhibited -> ?RT_BER:encode_enumerated(69,[]); affectsMultipleDSAs -> ?RT_BER:encode_enumerated(71,[]); other -> ?RT_BER:encode_enumerated(80,[]); Enumval1 -> exit({error,{asn1, {enumerated_not_in_range,Enumval1}}}) end, %%------------------------------------------------- %% attribute number 2 with type OCTET STRING %%------------------------------------------------- {EncBytes2,EncLen2} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(3,Val,matchedDN), []), %%------------------------------------------------- %% attribute number 3 with type OCTET STRING %%------------------------------------------------- {EncBytes3,EncLen3} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(4,Val,errorMessage), []), %%------------------------------------------------- %% attribute number 4 External ELDAPv3:Referral OPTIONAL %%------------------------------------------------- {EncBytes4,EncLen4} = case ?RT_BER:cindex(5,Val,referral) of asn1_NOVALUE -> {<<>>,0}; _ -> 'enc_Referral'(?RT_BER:cindex(5,Val,referral), [{tag,128,3,'IMPLICIT',32}]) end, BytesSoFar = [EncBytes1, EncBytes2, EncBytes3, EncBytes4], LenSoFar = EncLen1 + EncLen2 + EncLen3 + EncLen4, ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_LDAPResult'(Bytes, OptOrMand) -> 'dec_LDAPResult'(Bytes, OptOrMand, []). 'dec_LDAPResult'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type ENUMERATED %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_enumerated(Bytes2,[],[{success,0},{operationsError,1},{protocolError,2},{timeLimitExceeded,3},{sizeLimitExceeded,4},{compareFalse,5},{compareTrue,6},{authMethodNotSupported,7},{strongAuthRequired,8},{referral,10},{adminLimitExceeded,11},{unavailableCriticalExtension,12},{confidentialityRequired,13},{saslBindInProgress,14},{noSuchAttribute,16},{undefinedAttributeType,17},{inappropriateMatching,18},{constraintViolation,19},{attributeOrValueExists,20},{invalidAttributeSyntax,21},{noSuchObject,32},{aliasProblem,33},{invalidDNSyntax,34},{aliasDereferencingProblem,36},{inappropriateAuthentication,48},{invalidCredentials,49},{insufficientAccessRights,50},{busy,51},{unavailable,52},{unwillingToPerform,53},{loopDetect,54},{namingViolation,64},{objectClassViolation,65},{notAllowedOnNonLeaf,66},{notAllowedOnRDN,67},{entryAlreadyExists,68},{objectClassModsProhibited,69},{affectsMultipleDSAs,71},{other,80}],[], mandatory), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING %%------------------------------------------------- {Term2,Bytes4,Rb3} = ?RT_BER:decode_octet_string(Bytes3,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 3 with type OCTET STRING %%------------------------------------------------- {Term3,Bytes5,Rb4} = ?RT_BER:decode_octet_string(Bytes4,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 4 External ELDAPv3:Referral OPTIONAL %%------------------------------------------------- {Term4,Bytes6,Rb5} = case Bytes5 of <<2:2,_:1,3:5,_/binary>> -> 'dec_Referral'(Bytes5, opt_or_default, [{tag,128,3,'IMPLICIT',32}]); _ -> { asn1_NOVALUE, Bytes5, 0 } end, {Bytes7,Rb6} = ?RT_BER:restbytes2(RemBytes, Bytes6,noext), {{'LDAPResult', Term1, Term2, Term3, Term4}, Bytes7, Rb1+Rb2+Rb3+Rb4+Rb5+Rb6}. %%================================ %% Referral %%================================ 'enc_Referral'({'Referral',Val}, TagIn) -> 'enc_Referral'(Val, TagIn); 'enc_Referral'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_Referral_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], EncBytes, EncLen). 'enc_Referral_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_Referral_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = ?RT_BER:encode_octet_string([], H, []), 'enc_Referral_components'(T,[EncBytes|AccBytes], AccLen + EncLen). 'dec_Referral'(Bytes, OptOrMand) -> 'dec_Referral'(Bytes, OptOrMand, []). 'dec_Referral'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun(FBytes,_,_)-> ?RT_BER:decode_octet_string(FBytes,[],[], no_length, mandatory) end, [], []). %%================================ %% LDAPURL %%================================ 'enc_LDAPURL'({'LDAPURL',Val}, TagIn) -> 'enc_LDAPURL'(Val, TagIn); 'enc_LDAPURL'(Val, TagIn) -> ?RT_BER:encode_octet_string([], Val, TagIn ++ []). 'dec_LDAPURL'(Bytes, OptOrMand) -> 'dec_LDAPURL'(Bytes, OptOrMand, []). 'dec_LDAPURL'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_octet_string(Bytes,[],TagIn++[], no_length, OptOrMand). %%================================ %% Controls %%================================ 'enc_Controls'({'Controls',Val}, TagIn) -> 'enc_Controls'(Val, TagIn); 'enc_Controls'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_Controls_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], EncBytes, EncLen). 'enc_Controls_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_Controls_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = 'enc_Control'(H, []), 'enc_Controls_components'(T,[EncBytes|AccBytes], AccLen + EncLen). 'dec_Controls'(Bytes, OptOrMand) -> 'dec_Controls'(Bytes, OptOrMand, []). 'dec_Controls'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun 'dec_Control'/3, [], []). %%================================ %% Control %%================================ 'enc_Control'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,controlType), []), %%------------------------------------------------- %% attribute number 2 with type BOOLEAN DEFAULT = false %%------------------------------------------------- {EncBytes2,EncLen2} = case ?RT_BER:cindex(3,Val,criticality) of asn1_DEFAULT -> {<<>>,0}; false -> {<<>>,0}; _ -> ?RT_BER:encode_boolean(?RT_BER:cindex(3,Val,criticality), []) end, %%------------------------------------------------- %% attribute number 3 with type OCTET STRING OPTIONAL %%------------------------------------------------- {EncBytes3,EncLen3} = case ?RT_BER:cindex(4,Val,controlValue) of asn1_NOVALUE -> {<<>>,0}; _ -> ?RT_BER:encode_octet_string([], ?RT_BER:cindex(4,Val,controlValue), []) end, BytesSoFar = [EncBytes1, EncBytes2, EncBytes3], LenSoFar = EncLen1 + EncLen2 + EncLen3, ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_Control'(Bytes, OptOrMand) -> 'dec_Control'(Bytes, OptOrMand, []). 'dec_Control'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 with type BOOLEAN DEFAULT = false %%------------------------------------------------- {Term2,Bytes4,Rb3} = case Bytes3 of <<0:2,_:1,1:5,_/binary>> -> ?RT_BER:decode_boolean(Bytes3,[], mandatory); _ -> {false,Bytes3, 0 } end, %%------------------------------------------------- %% attribute number 3 with type OCTET STRING OPTIONAL %%------------------------------------------------- {Term3,Bytes5,Rb4} = case Bytes4 of <<0:2,_:1,4:5,_/binary>> -> ?RT_BER:decode_octet_string(Bytes4,[],[], no_length, mandatory); _ -> { asn1_NOVALUE, Bytes4, 0 } end, {Bytes6,Rb5} = ?RT_BER:restbytes2(RemBytes, Bytes5,noext), {{'Control', Term1, Term2, Term3}, Bytes6, Rb1+Rb2+Rb3+Rb4+Rb5}. %%================================ %% BindRequest %%================================ 'enc_BindRequest'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type INTEGER %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_integer([], ?RT_BER:cindex(2,Val,version), []), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING %%------------------------------------------------- {EncBytes2,EncLen2} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(3,Val,name), []), %%------------------------------------------------- %% attribute number 3 External ELDAPv3:AuthenticationChoice %%------------------------------------------------- {EncBytes3,EncLen3} = 'enc_AuthenticationChoice'(?RT_BER:cindex(4,Val,authentication), []), BytesSoFar = [EncBytes1, EncBytes2, EncBytes3], LenSoFar = EncLen1 + EncLen2 + EncLen3, ?RT_BER:encode_tags(TagIn ++ [{tag,64,0,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_BindRequest'(Bytes, OptOrMand) -> 'dec_BindRequest'(Bytes, OptOrMand, []). 'dec_BindRequest'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,64,0,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type INTEGER %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_integer(Bytes2,{1,127},[], mandatory), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING %%------------------------------------------------- {Term2,Bytes4,Rb3} = ?RT_BER:decode_octet_string(Bytes3,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 3 External ELDAPv3:AuthenticationChoice %%------------------------------------------------- {Term3,Bytes5,Rb4} = 'dec_AuthenticationChoice'(Bytes4, mandatory, []), {Bytes6,Rb5} = ?RT_BER:restbytes2(RemBytes, Bytes5,noext), {{'BindRequest', Term1, Term2, Term3}, Bytes6, Rb1+Rb2+Rb3+Rb4+Rb5}. %%================================ %% AuthenticationChoice %%================================ 'enc_AuthenticationChoice'({'AuthenticationChoice',Val}, TagIn) -> 'enc_AuthenticationChoice'(Val, TagIn); 'enc_AuthenticationChoice'(Val, TagIn) -> {EncBytes,EncLen} = case element(1,Val) of simple -> ?RT_BER:encode_octet_string([], element(2,Val), [{tag,128,0,'IMPLICIT',32}]); sasl -> 'enc_SaslCredentials'(element(2,Val), [{tag,128,3,'IMPLICIT',32}]); Else -> exit({error,{asn1,{invalid_choice_type,Else}}}) end, ?RT_BER:encode_tags(TagIn ++[], EncBytes, EncLen). 'dec_AuthenticationChoice'(Bytes, OptOrMand) -> 'dec_AuthenticationChoice'(Bytes, OptOrMand, []). 'dec_AuthenticationChoice'(Bytes, OptOrMand, TagIn) -> {{_,Len},Bytes1, RbExp} = ?RT_BER:check_tags(TagIn++[], Bytes, OptOrMand), IndefEndBytes = fun(indefinite,<<0,0,R/binary>>)-> R; (_,B)-> B end, IndefEndRb = fun(indefinite,<<0,0,_R/binary>>)-> 2; (_,_)-> 0 end, case Bytes1 of %% 'simple' <<2:2,_:1,0:5,_/binary>> -> {Dec, Rest, RbCho} = ?RT_BER:decode_octet_string(Bytes1,[],[{tag,128,0,'IMPLICIT',32}], no_length, mandatory), {{simple, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'sasl' <<2:2,_:1,3:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_SaslCredentials'(Bytes1, mandatory, [{tag,128,3,'IMPLICIT',32}]), {{sasl, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; Else -> case OptOrMand of mandatory ->exit({error,{asn1,{invalid_choice_tag,Else}}}); _ ->exit({error,{asn1,{no_optional_tag,Else}}}) end end. %%================================ %% SaslCredentials %%================================ 'enc_SaslCredentials'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,mechanism), []), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING OPTIONAL %%------------------------------------------------- {EncBytes2,EncLen2} = case ?RT_BER:cindex(3,Val,credentials) of asn1_NOVALUE -> {<<>>,0}; _ -> ?RT_BER:encode_octet_string([], ?RT_BER:cindex(3,Val,credentials), []) end, BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_SaslCredentials'(Bytes, OptOrMand) -> 'dec_SaslCredentials'(Bytes, OptOrMand, []). 'dec_SaslCredentials'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING OPTIONAL %%------------------------------------------------- {Term2,Bytes4,Rb3} = case Bytes3 of <<0:2,_:1,4:5,_/binary>> -> ?RT_BER:decode_octet_string(Bytes3,[],[], no_length, mandatory); _ -> { asn1_NOVALUE, Bytes3, 0 } end, {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'SaslCredentials', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. %%================================ %% BindResponse %%================================ 'enc_BindResponse'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type ENUMERATED %%------------------------------------------------- {EncBytes1,EncLen1} = case (case ?RT_BER:cindex(2,Val,resultCode) of {_,_}->element(2,?RT_BER:cindex(2,Val,resultCode));_->?RT_BER:cindex(2,Val,resultCode) end) of success -> ?RT_BER:encode_enumerated(0,[]); operationsError -> ?RT_BER:encode_enumerated(1,[]); protocolError -> ?RT_BER:encode_enumerated(2,[]); timeLimitExceeded -> ?RT_BER:encode_enumerated(3,[]); sizeLimitExceeded -> ?RT_BER:encode_enumerated(4,[]); compareFalse -> ?RT_BER:encode_enumerated(5,[]); compareTrue -> ?RT_BER:encode_enumerated(6,[]); authMethodNotSupported -> ?RT_BER:encode_enumerated(7,[]); strongAuthRequired -> ?RT_BER:encode_enumerated(8,[]); referral -> ?RT_BER:encode_enumerated(10,[]); adminLimitExceeded -> ?RT_BER:encode_enumerated(11,[]); unavailableCriticalExtension -> ?RT_BER:encode_enumerated(12,[]); confidentialityRequired -> ?RT_BER:encode_enumerated(13,[]); saslBindInProgress -> ?RT_BER:encode_enumerated(14,[]); noSuchAttribute -> ?RT_BER:encode_enumerated(16,[]); undefinedAttributeType -> ?RT_BER:encode_enumerated(17,[]); inappropriateMatching -> ?RT_BER:encode_enumerated(18,[]); constraintViolation -> ?RT_BER:encode_enumerated(19,[]); attributeOrValueExists -> ?RT_BER:encode_enumerated(20,[]); invalidAttributeSyntax -> ?RT_BER:encode_enumerated(21,[]); noSuchObject -> ?RT_BER:encode_enumerated(32,[]); aliasProblem -> ?RT_BER:encode_enumerated(33,[]); invalidDNSyntax -> ?RT_BER:encode_enumerated(34,[]); aliasDereferencingProblem -> ?RT_BER:encode_enumerated(36,[]); inappropriateAuthentication -> ?RT_BER:encode_enumerated(48,[]); invalidCredentials -> ?RT_BER:encode_enumerated(49,[]); insufficientAccessRights -> ?RT_BER:encode_enumerated(50,[]); busy -> ?RT_BER:encode_enumerated(51,[]); unavailable -> ?RT_BER:encode_enumerated(52,[]); unwillingToPerform -> ?RT_BER:encode_enumerated(53,[]); loopDetect -> ?RT_BER:encode_enumerated(54,[]); namingViolation -> ?RT_BER:encode_enumerated(64,[]); objectClassViolation -> ?RT_BER:encode_enumerated(65,[]); notAllowedOnNonLeaf -> ?RT_BER:encode_enumerated(66,[]); notAllowedOnRDN -> ?RT_BER:encode_enumerated(67,[]); entryAlreadyExists -> ?RT_BER:encode_enumerated(68,[]); objectClassModsProhibited -> ?RT_BER:encode_enumerated(69,[]); affectsMultipleDSAs -> ?RT_BER:encode_enumerated(71,[]); other -> ?RT_BER:encode_enumerated(80,[]); Enumval1 -> exit({error,{asn1, {enumerated_not_in_range,Enumval1}}}) end, %%------------------------------------------------- %% attribute number 2 with type OCTET STRING %%------------------------------------------------- {EncBytes2,EncLen2} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(3,Val,matchedDN), []), %%------------------------------------------------- %% attribute number 3 with type OCTET STRING %%------------------------------------------------- {EncBytes3,EncLen3} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(4,Val,errorMessage), []), %%------------------------------------------------- %% attribute number 4 External ELDAPv3:Referral OPTIONAL %%------------------------------------------------- {EncBytes4,EncLen4} = case ?RT_BER:cindex(5,Val,referral) of asn1_NOVALUE -> {<<>>,0}; _ -> 'enc_Referral'(?RT_BER:cindex(5,Val,referral), [{tag,128,3,'IMPLICIT',32}]) end, %%------------------------------------------------- %% attribute number 5 with type OCTET STRING OPTIONAL %%------------------------------------------------- {EncBytes5,EncLen5} = case ?RT_BER:cindex(6,Val,serverSaslCreds) of asn1_NOVALUE -> {<<>>,0}; _ -> ?RT_BER:encode_octet_string([], ?RT_BER:cindex(6,Val,serverSaslCreds), [{tag,128,7,'IMPLICIT',32}]) end, BytesSoFar = [EncBytes1, EncBytes2, EncBytes3, EncBytes4, EncBytes5], LenSoFar = EncLen1 + EncLen2 + EncLen3 + EncLen4 + EncLen5, ?RT_BER:encode_tags(TagIn ++ [{tag,64,1,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_BindResponse'(Bytes, OptOrMand) -> 'dec_BindResponse'(Bytes, OptOrMand, []). 'dec_BindResponse'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,64,1,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type ENUMERATED %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_enumerated(Bytes2,[],[{success,0},{operationsError,1},{protocolError,2},{timeLimitExceeded,3},{sizeLimitExceeded,4},{compareFalse,5},{compareTrue,6},{authMethodNotSupported,7},{strongAuthRequired,8},{referral,10},{adminLimitExceeded,11},{unavailableCriticalExtension,12},{confidentialityRequired,13},{saslBindInProgress,14},{noSuchAttribute,16},{undefinedAttributeType,17},{inappropriateMatching,18},{constraintViolation,19},{attributeOrValueExists,20},{invalidAttributeSyntax,21},{noSuchObject,32},{aliasProblem,33},{invalidDNSyntax,34},{aliasDereferencingProblem,36},{inappropriateAuthentication,48},{invalidCredentials,49},{insufficientAccessRights,50},{busy,51},{unavailable,52},{unwillingToPerform,53},{loopDetect,54},{namingViolation,64},{objectClassViolation,65},{notAllowedOnNonLeaf,66},{notAllowedOnRDN,67},{entryAlreadyExists,68},{objectClassModsProhibited,69},{affectsMultipleDSAs,71},{other,80}],[], mandatory), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING %%------------------------------------------------- {Term2,Bytes4,Rb3} = ?RT_BER:decode_octet_string(Bytes3,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 3 with type OCTET STRING %%------------------------------------------------- {Term3,Bytes5,Rb4} = ?RT_BER:decode_octet_string(Bytes4,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 4 External ELDAPv3:Referral OPTIONAL %%------------------------------------------------- {Term4,Bytes6,Rb5} = case Bytes5 of <<2:2,_:1,3:5,_/binary>> -> 'dec_Referral'(Bytes5, opt_or_default, [{tag,128,3,'IMPLICIT',32}]); _ -> { asn1_NOVALUE, Bytes5, 0 } end, %%------------------------------------------------- %% attribute number 5 with type OCTET STRING OPTIONAL %%------------------------------------------------- {Term5,Bytes7,Rb6} = case Bytes6 of <<2:2,_:1,7:5,_/binary>> -> ?RT_BER:decode_octet_string(Bytes6,[],[{tag,128,7,'IMPLICIT',32}], no_length, mandatory); _ -> { asn1_NOVALUE, Bytes6, 0 } end, {Bytes8,Rb7} = ?RT_BER:restbytes2(RemBytes, Bytes7,noext), {{'BindResponse', Term1, Term2, Term3, Term4, Term5}, Bytes8, Rb1+Rb2+Rb3+Rb4+Rb5+Rb6+Rb7}. %%================================ %% UnbindRequest %%================================ 'enc_UnbindRequest'({'UnbindRequest',Val}, TagIn) -> 'enc_UnbindRequest'(Val, TagIn); 'enc_UnbindRequest'(Val, TagIn) -> ?RT_BER:encode_null(Val, TagIn ++ [{tag,64,2,'IMPLICIT',32}]). 'dec_UnbindRequest'(Bytes, OptOrMand) -> 'dec_UnbindRequest'(Bytes, OptOrMand, []). 'dec_UnbindRequest'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_null(Bytes,TagIn++[{tag,64,2,'IMPLICIT',32}], OptOrMand). %%================================ %% SearchRequest %%================================ 'enc_SearchRequest'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,baseObject), []), %%------------------------------------------------- %% attribute number 2 with type ENUMERATED %%------------------------------------------------- {EncBytes2,EncLen2} = case (case ?RT_BER:cindex(3,Val,scope) of {_,_}->element(2,?RT_BER:cindex(3,Val,scope));_->?RT_BER:cindex(3,Val,scope) end) of baseObject -> ?RT_BER:encode_enumerated(0,[]); singleLevel -> ?RT_BER:encode_enumerated(1,[]); wholeSubtree -> ?RT_BER:encode_enumerated(2,[]); Enumval2 -> exit({error,{asn1, {enumerated_not_in_range,Enumval2}}}) end, %%------------------------------------------------- %% attribute number 3 with type ENUMERATED %%------------------------------------------------- {EncBytes3,EncLen3} = case (case ?RT_BER:cindex(4,Val,derefAliases) of {_,_}->element(2,?RT_BER:cindex(4,Val,derefAliases));_->?RT_BER:cindex(4,Val,derefAliases) end) of neverDerefAliases -> ?RT_BER:encode_enumerated(0,[]); derefInSearching -> ?RT_BER:encode_enumerated(1,[]); derefFindingBaseObj -> ?RT_BER:encode_enumerated(2,[]); derefAlways -> ?RT_BER:encode_enumerated(3,[]); Enumval3 -> exit({error,{asn1, {enumerated_not_in_range,Enumval3}}}) end, %%------------------------------------------------- %% attribute number 4 with type INTEGER %%------------------------------------------------- {EncBytes4,EncLen4} = ?RT_BER:encode_integer([], ?RT_BER:cindex(5,Val,sizeLimit), []), %%------------------------------------------------- %% attribute number 5 with type INTEGER %%------------------------------------------------- {EncBytes5,EncLen5} = ?RT_BER:encode_integer([], ?RT_BER:cindex(6,Val,timeLimit), []), %%------------------------------------------------- %% attribute number 6 with type BOOLEAN %%------------------------------------------------- {EncBytes6,EncLen6} = ?RT_BER:encode_boolean(?RT_BER:cindex(7,Val,typesOnly), []), %%------------------------------------------------- %% attribute number 7 External ELDAPv3:Filter %%------------------------------------------------- {EncBytes7,EncLen7} = 'enc_Filter'(?RT_BER:cindex(8,Val,filter), []), %%------------------------------------------------- %% attribute number 8 External ELDAPv3:AttributeDescriptionList %%------------------------------------------------- {EncBytes8,EncLen8} = 'enc_AttributeDescriptionList'(?RT_BER:cindex(9,Val,attributes), []), BytesSoFar = [EncBytes1, EncBytes2, EncBytes3, EncBytes4, EncBytes5, EncBytes6, EncBytes7, EncBytes8], LenSoFar = EncLen1 + EncLen2 + EncLen3 + EncLen4 + EncLen5 + EncLen6 + EncLen7 + EncLen8, ?RT_BER:encode_tags(TagIn ++ [{tag,64,3,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_SearchRequest'(Bytes, OptOrMand) -> 'dec_SearchRequest'(Bytes, OptOrMand, []). 'dec_SearchRequest'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,64,3,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 with type ENUMERATED %%------------------------------------------------- {Term2,Bytes4,Rb3} = ?RT_BER:decode_enumerated(Bytes3,[],[{baseObject,0},{singleLevel,1},{wholeSubtree,2}],[], mandatory), %%------------------------------------------------- %% attribute number 3 with type ENUMERATED %%------------------------------------------------- {Term3,Bytes5,Rb4} = ?RT_BER:decode_enumerated(Bytes4,[],[{neverDerefAliases,0},{derefInSearching,1},{derefFindingBaseObj,2},{derefAlways,3}],[], mandatory), %%------------------------------------------------- %% attribute number 4 with type INTEGER %%------------------------------------------------- {Term4,Bytes6,Rb5} = ?RT_BER:decode_integer(Bytes5,{0,2147483647},[], mandatory), %%------------------------------------------------- %% attribute number 5 with type INTEGER %%------------------------------------------------- {Term5,Bytes7,Rb6} = ?RT_BER:decode_integer(Bytes6,{0,2147483647},[], mandatory), %%------------------------------------------------- %% attribute number 6 with type BOOLEAN %%------------------------------------------------- {Term6,Bytes8,Rb7} = ?RT_BER:decode_boolean(Bytes7,[], mandatory), %%------------------------------------------------- %% attribute number 7 External ELDAPv3:Filter %%------------------------------------------------- {Term7,Bytes9,Rb8} = 'dec_Filter'(Bytes8, mandatory, []), %%------------------------------------------------- %% attribute number 8 External ELDAPv3:AttributeDescriptionList %%------------------------------------------------- {Term8,Bytes10,Rb9} = 'dec_AttributeDescriptionList'(Bytes9, mandatory, []), {Bytes11,Rb10} = ?RT_BER:restbytes2(RemBytes, Bytes10,noext), {{'SearchRequest', Term1, Term2, Term3, Term4, Term5, Term6, Term7, Term8}, Bytes11, Rb1+Rb2+Rb3+Rb4+Rb5+Rb6+Rb7+Rb8+Rb9+Rb10}. %%================================ %% Filter %%================================ 'enc_Filter'({'Filter',Val}, TagIn) -> 'enc_Filter'(Val, TagIn); 'enc_Filter'(Val, TagIn) -> {EncBytes,EncLen} = case element(1,Val) of 'and' -> 'enc_Filter_and'(element(2,Val), [{tag,128,0,'IMPLICIT',32}]); 'or' -> 'enc_Filter_or'(element(2,Val), [{tag,128,1,'IMPLICIT',32}]); 'not' -> 'enc_Filter'(element(2,Val), [{tag,128,2,'EXPLICIT',32}]); equalityMatch -> 'enc_AttributeValueAssertion'(element(2,Val), [{tag,128,3,'IMPLICIT',32}]); substrings -> 'enc_SubstringFilter'(element(2,Val), [{tag,128,4,'IMPLICIT',32}]); greaterOrEqual -> 'enc_AttributeValueAssertion'(element(2,Val), [{tag,128,5,'IMPLICIT',32}]); lessOrEqual -> 'enc_AttributeValueAssertion'(element(2,Val), [{tag,128,6,'IMPLICIT',32}]); present -> ?RT_BER:encode_octet_string([], element(2,Val), [{tag,128,7,'IMPLICIT',32}]); approxMatch -> 'enc_AttributeValueAssertion'(element(2,Val), [{tag,128,8,'IMPLICIT',32}]); extensibleMatch -> 'enc_MatchingRuleAssertion'(element(2,Val), [{tag,128,9,'IMPLICIT',32}]); Else -> exit({error,{asn1,{invalid_choice_type,Else}}}) end, ?RT_BER:encode_tags(TagIn ++[], EncBytes, EncLen). %%================================ %% Filter_and %%================================ 'enc_Filter_and'({'Filter_and',Val}, TagIn) -> 'enc_Filter_and'(Val, TagIn); 'enc_Filter_and'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_Filter_and_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,17,'IMPLICIT',32}], EncBytes, EncLen). 'enc_Filter_and_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_Filter_and_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = 'enc_Filter'(H, []), 'enc_Filter_and_components'(T,[EncBytes|AccBytes], AccLen + EncLen). 'dec_Filter_and'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,17,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun 'dec_Filter'/3, [], []). %%================================ %% Filter_or %%================================ 'enc_Filter_or'({'Filter_or',Val}, TagIn) -> 'enc_Filter_or'(Val, TagIn); 'enc_Filter_or'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_Filter_or_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,17,'IMPLICIT',32}], EncBytes, EncLen). 'enc_Filter_or_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_Filter_or_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = 'enc_Filter'(H, []), 'enc_Filter_or_components'(T,[EncBytes|AccBytes], AccLen + EncLen). 'dec_Filter_or'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,17,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun 'dec_Filter'/3, [], []). 'dec_Filter'(Bytes, OptOrMand) -> 'dec_Filter'(Bytes, OptOrMand, []). 'dec_Filter'(Bytes, OptOrMand, TagIn) -> {{_,Len},Bytes1, RbExp} = ?RT_BER:check_tags(TagIn++[], Bytes, OptOrMand), IndefEndBytes = fun(indefinite,<<0,0,R/binary>>)-> R; (_,B)-> B end, IndefEndRb = fun(indefinite,<<0,0,_R/binary>>)-> 2; (_,_)-> 0 end, case Bytes1 of %% 'and' <<2:2,_:1,0:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_Filter_and'(Bytes1, mandatory, [{tag,128,0,'IMPLICIT',32}]), {{'and', Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'or' <<2:2,_:1,1:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_Filter_or'(Bytes1, mandatory, [{tag,128,1,'IMPLICIT',32}]), {{'or', Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'not' <<2:2,_:1,2:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_Filter'(Bytes1, mandatory, [{tag,128,2,'EXPLICIT',32}]), {{'not', Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'equalityMatch' <<2:2,_:1,3:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_AttributeValueAssertion'(Bytes1, mandatory, [{tag,128,3,'IMPLICIT',32}]), {{equalityMatch, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'substrings' <<2:2,_:1,4:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_SubstringFilter'(Bytes1, mandatory, [{tag,128,4,'IMPLICIT',32}]), {{substrings, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'greaterOrEqual' <<2:2,_:1,5:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_AttributeValueAssertion'(Bytes1, mandatory, [{tag,128,5,'IMPLICIT',32}]), {{greaterOrEqual, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'lessOrEqual' <<2:2,_:1,6:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_AttributeValueAssertion'(Bytes1, mandatory, [{tag,128,6,'IMPLICIT',32}]), {{lessOrEqual, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'present' <<2:2,_:1,7:5,_/binary>> -> {Dec, Rest, RbCho} = ?RT_BER:decode_octet_string(Bytes1,[],[{tag,128,7,'IMPLICIT',32}], no_length, mandatory), {{present, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'approxMatch' <<2:2,_:1,8:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_AttributeValueAssertion'(Bytes1, mandatory, [{tag,128,8,'IMPLICIT',32}]), {{approxMatch, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'extensibleMatch' <<2:2,_:1,9:5,_/binary>> -> {Dec, Rest, RbCho} = 'dec_MatchingRuleAssertion'(Bytes1, mandatory, [{tag,128,9,'IMPLICIT',32}]), {{extensibleMatch, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; Else -> case OptOrMand of mandatory ->exit({error,{asn1,{invalid_choice_tag,Else}}}); _ ->exit({error,{asn1,{no_optional_tag,Else}}}) end end. %%================================ %% SubstringFilter %%================================ 'enc_SubstringFilter'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,type), []), %%------------------------------------------------- %% attribute number 2 with type SEQUENCE OF %%------------------------------------------------- {EncBytes2,EncLen2} = 'enc_SubstringFilter_substrings'(?RT_BER:cindex(3,Val,substrings), []), BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). %%================================ %% SubstringFilter_substrings %%================================ 'enc_SubstringFilter_substrings'({'SubstringFilter_substrings',Val}, TagIn) -> 'enc_SubstringFilter_substrings'(Val, TagIn); 'enc_SubstringFilter_substrings'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_SubstringFilter_substrings_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], EncBytes, EncLen). 'enc_SubstringFilter_substrings_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_SubstringFilter_substrings_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = 'enc_SubstringFilter_substrings_SEQOF'(H, []), 'enc_SubstringFilter_substrings_components'(T,[EncBytes|AccBytes], AccLen + EncLen). %%================================ %% SubstringFilter_substrings_SEQOF %%================================ 'enc_SubstringFilter_substrings_SEQOF'({'SubstringFilter_substrings_SEQOF',Val}, TagIn) -> 'enc_SubstringFilter_substrings_SEQOF'(Val, TagIn); 'enc_SubstringFilter_substrings_SEQOF'(Val, TagIn) -> {EncBytes,EncLen} = case element(1,Val) of initial -> ?RT_BER:encode_octet_string([], element(2,Val), [{tag,128,0,'IMPLICIT',32}]); any -> ?RT_BER:encode_octet_string([], element(2,Val), [{tag,128,1,'IMPLICIT',32}]); final -> ?RT_BER:encode_octet_string([], element(2,Val), [{tag,128,2,'IMPLICIT',32}]); Else -> exit({error,{asn1,{invalid_choice_type,Else}}}) end, ?RT_BER:encode_tags(TagIn ++[], EncBytes, EncLen). 'dec_SubstringFilter_substrings_SEQOF'(Bytes, OptOrMand, TagIn) -> {{_,Len},Bytes1, RbExp} = ?RT_BER:check_tags(TagIn++[], Bytes, OptOrMand), IndefEndBytes = fun(indefinite,<<0,0,R/binary>>)-> R; (_,B)-> B end, IndefEndRb = fun(indefinite,<<0,0,_R/binary>>)-> 2; (_,_)-> 0 end, case Bytes1 of %% 'initial' <<2:2,_:1,0:5,_/binary>> -> {Dec, Rest, RbCho} = ?RT_BER:decode_octet_string(Bytes1,[],[{tag,128,0,'IMPLICIT',32}], no_length, mandatory), {{initial, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'any' <<2:2,_:1,1:5,_/binary>> -> {Dec, Rest, RbCho} = ?RT_BER:decode_octet_string(Bytes1,[],[{tag,128,1,'IMPLICIT',32}], no_length, mandatory), {{any, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; %% 'final' <<2:2,_:1,2:5,_/binary>> -> {Dec, Rest, RbCho} = ?RT_BER:decode_octet_string(Bytes1,[],[{tag,128,2,'IMPLICIT',32}], no_length, mandatory), {{final, Dec}, IndefEndBytes(Len,Rest), RbExp + RbCho + IndefEndRb(Len,Rest)}; Else -> case OptOrMand of mandatory ->exit({error,{asn1,{invalid_choice_tag,Else}}}); _ ->exit({error,{asn1,{no_optional_tag,Else}}}) end end. 'dec_SubstringFilter_substrings'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun 'dec_SubstringFilter_substrings_SEQOF'/3, [], []). 'dec_SubstringFilter'(Bytes, OptOrMand) -> 'dec_SubstringFilter'(Bytes, OptOrMand, []). 'dec_SubstringFilter'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 with type SEQUENCE OF %%------------------------------------------------- {Term2,Bytes4,Rb3} = 'dec_SubstringFilter_substrings'(Bytes3, mandatory, []), {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'SubstringFilter', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. %%================================ %% MatchingRuleAssertion %%================================ 'enc_MatchingRuleAssertion'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING OPTIONAL %%------------------------------------------------- {EncBytes1,EncLen1} = case ?RT_BER:cindex(2,Val,matchingRule) of asn1_NOVALUE -> {<<>>,0}; _ -> ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,matchingRule), [{tag,128,1,'IMPLICIT',32}]) end, %%------------------------------------------------- %% attribute number 2 with type OCTET STRING OPTIONAL %%------------------------------------------------- {EncBytes2,EncLen2} = case ?RT_BER:cindex(3,Val,type) of asn1_NOVALUE -> {<<>>,0}; _ -> ?RT_BER:encode_octet_string([], ?RT_BER:cindex(3,Val,type), [{tag,128,2,'IMPLICIT',32}]) end, %%------------------------------------------------- %% attribute number 3 with type OCTET STRING %%------------------------------------------------- {EncBytes3,EncLen3} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(4,Val,matchValue), [{tag,128,3,'IMPLICIT',32}]), %%------------------------------------------------- %% attribute number 4 with type BOOLEAN DEFAULT = false %%------------------------------------------------- {EncBytes4,EncLen4} = case ?RT_BER:cindex(5,Val,dnAttributes) of asn1_DEFAULT -> {<<>>,0}; false -> {<<>>,0}; _ -> ?RT_BER:encode_boolean(?RT_BER:cindex(5,Val,dnAttributes), [{tag,128,4,'IMPLICIT',32}]) end, BytesSoFar = [EncBytes1, EncBytes2, EncBytes3, EncBytes4], LenSoFar = EncLen1 + EncLen2 + EncLen3 + EncLen4, ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_MatchingRuleAssertion'(Bytes, OptOrMand) -> 'dec_MatchingRuleAssertion'(Bytes, OptOrMand, []). 'dec_MatchingRuleAssertion'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING OPTIONAL %%------------------------------------------------- {Term1,Bytes3,Rb2} = case Bytes2 of <<2:2,_:1,1:5,_/binary>> -> ?RT_BER:decode_octet_string(Bytes2,[],[{tag,128,1,'IMPLICIT',32}], no_length, mandatory); _ -> { asn1_NOVALUE, Bytes2, 0 } end, %%------------------------------------------------- %% attribute number 2 with type OCTET STRING OPTIONAL %%------------------------------------------------- {Term2,Bytes4,Rb3} = case Bytes3 of <<2:2,_:1,2:5,_/binary>> -> ?RT_BER:decode_octet_string(Bytes3,[],[{tag,128,2,'IMPLICIT',32}], no_length, mandatory); _ -> { asn1_NOVALUE, Bytes3, 0 } end, %%------------------------------------------------- %% attribute number 3 with type OCTET STRING %%------------------------------------------------- {Term3,Bytes5,Rb4} = ?RT_BER:decode_octet_string(Bytes4,[],[{tag,128,3,'IMPLICIT',32}], no_length, mandatory), %%------------------------------------------------- %% attribute number 4 with type BOOLEAN DEFAULT = false %%------------------------------------------------- {Term4,Bytes6,Rb5} = case Bytes5 of <<2:2,_:1,4:5,_/binary>> -> ?RT_BER:decode_boolean(Bytes5,[{tag,128,4,'IMPLICIT',32}], mandatory); _ -> {false,Bytes5, 0 } end, {Bytes7,Rb6} = ?RT_BER:restbytes2(RemBytes, Bytes6,noext), {{'MatchingRuleAssertion', Term1, Term2, Term3, Term4}, Bytes7, Rb1+Rb2+Rb3+Rb4+Rb5+Rb6}. %%================================ %% SearchResultEntry %%================================ 'enc_SearchResultEntry'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,objectName), []), %%------------------------------------------------- %% attribute number 2 External ELDAPv3:PartialAttributeList %%------------------------------------------------- {EncBytes2,EncLen2} = 'enc_PartialAttributeList'(?RT_BER:cindex(3,Val,attributes), []), BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,64,4,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_SearchResultEntry'(Bytes, OptOrMand) -> 'dec_SearchResultEntry'(Bytes, OptOrMand, []). 'dec_SearchResultEntry'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,64,4,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 External ELDAPv3:PartialAttributeList %%------------------------------------------------- {Term2,Bytes4,Rb3} = 'dec_PartialAttributeList'(Bytes3, mandatory, []), {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'SearchResultEntry', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. %%================================ %% PartialAttributeList %%================================ 'enc_PartialAttributeList'({'PartialAttributeList',Val}, TagIn) -> 'enc_PartialAttributeList'(Val, TagIn); 'enc_PartialAttributeList'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_PartialAttributeList_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], EncBytes, EncLen). 'enc_PartialAttributeList_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_PartialAttributeList_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = 'enc_PartialAttributeList_SEQOF'(H, []), 'enc_PartialAttributeList_components'(T,[EncBytes|AccBytes], AccLen + EncLen). %%================================ %% PartialAttributeList_SEQOF %%================================ 'enc_PartialAttributeList_SEQOF'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,type), []), %%------------------------------------------------- %% attribute number 2 with type SET OF %%------------------------------------------------- {EncBytes2,EncLen2} = 'enc_PartialAttributeList_SEQOF_vals'(?RT_BER:cindex(3,Val,vals), []), BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). %%================================ %% PartialAttributeList_SEQOF_vals %%================================ 'enc_PartialAttributeList_SEQOF_vals'({'PartialAttributeList_SEQOF_vals',Val}, TagIn) -> 'enc_PartialAttributeList_SEQOF_vals'(Val, TagIn); 'enc_PartialAttributeList_SEQOF_vals'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_PartialAttributeList_SEQOF_vals_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,17,'IMPLICIT',32}], EncBytes, EncLen). 'enc_PartialAttributeList_SEQOF_vals_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_PartialAttributeList_SEQOF_vals_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = ?RT_BER:encode_octet_string([], H, []), 'enc_PartialAttributeList_SEQOF_vals_components'(T,[EncBytes|AccBytes], AccLen + EncLen). 'dec_PartialAttributeList_SEQOF_vals'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,17,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun(FBytes,_,_)-> ?RT_BER:decode_octet_string(FBytes,[],[], no_length, mandatory) end, [], []). 'dec_PartialAttributeList_SEQOF'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 with type SET OF %%------------------------------------------------- {Term2,Bytes4,Rb3} = 'dec_PartialAttributeList_SEQOF_vals'(Bytes3, mandatory, []), {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'PartialAttributeList_SEQOF', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. 'dec_PartialAttributeList'(Bytes, OptOrMand) -> 'dec_PartialAttributeList'(Bytes, OptOrMand, []). 'dec_PartialAttributeList'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun 'dec_PartialAttributeList_SEQOF'/3, [], []). %%================================ %% SearchResultReference %%================================ 'enc_SearchResultReference'({'SearchResultReference',Val}, TagIn) -> 'enc_SearchResultReference'(Val, TagIn); 'enc_SearchResultReference'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_SearchResultReference_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,64,19,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], EncBytes, EncLen). 'enc_SearchResultReference_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_SearchResultReference_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = ?RT_BER:encode_octet_string([], H, []), 'enc_SearchResultReference_components'(T,[EncBytes|AccBytes], AccLen + EncLen). 'dec_SearchResultReference'(Bytes, OptOrMand) -> 'dec_SearchResultReference'(Bytes, OptOrMand, []). 'dec_SearchResultReference'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,64,19,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun(FBytes,_,_)-> ?RT_BER:decode_octet_string(FBytes,[],[], no_length, mandatory) end, [], []). %%================================ %% SearchResultDone %%================================ 'enc_SearchResultDone'({'SearchResultDone',Val}, TagIn) -> 'enc_SearchResultDone'(Val, TagIn); 'enc_SearchResultDone'(Val, TagIn) -> 'enc_LDAPResult'(Val, TagIn ++ [{tag,64,5,'IMPLICIT',32}]). 'dec_SearchResultDone'(Bytes, OptOrMand) -> 'dec_SearchResultDone'(Bytes, OptOrMand, []). 'dec_SearchResultDone'(Bytes, OptOrMand, TagIn) -> 'dec_LDAPResult'(Bytes, OptOrMand, TagIn++[{tag,64,5,'IMPLICIT',32}]). %%================================ %% ModifyRequest %%================================ 'enc_ModifyRequest'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,object), []), %%------------------------------------------------- %% attribute number 2 with type SEQUENCE OF %%------------------------------------------------- {EncBytes2,EncLen2} = 'enc_ModifyRequest_modification'(?RT_BER:cindex(3,Val,modification), []), BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,64,6,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). %%================================ %% ModifyRequest_modification %%================================ 'enc_ModifyRequest_modification'({'ModifyRequest_modification',Val}, TagIn) -> 'enc_ModifyRequest_modification'(Val, TagIn); 'enc_ModifyRequest_modification'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_ModifyRequest_modification_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], EncBytes, EncLen). 'enc_ModifyRequest_modification_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_ModifyRequest_modification_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = 'enc_ModifyRequest_modification_SEQOF'(H, []), 'enc_ModifyRequest_modification_components'(T,[EncBytes|AccBytes], AccLen + EncLen). %%================================ %% ModifyRequest_modification_SEQOF %%================================ 'enc_ModifyRequest_modification_SEQOF'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type ENUMERATED %%------------------------------------------------- {EncBytes1,EncLen1} = case (case ?RT_BER:cindex(2,Val,operation) of {_,_}->element(2,?RT_BER:cindex(2,Val,operation));_->?RT_BER:cindex(2,Val,operation) end) of add -> ?RT_BER:encode_enumerated(0,[]); delete -> ?RT_BER:encode_enumerated(1,[]); replace -> ?RT_BER:encode_enumerated(2,[]); Enumval1 -> exit({error,{asn1, {enumerated_not_in_range,Enumval1}}}) end, %%------------------------------------------------- %% attribute number 2 External ELDAPv3:AttributeTypeAndValues %%------------------------------------------------- {EncBytes2,EncLen2} = 'enc_AttributeTypeAndValues'(?RT_BER:cindex(3,Val,modification), []), BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_ModifyRequest_modification_SEQOF'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type ENUMERATED %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_enumerated(Bytes2,[],[{add,0},{delete,1},{replace,2}],[], mandatory), %%------------------------------------------------- %% attribute number 2 External ELDAPv3:AttributeTypeAndValues %%------------------------------------------------- {Term2,Bytes4,Rb3} = 'dec_AttributeTypeAndValues'(Bytes3, mandatory, []), {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'ModifyRequest_modification_SEQOF', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. 'dec_ModifyRequest_modification'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun 'dec_ModifyRequest_modification_SEQOF'/3, [], []). 'dec_ModifyRequest'(Bytes, OptOrMand) -> 'dec_ModifyRequest'(Bytes, OptOrMand, []). 'dec_ModifyRequest'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,64,6,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 with type SEQUENCE OF %%------------------------------------------------- {Term2,Bytes4,Rb3} = 'dec_ModifyRequest_modification'(Bytes3, mandatory, []), {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'ModifyRequest', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. %%================================ %% AttributeTypeAndValues %%================================ 'enc_AttributeTypeAndValues'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,type), []), %%------------------------------------------------- %% attribute number 2 with type SET OF %%------------------------------------------------- {EncBytes2,EncLen2} = 'enc_AttributeTypeAndValues_vals'(?RT_BER:cindex(3,Val,vals), []), BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). %%================================ %% AttributeTypeAndValues_vals %%================================ 'enc_AttributeTypeAndValues_vals'({'AttributeTypeAndValues_vals',Val}, TagIn) -> 'enc_AttributeTypeAndValues_vals'(Val, TagIn); 'enc_AttributeTypeAndValues_vals'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_AttributeTypeAndValues_vals_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,17,'IMPLICIT',32}], EncBytes, EncLen). 'enc_AttributeTypeAndValues_vals_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_AttributeTypeAndValues_vals_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = ?RT_BER:encode_octet_string([], H, []), 'enc_AttributeTypeAndValues_vals_components'(T,[EncBytes|AccBytes], AccLen + EncLen). 'dec_AttributeTypeAndValues_vals'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,17,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun(FBytes,_,_)-> ?RT_BER:decode_octet_string(FBytes,[],[], no_length, mandatory) end, [], []). 'dec_AttributeTypeAndValues'(Bytes, OptOrMand) -> 'dec_AttributeTypeAndValues'(Bytes, OptOrMand, []). 'dec_AttributeTypeAndValues'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 with type SET OF %%------------------------------------------------- {Term2,Bytes4,Rb3} = 'dec_AttributeTypeAndValues_vals'(Bytes3, mandatory, []), {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'AttributeTypeAndValues', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. %%================================ %% ModifyResponse %%================================ 'enc_ModifyResponse'({'ModifyResponse',Val}, TagIn) -> 'enc_ModifyResponse'(Val, TagIn); 'enc_ModifyResponse'(Val, TagIn) -> 'enc_LDAPResult'(Val, TagIn ++ [{tag,64,7,'IMPLICIT',32}]). 'dec_ModifyResponse'(Bytes, OptOrMand) -> 'dec_ModifyResponse'(Bytes, OptOrMand, []). 'dec_ModifyResponse'(Bytes, OptOrMand, TagIn) -> 'dec_LDAPResult'(Bytes, OptOrMand, TagIn++[{tag,64,7,'IMPLICIT',32}]). %%================================ %% AddRequest %%================================ 'enc_AddRequest'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,entry), []), %%------------------------------------------------- %% attribute number 2 External ELDAPv3:AttributeList %%------------------------------------------------- {EncBytes2,EncLen2} = 'enc_AttributeList'(?RT_BER:cindex(3,Val,attributes), []), BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,64,8,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_AddRequest'(Bytes, OptOrMand) -> 'dec_AddRequest'(Bytes, OptOrMand, []). 'dec_AddRequest'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,64,8,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 External ELDAPv3:AttributeList %%------------------------------------------------- {Term2,Bytes4,Rb3} = 'dec_AttributeList'(Bytes3, mandatory, []), {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'AddRequest', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. %%================================ %% AttributeList %%================================ 'enc_AttributeList'({'AttributeList',Val}, TagIn) -> 'enc_AttributeList'(Val, TagIn); 'enc_AttributeList'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_AttributeList_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], EncBytes, EncLen). 'enc_AttributeList_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_AttributeList_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = 'enc_AttributeList_SEQOF'(H, []), 'enc_AttributeList_components'(T,[EncBytes|AccBytes], AccLen + EncLen). %%================================ %% AttributeList_SEQOF %%================================ 'enc_AttributeList_SEQOF'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,type), []), %%------------------------------------------------- %% attribute number 2 with type SET OF %%------------------------------------------------- {EncBytes2,EncLen2} = 'enc_AttributeList_SEQOF_vals'(?RT_BER:cindex(3,Val,vals), []), BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). %%================================ %% AttributeList_SEQOF_vals %%================================ 'enc_AttributeList_SEQOF_vals'({'AttributeList_SEQOF_vals',Val}, TagIn) -> 'enc_AttributeList_SEQOF_vals'(Val, TagIn); 'enc_AttributeList_SEQOF_vals'(Val, TagIn) -> {EncBytes,EncLen} = 'enc_AttributeList_SEQOF_vals_components'(Val,[],0), ?RT_BER:encode_tags(TagIn ++ [{tag,0,17,'IMPLICIT',32}], EncBytes, EncLen). 'enc_AttributeList_SEQOF_vals_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_AttributeList_SEQOF_vals_components'([H|T],AccBytes, AccLen) -> {EncBytes,EncLen} = ?RT_BER:encode_octet_string([], H, []), 'enc_AttributeList_SEQOF_vals_components'(T,[EncBytes|AccBytes], AccLen + EncLen). 'dec_AttributeList_SEQOF_vals'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,17,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun(FBytes,_,_)-> ?RT_BER:decode_octet_string(FBytes,[],[], no_length, mandatory) end, [], []). 'dec_AttributeList_SEQOF'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 with type SET OF %%------------------------------------------------- {Term2,Bytes4,Rb3} = 'dec_AttributeList_SEQOF_vals'(Bytes3, mandatory, []), {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'AttributeList_SEQOF', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. 'dec_AttributeList'(Bytes, OptOrMand) -> 'dec_AttributeList'(Bytes, OptOrMand, []). 'dec_AttributeList'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), ?RT_BER:decode_components(Rb1, Len, Bytes1, fun 'dec_AttributeList_SEQOF'/3, [], []). %%================================ %% AddResponse %%================================ 'enc_AddResponse'({'AddResponse',Val}, TagIn) -> 'enc_AddResponse'(Val, TagIn); 'enc_AddResponse'(Val, TagIn) -> 'enc_LDAPResult'(Val, TagIn ++ [{tag,64,9,'IMPLICIT',32}]). 'dec_AddResponse'(Bytes, OptOrMand) -> 'dec_AddResponse'(Bytes, OptOrMand, []). 'dec_AddResponse'(Bytes, OptOrMand, TagIn) -> 'dec_LDAPResult'(Bytes, OptOrMand, TagIn++[{tag,64,9,'IMPLICIT',32}]). %%================================ %% DelRequest %%================================ 'enc_DelRequest'({'DelRequest',Val}, TagIn) -> 'enc_DelRequest'(Val, TagIn); 'enc_DelRequest'(Val, TagIn) -> ?RT_BER:encode_octet_string([], Val, TagIn ++ [{tag,64,10,'IMPLICIT',32}]). 'dec_DelRequest'(Bytes, OptOrMand) -> 'dec_DelRequest'(Bytes, OptOrMand, []). 'dec_DelRequest'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_octet_string(Bytes,[],TagIn++[{tag,64,10,'IMPLICIT',32}], no_length, OptOrMand). %%================================ %% DelResponse %%================================ 'enc_DelResponse'({'DelResponse',Val}, TagIn) -> 'enc_DelResponse'(Val, TagIn); 'enc_DelResponse'(Val, TagIn) -> 'enc_LDAPResult'(Val, TagIn ++ [{tag,64,11,'IMPLICIT',32}]). 'dec_DelResponse'(Bytes, OptOrMand) -> 'dec_DelResponse'(Bytes, OptOrMand, []). 'dec_DelResponse'(Bytes, OptOrMand, TagIn) -> 'dec_LDAPResult'(Bytes, OptOrMand, TagIn++[{tag,64,11,'IMPLICIT',32}]). %%================================ %% ModifyDNRequest %%================================ 'enc_ModifyDNRequest'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,entry), []), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING %%------------------------------------------------- {EncBytes2,EncLen2} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(3,Val,newrdn), []), %%------------------------------------------------- %% attribute number 3 with type BOOLEAN %%------------------------------------------------- {EncBytes3,EncLen3} = ?RT_BER:encode_boolean(?RT_BER:cindex(4,Val,deleteoldrdn), []), %%------------------------------------------------- %% attribute number 4 with type OCTET STRING OPTIONAL %%------------------------------------------------- {EncBytes4,EncLen4} = case ?RT_BER:cindex(5,Val,newSuperior) of asn1_NOVALUE -> {<<>>,0}; _ -> ?RT_BER:encode_octet_string([], ?RT_BER:cindex(5,Val,newSuperior), [{tag,128,0,'IMPLICIT',32}]) end, BytesSoFar = [EncBytes1, EncBytes2, EncBytes3, EncBytes4], LenSoFar = EncLen1 + EncLen2 + EncLen3 + EncLen4, ?RT_BER:encode_tags(TagIn ++ [{tag,64,12,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_ModifyDNRequest'(Bytes, OptOrMand) -> 'dec_ModifyDNRequest'(Bytes, OptOrMand, []). 'dec_ModifyDNRequest'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,64,12,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING %%------------------------------------------------- {Term2,Bytes4,Rb3} = ?RT_BER:decode_octet_string(Bytes3,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 3 with type BOOLEAN %%------------------------------------------------- {Term3,Bytes5,Rb4} = ?RT_BER:decode_boolean(Bytes4,[], mandatory), %%------------------------------------------------- %% attribute number 4 with type OCTET STRING OPTIONAL %%------------------------------------------------- {Term4,Bytes6,Rb5} = case Bytes5 of <<2:2,_:1,0:5,_/binary>> -> ?RT_BER:decode_octet_string(Bytes5,[],[{tag,128,0,'IMPLICIT',32}], no_length, mandatory); _ -> { asn1_NOVALUE, Bytes5, 0 } end, {Bytes7,Rb6} = ?RT_BER:restbytes2(RemBytes, Bytes6,noext), {{'ModifyDNRequest', Term1, Term2, Term3, Term4}, Bytes7, Rb1+Rb2+Rb3+Rb4+Rb5+Rb6}. %%================================ %% ModifyDNResponse %%================================ 'enc_ModifyDNResponse'({'ModifyDNResponse',Val}, TagIn) -> 'enc_ModifyDNResponse'(Val, TagIn); 'enc_ModifyDNResponse'(Val, TagIn) -> 'enc_LDAPResult'(Val, TagIn ++ [{tag,64,13,'IMPLICIT',32}]). 'dec_ModifyDNResponse'(Bytes, OptOrMand) -> 'dec_ModifyDNResponse'(Bytes, OptOrMand, []). 'dec_ModifyDNResponse'(Bytes, OptOrMand, TagIn) -> 'dec_LDAPResult'(Bytes, OptOrMand, TagIn++[{tag,64,13,'IMPLICIT',32}]). %%================================ %% CompareRequest %%================================ 'enc_CompareRequest'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,entry), []), %%------------------------------------------------- %% attribute number 2 External ELDAPv3:AttributeValueAssertion %%------------------------------------------------- {EncBytes2,EncLen2} = 'enc_AttributeValueAssertion'(?RT_BER:cindex(3,Val,ava), []), BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,64,14,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_CompareRequest'(Bytes, OptOrMand) -> 'dec_CompareRequest'(Bytes, OptOrMand, []). 'dec_CompareRequest'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,64,14,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 External ELDAPv3:AttributeValueAssertion %%------------------------------------------------- {Term2,Bytes4,Rb3} = 'dec_AttributeValueAssertion'(Bytes3, mandatory, []), {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'CompareRequest', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. %%================================ %% CompareResponse %%================================ 'enc_CompareResponse'({'CompareResponse',Val}, TagIn) -> 'enc_CompareResponse'(Val, TagIn); 'enc_CompareResponse'(Val, TagIn) -> 'enc_LDAPResult'(Val, TagIn ++ [{tag,64,15,'IMPLICIT',32}]). 'dec_CompareResponse'(Bytes, OptOrMand) -> 'dec_CompareResponse'(Bytes, OptOrMand, []). 'dec_CompareResponse'(Bytes, OptOrMand, TagIn) -> 'dec_LDAPResult'(Bytes, OptOrMand, TagIn++[{tag,64,15,'IMPLICIT',32}]). %%================================ %% AbandonRequest %%================================ 'enc_AbandonRequest'({'AbandonRequest',Val}, TagIn) -> 'enc_AbandonRequest'(Val, TagIn); 'enc_AbandonRequest'(Val, TagIn) -> ?RT_BER:encode_integer([], Val, TagIn ++ [{tag,64,16,'IMPLICIT',32}]). 'dec_AbandonRequest'(Bytes, OptOrMand) -> 'dec_AbandonRequest'(Bytes, OptOrMand, []). 'dec_AbandonRequest'(Bytes, OptOrMand, TagIn) -> ?RT_BER:decode_integer(Bytes,{0,2147483647},TagIn++[{tag,64,16,'IMPLICIT',32}], OptOrMand). %%================================ %% ExtendedRequest %%================================ 'enc_ExtendedRequest'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {EncBytes1,EncLen1} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(2,Val,requestName), [{tag,128,0,'IMPLICIT',32}]), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING OPTIONAL %%------------------------------------------------- {EncBytes2,EncLen2} = case ?RT_BER:cindex(3,Val,requestValue) of asn1_NOVALUE -> {<<>>,0}; _ -> ?RT_BER:encode_octet_string([], ?RT_BER:cindex(3,Val,requestValue), [{tag,128,1,'IMPLICIT',32}]) end, BytesSoFar = [EncBytes1, EncBytes2], LenSoFar = EncLen1 + EncLen2, ?RT_BER:encode_tags(TagIn ++ [{tag,64,23,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_ExtendedRequest'(Bytes, OptOrMand) -> 'dec_ExtendedRequest'(Bytes, OptOrMand, []). 'dec_ExtendedRequest'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,64,23,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type OCTET STRING %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_octet_string(Bytes2,[],[{tag,128,0,'IMPLICIT',32}], no_length, mandatory), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING OPTIONAL %%------------------------------------------------- {Term2,Bytes4,Rb3} = case Bytes3 of <<2:2,_:1,1:5,_/binary>> -> ?RT_BER:decode_octet_string(Bytes3,[],[{tag,128,1,'IMPLICIT',32}], no_length, mandatory); _ -> { asn1_NOVALUE, Bytes3, 0 } end, {Bytes5,Rb4} = ?RT_BER:restbytes2(RemBytes, Bytes4,noext), {{'ExtendedRequest', Term1, Term2}, Bytes5, Rb1+Rb2+Rb3+Rb4}. %%================================ %% ExtendedResponse %%================================ 'enc_ExtendedResponse'(Val, TagIn) -> %%------------------------------------------------- %% attribute number 1 with type ENUMERATED %%------------------------------------------------- {EncBytes1,EncLen1} = case (case ?RT_BER:cindex(2,Val,resultCode) of {_,_}->element(2,?RT_BER:cindex(2,Val,resultCode));_->?RT_BER:cindex(2,Val,resultCode) end) of success -> ?RT_BER:encode_enumerated(0,[]); operationsError -> ?RT_BER:encode_enumerated(1,[]); protocolError -> ?RT_BER:encode_enumerated(2,[]); timeLimitExceeded -> ?RT_BER:encode_enumerated(3,[]); sizeLimitExceeded -> ?RT_BER:encode_enumerated(4,[]); compareFalse -> ?RT_BER:encode_enumerated(5,[]); compareTrue -> ?RT_BER:encode_enumerated(6,[]); authMethodNotSupported -> ?RT_BER:encode_enumerated(7,[]); strongAuthRequired -> ?RT_BER:encode_enumerated(8,[]); referral -> ?RT_BER:encode_enumerated(10,[]); adminLimitExceeded -> ?RT_BER:encode_enumerated(11,[]); unavailableCriticalExtension -> ?RT_BER:encode_enumerated(12,[]); confidentialityRequired -> ?RT_BER:encode_enumerated(13,[]); saslBindInProgress -> ?RT_BER:encode_enumerated(14,[]); noSuchAttribute -> ?RT_BER:encode_enumerated(16,[]); undefinedAttributeType -> ?RT_BER:encode_enumerated(17,[]); inappropriateMatching -> ?RT_BER:encode_enumerated(18,[]); constraintViolation -> ?RT_BER:encode_enumerated(19,[]); attributeOrValueExists -> ?RT_BER:encode_enumerated(20,[]); invalidAttributeSyntax -> ?RT_BER:encode_enumerated(21,[]); noSuchObject -> ?RT_BER:encode_enumerated(32,[]); aliasProblem -> ?RT_BER:encode_enumerated(33,[]); invalidDNSyntax -> ?RT_BER:encode_enumerated(34,[]); aliasDereferencingProblem -> ?RT_BER:encode_enumerated(36,[]); inappropriateAuthentication -> ?RT_BER:encode_enumerated(48,[]); invalidCredentials -> ?RT_BER:encode_enumerated(49,[]); insufficientAccessRights -> ?RT_BER:encode_enumerated(50,[]); busy -> ?RT_BER:encode_enumerated(51,[]); unavailable -> ?RT_BER:encode_enumerated(52,[]); unwillingToPerform -> ?RT_BER:encode_enumerated(53,[]); loopDetect -> ?RT_BER:encode_enumerated(54,[]); namingViolation -> ?RT_BER:encode_enumerated(64,[]); objectClassViolation -> ?RT_BER:encode_enumerated(65,[]); notAllowedOnNonLeaf -> ?RT_BER:encode_enumerated(66,[]); notAllowedOnRDN -> ?RT_BER:encode_enumerated(67,[]); entryAlreadyExists -> ?RT_BER:encode_enumerated(68,[]); objectClassModsProhibited -> ?RT_BER:encode_enumerated(69,[]); affectsMultipleDSAs -> ?RT_BER:encode_enumerated(71,[]); other -> ?RT_BER:encode_enumerated(80,[]); Enumval1 -> exit({error,{asn1, {enumerated_not_in_range,Enumval1}}}) end, %%------------------------------------------------- %% attribute number 2 with type OCTET STRING %%------------------------------------------------- {EncBytes2,EncLen2} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(3,Val,matchedDN), []), %%------------------------------------------------- %% attribute number 3 with type OCTET STRING %%------------------------------------------------- {EncBytes3,EncLen3} = ?RT_BER:encode_octet_string([], ?RT_BER:cindex(4,Val,errorMessage), []), %%------------------------------------------------- %% attribute number 4 External ELDAPv3:Referral OPTIONAL %%------------------------------------------------- {EncBytes4,EncLen4} = case ?RT_BER:cindex(5,Val,referral) of asn1_NOVALUE -> {<<>>,0}; _ -> 'enc_Referral'(?RT_BER:cindex(5,Val,referral), [{tag,128,3,'IMPLICIT',32}]) end, %%------------------------------------------------- %% attribute number 5 with type OCTET STRING OPTIONAL %%------------------------------------------------- {EncBytes5,EncLen5} = case ?RT_BER:cindex(6,Val,responseName) of asn1_NOVALUE -> {<<>>,0}; _ -> ?RT_BER:encode_octet_string([], ?RT_BER:cindex(6,Val,responseName), [{tag,128,10,'IMPLICIT',32}]) end, %%------------------------------------------------- %% attribute number 6 with type OCTET STRING OPTIONAL %%------------------------------------------------- {EncBytes6,EncLen6} = case ?RT_BER:cindex(7,Val,response) of asn1_NOVALUE -> {<<>>,0}; _ -> ?RT_BER:encode_octet_string([], ?RT_BER:cindex(7,Val,response), [{tag,128,11,'IMPLICIT',32}]) end, BytesSoFar = [EncBytes1, EncBytes2, EncBytes3, EncBytes4, EncBytes5, EncBytes6], LenSoFar = EncLen1 + EncLen2 + EncLen3 + EncLen4 + EncLen5 + EncLen6, ?RT_BER:encode_tags(TagIn ++ [{tag,64,24,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], BytesSoFar, LenSoFar). 'dec_ExtendedResponse'(Bytes, OptOrMand) -> 'dec_ExtendedResponse'(Bytes, OptOrMand, []). 'dec_ExtendedResponse'(Bytes, OptOrMand, TagIn) -> %%------------------------------------------------- %% decode tag and length %%------------------------------------------------- {{_,Len},Bytes1,Rb1} = ?RT_BER:check_tags(TagIn ++ [{tag,64,24,'IMPLICIT',32},{tag,0,16,'IMPLICIT',32}], Bytes, OptOrMand), {Bytes2,RemBytes} = ?RT_BER:split_list(Bytes1,Len), %%------------------------------------------------- %% attribute number 1 with type ENUMERATED %%------------------------------------------------- {Term1,Bytes3,Rb2} = ?RT_BER:decode_enumerated(Bytes2,[],[{success,0},{operationsError,1},{protocolError,2},{timeLimitExceeded,3},{sizeLimitExceeded,4},{compareFalse,5},{compareTrue,6},{authMethodNotSupported,7},{strongAuthRequired,8},{referral,10},{adminLimitExceeded,11},{unavailableCriticalExtension,12},{confidentialityRequired,13},{saslBindInProgress,14},{noSuchAttribute,16},{undefinedAttributeType,17},{inappropriateMatching,18},{constraintViolation,19},{attributeOrValueExists,20},{invalidAttributeSyntax,21},{noSuchObject,32},{aliasProblem,33},{invalidDNSyntax,34},{aliasDereferencingProblem,36},{inappropriateAuthentication,48},{invalidCredentials,49},{insufficientAccessRights,50},{busy,51},{unavailable,52},{unwillingToPerform,53},{loopDetect,54},{namingViolation,64},{objectClassViolation,65},{notAllowedOnNonLeaf,66},{notAllowedOnRDN,67},{entryAlreadyExists,68},{objectClassModsProhibited,69},{affectsMultipleDSAs,71},{other,80}],[], mandatory), %%------------------------------------------------- %% attribute number 2 with type OCTET STRING %%------------------------------------------------- {Term2,Bytes4,Rb3} = ?RT_BER:decode_octet_string(Bytes3,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 3 with type OCTET STRING %%------------------------------------------------- {Term3,Bytes5,Rb4} = ?RT_BER:decode_octet_string(Bytes4,[],[], no_length, mandatory), %%------------------------------------------------- %% attribute number 4 External ELDAPv3:Referral OPTIONAL %%------------------------------------------------- {Term4,Bytes6,Rb5} = case Bytes5 of <<2:2,_:1,3:5,_/binary>> -> 'dec_Referral'(Bytes5, opt_or_default, [{tag,128,3,'IMPLICIT',32}]); _ -> { asn1_NOVALUE, Bytes5, 0 } end, %%------------------------------------------------- %% attribute number 5 with type OCTET STRING OPTIONAL %%------------------------------------------------- {Term5,Bytes7,Rb6} = case Bytes6 of <<2:2,_:1,10:5,_/binary>> -> ?RT_BER:decode_octet_string(Bytes6,[],[{tag,128,10,'IMPLICIT',32}], no_length, mandatory); _ -> { asn1_NOVALUE, Bytes6, 0 } end, %%------------------------------------------------- %% attribute number 6 with type OCTET STRING OPTIONAL %%------------------------------------------------- {Term6,Bytes8,Rb7} = case Bytes7 of <<2:2,_:1,11:5,_/binary>> -> ?RT_BER:decode_octet_string(Bytes7,[],[{tag,128,11,'IMPLICIT',32}], no_length, mandatory); _ -> { asn1_NOVALUE, Bytes7, 0 } end, {Bytes9,Rb8} = ?RT_BER:restbytes2(RemBytes, Bytes8,noext), {{'ExtendedResponse', Term1, Term2, Term3, Term4, Term5, Term6}, Bytes9, Rb1+Rb2+Rb3+Rb4+Rb5+Rb6+Rb7+Rb8}. 'maxInt'() -> 2147483647. tsung-1.4.2/src/lib/uuid.erl0000644000201100017670000001325111701017117015350 0ustar nniclausdream%% @author Andrew Kreiling %% @copyright 2008 Andrew Kreiling . All Rights Reserved. %% %% @copyright 2010 Nicolas Niclausse: Add random_str fun %% %% @license : MIT %% %% @doc %% UUID module for Erlang %% %% This Erlang module was designed to be a simple library for generating UUIDs. It %% conforms to RFC 4122 whenever possible. %% -module(uuid). -author('Andrew Kreiling '). -behaviour(gen_server). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. -export([start/0, start/1, start_link/0, start_link/1, stop/0]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -export([v4/0, random/0, srandom/0, sha/2, md5/2, timestamp/0, timestamp/2, to_string/1, random_str/0]). -define(SERVER, ?MODULE). -define(UUID_DNS_NAMESPACE, <<107,167,184,16,157,173,17,209,128,180,0,192,79,212,48,200>>). -define(UUID_URL_NAMESPACE, <<107,167,184,17,157,173,17,209,128,180,0,192,79,212,48,200>>). -define(UUID_OID_NAMESPACE, <<107,167,184,18,157,173,17,209,128,180,0,192,79,212,48,200>>). -define(UUID_X500_NAMESPACE, <<107,167,184,20,157,173,17,209,128,180,0,192,79,212,48,200>>). -record(state, {node, clock_seq}). %% @type uuid() = binary(). A binary representation of a UUID %% @spec v4() -> uuid() %% @equiv random() %% @deprecated Please use the function random() instead. %% v4() -> random(). %% @spec random_str() -> string() %% @doc %% Generates a string representation of a random UUID %% random_str() -> to_string(random()). %% @spec random() -> uuid() %% @doc %% Generates a random UUID %% random() -> U = << (random:uniform(4294967296) - 1):32, (random:uniform(4294967296) - 1):32, (random:uniform(4294967296) - 1):32, (random:uniform(4294967296) - 1):32 >>, format_uuid(U, 4). %% @spec srandom() -> uuid() %% @doc %% Seeds random number generation with erlang:now() and generates a random UUID %% srandom() -> {A1,A2,A3} = erlang:now(), random:seed(A1, A2, A3), random(). %% @spec sha(Namespace, Name) -> uuid() %% where %% Namespace = dns | url | oid | x500 | uuid() %% Name = list() | binary() %% @doc %% Generates a UUID based on a crypto:sha() hash %% sha(Namespace, Name) when is_list(Name) -> sha(Namespace, list_to_binary(Name)); sha(Namespace, Name) -> Context = crypto:sha_update(crypto:sha_update(crypto:sha_init(), namespace(Namespace)), Name), U = crypto:sha_final(Context), format_uuid(U, 5). %% @spec md5(Namespace, Name) -> uuid() %% where %% Namespace = dns | url | oid | x500 | uuid() %% Name = list() | binary() %% @doc %% Generates a UUID based on a crypto:md5() hash %% md5(Namespace, Name) when is_list(Name) -> md5(Namespace, list_to_binary(Name)); md5(Namespace, Name) -> Context = crypto:md5_update(crypto:md5_update(crypto:md5_init(), namespace(Namespace)), Name), U = crypto:md5_final(Context), format_uuid(U, 3). %% @spec timestamp() -> uuid() %% @doc %% Generates a UUID based on timestamp %% %% Requires that the uuid gen_server is started %% timestamp() -> gen_server:call(?SERVER, timestamp). %% @spec timestamp(Node, CS) -> uuid() %% where %% Node = binary() %% CS = int() %% @doc %% Generates a UUID based on timestamp %% timestamp(Node, CS) -> {MegaSecs, Secs, MicroSecs} = erlang:now(), T = (((((MegaSecs * 1000000) + Secs) * 1000000) + MicroSecs) * 10) + 16#01b21dd213814000, format_uuid(T band 16#ffffffff, (T bsr 32) band 16#ffff, (T bsr 48) band 16#ffff, (CS bsr 8) band 16#ff, CS band 16#ff, Node, 1). %% @spec to_string(UUID) -> string() %% where %% UUID = uuid() %% @doc %% Generates a string representation of a UUID %% to_string(<> = _UUID) -> lists:flatten(io_lib:format("~8.16.0b-~4.16.0b-~4.16.0b-~2.16.0b~2.16.0b-~12.16.0b", [TL, TM, THV, CSR, CSL, N])). %% %% uuid gen_server for generating timestamps with saved state %% start() -> start([]). start(Args) -> gen_server:start({local, ?SERVER}, ?MODULE, Args, []). start_link() -> start_link([]). start_link(Args) -> gen_server:start_link({local, ?SERVER}, ?MODULE, Args, []). stop() -> gen_server:cast(?SERVER, stop). init(Options) -> {A1,A2,A3} = proplists:get_value(seed, Options, erlang:now()), random:seed(A1, A2, A3), State = #state{ node = proplists:get_value(node, Options, <<0:48>>), clock_seq = random:uniform(65536) }, error_logger:info_report("uuid server started"), {ok, State}. handle_call(timestamp, _From, State) -> Reply = timestamp(State#state.node, State#state.clock_seq), {reply, Reply, State}; handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. handle_cast(stop, State) -> {stop, normal, State}; handle_cast(_Msg, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> error_logger:info_report("uuid server stopped"), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %% %% Internal API %% namespace(dns) -> ?UUID_DNS_NAMESPACE; namespace(url) -> ?UUID_URL_NAMESPACE; namespace(oid) -> ?UUID_OID_NAMESPACE; namespace(x500) -> ?UUID_X500_NAMESPACE; namespace(UUID) when is_binary(UUID) -> UUID; namespace(_) -> error. format_uuid(TL, TM, THV, CSR, CSL, <>, V) -> format_uuid(<>, V); format_uuid(TL, TM, THV, CSR, CSL, N, V) -> format_uuid(<>, V). format_uuid(<>, V) -> <>. %% vim:sw=4:sts=4:ts=8:et tsung-1.4.2/src/lib/rfc4515_parser.erl0000644000201100017670000001207111701017117017046 0ustar nniclausdream%% Parsing functions for RFC4515 (String Representation of LDAP Search Filters) %% %% TODO: extensibleMatch features not implemented. %% %% Author : Pablo Polvorin -module(rfc4515_parser). -author('ppolv@yahoo.com.ar'). -export([tokenize/1,filter/1,filter_to_string/1]). %% tokenize/1 %% Tokenize the input string into a token list. Generated tokens: %% '(' , ')' , '=' , '<=' ,'>=' , '~=' , '*' , '&' , '|' , '*' , {text,Value} %% Ej: %% ldap_parser:tokenize("(&(!(prpr=a*s*sse*y))(pr~=sss))"). %% --> ['(', '&','(', '!', '(', {text,"prpr"}, '=', {text,"a"}, '*', {text,"s"}, '*', %% {text,"sse"},'*', {text,"y"}, ')', ')', '(', {text,"pr"}, '~=', {text,"sss"}, ')', ')'] %% tokenize(L) -> tokenizer(lists:flatten(L),[],[]). %%flatten because & ,etc. in xml attributes ends in deep lists ej:[[38]] tokenizer([$(|L],Current,Tokens) -> tokenizer(L,[],['('|add_current(Current,Tokens)]); tokenizer([$)|L],Current,Tokens) -> tokenizer(L,[],[')'|add_current(Current,Tokens)]); tokenizer([$&|L],Current,Tokens) -> tokenizer(L,[],['&'|add_current(Current,Tokens)]); tokenizer([$||L],Current,Tokens) -> tokenizer(L,[],['|'|add_current(Current,Tokens)]); tokenizer([$!|L],Current,Tokens) -> tokenizer(L,[],['!'|add_current(Current,Tokens)]); tokenizer([$=|L],Current,Tokens) -> tokenizer(L,[],['='|add_current(Current,Tokens)]); tokenizer([$*|L],Current,Tokens) -> tokenizer(L,[],['*'|add_current(Current,Tokens)]); tokenizer([$>,$=|L],Current,Tokens) -> tokenizer(L,[],['>='|add_current(Current,Tokens)]); tokenizer([$<,$=|L],Current,Tokens) -> tokenizer(L,[],['<='|add_current(Current,Tokens)]); tokenizer([$~,$=|L],Current,Tokens) -> tokenizer(L,[],['~='|add_current(Current,Tokens)]); %% an encoded valued start with a backslash '\' character followed by the %%two hexadecimal digits representing the ASCII value of the encoded character tokenizer([92|L],Current,Tokens) -> [H1,H2|L2] = L, tokenizer(L2,[decode([H1,H2])|Current],Tokens); tokenizer([C|L],Current,Tokens) -> tokenizer(L,[C|Current],Tokens); tokenizer([],[],Tokens) -> lists:reverse(Tokens). %%FIXME: accept trailing whitespaces add_current(Current,Tokens) -> case string:strip(Current) of [] -> Tokens ; X -> [{text,lists:reverse(X)}|Tokens] end. decode(Hex) -> {ok,[C],[]} = io_lib:fread("~#","16#" ++ Hex), C. %% filter/1 %% parse a token list into an AST-like structure. %% Ej: %% ldap_parser:filter(ldap_parser:tokenize("(&(!(prpr=a*s*sse*y))(pr~=sss))")). %% --> {{'and',[{'not',{substring,"prpr", %% [{initial,"a"}, %% {any,"s"}, %% {any,"sse"}, %% {final,"y"}]}}, %% {aprox,"pr","sss"}]}, %% []} filter(['('|L]) -> {R, [')'|L2]} =filtercomp(L), {R,L2}. filtercomp(['&'|L]) -> {R,L2} = filterlist(L), {{'and',R},L2}; filtercomp(['|'|L]) -> {R,L2} = filterlist(L), {{'or',R},L2}; filtercomp(['!'|L]) -> {R,L2} = filter(L), {{'not',R},L2}; filtercomp(L) -> item(L). filterlist(L) -> filterlist(L,[]). filterlist(L=[')'|_],List) -> {lists:reverse(List),L}; %% ')' marks the end of the filter list filterlist(L,List) -> {R,L2} = filter(L), filterlist(L2,[R|List]). item([{text,T}|L]) -> item2(L,T). item2(['~=',{text,V}|L],Attr) -> {{aprox,Attr,V},L}; item2(['>=',{text,V}|L],Attr) -> {{get,Attr,V},L}; item2(['<=',{text,V}|L],Attr) -> {{'let',Attr,V},L}; item2(['='|L],Attr) -> item3(L,Attr). % could be a presence, equality or substring match item3(L = [')'|_],Attr) -> {{eq,Attr,""},L}; %empty attr ej: (description=) item3(['*',')'|L],Attr) -> {{present,Attr},[')'|L]}; %presence ej: (description=*) item3([{text,V},')'|L],Attr) -> {{eq,Attr,V},[')'|L]}; %eq ej : = (description=some description) item3(L,Attr) -> {R,L2} = substring(L), {{substring,Attr,R},L2}. substring([{text,V},'*'|L]) -> any(L,[{initial,V}]); substring(['*'|L]) -> any(L,[]). any([{text,V},'*'|L],Subs) -> any(L,[{'any',V}|Subs]); any([{text,V},')'|L],Subs) -> {lists:reverse([{final,V}|Subs]),[')'|L]}; any(L = [')'|_],Subs) -> {lists:reverse(Subs),L}. filter_to_string({'and',L}) -> io_lib:format("(& ~s)",[lists:map(fun filter_to_string/1,L)]); filter_to_string({'or',L}) -> io_lib:format("(| ~s)",[lists:map(fun filter_to_string/1,L)]); filter_to_string({'not',I}) -> io_lib:format("(! ~s)",[filter_to_string(I)]); filter_to_string({'present',Attr}) -> io_lib:format("(~s=*)",[Attr]); filter_to_string({'substring',Attr,Subs}) -> io_lib:format("(~s=~s)",[Attr,print_substrings(Subs)]); filter_to_string({'aprox',Attr,Value}) -> io_lib:format("(~s~~=~s)",[Attr,Value]); filter_to_string({'let',Attr,Value}) -> io_lib:format("(~s<=~s)",[Attr,Value]); filter_to_string({'get',Attr,Value}) -> io_lib:format("(~s>=~s)",[Attr,Value]); filter_to_string({'eq',Attr,Value}) -> io_lib:format("(~s=~s)",[Attr,Value]). print_substrings(Subs) -> lists:map(fun print_substring/1,Subs). print_substring({initial,V}) -> io_lib:format("~s",[V]); print_substring({final,V}) -> io_lib:format("*~s",[V]); print_substring({any,V}) -> io_lib:format("*~s",[V]). tsung-1.4.2/src/lib/eldap.erl0000644000201100017670000010714111701017117015471 0ustar nniclausdream-module(eldap). %%% -------------------------------------------------------------------- %%% Created: 12 Oct 2000 by Tobbe %%% Function: Erlang client LDAP implementation according RFC 2251,2253 %%% and 2255. The interface is based on RFC 1823, and %%% draft-ietf-asid-ldap-c-api-00.txt %%% -------------------------------------------------------------------- -vc('$Id: eldap.erl,v 1.5 2006/11/24 09:38:11 etnt Exp $ '). -export([open/1,open/2,simple_bind/3,controlling_process/2, baseObject/0,singleLevel/0,wholeSubtree/0,close/1, equalityMatch/2,greaterOrEqual/2,lessOrEqual/2, approxMatch/2,search/2,substrings/2,present/1, 'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2, mod_replace/2, add/3, delete/2, modify_dn/5,parse_dn/1, parse_ldap_url/1]). -import(lists,[concat/1]). -include("ELDAPv3.hrl"). -include("eldap.hrl"). -define(LDAP_VERSION, 3). -define(LDAP_PORT, 389). -define(LDAPS_PORT, 636). -record(eldap, {version = ?LDAP_VERSION, host, % Host running LDAP server port = ?LDAP_PORT, % The LDAP server port fd, % Socket filedescriptor. binddn = "", % Name of the entry to bind as passwd, % Password for (above) entry id = 0, % LDAP Request ID log, % User provided log function timeout = infinity, % Request timeout anon_auth = false, % Allow anonymous authentication use_tls = false % LDAP/LDAPS }). %%% For debug purposes %%-define(PRINT(S, A), io:fwrite("~w(~w): " ++ S, [?MODULE,?LINE|A])). -define(PRINT(S, A), true). -define(elog(S, A), error_logger:info_msg("~w(~w): "++S,[?MODULE,?LINE|A])). %%% ==================================================================== %%% Exported interface %%% ==================================================================== %%% -------------------------------------------------------------------- %%% open(Hosts [,Opts] ) %%% -------------------- %%% Setup a connection to on of the Hosts in the argument %%% list. Stop at the first successful connection attempt. %%% Valid Opts are: Where: %%% %%% {port, Port} - Port is the port number %%% {log, F} - F(LogLevel, FormatString, ListOfArgs) %%% {timeout, milliSec} - request timeout %%% %%% -------------------------------------------------------------------- open(Hosts) -> open(Hosts, []). open(Hosts, Opts) when list(Hosts), list(Opts) -> Self = self(), Pid = spawn_link(fun() -> init(Hosts, Opts, Self) end), recv(Pid). %%% -------------------------------------------------------------------- %%% Shutdown connection (and process) asynchronous. %%% -------------------------------------------------------------------- close(Handle) when pid(Handle) -> send(Handle, close). %%% -------------------------------------------------------------------- %%% Set who we should link ourselves to %%% -------------------------------------------------------------------- controlling_process(Handle, Pid) when pid(Handle),pid(Pid) -> link(Pid), send(Handle, {cnt_proc, Pid}), recv(Handle). %%% -------------------------------------------------------------------- %%% Authenticate ourselves to the Directory %%% using simple authentication. %%% %%% Dn - The name of the entry to bind as %%% Passwd - The password to be used %%% %%% Returns: ok | {error, Error} %%% -------------------------------------------------------------------- simple_bind(Handle, Dn, Passwd) when pid(Handle) -> send(Handle, {simple_bind, Dn, Passwd}), recv(Handle). %%% -------------------------------------------------------------------- %%% Add an entry. The entry field MUST NOT exist for the AddRequest %%% to succeed. The parent of the entry MUST exist. %%% Example: %%% %%% add(Handle, %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", %%% [{"objectclass", ["person"]}, %%% {"cn", ["Bill Valentine"]}, %%% {"sn", ["Valentine"]}, %%% {"telephoneNumber", ["545 555 00"]}] %%% ) %%% -------------------------------------------------------------------- add(Handle, Entry, Attributes) when pid(Handle),list(Entry),list(Attributes) -> send(Handle, {add, Entry, add_attrs(Attributes)}), recv(Handle). %%% Do sanity check ! add_attrs(Attrs) -> F = fun({Type,Vals}) when list(Type),list(Vals) -> %% Confused ? Me too... :-/ {'AddRequest_attributes',Type, Vals} end, case catch lists:map(F, Attrs) of {'EXIT', _} -> throw({error, attribute_values}); Else -> Else end. %%% -------------------------------------------------------------------- %%% Delete an entry. The entry consists of the DN of %%% the entry to be deleted. %%% Example: %%% %%% delete(Handle, %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com" %%% ) %%% -------------------------------------------------------------------- delete(Handle, Entry) when pid(Handle), list(Entry) -> send(Handle, {delete, Entry}), recv(Handle). %%% -------------------------------------------------------------------- %%% Modify an entry. Given an entry a number of modification %%% operations can be performed as one atomic operation. %%% Example: %%% %%% modify(Handle, %%% "cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com", %%% [replace("telephoneNumber", ["555 555 00"]), %%% add("description", ["LDAP hacker"])] %%% ) %%% -------------------------------------------------------------------- modify(Handle, Object, Mods) when pid(Handle), list(Object), list(Mods) -> send(Handle, {modify, Object, Mods}), recv(Handle). %%% %%% Modification operations. %%% Example: %%% replace("telephoneNumber", ["555 555 00"]) %%% mod_add(Type, Values) when list(Type), list(Values) -> m(add, Type, Values). mod_delete(Type, Values) when list(Type), list(Values) -> m(delete, Type, Values). mod_replace(Type, Values) when list(Type), list(Values) -> m(replace, Type, Values). m(Operation, Type, Values) -> #'ModifyRequest_modification_SEQOF'{ operation = Operation, modification = #'AttributeTypeAndValues'{ type = Type, vals = Values}}. %%% -------------------------------------------------------------------- %%% Modify an entry. Given an entry a number of modification %%% operations can be performed as one atomic operation. %%% Example: %%% %%% modify_dn(Handle, %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", %%% "cn=Ben Emerson", %%% true, %%% "" %%% ) %%% -------------------------------------------------------------------- modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) when pid(Handle),list(Entry),list(NewRDN),atom(DelOldRDN),list(NewSup) -> send(Handle, {modify_dn, Entry, NewRDN, bool_p(DelOldRDN), optional(NewSup)}), recv(Handle). %%% Sanity checks ! bool_p(Bool) when Bool==true;Bool==false -> Bool. optional([]) -> asn1_NOVALUE; optional(Value) -> Value. %%% -------------------------------------------------------------------- %%% Synchronous search of the Directory returning a %%% requested set of attributes. %%% %%% Example: %%% %%% Filter = eldap:substrings("sn", [{any,"o"}]), %%% eldap:search(S, [{base, "dc=bluetail, dc=com"}, %%% {filter, Filter}, %%% {attributes,["cn"]}])), %%% %%% Returned result: {ok, #eldap_search_result{}} %%% %%% Example: %%% %%% {ok,{eldap_search_result, %%% [{eldap_entry, %%% "cn=Magnus Froberg, dc=bluetail, dc=com", %%% [{"cn",["Magnus Froberg"]}]}, %%% {eldap_entry, %%% "cn=Torbjorn Tornkvist, dc=bluetail, dc=com", %%% [{"cn",["Torbjorn Tornkvist"]}]}], %%% []}} %%% %%% -------------------------------------------------------------------- search(Handle, A) when pid(Handle), record(A, eldap_search) -> call_search(Handle, A); search(Handle, L) when pid(Handle), list(L) -> case catch parse_search_args(L) of {error, Emsg} -> {error, Emsg}; A when record(A, eldap_search) -> call_search(Handle, A) end. call_search(Handle, A) -> send(Handle, {search, A}), recv(Handle). parse_search_args(Args) -> parse_search_args(Args, #eldap_search{scope = wholeSubtree}). parse_search_args([{base, Base}|T],A) -> parse_search_args(T,A#eldap_search{base = Base}); parse_search_args([{filter, Filter}|T],A) -> parse_search_args(T,A#eldap_search{filter = Filter}); parse_search_args([{scope, Scope}|T],A) -> parse_search_args(T,A#eldap_search{scope = Scope}); parse_search_args([{attributes, Attrs}|T],A) -> parse_search_args(T,A#eldap_search{attributes = Attrs}); parse_search_args([{types_only, TypesOnly}|T],A) -> parse_search_args(T,A#eldap_search{types_only = TypesOnly}); parse_search_args([{timeout, Timeout}|T],A) when integer(Timeout) -> parse_search_args(T,A#eldap_search{timeout = Timeout}); parse_search_args([H|_],_) -> throw({error,{unknown_arg, H}}); parse_search_args([],A) -> A. %%% %%% The Scope parameter %%% baseObject() -> baseObject. singleLevel() -> singleLevel. wholeSubtree() -> wholeSubtree. %%% %%% Boolean filter operations %%% 'and'(ListOfFilters) when list(ListOfFilters) -> {'and',ListOfFilters}. 'or'(ListOfFilters) when list(ListOfFilters) -> {'or', ListOfFilters}. 'not'(Filter) when tuple(Filter) -> {'not',Filter}. %%% %%% The following Filter parameters consist of an attribute %%% and an attribute value. Example: F("uid","tobbe") %%% equalityMatch(Desc, Value) -> {equalityMatch, av_assert(Desc, Value)}. greaterOrEqual(Desc, Value) -> {greaterOrEqual, av_assert(Desc, Value)}. lessOrEqual(Desc, Value) -> {lessOrEqual, av_assert(Desc, Value)}. approxMatch(Desc, Value) -> {approxMatch, av_assert(Desc, Value)}. av_assert(Desc, Value) -> #'AttributeValueAssertion'{attributeDesc = Desc, assertionValue = Value}. %%% %%% Filter to check for the presence of an attribute %%% present(Attribute) when list(Attribute) -> {present, Attribute}. %%% %%% A substring filter seem to be based on a pattern: %%% %%% InitValue*AnyValue*FinalValue %%% %%% where all three parts seem to be optional (at least when %%% talking with an OpenLDAP server). Thus, the arguments %%% to substrings/2 looks like this: %%% %%% Type ::= string( ) %%% SubStr ::= listof( {initial,Value} | {any,Value}, {final,Value}) %%% %%% Example: substrings("sn",[{initial,"To"},{any,"kv"},{final,"st"}]) %%% will match entries containing: 'sn: Tornkvist' %%% substrings(Type, SubStr) when list(Type), list(SubStr) -> Ss = {'SubstringFilter_substrings',v_substr(SubStr)}, {substrings,#'SubstringFilter'{type = Type, substrings = Ss}}. %%% -------------------------------------------------------------------- %%% Worker process. We keep track of a controlling process to %%% be able to terminate together with it. %%% -------------------------------------------------------------------- init(Hosts, Opts, Cpid) -> Data = parse_args(Opts, Cpid, #eldap{}), case try_connect(Hosts, Data) of {ok,Data2} -> send(Cpid, {ok,self()}), put(req_timeout, Data#eldap.timeout), % kludge... loop(Cpid, Data2); Else -> send(Cpid, Else), unlink(Cpid), exit(Else) end. parse_args([{port, Port}|T], Cpid, Data) when integer(Port) -> parse_args(T, Cpid, Data#eldap{port = Port}); parse_args([{timeout, Timeout}|T], Cpid, Data) when integer(Timeout),Timeout>0 -> parse_args(T, Cpid, Data#eldap{timeout = Timeout}); parse_args([{anon_auth, true}|T], Cpid, Data) -> parse_args(T, Cpid, Data#eldap{anon_auth = false}); parse_args([{anon_auth, _}|T], Cpid, Data) -> parse_args(T, Cpid, Data); parse_args([{ssl, true}|T], Cpid, Data) -> parse_args(T, Cpid, Data#eldap{use_tls = true}); parse_args([{ssl, _}|T], Cpid, Data) -> parse_args(T, Cpid, Data); parse_args([{log, F}|T], Cpid, Data) when function(F) -> parse_args(T, Cpid, Data#eldap{log = F}); parse_args([{log, _}|T], Cpid, Data) -> parse_args(T, Cpid, Data); parse_args([H|_], Cpid, _) -> send(Cpid, {error,{wrong_option,H}}), exit(wrong_option); parse_args([], _, Data) -> Data. %%% Try to connect to the hosts in the listed order, %%% and stop with the first one to which a successful %%% connection is made. try_connect([Host|Hosts], Data) -> TcpOpts = [{packet, asn1}, {active,false}], case do_connect(Host, Data, TcpOpts) of {ok,Fd} -> {ok,Data#eldap{host = Host, fd = Fd}}; _ -> try_connect(Hosts, Data) end; try_connect([],_) -> {error,"connect failed"}. do_connect(Host, Data, Opts) when Data#eldap.use_tls == false -> gen_tcp:connect(Host, Data#eldap.port, Opts, Data#eldap.timeout); do_connect(Host, Data, Opts) when Data#eldap.use_tls == true -> Vsn = erlang:system_info(version), if Vsn >= "5.3" -> %% In R9C, but not in R9B {_,_,X} = erlang:now(), ssl:seed("bkrlnateqqo" ++ integer_to_list(X)); true -> true end, ssl:connect(Host, Data#eldap.port, [{verify,0}|Opts]). loop(Cpid, Data) -> receive {From, {search, A}} -> {Res,NewData} = do_search(Data, A), send(From,Res), loop(Cpid, NewData); {From, {modify, Obj, Mod}} -> {Res,NewData} = do_modify(Data, Obj, Mod), send(From,Res), loop(Cpid, NewData); {From, {modify_dn, Obj, NewRDN, DelOldRDN, NewSup}} -> {Res,NewData} = do_modify_dn(Data, Obj, NewRDN, DelOldRDN, NewSup), send(From,Res), loop(Cpid, NewData); {From, {add, Entry, Attrs}} -> {Res,NewData} = do_add(Data, Entry, Attrs), send(From,Res), loop(Cpid, NewData); {From, {delete, Entry}} -> {Res,NewData} = do_delete(Data, Entry), send(From,Res), loop(Cpid, NewData); {From, {simple_bind, Dn, Passwd}} -> {Res,NewData} = do_simple_bind(Data, Dn, Passwd), send(From,Res), loop(Cpid, NewData); {From, {cnt_proc, NewCpid}} -> unlink(Cpid), send(From,ok), ?PRINT("New Cpid is: ~p~n",[NewCpid]), loop(NewCpid, Data); {From, close} -> unlink(Cpid), exit(closed); {Cpid, 'EXIT', Reason} -> ?PRINT("Got EXIT from Cpid, reason=~p~n",[Reason]), exit(Reason); _XX -> ?PRINT("loop got: ~p~n",[_XX]), loop(Cpid, Data) end. %%% -------------------------------------------------------------------- %%% bindRequest %%% -------------------------------------------------------------------- %%% Authenticate ourselves to the directory using %%% simple authentication. do_simple_bind(Data, anon, anon) -> %% For testing do_the_simple_bind(Data, "", ""); do_simple_bind(Data, Dn, _Passwd) when Dn=="",Data#eldap.anon_auth==false -> {{error,anonymous_auth},Data}; do_simple_bind(Data, _Dn, Passwd) when Passwd=="",Data#eldap.anon_auth==false -> {{error,anonymous_auth},Data}; do_simple_bind(Data, Dn, Passwd) -> do_the_simple_bind(Data, Dn, Passwd). do_the_simple_bind(Data, Dn, Passwd) -> case catch exec_simple_bind(Data#eldap{binddn = Dn, passwd = Passwd, id = bump_id(Data)}) of {ok,NewData} -> {ok,NewData}; {error,Emsg} -> {{error,Emsg},Data}; Else -> {{error,Else},Data} end. exec_simple_bind(Data) -> Req = #'BindRequest'{version = Data#eldap.version, name = Data#eldap.binddn, authentication = {simple, Data#eldap.passwd}}, log2(Data, "bind request = ~p~n", [Req]), Reply = request(Data#eldap.fd, Data, Data#eldap.id, {bindRequest, Req}), log2(Data, "bind reply = ~p~n", [Reply]), exec_simple_bind_reply(Data, Reply). exec_simple_bind_reply(Data, {ok,Msg}) when Msg#'LDAPMessage'.messageID == Data#eldap.id -> case Msg#'LDAPMessage'.protocolOp of {bindResponse, Result} -> case Result#'BindResponse'.resultCode of success -> {ok,Data}; Error -> {error, Error} end; Other -> {error, Other} end; exec_simple_bind_reply(_, Error) -> {error, Error}. %%% -------------------------------------------------------------------- %%% searchRequest %%% -------------------------------------------------------------------- do_search(Data, A) -> case catch do_search_0(Data, A) of {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; {ok,Res,Ref,NewData} -> {{ok,polish(Res, Ref)},NewData}; Else -> {ldap_closed_p(Data, Else),Data} end. %%% %%% Polish the returned search result %%% polish(Res, Ref) -> R = polish_result(Res), %%% No special treatment of referrals at the moment. #eldap_search_result{entries = R, referrals = Ref}. polish_result([H|T]) when record(H, 'SearchResultEntry') -> ObjectName = H#'SearchResultEntry'.objectName, F = fun({_,A,V}) -> {A,V} end, Attrs = lists:map(F, H#'SearchResultEntry'.attributes), [#eldap_entry{object_name = ObjectName, attributes = Attrs}| polish_result(T)]; polish_result([]) -> []. do_search_0(Data, A) -> Req = #'SearchRequest'{baseObject = A#eldap_search.base, scope = v_scope(A#eldap_search.scope), derefAliases = neverDerefAliases, sizeLimit = 0, % no size limit timeLimit = v_timeout(A#eldap_search.timeout), typesOnly = v_bool(A#eldap_search.types_only), filter = v_filter(A#eldap_search.filter), attributes = v_attributes(A#eldap_search.attributes) }, Id = bump_id(Data), collect_search_responses(Data#eldap{id=Id}, Req, Id). %%% The returned answers cames in one packet per entry %%% mixed with possible referals collect_search_responses(Data, Req, ID) -> S = Data#eldap.fd, log2(Data, "search request = ~p~n", [Req]), send_request(S, Data, ID, {searchRequest, Req}), Resp = recv_response(S, Data), log2(Data, "search reply = ~p~n", [Resp]), collect_search_responses(Data, S, ID, Resp, [], []). collect_search_responses(Data, S, ID, {ok,Msg}, Acc, Ref) when record(Msg,'LDAPMessage') -> case Msg#'LDAPMessage'.protocolOp of {'searchResDone',R} when R#'LDAPResult'.resultCode == success -> log2(Data, "search reply = searchResDone ~n", []), {ok,Acc,Ref,Data}; {'searchResEntry',R} when record(R,'SearchResultEntry') -> Resp = recv_response(S, Data), log2(Data, "search reply = ~p~n", [Resp]), collect_search_responses(Data, S, ID, Resp, [R|Acc], Ref); {'searchResRef',R} -> %% At the moment we don't do anyting sensible here since %% I haven't been able to trigger the server to generate %% a response like this. Resp = recv_response(S, Data), log2(Data, "search reply = ~p~n", [Resp]), collect_search_responses(Data, S, ID, Resp, Acc, [R|Ref]); Else -> throw({error,Else}) end; collect_search_responses(_, _, _, Else, _, _) -> throw({error,Else}). %%% -------------------------------------------------------------------- %%% addRequest %%% -------------------------------------------------------------------- do_add(Data, Entry, Attrs) -> case catch do_add_0(Data, Entry, Attrs) of {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; {ok,NewData} -> {ok,NewData}; Else -> {ldap_closed_p(Data, Else),Data} end. do_add_0(Data, Entry, Attrs) -> Req = #'AddRequest'{entry = Entry, attributes = Attrs}, S = Data#eldap.fd, Id = bump_id(Data), log2(Data, "add request = ~p~n", [Req]), Resp = request(S, Data, Id, {addRequest, Req}), log2(Data, "add reply = ~p~n", [Resp]), check_reply(Data#eldap{id = Id}, Resp, addResponse). %%% -------------------------------------------------------------------- %%% deleteRequest %%% -------------------------------------------------------------------- do_delete(Data, Entry) -> case catch do_delete_0(Data, Entry) of {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; {ok,NewData} -> {ok,NewData}; Else -> {ldap_closed_p(Data, Else),Data} end. do_delete_0(Data, Entry) -> S = Data#eldap.fd, Id = bump_id(Data), log2(Data, "del request = ~p~n", [Entry]), Resp = request(S, Data, Id, {delRequest, Entry}), log2(Data, "del reply = ~p~n", [Resp]), check_reply(Data#eldap{id = Id}, Resp, delResponse). %%% -------------------------------------------------------------------- %%% modifyRequest %%% -------------------------------------------------------------------- do_modify(Data, Obj, Mod) -> case catch do_modify_0(Data, Obj, Mod) of {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; {ok,NewData} -> {ok,NewData}; Else -> {ldap_closed_p(Data, Else),Data} end. do_modify_0(Data, Obj, Mod) -> v_modifications(Mod), Req = #'ModifyRequest'{object = Obj, modification = Mod}, S = Data#eldap.fd, Id = bump_id(Data), log2(Data, "modify request = ~p~n", [Req]), Resp = request(S, Data, Id, {modifyRequest, Req}), log2(Data, "modify reply = ~p~n", [Resp]), check_reply(Data#eldap{id = Id}, Resp, modifyResponse). %%% -------------------------------------------------------------------- %%% modifyDNRequest %%% -------------------------------------------------------------------- do_modify_dn(Data, Entry, NewRDN, DelOldRDN, NewSup) -> case catch do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup) of {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; {ok,NewData} -> {ok,NewData}; Else -> {ldap_closed_p(Data, Else),Data} end. do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup) -> Req = #'ModifyDNRequest'{entry = Entry, newrdn = NewRDN, deleteoldrdn = DelOldRDN, newSuperior = NewSup}, S = Data#eldap.fd, Id = bump_id(Data), log2(Data, "modify DN request = ~p~n", [Req]), Resp = request(S, Data, Id, {modDNRequest, Req}), log2(Data, "modify DN reply = ~p~n", [Resp]), check_reply(Data#eldap{id = Id}, Resp, modDNResponse). %%% -------------------------------------------------------------------- %%% Send an LDAP request and receive the answer %%% -------------------------------------------------------------------- request(S, Data, ID, Request) -> send_request(S, Data, ID, Request), recv_response(S, Data). send_request(S, Data, ID, Request) -> Message = #'LDAPMessage'{messageID = ID, protocolOp = Request}, {ok,Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), case do_send(S, Data, Bytes) of {error,Reason} -> throw({gen_tcp_error,Reason}); Else -> Else end. do_send(S, Data, Bytes) when Data#eldap.use_tls == false -> gen_tcp:send(S, Bytes); do_send(S, Data, Bytes) when Data#eldap.use_tls == true -> ssl:send(S, Bytes). do_recv(S, Data, Len, Timeout) when Data#eldap.use_tls == false -> gen_tcp:recv(S, Len, Timeout); do_recv(S, Data, Len, Timeout) when Data#eldap.use_tls == true -> ssl:recv(S, Len, Timeout). recv_response(S, Data) -> Timeout = get(req_timeout), % kludge... case do_recv(S, Data, 0, Timeout) of {ok, Packet} -> check_tag(Packet), case asn1rt:decode('ELDAPv3', 'LDAPMessage', Packet) of {ok,Resp} -> {ok,Resp}; Error -> throw(Error) end; {error,Reason} -> throw({gen_tcp_error, Reason}); Error -> throw(Error) end. %%% Sanity check of received packet check_tag(Data) -> case asn1rt_ber_bin:decode_tag(b2l(Data)) of {_Tag, Data1, _Rb} -> case asn1rt_ber_bin:decode_length(b2l(Data1)) of {{_Len, _Data2}, _Rb2} -> ok; _ -> throw({error,decoded_tag_length}) end; _ -> throw({error,decoded_tag}) end. %%% Check for expected kind of reply check_reply(Data, {ok,Msg}, Op) when Msg#'LDAPMessage'.messageID == Data#eldap.id -> case Msg#'LDAPMessage'.protocolOp of {Op, Result} -> case Result#'LDAPResult'.resultCode of success -> {ok,Data}; Error -> {error, Error} end; Other -> {error, Other} end; check_reply(_, Error, _) -> {error, Error}. %%% -------------------------------------------------------------------- %%% Verify the input data %%% -------------------------------------------------------------------- v_filter({'and',L}) -> {'and',L}; v_filter({'or', L}) -> {'or',L}; v_filter({'not',L}) -> {'not',L}; v_filter({equalityMatch,AV}) -> {equalityMatch,AV}; v_filter({greaterOrEqual,AV}) -> {greaterOrEqual,AV}; v_filter({lessOrEqual,AV}) -> {lessOrEqual,AV}; v_filter({approxMatch,AV}) -> {approxMatch,AV}; v_filter({present,A}) -> {present,A}; v_filter({substrings,S}) when record(S,'SubstringFilter') -> {substrings,S}; v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}). v_modifications(Mods) -> F = fun({_,Op,_}) -> case lists:member(Op,[add,delete,replace]) of true -> true; _ -> throw({error,{mod_operation,Op}}) end end, lists:foreach(F, Mods). v_substr([{Key,Str}|T]) when list(Str),Key==initial;Key==any;Key==final -> [{Key,Str}|v_substr(T)]; v_substr([H|_]) -> throw({error,{substring_arg,H}}); v_substr([]) -> []. v_scope(baseObject) -> baseObject; v_scope(singleLevel) -> singleLevel; v_scope(wholeSubtree) -> wholeSubtree; v_scope(_Scope) -> throw({error,concat(["unknown scope: ",_Scope])}). v_bool(true) -> true; v_bool(false) -> false; v_bool(_Bool) -> throw({error,concat(["not Boolean: ",_Bool])}). v_timeout(I) when integer(I), I>=0 -> I; v_timeout(_I) -> throw({error,concat(["timeout not positive integer: ",_I])}). v_attributes(Attrs) -> F = fun(A) when list(A) -> A; (A) -> throw({error,concat(["attribute not String: ",A])}) end, lists:map(F,Attrs). %%% -------------------------------------------------------------------- %%% Log routines. Call a user provided log routine F. %%% -------------------------------------------------------------------- log1(Data, Str, Args) -> log(Data, Str, Args, 1). log2(Data, Str, Args) -> log(Data, Str, Args, 2). log(Data, Str, Args, Level) when function(Data#eldap.log) -> catch (Data#eldap.log)(Level, Str, Args); log(_, _, _, _) -> ok. %%% -------------------------------------------------------------------- %%% Misc. routines %%% -------------------------------------------------------------------- send(To,Msg) -> To ! {self(),Msg}. recv(From) -> receive {From,Msg} -> Msg end. ldap_closed_p(Data, Emsg) when Data#eldap.use_tls == true -> %% Check if the SSL socket seems to be alive or not case catch ssl:sockname(Data#eldap.fd) of {error, _} -> ssl:close(Data#eldap.fd), {error, ldap_closed}; {ok, _} -> {error, Emsg}; _ -> %% sockname crashes if the socket pid is not alive {error, ldap_closed} end; ldap_closed_p(Data, Emsg) -> %% non-SSL socket case inet:port(Data#eldap.fd) of {error,_} -> {error, ldap_closed}; _ -> {error,Emsg} end. bump_id(Data) -> Data#eldap.id + 1. %%% -------------------------------------------------------------------- %%% parse_dn/1 - Implementation of RFC 2253: %%% %%% "UTF-8 String Representation of Distinguished Names" %%% %%% Test cases: %%% %%% The simplest case: %%% %%% 1> eldap:parse_dn("CN=Steve Kille,O=Isode Limited,C=GB"). %%% {ok,[[{attribute_type_and_value,"CN","Steve Kille"}], %%% [{attribute_type_and_value,"O","Isode Limited"}], %%% [{attribute_type_and_value,"C","GB"}]]} %%% %%% The first RDN is multi-valued: %%% %%% 2> eldap:parse_dn("OU=Sales+CN=J. Smith,O=Widget Inc.,C=US"). %%% {ok,[[{attribute_type_and_value,"OU","Sales"}, %%% {attribute_type_and_value,"CN","J. Smith"}], %%% [{attribute_type_and_value,"O","Widget Inc."}], %%% [{attribute_type_and_value,"C","US"}]]} %%% %%% Quoting a comma: %%% %%% 3> eldap:parse_dn("CN=L. Eagle,O=Sue\\, Grabbit and Runn,C=GB"). %%% {ok,[[{attribute_type_and_value,"CN","L. Eagle"}], %%% [{attribute_type_and_value,"O","Sue\\, Grabbit and Runn"}], %%% [{attribute_type_and_value,"C","GB"}]]} %%% %%% A value contains a carriage return: %%% %%% 4> eldap:parse_dn("CN=Before %%% 4> After,O=Test,C=GB"). %%% {ok,[[{attribute_type_and_value,"CN","Before\nAfter"}], %%% [{attribute_type_and_value,"O","Test"}], %%% [{attribute_type_and_value,"C","GB"}]]} %%% %%% 5> eldap:parse_dn("CN=Before\\0DAfter,O=Test,C=GB"). %%% {ok,[[{attribute_type_and_value,"CN","Before\\0DAfter"}], %%% [{attribute_type_and_value,"O","Test"}], %%% [{attribute_type_and_value,"C","GB"}]]} %%% %%% An RDN in OID form: %%% %%% 6> eldap:parse_dn("1.3.6.1.4.1.1466.0=#04024869,O=Test,C=GB"). %%% {ok,[[{attribute_type_and_value,"1.3.6.1.4.1.1466.0","#04024869"}], %%% [{attribute_type_and_value,"O","Test"}], %%% [{attribute_type_and_value,"C","GB"}]]} %%% %%% %%% -------------------------------------------------------------------- parse_dn("") -> % empty DN string {ok,[]}; parse_dn([H|_] = Str) when H=/=$, -> % 1:st name-component ! case catch parse_name(Str,[]) of {'EXIT',Reason} -> {parse_error,internal_error,Reason}; Else -> Else end. parse_name("",Acc) -> {ok,lists:reverse(Acc)}; parse_name([$,|T],Acc) -> % N:th name-component ! parse_name(T,Acc); parse_name(Str,Acc) -> {Rest,NameComponent} = parse_name_component(Str), parse_name(Rest,[NameComponent|Acc]). parse_name_component(Str) -> parse_name_component(Str,[]). parse_name_component(Str,Acc) -> case parse_attribute_type_and_value(Str) of {[$+|Rest], ATV} -> parse_name_component(Rest,[ATV|Acc]); {Rest,ATV} -> {Rest,lists:reverse([ATV|Acc])} end. parse_attribute_type_and_value(Str) -> case parse_attribute_type(Str) of {Rest,[]} -> error(expecting_attribute_type,Str); {Rest,Type} -> Rest2 = parse_equal_sign(Rest), {Rest3,Value} = parse_attribute_value(Rest2), {Rest3,{attribute_type_and_value,Type,Value}} end. -define(IS_ALPHA(X) , X>=$a,X=<$z;X>=$A,X=<$Z ). -define(IS_DIGIT(X) , X>=$0,X=<$9 ). -define(IS_SPECIAL(X) , X==$,;X==$=;X==$+;X==$<;X==$>;X==$#;X==$; ). -define(IS_QUOTECHAR(X) , X=/=$\\,X=/=$" ). -define(IS_STRINGCHAR(X) , X=/=$,,X=/=$=,X=/=$+,X=/=$<,X=/=$>,X=/=$#,X=/=$;,?IS_QUOTECHAR(X) ). -define(IS_HEXCHAR(X) , ?IS_DIGIT(X);X>=$a,X=<$f;X>=$A,X=<$F ). parse_attribute_type([H|T]) when ?IS_ALPHA(H) -> %% NB: It must be an error in the RFC in the definition %% of 'attributeType', should be: (ALPHA *keychar) {Rest,KeyChars} = parse_keychars(T), {Rest,[H|KeyChars]}; parse_attribute_type([H|_] = Str) when ?IS_DIGIT(H) -> parse_oid(Str); parse_attribute_type(Str) -> error(invalid_attribute_type,Str). %%% Is a hexstring ! parse_attribute_value([$#,X,Y|T]) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> {Rest,HexString} = parse_hexstring(T), {Rest,[$#,X,Y|HexString]}; %%% Is a "quotation-sequence" ! parse_attribute_value([$"|T]) -> {Rest,Quotation} = parse_quotation(T), {Rest,[$"|Quotation]}; %%% Is a stringchar , pair or Empty ! parse_attribute_value(Str) -> parse_string(Str). parse_hexstring(Str) -> parse_hexstring(Str,[]). parse_hexstring([X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> parse_hexstring(T,[Y,X|Acc]); parse_hexstring(T,Acc) -> {T,lists:reverse(Acc)}. parse_quotation([$"|T]) -> % an empty: "" is ok ! {T,[$"]}; parse_quotation(Str) -> parse_quotation(Str,[]). %%% Parse to end of quotation parse_quotation([$"|T],Acc) -> {T,lists:reverse([$"|Acc])}; parse_quotation([X|T],Acc) when ?IS_QUOTECHAR(X) -> parse_quotation(T,[X|Acc]); parse_quotation([$\\,X|T],Acc) when ?IS_SPECIAL(X) -> parse_quotation(T,[X,$\\|Acc]); parse_quotation([$\\,$\\|T],Acc) -> parse_quotation(T,[$\\,$\\|Acc]); parse_quotation([$\\,$"|T],Acc) -> parse_quotation(T,[$",$\\|Acc]); parse_quotation([$\\,X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> parse_quotation(T,[Y,X,$\\|Acc]); parse_quotation(T,_) -> error(expecting_double_quote_mark,T). parse_string(Str) -> parse_string(Str,[]). parse_string("",Acc) -> {"",lists:reverse(Acc)}; parse_string([H|T],Acc) when ?IS_STRINGCHAR(H) -> parse_string(T,[H|Acc]); parse_string([$\\,X|T],Acc) when ?IS_SPECIAL(X) -> % is a pair ! parse_string(T,[X,$\\|Acc]); parse_string([$\\,$\\|T],Acc) -> % is a pair ! parse_string(T,[$\\,$\\|Acc]); parse_string([$\\,$" |T],Acc) -> % is a pair ! parse_string(T,[$" ,$\\|Acc]); parse_string([$\\,X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> % is a pair! parse_string(T,[Y,X,$\\|Acc]); parse_string(T,Acc) -> {T,lists:reverse(Acc)}. parse_equal_sign([$=|T]) -> T; parse_equal_sign(T) -> error(expecting_equal_sign,T). parse_keychars(Str) -> parse_keychars(Str,[]). parse_keychars([H|T],Acc) when ?IS_ALPHA(H) -> parse_keychars(T,[H|Acc]); parse_keychars([H|T],Acc) when ?IS_DIGIT(H) -> parse_keychars(T,[H|Acc]); parse_keychars([$-|T],Acc) -> parse_keychars(T,[$-|Acc]); parse_keychars(T,Acc) -> {T,lists:reverse(Acc)}. parse_oid(Str) -> parse_oid(Str,[]). parse_oid([H,$.|T], Acc) when ?IS_DIGIT(H) -> parse_oid(T,[$.,H|Acc]); parse_oid([H|T], Acc) when ?IS_DIGIT(H) -> parse_oid(T,[H|Acc]); parse_oid(T, Acc) -> {T,lists:reverse(Acc)}. error(Emsg,Rest) -> throw({parse_error,Emsg,Rest}). %%% -------------------------------------------------------------------- %%% Parse LDAP url according to RFC 2255 %%% %%% Test case: %%% %%% 2> eldap:parse_ldap_url("ldap://10.42.126.33:389/cn=Administrative%20CA,o=Post%20Danmark,c=DK?certificateRevokationList;binary"). %%% {ok,{{10,42,126,33},389}, %%% [[{attribute_type_and_value,"cn","Administrative%20CA"}], %%% [{attribute_type_and_value,"o","Post%20Danmark"}], %%% [{attribute_type_and_value,"c","DK"}]], %%% {attributes,["certificateRevokationList;binary"]}} %%% %%% -------------------------------------------------------------------- parse_ldap_url("ldap://" ++ Rest1 = Str) -> {Rest2,HostPort} = parse_hostport(Rest1), %% Split the string into DN and Attributes+etc {Sdn,Rest3} = split_string(rm_leading_slash(Rest2),$?), case parse_dn(Sdn) of {parse_error,internal_error,_Reason} -> {parse_error,internal_error,{Str,[]}}; {parse_error,Emsg,Tail} -> Head = get_head(Str,Tail), {parse_error,Emsg,{Head,Tail}}; {ok,DN} -> %% We stop parsing here for now and leave %% 'scope', 'filter' and 'extensions' to %% be implemented later if needed. {_Rest4,Attributes} = parse_attributes(Rest3), {ok,HostPort,DN,Attributes} end. rm_leading_slash([$/|Tail]) -> Tail; rm_leading_slash(Tail) -> Tail. parse_attributes([$?|Tail]) -> case split_string(Tail,$?) of {[],Attributes} -> {[],{attributes,string:tokens(Attributes,",")}}; {Attributes,Rest} -> {Rest,{attributes,string:tokens(Attributes,",")}} end. parse_hostport(Str) -> {HostPort,Rest} = split_string(Str,$/), case split_string(HostPort,$:) of {Shost,[]} -> {Rest,{parse_host(Rest,Shost),?LDAP_PORT}}; {Shost,[$:|Sport]} -> {Rest,{parse_host(Rest,Shost), parse_port(Rest,Sport)}} end. parse_port(Rest,Sport) -> case list_to_integer(Sport) of Port when integer(Port) -> Port; _ -> error(parsing_port,Rest) end. parse_host(Rest,Shost) -> case catch validate_host(Shost) of {parse_error,Emsg,_} -> error(Emsg,Rest); Host -> Host end. validate_host(Shost) -> case inet_parse:address(Shost) of {ok,Host} -> Host; _ -> case inet_parse:domain(Shost) of true -> Shost; _ -> error(parsing_host,Shost) end end. split_string(Str,Key) -> Pred = fun(X) when X==Key -> false; (_) -> true end, lists:splitwith(Pred, Str). get_head(Str,Tail) -> get_head(Str,Tail,[]). %%% Should always succeed ! get_head([H|Tail],Tail,Rhead) -> lists:reverse([H|Rhead]); get_head([H|Rest],Tail,Rhead) -> get_head(Rest,Tail,[H|Rhead]). b2l(B) when binary(B) -> B; b2l(L) when list(L) -> list_to_binary(L). tsung-1.4.2/src/lib/mochiweb_xpath.erl0000644000201100017670000001722111701017117017404 0ustar nniclausdream%% mochiweb_html_xpath.erl %% @author Pablo Polvorin %% created on <2008-04-29> %% %% XPath interpreter, navigate mochiweb's html structs %% Only a subset of xpath is implemented, see what is supported in test.erl -module(mochiweb_xpath). -export([execute/2,execute/3,compile_xpath/1]). %internal data -record(ctx, { root, ctx, functions }). %% @spec( string() ) -> compiled_xpath() compile_xpath(Expr) -> mochiweb_xpath_parser:compile_xpath(Expr). %% @doc Execute the given XPath expression against the given document, using %% the default set of functions. %% @spec execute(XPath,Doc) -> Results %% @type XPath = compiled_xpath() | string() %% @type Doc = node() %% @type Results = [node()] | binary() | boolean() | number() execute(XPathString,Doc) when is_list(XPathString) -> XPath = mochiweb_xpath_parser:compile_xpath(XPathString), execute(XPath,Doc); execute(XPath,Root) -> execute(XPath,Root,[]). %% @doc Execute the given XPath expression against the given document, %% using the default set of functions plus the user-supplied ones. %% %% @see mochiweb_xpath_functions.erl to see how to write functions %% %% @spec execute(XPath,Doc,Functions) -> Results %% @type XPath = compiled_xpath() | string() %% @type Doc = node() %% @type Functions = [FunctionDefinition] %% @type FunctionDefinition = {FunName,Fun,Signature} %% @type FunName = atom() %% @type Fun = fun/2 %% @type Signature = [ArgType] %% @type ArgType = node_set | string | number | boolean %% @type Results = [node()] | binary() | boolean() | number() %% TODO: should pass the user-defined functions when compiling %% the xpath expression (compile_xpath/1). Then the %% compiled expression would have all its functions %% resolved, and no function lookup would occur when %% the expression is executed execute(XPathString,Doc,Functions) when is_list(XPathString) -> XPath = mochiweb_xpath_parser:compile_xpath(XPathString), execute(XPath,Doc,Functions); execute(XPath,Doc,Functions) -> R = {root,none,[Doc]}, Funs = lists:foldl(fun(T={Key,_Fun,_Signature},Prev) -> lists:keystore(Key,1,Prev,T) end,mochiweb_xpath_functions:default_functions(),Functions), execute_expr(XPath,#ctx{ctx=[R],root=R,functions=Funs}). execute_expr({path,'abs',Path},Ctx =#ctx{root=Root}) -> do_path_expr(Path,Ctx#ctx{ctx=[Root]}); execute_expr({path,'rel',Path},Ctx) -> do_path_expr(Path,Ctx); execute_expr({comp,Comp,A,B},Ctx) -> CompFun = comp_fun(Comp), L = execute_expr(A,Ctx), R = execute_expr(B,Ctx), comp(CompFun,L,R); execute_expr({literal,L},_Ctx) -> [L]; execute_expr({number,N},_Ctx) -> [N]; execute_expr({function_call,Fun,Args},Ctx=#ctx{functions=Funs}) -> RealArgs = lists:map(fun(Arg) -> execute_expr(Arg,Ctx) end,Args), case lists:keysearch(Fun,1,Funs) of {value,{Fun,F,FormalSignature}} -> TypedArgs = lists:map(fun({Type,Arg}) -> mochiweb_xpath_utils:convert(Arg,Type) end,lists:zip(FormalSignature,RealArgs)), F(Ctx,TypedArgs); false -> throw({efun_not_found,Fun}) end. do_path_expr({step,{Axis,NodeTest,Predicates}},Ctx=#ctx{ctx=Context}) -> NewNodeList = axis(Axis,NodeTest,Context), apply_predicates(Predicates,NewNodeList,Ctx); do_path_expr({refine,Step1,Step2},Ctx) -> S1 = do_path_expr(Step1,Ctx), do_path_expr(Step2,Ctx#ctx{ctx=S1}). axis('child',{name,{Tag,_,_}},Context) -> F = fun ({Tag2,_,_}) when Tag2 == Tag -> true; (_) -> false end, N = lists:map(fun ({_,_,Childs}) -> lists:filter(F,Childs) ; (_) -> [] end, Context), lists:flatten(N); axis('child',{node_type,text},Context) -> L = lists:map(fun ({_,_,Childs}) -> case lists:filter(fun is_binary/1,Childs) of [] -> []; T -> list_to_binary(T) end; (_) -> [] end,Context), L; axis('child',{wildcard,wildcard},Context) -> L = lists:map(fun ({_,_,Children})-> Children; (_) -> [] end, Context), lists:flatten(L); axis(attribute,{name,{Attr,_Prefix,_Local}},Context) -> L = lists:foldl(fun ({_,Attrs,_},Acc) -> case proplists:get_value(Attr,Attrs) of undefined -> Acc; V -> [V|Acc] end; (_,Acc) -> Acc end,[],Context), lists:reverse(L); axis('descendant_or_self',{node_type,'node'},Context) -> descendant_or_self(Context); axis('self',{node_type,'node'},Context) -> Context. %%FIXME:The order of the result is wrong, it doesn't return the nodes in %% document order. Actually the problem isn't here, but in %% axis('child',{Tag,_,_},Ctx). We may need to find a better strategy %% to implement the child axis if document order is important descendant_or_self(Ctx) -> L = descendant_or_self(Ctx,[]), lists:reverse(L). descendant_or_self([],Acc) -> Acc; descendant_or_self([E={_,_,Children}|Rest],Acc) -> N = descendant_or_self(Children,[E|Acc]), descendant_or_self(Rest,N); %% text() nodes aren't included descendant_or_self([_|Rest],Acc) -> descendant_or_self(Rest,Acc). apply_predicates(Predicates,NodeList,Ctx) -> lists:foldl(fun(Pred,Nodes) -> apply_predicate(Pred,Nodes,Ctx) end, NodeList,Predicates). % special case: indexing apply_predicate({pred,{number,N}},NodeList,_Ctx) when length(NodeList) >= N -> [lists:nth(N,NodeList)]; apply_predicate({pred,Pred},NodeList,Ctx) -> Filter = fun(Node) -> mochiweb_xpath_utils:boolean_value( execute_expr(Pred,Ctx#ctx{ctx=[Node]})) end, L = lists:filter(Filter,NodeList), L. %% @see http://www.w3.org/TR/1999/REC-xpath-19991116 , section 3.4 comp(CompFun,L,R) when is_list(L), is_list(R) -> lists:any(fun(LeftValue) -> lists:any(fun(RightValue)-> CompFun(LeftValue,RightValue) end, R) end, L); comp(CompFun,L,R) when is_list(L) -> lists:any(fun(LeftValue) -> CompFun(LeftValue,R) end,L); comp(CompFun,L,R) when is_list(R) -> lists:any(fun(RightValue) -> CompFun(L,RightValue) end,R); comp(CompFun,L,R) -> CompFun(L,R). comp_fun('=') -> fun (A,B) when is_number(A) -> A == mochiweb_xpath_utils:number_value(B); (A,B) when is_number(B) -> mochiweb_xpath_utils:number_value(A) == B; (A,B) when is_boolean(A) -> A == mochiweb_xpath_utils:boolean_value(B); (A,B) when is_boolean(B) -> mochiweb_xpath_utils:boolean_value(A) == B; (A,B) -> mochiweb_xpath_utils:string_value(A) == mochiweb_xpath_utils:string_value(B) end; comp_fun('!=') -> fun(A,B) -> F = comp_fun('='), not F(A,B) end; comp_fun('>') -> fun(A,B) -> mochiweb_xpath_utils:number_value(A) > mochiweb_xpath_utils:number_value(B) end; comp_fun('<') -> fun(A,B) -> mochiweb_xpath_utils:number_value(A) < mochiweb_xpath_utils:number_value(B) end; comp_fun('<=') -> fun(A,B) -> mochiweb_xpath_utils:number_value(A) =< mochiweb_xpath_utils:number_value(B) end; comp_fun('>=') -> fun(A,B) -> mochiweb_xpath_utils:number_value(A) >= mochiweb_xpath_utils:number_value(B) end. tsung-1.4.2/src/lib/mochiweb_xpath_parser.erl0000644000201100017670000000340711701017117020761 0ustar nniclausdream%% @author Pablo Polvorin %% @doc Compile XPath expressions. %% This module uses the xpath parser of xmerl.. that interface isn't documented %% so could change between OTP versions.. its know to work with OTP R12B2 %% created on 2008-05-07 -module(mochiweb_xpath_parser). -export([compile_xpath/1]). %% Return a compiled XPath expression compile_xpath(XPathString) -> {ok,XPath} = xmerl_xpath_parse:parse(xmerl_xpath_scan:tokens(XPathString)), simplify(XPath). %% @doc Utility functions to convert between the *internal* representation of % xpath expressions used in xmerl(using lists and atoms), to a % representation using only binaries, to match the way in % which the mochiweb html parser represents data simplify({path,Type,Path}) -> {path,Type,simplify_path(Path)}; simplify({comp,Comp,A,B}) -> {comp,Comp,simplify(A),simplify(B)}; simplify({literal,L}) -> {literal,list_to_binary(L)}; simplify({number,N}) -> {number,N}; simplify({function_call,Fun,Args}) -> {function_call,Fun,lists:map(fun simplify/1,Args)}. simplify_path({step,{Axis,NodeTest,Predicates}}) -> {step,{Axis, simplify_node_test(NodeTest), simplify_predicates(Predicates)}}; simplify_path({refine,Path1,Path2}) -> {refine,simplify_path(Path1),simplify_path(Path2)}. simplify_node_test({name,{Tag,Prefix,Local}}) -> {name,{to_binary(Tag),Prefix,Local}}; simplify_node_test(A={node_type,Type}) when Type == 'text' ; Type == 'node' -> A; simplify_node_test(A={wildcard,wildcard}) -> A. simplify_predicates(X) -> lists:map(fun simplify_predicate/1,X). simplify_predicate({pred,Pred}) -> {pred,simplify(Pred)}. to_binary(X) when is_atom(X) -> list_to_binary(atom_to_list(X)). tsung-1.4.2/src/templates/0000755000201100017670000000000011701017143015123 5ustar nniclausdreamtsung-1.4.2/src/templates/report.thtml0000644000201100017670000001212111701017117017506 0ustar nniclausdream[% INCLUDE header.thtml %] [% USE format %] [% USE pf = format('%.5f') %]

tsung - Statistics

Main Statistics

[% FOREACH key = data.rate.keys.sort %] [% IF cat_data.$key == "stats" %] [% END %] [% END %]
Name highest 10sec mean lowest 10sec mean Highest Rate Mean Count
$key [% data.maxmean.$key %] [% data.minmean.$key %] [% data.rate.$key %] / sec [% data.mean.$key %] [% data.count.$key %]

Transactions Statistics

[% FOREACH key = data.rate.keys.sort %] [% IF cat_data.$key == "transaction" %] [% END %] [% END %]
Name highest 10sec mean lowest 10sec meanHighest Rate Mean Count
$key [% data.maxmean.$key %] [% data.minmean.$key %] [% data.rate.$key %] / sec [% data.mean.$key %] [% data.count.$key %]

Network Throughput

[% FOREACH key = data.rate.keys.sort %] [% IF cat_data.$key == "network" %] [% END %] [% END %]
Name Highest RateTotal
$key [% data.rate.$key %]/sec [% data.maxmean.$key %]

Counters Statistics

[% FOREACH key = data.rate.keys.sort %] [% IF cat_data.$key == "count" or cat_data.$key == "match" %] [% END %] [% END %]
Name Highest RateTotal number
$key [% data.rate.$key %] / sec [% data.maxmean.$key %]

[% FOREACH key = data.rate.keys.sort %] [% IF cat_data.$key == "gauge" %] [% END %] [% END %]
Name Max
$key [% data.maxmean.$key %]

[% IF errors %]

Errors

[% FOREACH key = data.rate.keys.sort %] [% IF cat_data.$key == "error" %] [% END %] [% END %]
Name Highest RateTotal number
$key [% data.rate.$key %] / sec [% data.maxmean.$key %]
[% END %] [% IF os_mon %]

Server monitoring

[% FOREACH key = data.rate.keys.sort %] [% IF cat_data.$key == "os_mon_cpu" %] [% END %] [% IF cat_data.$key == "os_mon_load" %] [% END %] [% IF cat_data.$key == "os_mon_free" %] [% END %] [% IF cat_data.$key == "os_mon_packets" %] [% END %] [% IF cat_data.$key == "os_mon_other" %] [% END %] [% END %]
Name highest 10sec meanlowest 10sec mean
$key [% data.maxmean.$key %] % [% data.minmean.$key %] %
$key [% data.maxmean.$key %] [% data.minmean.$key %]
$key [% data.maxmean.$key %] MB [% data.minmean.$key %] MB
$key [% data.maxmean.$key %] / sec [% data.minmean.$key %] / sec
$key [% data.maxmean.$key %] / sec [% data.minmean.$key %] / sec
[% END %] [% IF http %]

HTTP return code

[% FOREACH key = data.rate.keys.sort %] [% IF cat_data.$key == "http_status" %] [% END %] [% END %]
Code Highest RateTotal number
$key [% data.rate.$key %] / sec [% data.maxmean.$key %]
[% END %]
[% INCLUDE footer.thtml %] tsung-1.4.2/src/templates/header.thtml0000644000201100017670000000752211701017117017434 0ustar nniclausdream [% title %] - [% subtitle %]

[% title %]

version [% version %]

[% stats_subtitle %]

[% graph_subtitle %]

[% IF conf %]

XML Config file

[% END %]
tsung-1.4.2/src/templates/dygraph-combined.js0000644000201100017670000016042411701017117020705 0ustar nniclausdreamDygraphLayout=function(b,a){this.dygraph_=b;this.options={};Dygraph.update(this.options,a?a:{});this.datasets=new Array();this.annotations=new Array()};DygraphLayout.prototype.attr_=function(a){return this.dygraph_.attr_(a)};DygraphLayout.prototype.addDataset=function(a,b){this.datasets[a]=b};DygraphLayout.prototype.setAnnotations=function(d){this.annotations=[];var e=this.attr_("xValueParser");for(var c=0;c1){var b=d[0][0];if(!this.minxval||bthis.maxxval){this.maxxval=a}}}}this.xrange=this.maxxval-this.minxval;this.xscale=(this.xrange!=0?1/this.xrange:1);this.minyval=this.options.yAxis[0];this.maxyval=this.options.yAxis[1];this.yrange=this.maxyval-this.minyval;this.yscale=(this.yrange!=0?1/this.yrange:1)};DygraphLayout.prototype._evaluateLineCharts=function(){this.points=new Array();for(var e in this.datasets){if(!this.datasets.hasOwnProperty(e)){continue}var d=this.datasets[e];for(var b=0;b=1){a.y=1}this.points.push(a)}}};DygraphLayout.prototype._evaluateLineTicks=function(){this.xticks=new Array();for(var c=0;c=0)&&(d<=1)){this.xticks.push([d,a])}}this.yticks=new Array();for(var c=0;c=0)&&(d<=1)){this.yticks.push([d,a])}}};DygraphLayout.prototype.evaluateWithError=function(){this.evaluate();if(!this.options.errorBars){return}var d=0;for(var g in this.datasets){if(!this.datasets.hasOwnProperty(g)){continue}var c=0;var f=this.datasets[g];for(var c=0;c0){for(var e=0;ethis.height){k.style.bottom="0px"}else{k.style.top=h+"px"}k.style.left="0px";k.style.textAlign="right";k.style.width=this.options.yAxisLabelWidth+"px";this.container.appendChild(k);this.ylabels.push(k)}var m=this.ylabels[0];var n=this.options.axisLabelFontSize;var a=parseInt(m.style.top)+n;if(a>this.height-n){m.style.top=(parseInt(m.style.top)-n/2)+"px"}}b.beginPath();b.moveTo(this.area.x,this.area.y);b.lineTo(this.area.x,this.area.y+this.area.h);b.closePath();b.stroke()}if(this.options.drawXAxis){if(this.layout.xticks){for(var e=0;ethis.width){c=this.width-this.options.xAxisLabelWidth;k.style.textAlign="right"}if(c<0){c=0;k.style.textAlign="left"}k.style.left=c+"px";k.style.width=this.options.xAxisLabelWidth+"px";this.container.appendChild(k);this.xlabels.push(k)}}b.beginPath();b.moveTo(this.area.x,this.area.y+this.area.h);b.lineTo(this.area.x+this.area.w,this.area.y+this.area.h);b.closePath();b.stroke()}b.restore()};DygraphCanvasRenderer.prototype._renderAnnotations=function(){var h={position:"absolute",fontSize:this.options.axisLabelFontSize+"px",zIndex:10,overflow:"hidden"};var j=function(q,r,s,a){return function(t){var p=s.annotation;if(p.hasOwnProperty(q)){p[q](p,s,a.dygraph_,t)}else{if(a.dygraph_.attr_(r)){a.dygraph_.attr_(r)(p,s,a.dygraph_,t)}}}};var m=this.layout.annotated_points;for(var g=0;gthis.area.x+this.area.w){continue}var k=e.annotation;var l=6;if(k.hasOwnProperty("tickHeight")){l=k.tickHeight}var c=document.createElement("div");for(var b in h){if(h.hasOwnProperty(b)){c.style[b]=h[b]}}if(!k.hasOwnProperty("icon")){c.className="dygraphDefaultAnnotation"}if(k.hasOwnProperty("cssClass")){c.className+=" "+k.cssClass}var d=k.hasOwnProperty("width")?k.width:16;var n=k.hasOwnProperty("height")?k.height:16;if(k.hasOwnProperty("icon")){var f=document.createElement("img");f.src=k.icon;f.width=d;f.height=n;c.appendChild(f)}else{if(e.annotation.hasOwnProperty("shortText")){c.appendChild(document.createTextNode(e.annotation.shortText))}}c.style.left=(e.canvasx-d/2)+"px";if(k.attachAtBottom){c.style.top=(this.area.h-n-l)+"px"}else{c.style.top=(e.canvasy-n-l)+"px"}c.style.width=d+"px";c.style.height=n+"px";c.title=e.annotation.text;c.style.color=this.colors[e.name];c.style.borderColor=this.colors[e.name];k.div=c;Dygraph.addEvent(c,"click",j("clickHandler","annotationClickHandler",e,this));Dygraph.addEvent(c,"mouseover",j("mouseOverHandler","annotationMouseOverHandler",e,this));Dygraph.addEvent(c,"mouseout",j("mouseOutHandler","annotationMouseOutHandler",e,this));Dygraph.addEvent(c,"dblclick",j("dblClickHandler","annotationDblClickHandler",e,this));this.container.appendChild(c);this.annotations.push(c);var o=this.element.getContext("2d");o.strokeStyle=this.colors[e.name];o.beginPath();if(!k.attachAtBottom){o.moveTo(e.canvasx,e.canvasy);o.lineTo(e.canvasx,e.canvasy-2-l)}else{o.moveTo(e.canvasx,this.area.h);o.lineTo(e.canvasx,this.area.h-2-l)}o.closePath();o.stroke()}};DygraphCanvasRenderer.prototype._renderLineChart=function(){var c=this.element.getContext("2d");var f=this.options.colorScheme.length;var o=this.options.colorScheme;var z=this.options.fillAlpha;var E=this.layout.options.errorBars;var t=this.layout.options.fillGraph;var d=this.layout.options.stackedGraph;var l=this.layout.options.stepPlot;var G=[];for(var H in this.layout.datasets){if(this.layout.datasets.hasOwnProperty(H)){G.push(H)}}var A=G.length;this.colors={};for(var C=0;C1){b=1}}b=this.area.h*b+this.area.y;var q=[];for(var C=A-1;C>=0;C--){var k=G[C];var x=this.colors[k];u.save();var h=NaN;var g=[-1,-1];var D=this.layout.yscale;var a=new RGBColor(x);var F="rgba("+a.r+","+a.g+","+a.b+","+z+")";u.fillStyle=F;u.beginPath();for(var y=0;y0){if(arguments.length==4){this.warn("Using deprecated four-argument dygraph constructor");this.__old_init__(c,b,arguments[2],arguments[3])}else{this.__init__(c,b,a)}}};Dygraph.NAME="Dygraph";Dygraph.VERSION="1.2";Dygraph.__repr__=function(){return"["+this.NAME+" "+this.VERSION+"]"};Dygraph.toString=function(){return this.__repr__()};Dygraph.DEFAULT_ROLL_PERIOD=1;Dygraph.DEFAULT_WIDTH=480;Dygraph.DEFAULT_HEIGHT=320;Dygraph.AXIS_LINE_WIDTH=0.3;Dygraph.DEFAULT_ATTRS={highlightCircleSize:3,pixelsPerXLabel:60,pixelsPerYLabel:30,labelsDivWidth:250,labelsDivStyles:{},labelsSeparateLines:false,labelsShowZeroValues:true,labelsKMB:false,labelsKMG2:false,showLabelsOnHighlight:true,yValueFormatter:function(a){return Dygraph.round_(a,2)},strokeWidth:1,axisTickSize:3,axisLabelFontSize:14,xAxisLabelWidth:50,yAxisLabelWidth:50,xAxisLabelFormatter:Dygraph.dateAxisFormatter,rightGap:5,showRoller:false,xValueFormatter:Dygraph.dateString_,xValueParser:Dygraph.dateParser,xTicker:Dygraph.dateTicker,delimiter:",",logScale:false,sigma:2,errorBars:false,fractions:false,wilsonInterval:true,customBars:false,fillGraph:false,fillAlpha:0.15,connectSeparatedPoints:false,stackedGraph:false,hideOverlayOnMouseOut:true,stepPlot:false,avoidMinZero:false};Dygraph.DEBUG=1;Dygraph.INFO=2;Dygraph.WARNING=3;Dygraph.ERROR=3;Dygraph.addedAnnotationCSS=false;Dygraph.prototype.__old_init__=function(f,d,e,b){if(e!=null){var a=["Date"];for(var c=0;cthis.rawData_.length){return null}if(a<0||a>this.rawData_[b].length){return null}return this.rawData_[b][a]};Dygraph.addEvent=function(c,a,b){var d=function(f){if(!f){var f=window.event}b(f)};if(window.addEventListener){c.addEventListener(a,d,false)}else{c.attachEvent("on"+a,d)}};Dygraph.clipCanvas_=function(b,c){var a=b.getContext("2d");a.beginPath();a.rect(c.left,c.top,c.width,c.height);a.clip()};Dygraph.prototype.createInterface_=function(){var a=this.maindiv_;this.graphDiv=document.createElement("div");this.graphDiv.style.width=this.width_+"px";this.graphDiv.style.height=this.height_+"px";a.appendChild(this.graphDiv);var c={top:0,left:this.attr_("yAxisLabelWidth")+2*this.attr_("axisTickSize")};c.width=this.width_-c.left-this.attr_("rightGap");c.height=this.height_-this.attr_("axisLabelFontSize")-2*this.attr_("axisTickSize");this.clippingArea_=c;this.canvas_=Dygraph.createCanvas();this.canvas_.style.position="absolute";this.canvas_.width=this.width_;this.canvas_.height=this.height_;this.canvas_.style.width=this.width_+"px";this.canvas_.style.height=this.height_+"px";this.hidden_=this.createPlotKitCanvas_(this.canvas_);this.graphDiv.appendChild(this.hidden_);this.graphDiv.appendChild(this.canvas_);this.mouseEventElement_=this.canvas_;Dygraph.clipCanvas_(this.hidden_,this.clippingArea_);Dygraph.clipCanvas_(this.canvas_,this.clippingArea_);var b=this;Dygraph.addEvent(this.mouseEventElement_,"mousemove",function(d){b.mouseMove_(d)});Dygraph.addEvent(this.mouseEventElement_,"mouseout",function(d){b.mouseOut_(d)});this.layoutOptions_={xOriginIsZero:false};Dygraph.update(this.layoutOptions_,this.attrs_);Dygraph.update(this.layoutOptions_,this.user_attrs_);Dygraph.update(this.layoutOptions_,{errorBars:(this.attr_("errorBars")||this.attr_("customBars"))});this.layout_=new DygraphLayout(this,this.layoutOptions_);this.renderOptions_={colorScheme:this.colors_,strokeColor:null,axisLineWidth:Dygraph.AXIS_LINE_WIDTH};Dygraph.update(this.renderOptions_,this.attrs_);Dygraph.update(this.renderOptions_,this.user_attrs_);this.plotter_=new DygraphCanvasRenderer(this,this.hidden_,this.layout_,this.renderOptions_);this.createStatusMessage_();this.createRollInterface_();this.createDragInterface_()};Dygraph.prototype.destroy=function(){var a=function(c){while(c.hasChildNodes()){a(c.firstChild);c.removeChild(c.firstChild)}};a(this.maindiv_);var b=function(c){for(var d in c){if(typeof(c[d])==="object"){c[d]=null}}};b(this.layout_);b(this.plotter_);b(this)};Dygraph.prototype.createPlotKitCanvas_=function(a){var b=Dygraph.createCanvas();b.style.position="absolute";b.style.top=a.style.top;b.style.left=a.style.left;b.width=this.width_;b.height=this.height_;b.style.width=this.width_+"px";b.style.height=this.height_+"px";return b};Dygraph.hsvToRGB=function(h,g,k){var c;var d;var l;if(g===0){c=k;d=k;l=k}else{var e=Math.floor(h*6);var j=(h*6)-e;var b=k*(1-g);var a=k*(1-(g*j));var m=k*(1-(g*(1-j)));switch(e){case 1:c=a;d=k;l=b;break;case 2:c=b;d=k;l=m;break;case 3:c=b;d=a;l=k;break;case 4:c=m;d=b;l=k;break;case 5:c=k;d=b;l=a;break;case 6:case 0:c=k;d=m;l=b;break}}c=Math.floor(255*c+0.5);d=Math.floor(255*d+0.5);l=Math.floor(255*l+0.5);return"rgb("+c+","+d+","+l+")"};Dygraph.prototype.setColors_=function(){var e=this.attr_("labels").length-1;this.colors_=[];var a=this.attr_("colors");if(!a){var c=this.attr_("colorSaturation")||1;var b=this.attr_("colorValue")||0.5;var j=Math.ceil(e/2);for(var d=1;d<=e;d++){if(!this.visibility()[d-1]){continue}var g=d%2?Math.ceil(d/2):(j+d/2);var f=(1*g/(1+e));this.colors_.push(Dygraph.hsvToRGB(f,c,b))}}else{for(var d=0;d=10){o.doZoom_(Math.min(b,n),Math.max(b,n))}else{o.canvas_.getContext("2d").clearRect(0,0,o.canvas_.width,o.canvas_.height)}b=null;a=null}if(e){e=false;m=null;k=null}});Dygraph.addEvent(this.mouseEventElement_,"dblclick",function(p){if(o.dateWindow_==null){return}o.dateWindow_=null;o.drawGraph_(o.rawData_);var q=o.rawData_[0][0];var r=o.rawData_[o.rawData_.length-1][0];if(o.attr_("zoomCallback")){o.attr_("zoomCallback")(q,r)}})};Dygraph.prototype.drawZoomRect_=function(c,d,b){var a=this.canvas_.getContext("2d");if(b){a.clearRect(Math.min(c,b),0,Math.abs(c-b),this.height_)}if(d&&c){a.fillStyle="rgba(128,128,128,0.33)";a.fillRect(Math.min(c,d),0,Math.abs(d-c),this.height_)}};Dygraph.prototype.doZoom_=function(d,a){var b=this.toDataCoords(d,null);var c=b[0];b=this.toDataCoords(a,null);var e=b[0];this.dateWindow_=[c,e];this.drawGraph_(this.rawData_);if(this.attr_("zoomCallback")){this.attr_("zoomCallback")(c,e)}};Dygraph.prototype.mouseMove_=function(b){var a=Dygraph.pageX(b)-Dygraph.findPosX(this.mouseEventElement_);var r=this.layout_.points;var m=-1;var j=-1;var o=1e+100;var q=-1;for(var f=0;fo){continue}o=h;q=f}if(q>=0){m=r[q].xval}if(a>r[r.length-1].canvasx){m=r[r.length-1].xval}this.selPoints_=[];var d=r.length;if(!this.attr_("stackedGraph")){for(var f=0;f=0;f--){if(r[f].xval==m){var c={};for(var e in r[f]){c[e]=r[f][e]}c.yval-=g;g+=c.yval;this.selPoints_.push(c)}}this.selPoints_.reverse()}if(this.attr_("highlightCallback")){var n=this.lastx_;if(n!==null&&m!=n){this.attr_("highlightCallback")(b,m,this.selPoints_)}}this.lastx_=m;this.updateSelection_()};Dygraph.prototype.updateSelection_=function(){var q=this.canvas_.getContext("2d");if(this.previousVerticalX_>=0){var h=0;var j=this.attr_("labels");for(var g=1;gh){h=b}}var o=this.previousVerticalX_;q.clearRect(o-h-1,0,2*h+2,this.height_)}var p=function(c){return c&&!isNaN(c)};if(this.selPoints_.length>0){var d=this.selPoints_[0].canvasx;var e=this.attr_("xValueFormatter")(this.lastx_,this)+":";var f=this.attr_("yValueFormatter");var m=this.colors_.length;if(this.attr_("showLabelsOnHighlight")){for(var g=0;g"}var n=this.selPoints_[g];var l=new RGBColor(this.plotter_.colors[n.name]);var k=f(n.yval);e+=" "+n.name+":"+k}this.attr_("labelsDiv").innerHTML=e}q.save();for(var g=0;g=0){for(var b in this.layout_.datasets){if(c=Dygraph.MONTHLY){return b.strftime("%b %y")}else{var a=b.getHours()*3600+b.getMinutes()*60+b.getSeconds()+b.getMilliseconds();if(a==0||c>=Dygraph.DAILY){return new Date(b.getTime()+3600*1000).strftime("%d%b")}else{return Dygraph.hmsString_(b.getTime())}}};Dygraph.dateString_=function(b,k){var c=Dygraph.zeropad;var g=new Date(b);var h=""+g.getFullYear();var e=c(g.getMonth()+1);var j=c(g.getDate());var f="";var a=g.getHours()*3600+g.getMinutes()*60+g.getSeconds();if(a){f=" "+Dygraph.hmsString_(b)}return h+"/"+e+"/"+j+f};Dygraph.round_=function(c,b){var a=Math.pow(10,b);return Math.round(c*a)/a};Dygraph.prototype.loadedEvent_=function(a){this.rawData_=this.parseCSV_(a);this.drawGraph_(this.rawData_)};Dygraph.prototype.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];Dygraph.prototype.quarters=["Jan","Apr","Jul","Oct"];Dygraph.prototype.addXTicks_=function(){var a,c;if(this.dateWindow_){a=this.dateWindow_[0];c=this.dateWindow_[1]}else{a=this.rawData_[0][0];c=this.rawData_[this.rawData_.length-1][0]}var b=this.attr_("xTicker")(a,c,this);this.layout_.updateOptions({xTicks:b})};Dygraph.SECONDLY=0;Dygraph.TWO_SECONDLY=1;Dygraph.FIVE_SECONDLY=2;Dygraph.TEN_SECONDLY=3;Dygraph.THIRTY_SECONDLY=4;Dygraph.MINUTELY=5;Dygraph.TWO_MINUTELY=6;Dygraph.FIVE_MINUTELY=7;Dygraph.TEN_MINUTELY=8;Dygraph.THIRTY_MINUTELY=9;Dygraph.HOURLY=10;Dygraph.TWO_HOURLY=11;Dygraph.SIX_HOURLY=12;Dygraph.DAILY=13;Dygraph.WEEKLY=14;Dygraph.MONTHLY=15;Dygraph.QUARTERLY=16;Dygraph.BIANNUAL=17;Dygraph.ANNUAL=18;Dygraph.DECADAL=19;Dygraph.NUM_GRANULARITIES=20;Dygraph.SHORT_SPACINGS=[];Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY]=1000*1;Dygraph.SHORT_SPACINGS[Dygraph.TWO_SECONDLY]=1000*2;Dygraph.SHORT_SPACINGS[Dygraph.FIVE_SECONDLY]=1000*5;Dygraph.SHORT_SPACINGS[Dygraph.TEN_SECONDLY]=1000*10;Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_SECONDLY]=1000*30;Dygraph.SHORT_SPACINGS[Dygraph.MINUTELY]=1000*60;Dygraph.SHORT_SPACINGS[Dygraph.TWO_MINUTELY]=1000*60*2;Dygraph.SHORT_SPACINGS[Dygraph.FIVE_MINUTELY]=1000*60*5;Dygraph.SHORT_SPACINGS[Dygraph.TEN_MINUTELY]=1000*60*10;Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_MINUTELY]=1000*60*30;Dygraph.SHORT_SPACINGS[Dygraph.HOURLY]=1000*3600;Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]=1000*3600*2;Dygraph.SHORT_SPACINGS[Dygraph.SIX_HOURLY]=1000*3600*6;Dygraph.SHORT_SPACINGS[Dygraph.DAILY]=1000*86400;Dygraph.SHORT_SPACINGS[Dygraph.WEEKLY]=1000*604800;Dygraph.prototype.NumXTicks=function(e,b,g){if(gh){continue}y.push({v:k,label:r(new Date(k),a)})}}}return y};Dygraph.dateTicker=function(a,f,d){var b=-1;for(var e=0;e=d.attr_("pixelsPerXLabel")){b=e;break}}if(b>=0){return d.GetXAxis(a,f,b)}else{}};Dygraph.numericTicks=function(w,v,l,t){if(l.attr_("labelsKMG2")){var f=[1,2,4,8]}else{var f=[1,2,5]}var y,p,a,q;var h=l.attr_("pixelsPerYLabel");for(var u=-10;u<50;u++){if(l.attr_("labelsKMG2")){var c=Math.pow(16,u)}else{var c=Math.pow(10,u)}for(var s=0;sh){break}}if(d>h){break}}var x=[];var r;var o=[];if(l.attr_("labelsKMB")){r=1000;o=["K","M","B","T"]}if(l.attr_("labelsKMG2")){if(r){l.warn("Setting both labelsKMB and labelsKMG2. Pick one!")}r=1024;o=["k","M","G","T"]}if(p>a){y*=-1}for(var u=0;u=0;s--,m/=r){if(b>=m){e=Dygraph.round_(g/m,1)+o[s];break}}}x.push({label:e,v:g})}return x};Dygraph.prototype.addYTicks_=function(d,c){var a=this.attr_("yAxisLabelFormatter")?this.attr_("yAxisLabelFormatter"):this.attr_("yValueFormatter");var b=Dygraph.numericTicks(d,c,this,a);this.layout_.updateOptions({yAxis:[d,c],yTicks:b})};Dygraph.prototype.extremeValues_=function(d){var h=null,f=null;var b=this.attr_("errorBars")||this.attr_("customBars");if(b){for(var c=0;cg){a=g}if(ef){f=e}if(h==null||af){f=g}if(h==null||g=1;w--){if(!this.visibility()[w-1]){continue}var b=this.attr_("connectSeparatedPoints",w);var m=[];for(var u=0;u=F&&e===null){e=t}if(m[t][0]<=g){E=t}}if(e===null){e=0}if(e>0){e--}if(E===null){E=m.length-1}if(Ey)){y=o}if(p){for(var u=0;uy){y=d[h]}}}}f[w]=m}for(var w=1;w0){z=0}var v=y-z;if(v==0){v=y}var c=y+0.1*v;var C=z-0.1*v;if(!this.attr_("avoidMinZero")){if(C<0&&z>=0){C=0}if(c>0&&y<=0){c=0}}if(this.attr_("includeZero")){if(y<0){c=0}if(z>0){C=0}}this.addYTicks_(C,c);this.displayedYRange_=[C,c]}this.addXTicks_();this.layout_.updateOptions({dateWindow:this.dateWindow_});this.layout_.evaluateWithError();this.plotter_.clear();this.plotter_.render();this.canvas_.getContext("2d").clearRect(0,0,this.canvas_.width,this.canvas_.height);if(this.attr_("drawCallback")!==null){this.attr_("drawCallback")(this,n)}};Dygraph.prototype.rollingAverage=function(m,d){if(m.length<2){return m}var d=Math.min(d,m.length-1);var b=[];var s=this.attr_("sigma");if(this.fractions_){var k=0;var h=0;var e=100;for(var x=0;x=0){k-=m[x-d][1][0];h-=m[x-d][1][1]}var B=m[x][0];var v=h?k/h:0;if(this.attr_("errorBars")){if(this.wilsonInterval_){if(h){var t=v<0?0:v,u=h;var A=s*Math.sqrt(t*(1-t)/u+s*s/(4*u*u));var a=1+s*s/h;var F=(t+s*s/(2*h)-A)/a;var o=(t+s*s/(2*h)+A)/a;b[x]=[B,[t*e,(t-F)*e,(o-t)*e]]}else{b[x]=[B,[0,0,0]]}}else{var z=h?s*Math.sqrt(v*(1-v)/h):1;b[x]=[B,[e*v,e*z,e*z]]}}else{b[x]=[B,e*v]}}}else{if(this.attr_("customBars")){var F=0;var C=0;var o=0;var g=0;for(var x=0;x=0){var r=m[x-d];if(r[1][1]!=null&&!isNaN(r[1][1])){F-=r[1][0];C-=r[1][1];o-=r[1][2];g-=1}}b[x]=[m[x][0],[1*C/g,1*(C-F)/g,1*(o-C)/g]]}}else{var q=Math.min(d-1,m.length-2);if(!this.attr_("errorBars")){if(d==1){return m}for(var x=0;x=0||b.indexOf("/")>=0||isNaN(parseFloat(b))){a=true}else{if(b.length==8&&b>"19700101"&&b<"20371231"){a=true}}if(a){this.attrs_.xValueFormatter=Dygraph.dateString_;this.attrs_.xValueParser=Dygraph.dateParser;this.attrs_.xTicker=Dygraph.dateTicker;this.attrs_.xAxisLabelFormatter=Dygraph.dateAxisFormatter}else{this.attrs_.xValueFormatter=function(c){return c};this.attrs_.xValueParser=function(c){return parseFloat(c)};this.attrs_.xTicker=Dygraph.numericTicks;this.attrs_.xAxisLabelFormatter=this.attrs_.xValueFormatter}};Dygraph.prototype.parseCSV_=function(h){var n=[];var r=h.split("\n");var b=this.attr_("delimiter");if(r[0].indexOf(b)==-1&&r[0].indexOf("\t")>=0){b="\t"}var a=0;if(this.labelsFromCSV_){a=1;this.attrs_.labels=r[0].split(b)}var k=function(j){var s=parseFloat(j);return isNaN(s)?null:s};var c;var p=false;var d=this.attr_("labels").length;var m=false;for(var g=a;g0&&l[0]0&&d[0]0){this.setAnnotations(a,true)}};Dygraph.update=function(b,c){if(typeof(c)!="undefined"&&c!==null){for(var a in c){if(c.hasOwnProperty(a)){b[a]=c[a]}}}return b};Dygraph.isArrayLike=function(b){var a=typeof(b);if((a!="object"&&!(a=="function"&&typeof(b.item)=="function"))||b===null||typeof(b.length)!="number"||b.nodeType===3){return false}return true};Dygraph.isDateLike=function(a){if(typeof(a)!="object"||a===null||typeof(a.getTime)!="function"){return false}return true};Dygraph.clone=function(c){var b=[];for(var a=0;a=0){this.loadedEvent_(this.file_)}else{var b=new XMLHttpRequest();var a=this;b.onreadystatechange=function(){if(b.readyState==4){if(b.status==200){a.loadedEvent_(b.responseText)}}};b.open("GET",this.file_,true);b.send(null)}}else{this.error("Unknown data format: "+(typeof this.file_))}}}}};Dygraph.prototype.updateOptions=function(a){if(a.rollPeriod){this.rollPeriod_=a.rollPeriod}if(a.dateWindow){this.dateWindow_=a.dateWindow}if(a.valueRange){this.valueRange_=a.valueRange}Dygraph.update(this.user_attrs_,a);Dygraph.update(this.renderOptions_,a);this.labelsFromCSV_=(this.attr_("labels")==null);this.layout_.updateOptions({errorBars:this.attr_("errorBars")});if(a.file){this.file_=a.file;this.start_()}else{this.drawGraph_(this.rawData_)}};Dygraph.prototype.resize=function(b,a){if(this.resize_lock){return}this.resize_lock=true;if((b===null)!=(a===null)){this.warn("Dygraph.resize() should be called with zero parameters or two non-NULL parameters. Pretending it was zero.");b=a=null}this.maindiv_.innerHTML="";this.attrs_.labelsDiv=null;if(b){this.maindiv_.style.width=b+"px";this.maindiv_.style.height=a+"px";this.width_=b;this.height_=a}else{this.width_=this.maindiv_.offsetWidth;this.height_=this.maindiv_.offsetHeight}this.createInterface_();this.drawGraph_(this.rawData_);this.resize_lock=false};Dygraph.prototype.adjustRoll=function(a){this.rollPeriod_=a;this.drawGraph_(this.rawData_)};Dygraph.prototype.visibility=function(){if(!this.attr_("visibility")){this.attrs_.visibility=[]}while(this.attr_("visibility").length=a.length){this.warn("invalid series number in setVisibility: "+b)}else{a[b]=c;this.drawGraph_(this.rawData_)}};Dygraph.prototype.setAnnotations=function(b,a){Dygraph.addAnnotationRule();this.annotations_=b;this.layout_.setAnnotations(this.annotations_);if(!a){this.drawGraph_(this.rawData_)}};Dygraph.prototype.annotations=function(){return this.annotations_};Dygraph.prototype.indexFromSetName=function(a){var c=this.attr_("labels");for(var b=0;b0){b=document.styleSheets[0]}else{var d=document.createElement("style");d.type="text/css";document.getElementsByTagName("head")[0].appendChild(d);for(i=0;i255)?255:this.r);this.g=(this.g<0||isNaN(this.g))?0:((this.g>255)?255:this.g);this.b=(this.b<0||isNaN(this.b))?0:((this.b>255)?255:this.b);this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"};this.toHex=function(){var l=this.r.toString(16);var k=this.g.toString(16);var j=this.b.toString(16);if(l.length==1){l="0"+l}if(k.length==1){k="0"+k}if(j.length==1){j="0"+j}return"#"+l+k+j}}Date.ext={};Date.ext.util={};Date.ext.util.xPad=function(a,c,b){if(typeof(b)=="undefined"){b=10}for(;parseInt(a,10)1;b/=10){a=c.toString()+a}return a.toString()};Date.prototype.locale="en-GB";if(document.getElementsByTagName("html")&&document.getElementsByTagName("html")[0].lang){Date.prototype.locale=document.getElementsByTagName("html")[0].lang}Date.ext.locales={};Date.ext.locales.en={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],x:"%d/%m/%y",X:"%T"};Date.ext.locales["en-US"]=Date.ext.locales.en;Date.ext.locales["en-US"].c="%a %d %b %Y %r %Z";Date.ext.locales["en-US"].x="%D";Date.ext.locales["en-US"].X="%r";Date.ext.locales["en-GB"]=Date.ext.locales.en;Date.ext.locales["en-AU"]=Date.ext.locales["en-GB"];Date.ext.formats={a:function(a){return Date.ext.locales[a.locale].a[a.getDay()]},A:function(a){return Date.ext.locales[a.locale].A[a.getDay()]},b:function(a){return Date.ext.locales[a.locale].b[a.getMonth()]},B:function(a){return Date.ext.locales[a.locale].B[a.getMonth()]},c:"toLocaleString",C:function(a){return Date.ext.util.xPad(parseInt(a.getFullYear()/100,10),0)},d:["getDate","0"],e:["getDate"," "],g:function(a){return Date.ext.util.xPad(parseInt(Date.ext.util.G(a)/100,10),0)},G:function(c){var e=c.getFullYear();var b=parseInt(Date.ext.formats.V(c),10);var a=parseInt(Date.ext.formats.W(c),10);if(a>b){e++}else{if(a===0&&b>=52){e--}}return e},H:["getHours","0"],I:function(b){var a=b.getHours()%12;return Date.ext.util.xPad(a===0?12:a,0)},j:function(c){var a=c-new Date(""+c.getFullYear()+"/1/1 GMT");a+=c.getTimezoneOffset()*60000;var b=parseInt(a/60000/60/24,10)+1;return Date.ext.util.xPad(b,0,100)},m:function(a){return Date.ext.util.xPad(a.getMonth()+1,0)},M:["getMinutes","0"],p:function(a){return Date.ext.locales[a.locale].p[a.getHours()>=12?1:0]},P:function(a){return Date.ext.locales[a.locale].P[a.getHours()>=12?1:0]},S:["getSeconds","0"],u:function(a){var b=a.getDay();return b===0?7:b},U:function(e){var a=parseInt(Date.ext.formats.j(e),10);var c=6-e.getDay();var b=parseInt((a+c)/7,10);return Date.ext.util.xPad(b,0)},V:function(e){var c=parseInt(Date.ext.formats.W(e),10);var a=(new Date(""+e.getFullYear()+"/1/1")).getDay();var b=c+(a>4||a<=1?0:1);if(b==53&&(new Date(""+e.getFullYear()+"/12/31")).getDay()<4){b=1}else{if(b===0){b=Date.ext.formats.V(new Date(""+(e.getFullYear()-1)+"/12/31"))}}return Date.ext.util.xPad(b,0)},w:"getDay",W:function(e){var a=parseInt(Date.ext.formats.j(e),10);var c=7-Date.ext.formats.u(e);var b=parseInt((a+c)/7,10);return Date.ext.util.xPad(b,0,10)},y:function(a){return Date.ext.util.xPad(a.getFullYear()%100,0)},Y:"getFullYear",z:function(c){var b=c.getTimezoneOffset();var a=Date.ext.util.xPad(parseInt(Math.abs(b/60),10),0);var e=Date.ext.util.xPad(b%60,0);return(b>0?"-":"+")+a+e},Z:function(a){return a.toString().replace(/^.*\(([^)]+)\)$/,"$1")},"%":function(a){return"%"}};Date.ext.aggregates={c:"locale",D:"%m/%d/%y",h:"%b",n:"\n",r:"%I:%M:%S %p",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"};Date.ext.aggregates.z=Date.ext.formats.z(new Date());Date.ext.aggregates.Z=Date.ext.formats.Z(new Date());Date.ext.unsupported={};Date.prototype.strftime=function(a){if(!(this.locale in Date.ext.locales)){if(this.locale.replace(/-[a-zA-Z]+$/,"") in Date.ext.locales){this.locale=this.locale.replace(/-[a-zA-Z]+$/,"")}else{this.locale="en-GB"}}var c=this;while(a.match(/%[cDhnrRtTxXzZ]/)){a=a.replace(/%([cDhnrRtTxXzZ])/g,function(e,d){var g=Date.ext.aggregates[d];return(g=="locale"?Date.ext.locales[c.locale][d]:g)})}var b=a.replace(/%([aAbBCdegGHIjmMpPSuUVwWyY%])/g,function(e,d){var g=Date.ext.formats[d];if(typeof(g)=="string"){return c[g]()}else{if(typeof(g)=="function"){return g.call(c,c)}else{if(typeof(g)=="object"&&typeof(g[0])=="string"){return Date.ext.util.xPad(c[g[0]](),g[1])}else{return d}}}});c=null;return b};tsung-1.4.2/src/templates/footer.thtml0000644000201100017670000000034511701017117017476 0ustar nniclausdream
tsung-1.4.2/src/templates/graph.thtml0000644000201100017670000000711511701017117017303 0ustar nniclausdream[% INCLUDE header.thtml %]

tsung - Graphical Reports

Response Time

TransactionsRequests and connection establishment
transaction response time mean request response time

Throughput

[% IF async %] [% END %]
TransactionsRequests
transaction rate req/sec
Noack/Bidi
req/sec
Network trafficNew Users
Kb/sec visit/sec

Simultaneous Users

[% IF match %] [% END %] [% IF match %] [% END %]
Simultaneous UsersMatching responses
Users Match
[% IF os_mon %]

Server OS monitoring

[% IF os_mon_other %] [% USE table(os_mon_other, cols=2) %] [% FOREACH row = table.rows %] [% FOREACH key = row %] [% END %] [% END %] [% END %]
CPU%Free Memory
cpu free memory
CPU Load
load
other
[% END %] [% IF http %]

HTTP return code Status (rate)

HTTP_CODE-rate
[% END %] [% IF errors %]

Errors (rate)

Errors-rate
[% END %]
[% INCLUDE footer.thtml %] tsung-1.4.2/src/templates/graph_dy.thtml0000644000201100017670000001207211701017117017775 0ustar nniclausdream[% INCLUDE header.thtml %]

tsung - Graphical Reports

Response Time

TransactionsRequests and connection establishment

Throughput

[% IF async %] [% END %]
TransactionsRequests
Noack/Bidi
Network trafficNew Users

Simultaneous Users

[% IF match %] [% END %]
[% IF match %]
[% END %]
Simultaneous UsersMatching responses
[% IF os_mon %]

Server OS monitoring

CPU%Free Memory
CPU Load
[% END %] [% IF http %]

HTTP return code Status (rate)

[% END %] [% IF errors %]

Errors (rate)

[% END %]
[% INCLUDE footer.thtml %] tsung-1.4.2/src/tsung-plotter/0000755000201100017670000000000011701017143015754 5ustar nniclausdreamtsung-1.4.2/src/tsung-plotter/tsplot.py.in0000644000201100017670000002613711701017117020272 0ustar nniclausdream#! /usr/bin/python # -*- Mode: python -*- # -*- coding: utf-8 -*- # # Copyright: 2006 by Dalibo # Copyright: 2007 Dimitri Fontaine # Created: 2006 by Dimitri Fontaine # # Modified: 2008 by Nicolas Niclausse # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # # In addition, as a special exception, you have the permission to # link the code of this program with any library released under # the EPL license and distribute linked combinations including # the two. # A plotter for tsung text generated data import os, sys LIBDIR = "@EXPANDED_LIBDIR@/tsung" SHAREDIR = "@EXPANDED_SHAREDIR@" USERDIR = ".tsung" CONF = "http.plots.en.conf" SYS_STATS_CONF = os.path.join(SHAREDIR, 'tsung_plotter', 'stats.conf') sys.path.append(LIBDIR) from tsung_plotter.tsung import TsungLog from ConfigParser import ConfigParser from pylab import * class Plot: """ The ploting class, using matplotlib """ def __init__(self, name = "", xlabel = "", ylabel = "", title = "", legends = None, position = 0, styles = ['b-', 'r-', 'g-', 'c-', 'y^', 'kv'], colors = ['b', 'r', 'g', 'c', 'y', 'k'], dpi = 150, tn_dpi = 50, xfactor = 1, yfactor = 1, yscale = 'linear', plottype = 'lineplot', # can be lineplot or bar outdir = '/tmp/tsung-plotter', imgtype = 'png'): # Only used if no plot type given self.name = name self.xlabel = xlabel self.ylabel = ylabel self.title = title self.legends = legends self.position = position self.dpi = dpi self.thumb_dpi = tn_dpi self.xfactor = xfactor self.yfactor = yfactor self.yscale = yscale self.plottype = plottype self.styles = styles self.colors = colors self.outdir = outdir self.imgtype = imgtype def plot(self, stats, dataset): """ draw a simple timeline or bar plots from two dataset """ if self.plottype == "lineplot": # prepare matplotlib graph clf() ax = subplot(111) ax.set_yscale(self.yscale) count = 0 for data in dataset: # get give type data for plotting nstats = 0 for (name, stat) in stats: pdata = data.stat(name, stat) # we want timestamp sorted data to plot ts = pdata.keys() ts.sort() values = [pdata[t] / self.yfactor[nstats%len(self.yfactor)] for t in ts] if self.xfactor not in [1, "1"]: ts = [x / self.xfactor for x in ts] # Now use matplotlib to do the plotting plot(ts, values, self.styles[count%len(self.styles)]) count += 1 nstats += 1 # Now setup the legend title(self.title) xlabel(self.xlabel) ylabel(self.ylabel) legend(self.legends, loc=self.position) # we want to draw a grid grid(True) filename = os.path.join(self.outdir, "%s.%s" % (self.name, self.imgtype)) thumbnail = os.path.join(self.outdir, "%s_tn.%s" % (self.name, self.imgtype)) savefig(filename, dpi=self.dpi, orientation='portrait') savefig(thumbnail, dpi=self.thumb_dpi, orientation='portrait') return filename else: # box chart for gmean clf() width = 1.0/len(dataset) nbox=len(dataset) ind=arange(1) count = 0 current = {} percent = [] ticksp = [] ax = subplot(111) ax.set_yscale(self.yscale) for data in dataset: nstats=0 for (name, stat) in stats: pdata = data.stat(name, stat) size= len(pdata) if size > 0 : # the data we use is the latest value: current[count] = pdata.values()[size-1] / self.yfactor[nstats] ax.bar(ind+count*width, ( current[count] ), width, color=self.colors[count] ) # get percent of increase/decrease: if count > 0: percent.append(str(round(-100 + current[count] / current[0] * 100)) + "% ") else: percent.append("reference") currenttick=(0.5 + count)/nbox ticksp.append(currenttick) count += 1 nstats += 1 boxchart = os.path.join(self.outdir, "%s.%s" % (self.name, self.imgtype)) boxchart_thumb = os.path.join(self.outdir, "%s_tn.%s" % (self.name, self.imgtype)) title(self.title) legend(self.legends, loc=self.position) ax.set_xticks(ticksp) ax.set_xticklabels(percent) ylabel(self.ylabel) savefig(boxchart, dpi=self.dpi, orientation='portrait') savefig(boxchart_thumb, dpi=self.thumb_dpi, orientation='portrait') return boxchart def main(conffile, logs, legends, outdir, verbose): """ Produce plots from given """ dataset = [d for f, d in logs] config = ConfigParser() config.read(conffile) for s in config.sections(): p = Plot(name = s, outdir = outdir) # defaults for d, v in config.defaults().items(): if d != 'encoding': p.__dict__[d] = v else: encoding = v # stats # conf: 200.count, 400.count # result: [["200", "count"], ["400", "count"]] try: stats = [x.strip().rsplit('.', 1) for x in config.get(s, 'stats').split(' ')] except: print 'error: unable to read plot "%s" stats' % s continue if config.has_option(s, 'styles'): p.styles = [x.strip() for x in config.get(s, 'styles').split(' ')] if config.has_option(s, 'legend'): # this is the legend prefix$ l = [] count = 0 clegends = config.get(s, 'legend').decode(encoding).split(',') for f in logs: l += ['%s %s' % (x.strip(), legends[count]) \ for x in clegends] count += 1 p.legends = l if config.has_option(s, 'position'): # legend position according to matplotlib standard values (1-10) val = config.get(s, 'position').decode(encoding) if val.isdigit(): p.position = int(val) else: p.position = val if config.has_option(s, 'yfactor'): try: p.__dict__['yfactor'] = map(float,config.get(s, 'yfactor').decode(encoding).split(',')) except ValueError: print 'warning: %s yfactor not a number: %s' \ % (p.name, config.get(s, yfactor)) # Text parameters - to decode into specified encoding for attr in ['title', 'xlabel', 'ylabel', 'plottype', 'yscale']: if config.has_option(s, attr): cstring = config.get(s, attr).decode(encoding) p.__dict__[attr] = cstring # Numerical parameters for attr in ['xfactor', 'dpi', 'tn_dpi']: if config.has_option(s, attr): try: p.__dict__[attr] = config.getfloat(s, attr) except ValueError: print 'warning: %s %s not a number: %s' \ % (p.name, attr, config.get(s, attr)) outfile = p.plot(stats, dataset) if verbose: print 'Generated plot %s' % outfile if __name__ == "__main__": from optparse import OptionParser parser = OptionParser() parser.add_option("-c", "--config", dest="config", default=None, help="configuration file") parser.add_option("-d", "--outdir", dest="outdir", default="/tmp/tsung", help="output dir where to save plots (/tmp/tsung)") parser.add_option("-v", action="store_true", dest="verbose", default=False, help="be verbose") (options, args) = parser.parse_args() if options.config is None: userconf = os.path.join(os.environ['HOME'], USERDIR, CONF) if os.access(userconf, os.R_OK): config = userconf else: config = os.path.join(SHAREDIR, 'tsung_plotter', CONF) else: config = options.config if options.verbose: print 'Using %s configuration file' % config if not os.access(config, os.R_OK): print "can't read configuration file: %s" % config sys.exit(1) # FIXME: error control # OSError: [Errno 17] Le fichier existe.: '/tmp/tsung' try: os.makedirs(options.outdir) except: pass # args are legend then file, any times wanted by user if len(args) % 2 != 0: print "error: please provide legend and tsung log filename" sys.exit(3) count = 0 legends = [] files = [] for a in args: if count % 2 == 0: legends.append(a) else: files.append(a) count += 1 if options.verbose: print 'Using %s stats configuration file' % SYS_STATS_CONF logs = [] for logfile in files: if not os.access(logfile, os.R_OK): print "error: unable to read file %s" % logfile else: if options.verbose: print 'Parsing Tsung log file', logfile logs.append((logfile, TsungLog(SYS_STATS_CONF, logfile))) if len(logs) != len(args) / 2: print 'error while parsing files (%d != %d)' % (len(logs), len(args)/2) sys.exit(2) main(config, logs, legends, options.outdir, options.verbose) tsung-1.4.2/src/tsung-plotter/http.plots.fr.conf0000644000201100017670000000317511701017117021357 0ustar nniclausdream# tsung plotter configuration # # Define in this file the plots you want tsung-plotter to generate [DEFAULT] encoding = latin-1 dpi = 150 tn_dpi = 50 imgtype = png xlabel = Minutes coules xfactor = 60 yfactor = 1 [users] title = Utilisateurs Simultans ylabel = Utilisateurs stats = users.count legend = Utilisateurs [connected] title = Utilisateurs connects ylabel = Connexions simultanes stats = connected.totalcount legend = Utilisateurs connects position = best [http] title = Rponses http par seconde ylabel = Rponses http par seconde stats = 200.count 400.count styles = b- g+ r- cx legend = http ok, http 400 [size_sent] title = Dbit rseau en mission ylabel = Dbit en Mbps stats = size_sent.count legend = Sent yfactor = 1310720 position = 2 [size_rcv] title = Dbit rseau en rception ylabel = Dbit en Mbps stats = size_rcv.count legend = Received yfactor = 1310720 [finish] title = Sorties d'utilisateurs par seconde ylabel = Sorties d'utilisateurs par seconde stats = finish_users_count.count legend = Utilisateurs sortants - [request_count] title = Requtes http par seconde ylabel = Nombres de requtes http par seconde stats = request.count legend = Requtes [request_mean] title = Dure moyenne des requtes http ylabel = Dure en secondes stats = request.mean legend = Requtes yfactor = 1000 [page_count] title = Nombre de pages obtenues par seconde ylabel = Nombre de pages obtenues par seconde stats = page.count legend = Pages [page_mean] title = Dure moyenne d'obtention de pages ylabel = Dure en secondes stats = page.mean legend = Pages yfactor = 1000 tsung-1.4.2/src/tsung-plotter/pgsql.plots.en.conf0000644000201100017670000000362611701017117021522 0ustar nniclausdream# tsung pgsql plotter configuration # # Define in this file the plots you want tsung-plotter to generate [DEFAULT] encoding = latin-1 dpi = 150 tn_dpi = 50 imgtype = png xlabel = Seconds elapsed xfactor = 1 yfactor = 1 styles = b- r- g- [users] title = Simultaneous Users ylabel = Simultaneous Users stats = users.count legend = Users yfactor = 0.1 [finish] title = Ending users per second ylabel = Ending users per second stats = finish_users_count.count legend = Ending users - [request_count] title = SQL requests per second ylabel = Number of sql requests per second stats = request.count legend = Requests [request_mean] title = Mean duration of SQL requests ylabel = Duration in seconds stats = request.mean legend = Requests yfactor = 1000 [connect_count] title = PostgreSQL connections per second ylabel = Number of pgsql connections per second stats = connect.count legend = Connection [connect_mean] title = Mean duration of connection establishment ylabel = Duration in seconds stats = connect.mean legend = Connections yfactor = 1000 [session_count] title = PostgreSQL sessions per second ylabel = Number of pgsql sessions per second stats = connect.count legend = Sessions [session_mean] title = Mean duration of PostgreSQL sessions ylabel = Duration in seconds stats = session.mean legend = Sessions yfactor = 1000 [timeout] title = Non established connections because of timeout ylabel = Number of timeouts per second stats = error_connect_etimedout.count legend = Timeout [trafic] title = Network Traffic ylabel = kbits/sec stats = size_sent.count, size_rcv.count legend = sent, received styles = b+ b- r+ r- g+ g- yfactor = 1310720 [cpu] title = Mean CPU load ylabel = Mean CPU load stats = {cpu,"os_mon@localhost"}.mean legend = CPU [freemem] title = Free memory ylabel = Free memory stats = {freemem,"os_mon@localhost"}.mean legend = RAM tsung-1.4.2/src/tsung-plotter/http.plots.en.conf0000644000201100017670000000277111701017117021353 0ustar nniclausdream# tsung plotter configuration # # Define in this file the plots you want tsung-plotter to generate [DEFAULT] encoding = latin-1 dpi = 150 tn_dpi = 50 imgtype = png xlabel = Minutes elapsed xfactor = 60 yfactor = 1 [users] title = Simultaneous Users ylabel = Users stats = users.count legend = Users [connected] title = Connected Users ylabel = Simultaneous connections stats = connected.totalcount legend = Connected users position = best [http] title = HTTP requests per second ylabel = HTTP requests per second stats = 200.count 404.count styles = b- g+ r- cx legend = http OK, http 404 yfactor = 10 [size_sent] title = Network Throughput (emit) ylabel = Mbps stats = size_sent.count legend = Sent yfactor = 1310720 position = 2 [size_rcv] title = Network Throughput (received) ylabel = Mbps stats = size_rcv.count legend = Received yfactor = 1310720 [finish] title = Ending users per second ylabel = Ending users per second stats = finish_users_count.count legend = Ending users - [request_count] title = Requests per second ylabel = number of requests per second stats = request.count legend = Requests [request_mean] title = Mean duration of requests ylabel = Duration in seconds stats = request.mean legend = Requests yfactor = 1000 [page_count] title = Page per seconds ylabel = Page per seconds stats = page.count legend = Pages [page_mean] title = Mean duration of pages ylabel = Duration in seconds stats = page.mean legend = Pages yfactor = 1000 tsung-1.4.2/src/tsung-plotter/pgsql.plots.fr.conf0000644000201100017670000000501011701017117021514 0ustar nniclausdream# tsung pgsql plotter configuration # # Define in this file the plots you want tsung-plotter to generate [DEFAULT] encoding = latin-1 dpi = 150 tn_dpi = 50 imgtype = png xlabel = Secondes coules xfactor = 1 yfactor = 1 styles = b- r- g- [users] title = Utilisateurs Simultans ylabel = Utilisateurs simultans stats = users.count legend = Utilisateurs yfactor = 0.1 [finish] title = Sorties d'utilisateurs par seconde ylabel = Sorties d'utilisateurs par seconde stats = finish_users_count.count legend = Utilisateurs sortants - [request_count] title = Requtes SQL par seconde ylabel = Nombres de requtes sql par seconde stats = request.count legend = Requtes [request_mean] title = Dure moyenne des requtes SQL ylabel = Dure en secondes stats = request.mean legend = Requtes yfactor = 1000 [request_count] title = Requtes SQL par seconde ylabel = Nombres de requtes SQL par seconde stats = request.count legend = Requtes [request_mean] title = Dure moyenne des requtes SQL ylabel = Dure en secondes stats = request.mean legend = Requtes yfactor = 1000 [request_count] title = Requtes SQL par seconde ylabel = Nombres de requtes SQL par seconde stats = request.count legend = Requtes [request_mean] title = Dure moyenne des requtes SQL ylabel = Dure en secondes stats = request.mean legend = Requtes yfactor = 1000 [connect_count] title = Connexions PostgreSQL par seconde ylabel = Nombres de connexions pgsql par seconde stats = connect.count legend = Connexion [connect_mean] title = Dure moyenne des connexions PostgreSQL ylabel = Dure en secondes stats = connect.mean legend = Connexions yfactor = 1000 [session_count] title = Sessions PostgreSQL par seconde ylabel = Nombres de sessions pgsql par seconde stats = connect.count legend = Sessions [session_mean] title = Dure moyenne des sessions PostgreSQL ylabel = Dure en secondes stats = session.mean legend = Sessions yfactor = 1000 [timeout] title = Connexions non tablies pour timeout ylabel = Nombre de timeout par seconde stats = error_connect_etimedout.count legend = Timeout [trafic] title = Trafic rseau ylabel = kbits/sec stats = size_sent.count, size_rcv.count legend = sent, received styles = b+ b- r+ r- g+ g- yfactor = 100 [cpu] title = Charge moyenne du CPU ylabel = Charge moyenne du CPU stats = {cpu,"os_mon@localhost"}.mean legend = CPU [freemem] title = Mmoire libre moyenne ylabel = Mmoire libre moyenne stats = {freemem,"os_mon@localhost"}.mean legend = RAM tsung-1.4.2/src/tsung-plotter/tsung/0000755000201100017670000000000011701017143017114 5ustar nniclausdreamtsung-1.4.2/src/tsung-plotter/tsung/tsung.py0000644000201100017670000001641511701017117020636 0ustar nniclausdream#! /usr/bin/python # -*- coding: utf-8 -*- # Copyright: 2006 by Dalibo # Copyright: 2007 Dimitri Fontaine # Created: 2006 by Dimitri Fontaine # # Modified: 2008 by Nicolas Niclausse # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # # In addition, as a special exception, you have the permission to # link the code of this program with any library released under # the EPL license and distribute linked combinations including # the two. """ Python classes for tsung log data usage data: generic tsung data sample counter gauge log: tsung log file parser, using sample, gauge and counter classes This package has its own configuration file where to associate tsung stats names with tsung stat type. """ from ConfigParser import ConfigParser # data are produced every 10 seconds, by default # we read real used interval in log file (deduced from timestamps) INTERVAL = 10 SEPARATOR = "# stats: dump at " PREFIX = "stats: " class data: """ Tsung logged data, to be specialized into sample and counter """ def __init__(self): pass def get(self, name): """ get named statistic """ if name not in self.__dict__.keys(): return None # don't return any value when no measure have been taken # durung interval if self.count == 0: return None if name == 'count' : if self.interval > 0: return self.count / self.interval else: return 0 return self.__dict__[name] class sample(data): """ tsung sample stats """ def __init__(self, interval = INTERVAL, values = [0, 0, 0, 0, 0, 0]): """ init log data values """ self.interval = interval self.count = float(values[0]) self.mean = float(values[1]) self.stdvar = float(values[2]) self.min = float(values[3]) self.max = float(values[4]) self.gmean = float(values[5]) def __repr__(self): """ human readable output """ return "sample: %s %s %s %s %s" % (self.value, self.mean, self.stdvar, self.min, self.max, self.gmean) class counter(data): """ tsung counter stats """ def __init__(self, interval = INTERVAL, values = [0, 0]): """ init log data values """ self.interval = 1 self.count = float(values[0]) self.totalcount = float(values[1]) def __repr__(self): """ human readable output """ return "counter: %s %s" % (self.value, self.totalcount) class gauge(data): """ tsung gauge stats """ def __init__(self, interval = INTERVAL, values = [0, 0]): """ init log data values """ self.interval = 1 self.count = float(values[0]) self.max = float(values[1]) def __repr__(self): """ human readable output """ return "gauge: %s %s" % (self.count, self.max) class TsungLog: """ Tsung logged data parser and representation data is a {timestamp, data} hash, with data either sample or counter instance. """ def __init__(self, config_filename, filename): """ constructor """ self.conffile = config_filename self.filename = filename self.types = {} self.data = {} self.unknown = [] self.configure() self.parse() def configure(self): """ read configuration file to associates stat types to stat names """ config = ConfigParser() config.read(self.conffile) for s in config.sections(): for (name, type) in config.items(s): if type == 'sample': self.types[name] = sample() if type == 'counter': self.types[name] = counter() if type == 'gauge': self.types[name] = gauge() def parse(self): """ read data from self.filename """ first_ts = 0 current_ts = 0 record_ts = 0 import re for line in file(self.filename): # chomp \n line = line[:-1] if line.find(SEPARATOR) == 0: measure_ts = int(line[len(SEPARATOR):].strip()) # interval in seconds between two records. As tsung # generates a reference line, sensible values won't # have to divide by 0 interval = measure_ts - current_ts # now we change current_ts current_ts = measure_ts if first_ts == 0: first_ts = current_ts record_ts = current_ts - first_ts elif line.find("stats: ") == 0: array = [v.strip() for v in line.split(" ")] name = array[1] values = array[2:] if self.types.has_key(name): data = self.types[name].__class__(interval, values) if not self.data.has_key(record_ts): self.data[record_ts] = {} self.data[record_ts][name] = data else: is_re = False x = re.compile("^[\w\d]+$") for k in self.types.keys(): if not re.match(x, k): y = re.compile(k) if re.match(y, name): data = self.types[k].__class__(interval, values) if not self.data.has_key(record_ts): self.data[record_ts] = {} self.data[record_ts][name] = data is_re = True break if name not in self.unknown and not is_re: print 'WARNING: tsung %s data is not configured' % name self.unknown.append(name) def stat(self, name, stat): """ returns a {timestamp: date_type} dict for given named statistic """ ret = {} for ts, stats in self.data.items(): if stats.has_key(name) and stats[name].get(stat) is not None: ret[ts] = stats[name].get(stat) return ret if __name__ == '__main__': # some unit testing import sys config = "stats.conf" logfile = sys.argv[1] tsunglog = TsungLog(config, logfile) from pprint import pprint pprint(tsunglog.stat('request', 'mean')) pprint(tsunglog.stat('users', 'max')) pprint(tsunglog.stat('finish_users_count', 'count')) pprint(tsunglog.stat('200', 'count')) tsung-1.4.2/src/tsung-plotter/tsung/__init__.py0000644000201100017670000000003711701017117021226 0ustar nniclausdream""" Tsung log data parser. """ tsung-1.4.2/src/tsung-plotter/tsung/stats.conf0000644000201100017670000000267011701017117021127 0ustar nniclausdream# tsung plotter configuration # # tsung provides three types of statistics: # # sample: 'name';'count(during the last 10sec)';mean;stdvar;max;min;globalmean;globalcount # counter: 'name';'count in the last 10sec interval';globalcount(since the beginning) # gauge: 'name';'current value'; max since the beginning # # matching between internal representation of tsung stats: # sample: sample, sample_counter # counter: sum, count # gauge: only 'users' data (special case) # This file associates name stats with their type [all] request = sample connect = sample reconnect = sample page = sample session = sample size_rcv = counter size_sent = counter connected = counter users = gauge users_count = counter finish_users_count = counter match = counter match_(\w+) = counter nomatch = counter newphase = counter [errors] error_(\w+) = counter [transactions] tr_(\w+) = sample [monitoring] {freemem(.*) = sample {cpu(.*) = sample {load(.*) = sample {sentpackets(.*) = sample {recvpackets(.*) = sample [http] ^(\d+)$ = counter [jabber] request_noack = counter async_unknown_data_rcv = counter async_data_sent = counter tsung-1.4.2/src/tsung-plotter/fs.plots.en.conf0000644000201100017670000000416711701017117021005 0ustar nniclausdream# tsung plotter configuration # # Define in this file the plots you want tsung-plotter to generate [DEFAULT] encoding = latin-1 dpi = 150 tn_dpi = 50 imgtype = png xlabel = Minutes elapsed xfactor = 60 yfactor = 1 [users] title = Simultaneous Users ylabel = Users stats = users.count legend = Users [connected] title = Connected Users ylabel = Simultaneous connections stats = connected.totalcount legend = Connected users position = best [read_file_count] title = Reads per second ylabel = Reads per second stats = tr_read_file.count legend = Reads [write_file_count] title = Writes per second ylabel = Writes per second stats = tr_write_file.count legend = Writes [read_file_mean] title = Reads duration ylabel = Duration in seconds stats = tr_read_file.mean legend = Reads yfactor = 1000 [write_file_mean] title = Writes duration ylabel = Duration in seconds stats = tr_write_file.mean legend = Writes yfactor = 1000 [read_file_gmean] title = Reads duration (global mean) ylabel = Duration in seconds stats = tr_read_file.gmean legend = Reads yfactor = 1000 [write_file_gmean] title = Writes duration (global mean) ylabel = Duration in seconds stats = tr_write_file.gmean legend = Writes yfactor = 1000 [size_sent] title = Network Throughput (emit) ylabel = Mbps stats = size_sent.count legend = Sent yfactor = 1310720 position = 2 [size_rcv] title = Network Throughput (received) ylabel = Mbps stats = size_rcv.count legend = Received yfactor = 1310720 [finish] title = Ending users per second ylabel = Ending users per second stats = finish_users_count.count legend = Ending users - [request_count] title = Requests per second ylabel = number of requests per second stats = request.count legend = Requests [request_mean] title = Mean duration of requests ylabel = Duration in seconds stats = request.mean legend = Requests yfactor = 1000 [page_count] title = Page per seconds ylabel = Page per seconds stats = page.count legend = Pages [page_mean] title = Mean duration of pages ylabel = Duration in seconds stats = page.mean legend = Pages yfactor = 1000 tsung-1.4.2/src/test/0000755000201100017670000000000011701017143014104 5ustar nniclausdreamtsung-1.4.2/src/test/thinkfirst.xml.in0000644000201100017670000000330011701017117017415 0ustar nniclausdream tsung-1.4.2/src/test/procnetdev_test.txt0000644000201100017670000000127011701017117020056 0ustar nniclausdreamInter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed lo:48910337 16323 0 0 0 0 0 0 48910337 16323 0 0 0 0 0 0 eth0:2949197601 10106167 0 0 0 0 0 19182 419719531 2609645 0 0 0 0 0 0 eth1: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 virbr0: 0 0 0 0 0 0 0 0 4012 25 0 0 0 0 0 0 tsung-1.4.2/src/test/procnetdev_test7chars.txt0000644000201100017670000000127111701017117021167 0ustar nniclausdreamInter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed lo:48910337 16323 0 0 0 0 0 0 48910337 16323 0 0 0 0 0 0 eth0:2949197601 10106167 0 0 0 0 0 19182 419719531 2609645 0 0 0 0 0 0 eth1.14: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 virbr0: 0 0 0 0 0 0 0 0 4012 25 0 0 0 0 0 0 tsung-1.4.2/src/test/ts_test_recorder.erl0000644000201100017670000001040611701017117020164 0ustar nniclausdream%%%------------------------------------------------------------------- %%% File : ts_test_recorder.erl %%% Author : Nicolas Niclausse %%% Description : %%% %%% Created : 20 Mar 2005 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_test_recorder). -compile(export_all). -include("ts_http.hrl"). -include("ts_profile.hrl"). -include_lib("eunit/include/eunit.hrl"). -import(ts_http_common,[parse_req/1, parse_req/2]). -define(HTTP_GET_RES,{ok, #http_request{method='GET', version="1.0"}, _}). test()-> ok. parse_http_request_test() -> ?assertMatch(?HTTP_GET_RES, parse_req("GET / HTTP/1.0\r\n\r\n")). parse_http_partial_request_test() -> % ?log("Testing HTTP request parsing, partial first line ", []), {more,H,Res} = parse_req("GET / HTTP/1.0\r"), ?assertMatch(?HTTP_GET_RES, parse_req(H,Res ++ "\n\r\n")). parse_http_partiel_request2_test() -> {more,H,Res} = parse_req("GET / HTTP/1.0\r\n"), ?assertMatch(?HTTP_GET_RES,parse_req(H,Res ++ "\r\n")). parse_http_request3_test() -> Res = parse_req("POST / HTTP/1.0\r\n\r\nmesdata\r\nsdfsdfs\r\n\r\n"), ?assertMatch({ok, #http_request{method='POST', version="1.0"},"mesdata\r\nsdfsdfs\r\n\r\n"},Res). parse_http_request5_test() -> % ?log("Testing HTTP request parsing, POST with content-length ", []), {ok, Http, Body} = parse_req("POST / HTTP/1.0\r\n" ++"Server: www.glop.org\r\n" ++"Content-length: 16\r\n\r\n" ++"mesdata\r\nsdfsdfs\r\n\r\n"), CL = ts_utils:key1search(Http#http_request.headers,"content-length"), ?assertEqual({16, "mesdata\r\nsdfsdfs\r\n\r\n"},{list_to_integer(CL), Body}). parse_http_request6_test() -> % ?log("Testing HTTP request parsing, POST with content-length; partial ", []), {more, Http, Body} = parse_req("POST / HTTP/1.0\r\n" ++"Server: www.glop.org\r\n" ++"Content-le"), Rest = "ngth: 16\r\n\r\n"++"mesdata\r\nsdfsdfs\r\n\r\n", {ok, Http2, Body2} = parse_req(Http,Body ++ Rest), CL = ts_utils:key1search(Http2#http_request.headers,"content-length"), ?assertEqual({16, "mesdata\r\nsdfsdfs\r\n\r\n"},{list_to_integer(CL), Body2}). parse_http_request7_test() -> Req= "GET http://www.niclux.org/ HTTP/1.1\r\nHost: www.niclux.org\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.5) Gecko/20041209 Firefox/1.0 (Ubuntu) (Ubuntu package 1.0-2ubuntu4-warty99)\r\nAccept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language: fr-fr,en-us;q=0.7,en;q=0.3\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nProxy-Connection: keep-alive\r\n\r\n", ?assertMatch({ok, #http_request{method='GET', version="1.1"}, []},parse_req(Req)). decode_base64_test()-> Base="QWxhZGRpbjpvcGVuIHNlc2FtZQ==", ?assertEqual({"Aladdin","open sesame"}, ts_proxy_http:decode_basic_auth(Base)). %%TODO: should be in ts_test_http encode_base64_test()-> Base="QWxhZGRpbjpvcGVuIHNlc2FtZQ==", ?assertEqual(["Authorization: Basic ",Base,?CRLF], ts_http_common:authenticate("Aladdin","open sesame")). rewrite_http_secure_cookie_test()-> Data="HTTP/1.1 200 OK\r\nSet-Cookie: JSESSIONID=F949C9182402EB74258F43FDC3F3C63F; Path=/; Secure\r\nLocation: https://foo.bar/\r\nContent-Length: 0\r\n\r\n", NewData="HTTP/1.1 200 OK\r\nSet-Cookie: JSESSIONID=F949C9182402EB74258F43FDC3F3C63F; Path=/\r\nLocation: http://-foo.bar/\r\nContent-Length: 0\r\n\r\n", {ok,Res} = ts_utils:from_https(Data), ?assertEqual(list_to_binary(NewData),iolist_to_binary(Res) ). rewrite_http_secure_cookies_test()-> Data="HTTP/1.1 200 OK\r\nSet-Cookie: JSESSIONID=F949C9182402EB74258F43FDC3F3C63F; Path=/; Secure\r\nSet-Cookie: JSESSIONID=32; Path=/foo; Secure\r\nLocation: https://foo.bar/\r\nContent-Length: 0\r\n\r\n", NewData="HTTP/1.1 200 OK\r\nSet-Cookie: JSESSIONID=F949C9182402EB74258F43FDC3F3C63F; Path=/\r\nSet-Cookie: JSESSIONID=32; Path=/foo\r\nLocation: http://-foo.bar/\r\nContent-Length: 0\r\n\r\n", {ok,Res} = ts_utils:from_https(Data), ?assertEqual(list_to_binary(NewData),iolist_to_binary(Res) ). tsung-1.4.2/src/test/ts_test_config.erl0000644000201100017670000002074511701017117017633 0ustar nniclausdream%%%------------------------------------------------------------------- %%% File : ts_test_recorder.erl %%% Author : Nicolas Niclausse %%% Description : %%% %%% Created : 20 Mar 2005 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_test_config). -compile(export_all). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include_lib("eunit/include/eunit.hrl"). -include("xmerl.hrl"). test()-> ok. read_config_http_test() -> myset_env(), ?assertMatch({ok, Config}, ts_config:read("./examples/http_simple.xml",".")). read_config_http2_test() -> myset_env(), ?assertMatch({ok, Config}, ts_config:read("./examples/http_distributed.xml",".")). read_config_pgsql_test() -> myset_env(), ?assertMatch({ok, Config}, ts_config:read("./examples/pgsql.xml",".")). read_config_jabber_test() -> myset_env(), ts_user_server:start([]), ?assertMatch({ok, Config}, ts_config:read("./examples/jabber.xml",".")). read_config_jabber_muc_test() -> myset_env(), ts_user_server:start([]), ?assertMatch({ok, Config}, ts_config:read("./examples/jabber_muc.xml",".")). read_config_xmpp_muc_test() -> myset_env(), ts_user_server:start([]), ?assertMatch({ok, Config}, ts_config:read("./src/test/xmpp-muc.xml",".")). config_get_session_test() -> myset_env(), ts_user_server:start([]), ts_config_server:start_link(["/tmp"]), ok = ts_config_server:read_config("./examples/http_setdynvars.xml"), {ok, Session=#session{userid=1,dump=full} } = ts_config_server:get_next_session("localhost"), ?assertEqual(1, Session#session.id). config_get_session_size_test() -> myset_env(), {ok, Session=#session{userid=2} } = ts_config_server:get_next_session("localhost"), ?assertEqual(13, Session#session.size). read_config_badpop_test() -> myset_env(), ts_user_server:start([]), {ok, Config} = ts_config:read("./src/test/badpop.xml","."), ?assertMatch({error,[{error,{bad_sum,_,_}}]}, ts_config_server:check_config(Config)). read_config_thinkfirst_test() -> myset_env(), ?assertMatch({ok, Config}, ts_config:read("./src/test/thinkfirst.xml",".")). config_minmax_test() -> myset_env(), {ok, Session=#session{userid=3} } = ts_config_server:get_next_session("localhost"), Id = Session#session.id, ?assertMatch({thinktime,{range,2000,4000}}, ts_config_server:get_req(Id,7)). config_minmax2_test() -> myset_env(), {ok, Session=#session{userid=4} } = ts_config_server:get_next_session("localhost"), Id = Session#session.id, {thinktime, Req} = ts_config_server:get_req(Id,7), Think=ts_client:set_thinktime(Req), Resp = receive Data-> Data end, ?assertMatch({timeout,_,end_thinktime}, Resp). config_thinktime_test() -> myset_env(), ok = ts_config_server:read_config("./examples/thinks.xml"), {ok, Session=#session{userid=5} } = ts_config_server:get_next_session("localhost"), Id = Session#session.id, {thinktime, Req=2000} = ts_config_server:get_req(Id,5), {thinktime, 2000} = ts_config_server:get_req(Id,7), Think=ts_client:set_thinktime(Req), Resp = receive Data-> Data end, ?assertMatch({timeout,_,end_thinktime}, Resp). config_thinktime2_test() -> myset_env(), ok = ts_config_server:read_config("./examples/thinks2.xml"), {ok, Session=#session{userid=6} } = ts_config_server:get_next_session("localhost"), Id = Session#session.id, {thinktime, Req} = ts_config_server:get_req(Id,5), Ref=ts_client:set_thinktime(Req), receive {timeout,Ref2,end_thinktime} -> ok end, random:seed(), % reinit seed for others tests ?assertMatch({random,1000}, Req). config_arrivalrate_test() -> myset_env(), ok = ts_config_server:read_config("./examples/thinks.xml"), {ok, {[Phase1,Phase2, Phase3],_,_} } = ts_config_server:get_client_config("localhost"), RealDur = 10 * 60 * 1000, RealNU = 1200, RealIntensity = 2 / 1000, ?assertEqual({RealIntensity,RealNU,RealDur}, Phase1), ?assertEqual({RealIntensity/60, RealNU div 60,RealDur}, Phase2), ?assertEqual({RealIntensity/3600,12,RealDur*36}, Phase3). config_interarrival_test() -> myset_env(), ok = ts_config_server:read_config("./examples/thinks2.xml"), {ok, {[Phase1,Phase2, Phase3],_,_} } = ts_config_server:get_client_config("localhost"), RealDur = 10 * 60 * 1000, RealNU = 1200, RealIntensity = 2 / 1000, ?assertEqual({RealIntensity,RealNU,RealDur}, Phase1), ?assertEqual({RealIntensity/60,RealNU div 60,RealDur}, Phase2), ?assertEqual({RealIntensity/3600,12,RealDur*36}, Phase3). read_config_maxusers_test() -> read_config_maxusers({5,15},10,"./src/test/thinkfirst.xml"). read_config_maxusers({MaxNumber1,MaxNumber2},Clients,File) -> myset_env(), C=lists:map(fun(A)->"client"++integer_to_list(A) end, lists:seq(1,Clients)), ts_config_server:read_config("./src/test/thinkfirst.xml"), {M1,M2} = lists:unzip(lists:map(fun(X)-> {ok,{[{_,Max,_},{_,Max2,_}],_,_}} = ts_config_server:get_client_config(X), {Max,Max2} end, C)), [Head1|_]=M1, [Head2|_]=M2, ?assertEqual(1, Head1), ?assertEqual(1, Head2), ?assert(lists:min(M1) >= 0), ?assert(lists:min(M2) >= 0), ?assertEqual(lists:sum(M1), MaxNumber1), ?assertEqual(lists:sum(M2), MaxNumber2). read_config_static_test() -> myset_env(), C=lists:map(fun(A)->"client"++integer_to_list(A) end, lists:seq(1,10)), M = lists:map(fun(X)-> {ok,Res,_} = ts_config_server:get_client_config(static,X), ?LOGF("X: ~p~n",[length(Res)],?ERR), length(Res) end, C), ?assertEqual(lists:sum(M) , 4). cport_list_node_test() -> List=['tsung1@toto', 'tsung3@titi', 'tsung2@toto', 'tsung7@titi', 'tsung6@toto', 'tsung4@tutu'], Rep = ts_config_server:get_one_node_per_host(List), ?assertEqual(['tsung1@toto', 'tsung3@titi', 'tsung4@tutu'], lists:sort(Rep)). ifalias_test() -> Res=ts_ip_scan:get_intf_aliases("lo"), ?assertEqual([{127,0,0,1}],Res). ifalias2_test() -> {ok, L}=ts_utils:file_to_list("src/test/ifcfg.out"), Out=ts_ip_scan:get_intf_aliases(L,"eth0",[],[]), Res=lists:foldl(fun(A,L) -> [{192,168,76,A}|L] end, [],lists:seq(183,190)), ?assertEqual(Out,Res). ifalias_ip_test() -> {ok, L}=ts_utils:file_to_list("src/test/ipcfg.out"), Out=ts_ip_scan:get_ip_aliases(L,[]), Res=lists:foldl(fun(A,L) -> [{192,12,0,A}|L] end, [],lists:seq(1,12)), ?assertEqual(Out,Res). encode_test() -> Encoded="ts_encoded_47myfilepath_47toto_47titi_58sdfsdf_45sdfsdf_44aa_47", Str="/myfilepath/toto/titi:sdfsdf-sdfsdf,aa/", ?assertEqual(Encoded,ts_config_server:encode_filename(Str)). decode_test() -> Encoded="ts_encoded_47myfilepath_47toto_47titi_58sdfsdf_45sdfsdf_44aa_47", Str="/myfilepath/toto/titi:sdfsdf-sdfsdf,aa/", ?assertEqual(Str,ts_config_server:decode_filename(Encoded)). concat_atoms_test() -> ?assertEqual('helloworld', ts_utils:concat_atoms(['hello','world'])). int_or_string_test() -> ?assertEqual(123, ts_config:getAttr(integer_or_string,[#xmlAttribute{name=to,value="123"}],to)). int_or_string2_test() -> ?assertEqual("%%_toto%%", ts_config:getAttr(integer_or_string,[#xmlAttribute{name=to,value="%%_toto%%"}],to)). int_test() -> ?assertEqual(100, ts_config:getAttr(integer,[#xmlAttribute{name=to,value="100"}],to)). launcher_empty_test() -> Intensity=10, Users=2, Duration=25, Res=ts_launcher:wait_static({static,0},#launcher{nusers=0,phase_duration=300,phases=[{Intensity,Users,Duration}]}), ?assertMatch({next_state,launcher,#launcher{phases = [], nusers = Users, phase_nusers = Users, phase_duration=Duration, phase_start = _, intensity = Intensity},_},Res). myset_env()-> myset_env(0). myset_env(Level)-> catch ts_user_server_sup:start_link() , application:set_env(stdlib,debug_level,Level), application:set_env(stdlib,warm_time,1000), application:set_env(stdlib,thinktime_value,"5"), application:set_env(stdlib,thinktime_override,"false"), application:set_env(stdlib,thinktime_random,"false"). tsung-1.4.2/src/test/ts_test_stats.erl0000644000201100017670000000123411701017117017514 0ustar nniclausdream%%%------------------------------------------------------------------- %%% File : ts_test_stats.erl %%% Author : Nicolas Niclausse %%% Description : %%% %%% Created : 12 Jul 2011 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_test_stats). -compile(export_all). -include_lib("eunit/include/eunit.hrl"). -include_lib("ts_profile.hrl"). -include_lib("ts_config.hrl"). set_dynvar_random_test() -> Min=1, Max=10, R=lists:map(fun(_)->ts_stats:uniform(Min,Max) end, lists:seq(1,1000)), ?assertEqual(Max,lists:max(R)), ?assertEqual(Min,lists:min(R)). tsung-1.4.2/src/test/ts_test_user_server.erl0000644000201100017670000000630511701017117020726 0ustar nniclausdream%%%------------------------------------------------------------------- %%% File : ts_test_user_server.erl %%% Author : Nicolas Niclausse %%% Description : %%% %%% Created : 20 Mar 2005 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_test_user_server). -compile(export_all). -include_lib("eunit/include/eunit.hrl"). -include_lib("ts_profile.hrl"). -include_lib("ts_config.hrl"). test()-> ok. next_test() -> myset_env(), ts_user_server:start(), ts_user_server:reset(100), ts_user_server:get_idle(), B=ts_user_server:get_idle(), ?assertMatch(B,2). remove_test() -> myset_env(), ts_user_server:start(), ts_user_server:reset(100), 1=ts_user_server:get_idle(), B=ts_user_server:get_idle(), ts_user_server:remove_connected(B), C=ts_user_server:get_idle(), ?assertMatch(C,3). full_offline_test() -> myset_env(), ts_user_server:start(), ts_user_server:reset(3), 1=ts_user_server:get_idle(), 2=ts_user_server:get_idle(), 3=ts_user_server:get_idle(), ?assertMatch({error,no_free_userid},ts_user_server:get_idle()). full_free_offline_test() -> myset_env(), ts_user_server:start(), ts_user_server:reset(3), 1=ts_user_server:get_idle(), B=ts_user_server:get_idle(), 3=ts_user_server:get_idle(), {error,no_free_userid}=ts_user_server:get_idle(), ts_user_server:remove_connected(B), ?assertMatch(B,ts_user_server:get_idle()). full_free_offline_refull_test() -> myset_env(), ts_user_server:start(), ts_user_server:reset(3), A=ts_user_server:get_idle(), B=ts_user_server:get_idle(), 3=ts_user_server:get_idle(), {error,no_free_userid}=ts_user_server:get_idle(), ts_user_server:remove_connected(A), ts_user_server:remove_connected(B), A=ts_user_server:get_idle(), ?assertMatch(B,ts_user_server:get_idle()). full_huge_offline_test() -> myset_env(), ts_user_server:start(), ts_user_server:reset(1000000), A=ts_user_server:get_idle(), ?assertMatch(2,ts_user_server:get_idle()). offline_test() -> myset_env(), ts_user_server:start(), ts_user_server:reset(3), {ok,1}=ts_user_server:get_offline(), {ok,2}=ts_user_server:get_offline(), {ok,3}=ts_user_server:get_offline(), ?assertMatch({ok,1},ts_user_server:get_offline()). offline_full_test() -> myset_env(), ts_user_server:start(), ts_user_server:reset(2), ts_user_server:get_idle(), ts_user_server:get_idle(), ?assertMatch({error,no_offline},ts_user_server:get_offline()). online_test() -> myset_env(), ts_user_server:start(), ts_user_server:reset(3), A=ts_user_server:get_idle(), B=ts_user_server:get_idle(), ts_user_server:add_to_online(A), ts_user_server:add_to_online(B), ?assertMatch({ok, A},ts_user_server:get_online(B)). online_full_test() -> myset_env(), ts_user_server:start(), ts_user_server:reset(10), A=ts_user_server:get_idle(), B=ts_user_server:get_idle(), ts_user_server:add_to_online(3), ?assertMatch({error,no_online},ts_user_server:get_online(B)). myset_env()-> application:set_env(stdlib,debug_level,0). tsung-1.4.2/src/test/ts_test_file_server.erl0000644000201100017670000000540511701017117020667 0ustar nniclausdream%%%------------------------------------------------------------------- %%% File : ts_test_recorder.erl %%% Author : Nicolas Niclausse %%% Description : %%% %%% Created : 20 Mar 2005 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_test_file_server). -compile(export_all). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include_lib("eunit/include/eunit.hrl"). -define(CSVSIZE,10000). test()-> ok. config_file_server1_test()-> myset_env(), ts_file_server:start(), ts_file_server:read([{default,"./src/test/test_file_server.csv"}, {user,"./src/test/test_file_server2.csv"} ]), ?assertMatch({ok,"username1;glop;"}, ts_file_server:get_next_line()). config_file_server2_test()-> myset_env(), ?assertMatch({ok,"username2;;"}, ts_file_server:get_next_line()). config_file_server3_test()-> myset_env(), ?assertMatch({ok,"user1"}, ts_file_server:get_next_line(user)). config_file_server4_test()-> myset_env(), ?assertMatch({ok,"username3;glop4;"}, ts_file_server:get_next_line()). config_file_server_dynfun_test()-> myset_env(), ?assertMatch("username1;glop;", ts_file_server:get_next_line({self(), {}})). config_file_server_huge_test()-> myset_env(), ts_file_server:stop(), ts_file_server:start(), CSV=lists:foldl(fun(I,Acc)-> IStr=integer_to_list(I), [Acc,"user",IStr,";passwd",IStr,"\n"] end, [],lists:seq(1,?CSVSIZE)), File="./src/test/usersdb.csv", file:write_file(File,list_to_binary(CSV)), ts_file_server:read([{default,File}]), {Time, Out } = timer:tc( lists, foreach, [ fun(_)-> ts_file_server:get_random_line() end,lists:seq(1,?CSVSIZE)]), erlang:display([?CSVSIZE," read_file:", Time]), ?assertMatch(ok, Out). config_file_server_cycle_test()-> myset_env(), ts_file_server:stop(), ts_file_server:start(), ts_file_server:read([{default,"./src/test/test_file_server.csv"}]), ts_file_server:get_next_line(), ts_file_server:get_next_line(), ts_file_server:get_next_line(), ?assertMatch({ok,"username1;glop;"}, ts_file_server:get_next_line()). config_file_server_all_test()-> myset_env(), ?assertMatch({ok,["username1;glop;","username2;;","username3;glop4;"]}, ts_file_server:get_all_lines()). file_to_list_test()-> Val = ["username1;glop;","username2;;","username3;glop4;"], ?assertMatch({ok, Val},ts_utils:file_to_list("./src/test/test_file_server.csv")). myset_env()-> application:set_env(stdlib,file_server_timeout,30000), application:set_env(stdlib,debug_level,0), application:set_env(stdlib,thinktime_override,"false"), application:set_env(stdlib,thinktime_random,"false"). tsung-1.4.2/src/test/test_file_server.csv0000644000201100017670000000005511701017117020166 0ustar nniclausdreamusername1;glop; username2;; username3;glop4; tsung-1.4.2/src/test/ts_test_dynvars_api.erl0000644000201100017670000000555611701017117020710 0ustar nniclausdream%% ts_test_dynvars_api.erl %% @author Pablo Polvorin %% @doc Test for the ts_dynvars module %% created on 2008-08-22 -module(ts_test_dynvars_api). -compile(export_all). -include_lib("eunit/include/eunit.hrl"). from_keyval_list(KeyValues) -> lists:foldl(fun({K,V},DynVars) -> ts_dynvars:set(K,V,DynVars) end, ts_dynvars:new(), KeyValues ). test() -> ok. dynvars_new_ok_test() -> Keys = [one,two,three,four], Values = [1,2,"three",4], ?assertEqual([{one,1},{two,2},{three,"three"},{four,4}], ts_dynvars:new(Keys, Values)). dynvars_array_test() -> Keys = [one,two,three,four], Values = [[10,11,12],2,"three",4], DynVars= ts_dynvars:new(Keys, Values), ?assertEqual({ok,[10,11,12]}, ts_dynvars:lookup(one, DynVars)), ?assertEqual({ok,11}, ts_dynvars:lookup({one,2}, DynVars)). dynvars_new_more_test() -> Keys = [one,two,three], Values = [1,2,"three",[]], ?assertEqual([{one,1},{two,2},{three,"three"}], ts_dynvars:new(Keys, Values)). dynvars_new_less_test() -> Keys = [one,two,three,four], Values = [1,2,"three"], ?assertEqual([{one,1},{two,2},{three,"three"},{four,""}], ts_dynvars:new(Keys, Values)). dynvars_set_test() -> KeyValues = [one,two,three,four], DynVars = from_keyval_list([{K,K} || K <- KeyValues]), ?assertEqual([{ok,K} || K <- KeyValues], [ts_dynvars:lookup(Key, DynVars) || Key <- KeyValues]). dynvars_set2_test() -> D = ts_dynvars:set(one,two, ts_dynvars:set(one,one, ts_dynvars:new())), ?assertEqual({ok,two},ts_dynvars:lookup(one,D)). dynvars_undefined_test() -> ?assertEqual(false,ts_dynvars:lookup(one, ts_dynvars:new())). dynvars_default_test() -> ?assertEqual({ok,default},ts_dynvars:lookup(one,ts_dynvars:new(), default)). dynvars_entries_test() -> KeyValues = [{K,K} || K <- [one,two,three,four]], ?assertEqual(lists:reverse(KeyValues), ts_dynvars:entries(from_keyval_list(KeyValues))). dynvars_map_test() -> KeyValues = [{K,K} || K <- [one,two,three,four]], ?assertEqual({ok,[two,two]},ts_dynvars:lookup(two,ts_dynvars:map(fun(X) -> [X,X] end, two, default, from_keyval_list(KeyValues)) )). dynvars_map_default_test() -> KeyValues = [{K,K} || K <- [one,two,three,four]], ?assertEqual({ok,[one]},ts_dynvars:lookup(five, ts_dynvars:map(fun(X) -> [one|X] end, five, [], from_keyval_list(KeyValues)) )). tsung-1.4.2/src/test/ts_test_pgsql.erl0000644000201100017670000001264011701017117017507 0ustar nniclausdream%%%------------------------------------------------------------------- %%% File : ts_test_pgsql.erl %%% Author : Nicolas Niclausse %%% Description : %%% %%% Created : 10 Apr 2008 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_test_pgsql). -compile(export_all). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("ts_pgsql.hrl"). -include("ts_recorder.hrl"). -include_lib("eunit/include/eunit.hrl"). -define(PARSEBIN,<< 115,99,117,49,0,100,101,99,108,97, 114,101,32,115,99,117,49,32,99,117,114,115,111, 114,32,119,105,116,104,32,104,111,108,100,32,102, 111,114,32,115,101,108,101,99,116,32,98,114,110, 95,99,100,44,32,112,114,101,118,95,112,114,95, 100,116,44,32,99,117,114,114,95,112,114,95,100, 116,44,32,110,101,120,116,95,112,114,95,100,116, 44,32,98,114,110,95,110,109,44,32,98,114,110,95, 97,100,100,114,49,44,32,98,114,110,95,97,100,100, 114,50,44,32,98,114,110,95,97,100,100,114,51,44, 32,99,111,109,112,95,110,109,44,32,99,97,115,104, 95,97,99,44,32,105,98,116,95,103,114,112,95,99, 100,44,32,98,97,110,107,95,99,100,44,32,108,111, 103,95,112,97,116,104,44,32,99,111,95,98,114,110, 95,99,100,32,102,114,111,109,32,32,32,98,114,110, 32,32,119,104,101,114,101,32,98,114,110,46,98, 114,110,95,99,100,32,61,32,36,49,0,0,1,0,0,4,18 >>). test()-> ok. utils_md5_test()-> myset_env(), Password="sesame", User="benchmd5", Salt= << 54,195,212,197 >>, Hash= list_to_binary(["md5967c89f451d1d504a1f02fc69fb65cb5",0]), PacketSize= 4+size(Hash), Bin= <<$p,PacketSize:32/integer, Hash/binary>>, ?assertMatch(Bin, pgsql_proto:encode_message(pass_md5, {User,Password,Salt} ) ). extended_test()-> Data= << 80,0,0,0,75,115,99,117,49,0,100,101,99,108,97,114,101,32,115,99,117,49,32,99,117,114, 115,111,114,32,119,105,116,104,32,104,111,108,100,32,102,111,114,32,115,101,108,101, 99,116,32,67,79,85,78,84,40,42,41,32,102,114,111,109,32,32,32,98,114,46,97,104,32,0,0, 0,83,0,0,0,4 >>, Result=ts_proxy_pgsql:process_data(#proxy{},Data), ?assertMatch(#proxy{}, Result). extended2_test()-> Data = <<66,0,0,0,28, 0, 115,99,117,49,0, 0,1, 0,0, 0,1, 0,0,0,4, 78,68,83,66, 0,1,0,0, 68,0,0,0,6,80,0,69,0,0,0,9,0,0,0,0,0,83,0,0,0,4>>, Result=ts_proxy_pgsql:process_data(#proxy{},Data), ?assertMatch(#proxy{}, Result). extended3_test()-> Data = <<0, 99,117,51,0, 0,10, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,10, 0,0,0,26, 50,48,48,56,45,49,50,45,48,53,32,48,57,58,49,57,58,48,48,46,48,48,48,48,48,48, 0,0,0,26, 50,48,49,49,45,48,56,45,50,51,32,48,56,58,52,55,58,48,48,46,48,48,48,48,48,48, 0,0,0,10, 49,51,52,52,51,55,32,32,32,32, 0,0,0,1, 69, 0,0,0,5, 75,78,32,32,32, 0,0,0,35, 75,79,78,84,69,78,65,32,78,65,83,73,79,78,65,76,32,66,72,68,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32, 0,0,0,1, 89, 0,0,0,1, 89, 255,255,255,255, 0,0,0,5, 75,78,32,32,32, 0,1, 0,0 >>, Result=ts_proxy_pgsql:decode_packet($B,Data), Portal = <<>>, Statement = <<"cu3">>, Params= [<<"2008-12-05 09:19:00.000000">>, <<"2011-08-23 08:47:00.000000">>, <<"134437 ">>, <<"E">>, <<"KN ">>, <<"KONTENA NASIONAL BHD ">>, <<"Y">>, <<"Y">>, 'null', <<"KN ">>], Bind = {bind, {Portal, Statement, Params , auto, [text]}}, ?assertEqual(Bind, Result). extended_parse_test()-> Prep = <<"scu1">>, Query = <<"declare scu1 cursor with hold for select brn_cd, prev_pr_dt, curr_pr_dt, next_pr_dt, brn_nm, brn_addr1, brn_addr2, brn_addr3, comp_nm, cash_ac, ibt_grp_cd, bank_cd, log_path, co_brn_cd from brn where brn.brn_cd = $1">>, Result = ts_proxy_pgsql:decode_packet($P,?PARSEBIN), ?assertMatch({parse,{Prep, Query,[1042]}}, Result). %% {ok,Dev}=file:open("/tmp/toto.erl.log",[write]), %% State=#state_rec{logfd=Dev}, %% Rec = #pgsql_request{type=parse, parameters=[1042], name_prepared=Prep, equery=Query}, %% ?assertMatch({ok,State}, ts_proxy_pgsql:record_request(State,Rec)). encode_parse_test()-> Prep = <<"scu1">>, Query = <<"declare scu1 cursor with hold for select brn_cd, prev_pr_dt, curr_pr_dt, next_pr_dt, brn_nm, brn_addr1, brn_addr2, brn_addr3, comp_nm, cash_ac, ibt_grp_cd, bank_cd, log_path, co_brn_cd from brn where brn.brn_cd = $1">>, Bin=?PARSEBIN, Res= << 80,0,0,0,234,Bin/binary>>, Rep=pgsql_proto:encode_message(parse,{Prep,Query,[1042]}), ?assertEqual(Res,Rep). encode_parse2_test()-> Rep=pgsql_proto:encode_message(parse,{<< >>,<< >>,[]}), ?assertEqual( << 80,0,0,0,8,0,0,0,0 >> ,Rep). myset_env()-> myset_env(0). myset_env(Val)-> application:set_env(stdlib,debug_level,Val). tsung-1.4.2/src/test/ifcfg.out0000644000201100017670000000425711701017117015724 0ustar nniclausdreameth0 Link encap:Ethernet HWaddr 68:B5:99:79:71:5C inet addr:192.168.76.183 Bcast:192.168.79.255 Mask:255.255.248.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:2853444 errors:0 dropped:0 overruns:0 frame:0 TX packets:1157524 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:342116836 (326.2 MiB) TX bytes:147190992 (140.3 MiB) Interrupt:122 Memory:fb000000-fb7fffff eth0:0 Link encap:Ethernet HWaddr 68:B5:99:79:71:5C inet addr:192.168.76.184 Bcast:192.168.79.255 Mask:255.255.248.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 Interrupt:122 Memory:fb000000-fb7fffff eth0:1 Link encap:Ethernet HWaddr 68:B5:99:79:71:5C inet addr:192.168.76.185 Bcast:192.168.79.255 Mask:255.255.248.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 Interrupt:122 Memory:fb000000-fb7fffff eth0:2 Link encap:Ethernet HWaddr 68:B5:99:79:71:5C inet addr:192.168.76.186 Bcast:192.168.79.255 Mask:255.255.248.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 Interrupt:122 Memory:fb000000-fb7fffff eth0:3 Link encap:Ethernet HWaddr 68:B5:99:79:71:5C inet addr:192.168.76.187 Bcast:192.168.79.255 Mask:255.255.248.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 Interrupt:122 Memory:fb000000-fb7fffff eth0:4 Link encap:Ethernet HWaddr 68:B5:99:79:71:5C inet addr:192.168.76.188 Bcast:192.168.79.255 Mask:255.255.248.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 Interrupt:122 Memory:fb000000-fb7fffff eth0:5 Link encap:Ethernet HWaddr 68:B5:99:79:71:5C inet addr:192.168.76.189 Bcast:192.168.79.255 Mask:255.255.248.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 Interrupt:122 Memory:fb000000-fb7fffff eth0:6 Link encap:Ethernet HWaddr 68:B5:99:79:71:5C inet addr:192.168.76.190 Bcast:192.168.79.255 Mask:255.255.248.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 Interrupt:122 Memory:fb000000-fb7fffff tsung-1.4.2/src/test/ipcfg.out0000644000201100017670000000106611701017117015731 0ustar nniclausdream5: eth0 inet 192.12.0.1/32 scope global eth0 5: eth0 inet 192.12.0.2/32 scope global eth0:0 5: eth0 inet 192.12.0.3/32 scope global eth0:1 5: eth0 inet 192.12.0.4/32 scope global eth0:2 5: eth0 inet 192.12.0.5/32 scope global eth0:3 5: eth0 inet 192.12.0.6/32 scope global eth0:4 5: eth0 inet 192.12.0.7/32 scope global eth0:5 5: eth0 inet 192.12.0.8/32 scope global eth0:6 5: eth0 inet 192.12.0.9/32 scope global eth0:7 5: eth0 inet 192.12.0.10/32 scope global eth0:8 5: eth0 inet 192.12.0.11/32 scope global eth0:9 5: eth0 inet 192.12.0.12/32 scope global eth0:10 tsung-1.4.2/src/test/test_file_server2.csv0000644000201100017670000000000611701017117020244 0ustar nniclausdreamuser1 tsung-1.4.2/src/test/ts_test_match.erl0000644000201100017670000001141711701017117017456 0ustar nniclausdream%%%------------------------------------------------------------------- %%% File : ts_test_search.erl %%% Author : Nicolas Niclausse %%% Description : unit tests for ts_search module %%% %%% $Id: ts_test_search.erl 904 2008-10-08 08:16:38Z nniclausse $ %%%------------------------------------------------------------------- -module(ts_test_match). -compile(export_all). -include_lib("eunit/include/eunit.hrl"). -include_lib("ts_profile.hrl"). -include_lib("ts_config.hrl"). -define(MAX_COUNT,42). -define(COUNT,5). -define(USER_ID,2). -define(SESSION_ID,1). -define(COUNTS,{5,42,2,1}). test()-> ok. match_abort_ok_test() -> myset_env(), Data="C'est n'est pas une chaine de caractere", ?assertMatch(?COUNT, ts_search:match([#match{regexp="Erreur", do=abort, 'when'=match}],Data, ?COUNTS,[])). match_abort_nok_test() -> myset_env(), Data="Ceci est une Erreur", ?assertMatch(0, ts_search:match([#match{regexp="Erreur", do=abort, 'when'=match}],Data, ?COUNTS,[])). nomatch_abort_ok_test() -> myset_env(), Data="C'est n'est pas une chaine de caractere", ?assertMatch(0, ts_search:match([#match{regexp="Erreur", do=abort, 'when'=nomatch}],Data, ?COUNTS,[])). nomatch_abort_nok_test() -> myset_env(), Data="Ceci est une Erreur", ?assertMatch(?COUNT, ts_search:match([#match{regexp="Erreur", do=abort, 'when'=nomatch}],Data, ?COUNTS,[])). nomatch_continue_ok_test() -> myset_env(), Data="C'est n'est pas une chaine de caractere", ?assertMatch(?COUNT, ts_search:match([#match{regexp="Erreur", do=continue, 'when'=nomatch}],Data, ?COUNTS,[])). nomatch_continue_nok_test() -> myset_env(), Data="Ceci est une Erreur", ?assertMatch(?COUNT, ts_search:match([#match{regexp="Erreur", do=continue, 'when'=nomatch}],Data, ?COUNTS,[])). match_continue_ok_test() -> myset_env(), Data="C'est n'est pas une chaine de caractere", ?assertMatch(?COUNT, ts_search:match([#match{regexp="Erreur", do=continue, 'when'=match}],Data, ?COUNTS,[])). match_continue_nok_test() -> myset_env(), Data="Ceci est une Erreur", ?assertMatch(?COUNT, ts_search:match([#match{regexp="Erreur", do=continue, 'when'=match}],Data, ?COUNTS,[])). nomatch_loop_ok_test() -> myset_env(), Data="C'est n'est pas une chaine de caractere", ?assertMatch(?COUNT+1, ts_search:match([#match{regexp="Erreur", do=loop, max_loop=?COUNT, loop_back=0, sleep_loop=1,'when'=nomatch}],Data, ?COUNTS,[])). nomatch_loop_nok_test() -> myset_env(), Data="Ceci est une Erreur", ?assertMatch(?COUNT, ts_search:match([#match{regexp="Erreur", do=loop, max_loop=?COUNT, loop_back=0, sleep_loop=1,'when'=nomatch}],Data, ?COUNTS,[])). match_loop_ok_test() -> myset_env(), Data="C'est n'est pas une chaine de caractere", ?assertMatch(?COUNT, ts_search:match([#match{regexp="Erreur", do=loop, max_loop=?COUNT, loop_back=0, sleep_loop=1,'when'=match}],Data, ?COUNTS,[])). match_loop_nok_test() -> myset_env(), Data="Ceci est une Erreur", ?assertMatch(?COUNT+1, ts_search:match([#match{regexp="Erreur", do=loop, max_loop=?COUNT, loop_back=0, sleep_loop=1, 'when'=match}],Data, ?COUNTS,[])). nomatch_restart_ok_test() -> myset_env(), Data="C'est n'est pas une chaine de caractere", ?assertMatch(?MAX_COUNT, ts_search:match([#match{regexp="Erreur", do=restart, max_restart=?COUNT,'when'=nomatch}],Data, ?COUNTS,[])). nomatch_restart_nok_test() -> myset_env(), Data="Ceci est une Erreur", ?assertMatch(?COUNT, ts_search:match([#match{regexp="Erreur", do=restart, max_restart=?COUNT,'when'=nomatch}],Data, ?COUNTS,[])). match_restart_ok_test() -> myset_env(), Data="C'est n'est pas une chaine de caractere", ?assertMatch(?COUNT, ts_search:match([#match{regexp="Erreur", do=restart, max_restart=?COUNT,'when'=match}],Data, ?COUNTS,[])). match_restart_nok_test() -> myset_env(), Data="Ceci est une Erreur", ?assertMatch(?MAX_COUNT, ts_search:match([#match{regexp="Erreur", do=restart, max_restart=?COUNT, 'when'=match}],Data, ?COUNTS,[])). match_subst_undef_test() -> myset_env(), Data="Ceci est une Erreur", ?assertMatch(?COUNT, ts_search:match([#match{regexp="%%_mydynvar%%", do=restart, subst=true, 'when'=match}],Data, ?COUNTS,[])). match_subst_undef2_test() -> myset_env(), Data="Ceci est une Erreur", ?assertMatch(?COUNT, ts_search:match([#match{regexp="ttt%%_mydynvar%%", do=restart, subst=true, 'when'=match}],Data, ?COUNTS,[])). match_subst_test() -> myset_env(), Data="Ceci est une Erreur ", Dynvar=ts_dynvars:new(mydynvar,"Erreur"), ?assertMatch(?MAX_COUNT, ts_search:match([#match{regexp="%%_mydynvar%%", do=restart, subst=true, 'when'=match}],Data, ?COUNTS,Dynvar)). myset_env()-> myset_env(0). myset_env(Level)-> application:set_env(stdlib,debug_level,Level). tsung-1.4.2/src/test/ts_test_options.erl0000644000201100017670000000127511701017117020056 0ustar nniclausdream%% ts_test_rate.erl %% @author Nicolas Niclausse %% @doc Test for options like rate limiting feature %% created on 2011-03-14 -module(ts_test_options). -compile(export_all). -include_lib("eunit/include/eunit.hrl"). test() -> ok. rate1_test() -> R=10000 div 1000, B=15000, T0={0,0,0}, T1={0,10,0}, P1=14000, Res = ts_client:token_bucket(R,B,0,T0,P1,T1,false), ?assertEqual({1000,0},Res). rate2_test() -> R=10000 div 1000, B=15000, T0={0,0,0}, T1={0,10,0}, T2={0,11,0}, P1=14000, P2=14000, {S2,0} = ts_client:token_bucket(R,B,0,T0,P1,T1,false), Res = ts_client:token_bucket(R,B,S2,T1,P2,T2,false), ?assertEqual({0,300},Res). tsung-1.4.2/src/test/ts_test_jabber.erl0000644000201100017670000001342411701017117017607 0ustar nniclausdream%%% %%% Copyright Nicolas Niclausse 2007 %%% %%% Author : Nicolas Niclausse %%% Created: 17 Mar 2007 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% -module(ts_test_jabber). -vc('$Id$ '). -author('Nicolas.Niclausse@niclux.org'). -compile(export_all). -include("ts_profile.hrl"). -include("ts_jabber.hrl"). -include_lib("eunit/include/eunit.hrl"). test()->ok. bidi_subscribeok_test()-> myset_env(), Req=list_to_binary(" Hi dude. "), Resp=list_to_binary(""), State=#state_rcv{}, ?assertMatch({Resp,State}, ts_jabber:parse_bidi(Req,State)). bidi_multisubscribeok_test()-> myset_env(), Req=list_to_binary(" Hi dude. Copaing?."), Resp=list_to_binary(""), State=#state_rcv{}, ?assertMatch({Resp,State}, ts_jabber:parse_bidi(Req,State)). bidi_multisubscribe_nok_test()-> myset_env(), Req=list_to_binary(" Hi dude. Copaing?."), Resp=list_to_binary(""), State=#state_rcv{}, ?assertMatch({Resp,State}, ts_jabber:parse_bidi(Req,State)). bidi_subscribe_nok_test()-> myset_env(), Req=list_to_binary(" Hi dude. "), State=#state_rcv{}, ?assertMatch({nodata,State}, ts_jabber:parse_bidi(Req,State)). bidi_nok_test()-> myset_env(), Req=list_to_binary("Alive."), State=#state_rcv{}, ?assertMatch({nodata,State}, ts_jabber:parse_bidi(Req,State)). auth_sasl_test()-> myset_env(), Res = << "AGp1bGlldAByMG0zMG15cjBtMzA=" >>, ?assertMatch(Res, ts_jabber_common:auth_sasl("juliet","r0m30myr0m30","PLAIN")). choose_id_user_test()-> ?assertEqual({user_defined,"user","pwd"}, ts_jabber:choose_or_cache_user_id(user_defined,"user","pwd")). choose_id_user1_test()-> ts_jabber:choose_or_cache_user_id(user_defined,"user","pwd"), ?assertEqual({user_defined,"user","pwd"}, ts_jabber:choose_or_cache_user_id(0,"user","pwd")). choose_id2_test()-> erase(xmpp_user_id), ts_user_server:start(), ts_user_server:reset(100), ?assertEqual({1,"user","pwd"}, ts_jabber:choose_or_cache_user_id(0,"user","pwd")). choose_id3_test()-> erase(xmpp_user_id), ts_jabber:choose_or_cache_user_id(0,"user","pwd"), ?assertEqual({2,"user","pwd"}, ts_jabber:choose_or_cache_user_id(0,"user","pwd")). add_dynparams_test()-> erase(xmpp_user_id), Session = #jabber{id=0,username="foo",passwd="bar",domain={domain,"localdomain"}}, ?assertEqual(Session#jabber{id=3,user_server=default,domain="localdomain"}, ts_jabber:add_dynparams(true,[],Session,"localhost")). add_dynparams2_test()-> Session = #jabber{id=0,username="foo",passwd="bar",domain={domain,"localdomain"}}, ?assertEqual(Session#jabber{id=3,user_server=default,domain="localdomain"}, ts_jabber:add_dynparams(true,[],Session,"localhost")). get_message_test()-> erase(xmpp_user_id), ts_msg_server:start(), Session = #jabber{id=0,username="foo",type='auth_set_plain',passwd="bar",domain={domain,"localdomain"}}, Req=ts_jabber:add_dynparams(false,[],Session,"localhost"), RepOK={<<"foo4tsungbar4" >>,undefined}, Rep=ts_jabber:get_message(Req,#state_rcv{}), ?assertEqual(RepOK,Rep ). get_message2_test()-> erase(xmpp_user_id), Session = #jabber{id=user_defined,username="foo",type='auth_set_plain',passwd="bar",domain={domain,"localdomain"}}, Req=ts_jabber:add_dynparams(false,[],Session,"localhost"), RepOK={<<"footsungbar" >>,undefined}, Rep=ts_jabber:get_message(Req,#state_rcv{}), ?assertEqual(RepOK,Rep ). choose_id_limit_test()-> ts_user_server:reset(3), erase(xmpp_user_id), ts_jabber:choose_or_cache_user_id(0,"user","pwd"), erase(xmpp_user_id), ts_jabber:choose_or_cache_user_id(0,"user","pwd"), erase(xmpp_user_id), ts_jabber:choose_or_cache_user_id(0,"user","pwd"), erase(xmpp_user_id), ?assertExit(no_free_userid, ts_jabber:choose_or_cache_user_id(0,"user","pwd")). myset_env()-> myset_env(0). myset_env(Val)-> application:set_env(stdlib,debug_level,Val). tsung-1.4.2/src/test/xmpp-muc.xml.in0000644000201100017670000001101411701017117016777 0ustar nniclausdream tsung-1.4.2/src/test/ts_test_proxy.erl0000644000201100017670000001650411701017117017545 0ustar nniclausdream%%%------------------------------------------------------------------- %%% File : ts_test_recorder.erl %%% Author : Nicolas Niclausse %%% Description : %%% %%% Created : 20 June 2007 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_test_proxy). -compile(export_all). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("ts_http.hrl"). -include("ts_recorder.hrl"). -include_lib("eunit/include/eunit.hrl"). test()-> ok. relative_url_test()-> myset_env(), String= "foo http://www.glop.com/bar/foo.html foo bar", AbsURI="http://www.glop.com/bar/foo.html?toto=bar", RelURL="/bar/foo.html?toto=bar", ?assertMatch({ok,"foo /bar/foo.html foo bar"}, ts_proxy_http:relative_url(false,String,AbsURI,RelURL)). relative_url2_test()-> myset_env(), String= "foo http://www.glop.com/(;-)/foo.html foo bar", AbsURI="http://www.glop.com/(;-)/foo.html?toto=bar", RelURL="/(;-)/foo.html?toto=bar", ?assertMatch({ok,"foo /(;-)/foo.html foo bar"}, ts_proxy_http:relative_url(false,String,AbsURI,RelURL)). rewrite_http_none_test()-> myset_env(), Data="HTTP/1.1 200 OK Server: Apache/2.0.46 (White Box) Content-Length: 30

http://foo.bar/toto1

", ?assertMatch({ok,Data}, ts_utils:from_https(Data)). rewrite_http_test()-> myset_env(), Data="HTTP/1.1 200 OK Server: Apache/2.0.46 (White Box) Content-Length: 30

https://foo.bar/toto

", NewData="HTTP/1.1 200 OK Server: Apache/2.0.46 (White Box) Content-Length: 30

http://-foo.bar/toto

", {ok,Res}=ts_utils:from_https(Data), ?assertEqual(list_to_binary(NewData),iolist_to_binary(Res) ). rewrite_http_location_test()-> myset_env(), Data="HTTP/1.1 200 OK Server: Apache/2.0.46 (White Box) Location: https://foo.bar/ Content-Length: 30

https://foo.bar/toto or https://foo.bar/glop

", NewData="HTTP/1.1 200 OK Server: Apache/2.0.46 (White Box) Location: http://-foo.bar/ Content-Length: 30

http://-foo.bar/toto or http://-foo.bar/glop

", {ok, Res}=ts_utils:from_https(Data), ?assertEqual(list_to_binary(NewData),iolist_to_binary(Res) ). rewrite_http_location_nourl_test()-> myset_env(), Data="HTTP/1.1 200 OK Server: Apache/2.0.46 (White Box) Location: https://foo.bar/ Content-Length: 30 ", NewData="HTTP/1.1 200 OK Server: Apache/2.0.46 (White Box) Location: http://-foo.bar/ Content-Length: 30 ", {ok, Res} = ts_utils:from_https(Data), ?assertEqual(list_to_binary(NewData), iolist_to_binary(Res)). rewrite_http_encode_test()-> myset_env(), Data="GET http://-foobar.foo42.fr/ HTTP/1.1\r\nHost: -foobar.foo42.fr\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7\r\n\r\n", NewData="GET https://foobar.foo42.fr/ HTTP/1.1\r\nHost: foobar.foo42.fr\r\nAccept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7\r\n\r\n", {ok, Res} = ts_utils:to_https({request,Data}), ?assertEqual(list_to_binary(NewData), iolist_to_binary(Res)). rewrite_http_encode2_test()-> myset_env(), Data="GET http://gforge-qualif.foo.fr/ HTTP/1.1\r\nHost: gforge-qualif.foo.fr\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.1.6) Gecko/20100107 Fedora/3.5.6-1.fc12 Firefox/3.5.6\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: fr,en-us;q=0.7,en;q=0.3\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nProxy-Connection: keep-alive\r\nPragma: no-cache\r\nCache-Control: no-cache\r\n\r\n", NewData="GET http://gforge-qualif.foo.fr/ HTTP/1.1\r\nHost: gforge-qualif.foo.fr\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.1.6) Gecko/20100107 Fedora/3.5.6-1.fc12 Firefox/3.5.6\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: fr,en-us;q=0.7,en;q=0.3\r\nAccept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nProxy-Connection: keep-alive\r\nPragma: no-cache\r\nCache-Control: no-cache\r\n\r\n", {ok, Res} = ts_utils:to_https({request,Data}), ?assertEqual(list_to_binary(NewData), iolist_to_binary(Res)). rewrite_http_encode3_test()-> myset_env(), Data="GET http://-secure.foo.com/ HTTP/1.1\r\nHost: -secure.com\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:2.0b9) Gecko/20100101 Firefox/4.0b9\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: fr,en-us;q=0.7,en;q=0.3\r\nAccept-Encoding: gzip, deflate\r\nAccept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 115\r\nProxy-Connection: keep-alive\r\n\r\n", NewData="GET https://secure.foo.com/ HTTP/1.1\r\nHost: secure.com\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:2.0b9) Gecko/20100101 Firefox/4.0b9\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: fr,en-us;q=0.7,en;q=0.3\r\nAccept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 115\r\nProxy-Connection: keep-alive\r\n\r\n", {ok, Res} = ts_utils:to_https({request,Data}), ?assertEqual(list_to_binary(NewData), iolist_to_binary(Res)). rewrite_webdav_test()-> myset_env(), Data = "REPORT /tsung/!svn/vcc/default HTTP/1.1\r\nUser-Agent: SVN/1.4.4 (r25188) neon/0.25.5\r\nConnection: TE\r\nTE: trailers\r\nContent-Length: 172\r\nContent-Type: text/xml\r\nAccept-Encoding: svndiff1;q=0.9,svndiff;q=0.8\r\nAccept-Encoding: gzip\r\nAccept-Encoding: gzip\r\n\r\nhttp://-svn.process-one.net/tsung/trunk/examples", NewData="REPORT /tsung/!svn/vcc/default HTTP/1.1\r\nUser-Agent: SVN/1.4.4 (r25188) neon/0.25.5\r\nConnection: TE\r\nTE: trailers\r\nContent-Length: 172\r\nContent-Type: text/xml\r\nAccept-Encoding: svndiff1;q=0.9,svndiff;q=0.8\r\n\r\nhttps://svn.process-one.net/tsung/trunk/examples", {ok, Res} = ts_utils:to_https({request,Data}), ?assertEqual(list_to_binary(NewData), iolist_to_binary(Res)). rewrite_http_encode_post_test()-> myset_env(), Data="POST http://-foobar.foo42.fr/ HTTP/1.1\r\nHost: -foobar.foo42.fr\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-15,utf-8;q=0.7,;q=0.7Content-Type: application/x-www-form-urlencoded\r\nContent-Length: 24\r\n\r\nuname=admin&upass=*****", NewData="POST https://foobar.foo42.fr/ HTTP/1.1\r\nHost: foobar.foo42.fr\r\nAccept-Charset: ISO-8859-15,utf-8;q=0.7,;q=0.7Content-Type: application/x-www-form-urlencoded\r\nContent-Length: 24\r\n\r\nuname=admin&upass=*****", {ok,Res}=ts_utils:to_https({request,Data}), ?assertEqual(list_to_binary(NewData),iolist_to_binary(Res)). %% parse_http_test()-> %% myset_env(), %% ?assertMatch({ok,""}, %% ts_proxy_http:parse(State,ClientSocket,Socket,Data)). myset_env(Val)-> application:set_env(stdlib,debug_level,Val). myset_env()-> myset_env(0). tsung-1.4.2/src/test/ts_test_http.erl0000644000201100017670000002601711701017117017343 0ustar nniclausdream%%% %%% Copyright Nicolas Niclausse 2007 %%% %%% Author : Nicolas Niclausse %%% Created: 17 Mar 2007 by Nicolas Niclausse %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by %%% the Free Software Foundation; either version 2 of the License, or %%% (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. %%% -module(ts_test_http). -vc('$Id: ts_test_jabber.erl 768 2007-11-15 11:01:01Z mremond $ '). -author('Nicolas.Niclausse@niclux.org'). -compile(export_all). -include("ts_profile.hrl"). -include("ts_http.hrl"). -include("ts_config.hrl"). -include_lib("eunit/include/eunit.hrl"). test()->ok. ipv6_url_test() -> URL=ts_config_http:parse_URL("http://[2178:2:5:0:28f:0:3]:8080/toto.php?titi=[43]"), ?assertMatch(#url{path="/toto.php",port=8080,host="2178:2:5:0:28f:0:3",scheme=http}, URL). ipv6_url2_test() -> S=ts_config_http:server_to_url(#server{host="2178:2:5:0:28f:0:3",port=80,type=gen_tcp} ), ?assertEqual("http://[2178:2:5:0:28f:0:3]", S). ipv6_url3_test() -> S=ts_config_http:server_to_url(#server{host="[2178:2:5:0:28f:0:3]",port=80,type=gen_tcp} ), ?assertEqual("http://[2178:2:5:0:28f:0:3]", S). ipv6_url4_test() -> S=ts_config_http:server_to_url(#server{host="2178:2:5:0:28f:0:3",port=8080,type=gen_tcp} ), ?assertEqual("http://[2178:2:5:0:28f:0:3]:8080", S). ipv4_url_test() -> URL=ts_config_http:parse_URL("http://127.0.0.1:8080/"), ?assertMatch(#url{path="/",port=8080,host="127.0.0.1",scheme=http}, URL). subst_url_test() -> DynVars=ts_dynvars:new('image', "/images/my image with spaces.png"), Req=ts_http:subst(#http_request{url="%%_image%%"}, DynVars), ?assertEqual("/images/my%20image%20with%20spaces.png", Req#http_request.url). subst_redirect_test()-> myset_env(), URL="%%_redirect%%", Cookie="toto=bar; path=/; domain=erlang.org", Cookies=ts_http_common:add_new_cookie(Cookie,"erlang.org",[]), Proto=#http_dyndata{cookies=Cookies,user_agent="Firefox"}, DynVars=ts_dynvars:new(redirect,"http://erlang.org/bidule/truc"), {Req,_}=ts_http:add_dynparams(true,#dyndata{proto=Proto,dynvars=DynVars}, #http_request{url=URL}, {"erlang.org",80,gen_tcp}), Str="GET /bidule/truc HTTP/1.1\r\nHost: erlang.org\r\nUser-Agent: Firefox\r\nCookie: toto=bar\r\n\r\n", {Res,_}=ts_http:get_message(Req,#state_rcv{}), ?assertMatch(Str, binary_to_list(Res)). subst_redirect_proto_test()-> myset_env(), URL="%%_redirect%%", Cookie="toto=bar; path=/; domain=erlang.org", Cookies=ts_http_common:add_new_cookie(Cookie,"erlang.org",[]), Proto=#http_dyndata{cookies=Cookies,user_agent="Firefox"}, DynVars=ts_dynvars:new(redirect,"http://erlang.org/bidule/truc"), Rep=ts_http:add_dynparams(true,#dyndata{proto=Proto,dynvars=DynVars}, #http_request{url=URL}, {"erlang.org",80,gen_tcp6}), ?assertMatch({_,{"erlang.org",80,gen_tcp6}}, Rep). cookie_subdomain_test()-> myset_env(), URL="/bidule/truc", Cookie="toto=bar; path=/; domain=.domain.org", Cookies=ts_http_common:add_new_cookie(Cookie,"domain.org",[]), Proto=#http_dyndata{cookies=Cookies,user_agent="Firefox"}, DynVars=ts_dynvars:new(), Req=ts_http:add_dynparams(false,#dyndata{proto=Proto,dynvars=DynVars}, #http_request{url=URL}, {"www.domain.org",80,gen_tcp}), Str="GET /bidule/truc HTTP/1.1\r\nHost: www.domain.org:80\r\nUser-Agent: Firefox\r\nCookie: toto=bar\r\n\r\n", {Res,_}=ts_http:get_message(Req,#state_rcv{}), ?assertEqual(Str, binary_to_list(Res)). cookie_dotdomain_test()-> myset_env(), URL="/bidule/truc", Cookie="toto=bar; path=/; domain=.www.domain.org", Cookies=ts_http_common:add_new_cookie(Cookie,"www.domain.org",[]), Proto=#http_dyndata{cookies=Cookies,user_agent="Firefox"}, DynVars=ts_dynvars:new(), Req=ts_http:add_dynparams(false,#dyndata{proto=Proto,dynvars=DynVars}, #http_request{url=URL}, {"www.domain.org",80, gen_tcp}), Str="GET /bidule/truc HTTP/1.1\r\nHost: www.domain.org:80\r\nUser-Agent: Firefox\r\nCookie: toto=bar\r\n\r\n", {Res,_}=ts_http:get_message(Req,#state_rcv{}), ?assertEqual(Str, binary_to_list(Res)). add_cookie_samekey_samedomain_test()-> myset_env(), Cookie1="RMID=732423sdfs73242; path=/; domain=.example.net", Cookie2="RMID=42; path=/; domain=.example.net", Val1=#cookie{key="RMID",value="732423sdfs73242",domain=".example.net",path="/"}, Val2=#cookie{key="RMID",value="42",domain=".example.net",path="/"}, Cookies=ts_http_common:add_new_cookie(Cookie1,"foobar.com",[]), %% same domain, second cookie should erase the first one Res=ts_http_common:add_new_cookie(Cookie2,"foobar.com",Cookies), ?assertMatch([Val2],Res). add_cookie_replace_key_default_domain_test()-> myset_env(), Cookie1="RMID=732423sdfs73242; path=/; ", Cookie2="RMID=42; path=/; domain=.example.net", Val2=#cookie{key="RMID",value="42",domain=".example.net",path="/"}, Cookies=ts_http_common:add_new_cookie(Cookie1,"example.net",[]), %% same domain, second cookie should erase the first one Res=ts_http_common:add_new_cookie(Cookie2,"foobar.com",Cookies), ?assertEqual([Val2],Res). set_cookie_test()-> myset_env(), Cookie="RMID=732423sdfs73242; path=/; domain=.foobar.com", Val="Cookie: RMID=732423sdfs73242\r\n", Cookies=ts_http_common:add_new_cookie(Cookie,"www.foobar.com",[]), ?assertEqual(Val,lists:flatten(ts_http_common:set_cookie_header({Cookies,"www.foobar.com","/toto.html"}))). add_cookie_test()-> myset_env(), Cookie1="RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net", Cookie2="ID=42; path=/; domain=.example.net", Val1=#cookie{key="RMID",value="732423sdfs73242",domain=".example.net",path="/",expires="Fri, 31-Dec-2010 23:59:59 GMT"}, Val2=#cookie{key="ID",value="42",domain=".example.net",path="/"}, Cookies=ts_http_common:add_new_cookie(Cookie1,"foobar.com",[]), ?assertEqual([Val2,Val1],ts_http_common:add_new_cookie(Cookie2,"foobar.com",Cookies)). add_cookie_samekey_nodomain_test()-> myset_env(), Cookie1="RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net", Cookie2="RMID=42; path=/; domain=.foobar.net", Val1=#cookie{key="RMID",value="732423sdfs73242",domain=".example.net",path="/",expires="Fri, 31-Dec-2010 23:59:59 GMT"}, Val2=#cookie{key="RMID",value="42",domain=".foobar.net",path="/"}, Cookies=ts_http_common:add_new_cookie(Cookie1,"foobar.com",[]), %% two different domains, two cookies ?assertEqual([Val2,Val1],ts_http_common:add_new_cookie(Cookie2,"foobar.com",Cookies)). add_cookie_samekey_nodomain_req_test()-> myset_env(), URL="/bidule/truc", Cookie1="RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net", Cookie2="RMID=42; path=/; domain=.foobar.net", Cookies1=ts_http_common:add_new_cookie(Cookie1,"",[]), Cookies = ts_http_common:add_new_cookie(Cookie2,"",Cookies1), Proto=#http_dyndata{cookies=Cookies,user_agent="Firefox"}, DynVars=ts_dynvars:new(), Req=ts_http:add_dynparams(false,#dyndata{proto=Proto,dynvars=DynVars}, #http_request{url=URL}, {"www.foobar.net",80, gen_tcp}), Str="GET /bidule/truc HTTP/1.1\r\nHost: www.foobar.net:80\r\nUser-Agent: Firefox\r\nCookie: RMID=42\r\n\r\n", {Res,_}=ts_http:get_message(Req,#state_rcv{}), ?assertEqual(Str, binary_to_list(Res)). chunk_header_ok1_test()-> Rep=ts_http_common:parse_line("transfer-encoding: chunked\r\n",#http{},[]), ?assertMatch(#http{chunk_toread=0}, Rep). chunk_header_ok2_test()-> Rep=ts_http_common:parse_line("transfer-encoding: Chunked\r\n",#http{},[]), ?assertMatch(#http{chunk_toread=0}, Rep). chunk_header_ok3_test()-> Rep=ts_http_common:parse_line("transfer-encoding:chunked\r\n",#http{},[]), ?assertMatch(#http{chunk_toread=0}, Rep). chunk_header_bad_test()-> Rep=ts_http_common:parse_line("transfer-encoding: cheddar\r\n",#http{},[]), ?assertMatch(#http{chunk_toread=-1}, Rep). split_body_test() -> Data = << "HTTP header\r\nHeader: value\r\n\r\nbody\r\n" >>, ?assertEqual({<< "HTTP header\r\nHeader: value" >>, << "body\r\n" >>}, ts_http:split_body(Data)). split_body2_test() -> Data = << "HTTP header\r\nHeader: value\r\n\r\nbody\r\n\r\nnewline in body\r\n" >>, ?assertEqual({<< "HTTP header\r\nHeader: value" >>, << "body\r\n\r\nnewline in body\r\n" >>}, ts_http:split_body(Data)). split_body3_test() -> Data = << "HTTP header\r\nHeader: value\r\nTransfer-Encoding: chunked\r\n\r\n19\r\nbody\r\n\r\nnewline in body\r\n\r\n" >>, ?assertEqual({<< "HTTP header\r\nHeader: value\r\nTransfer-Encoding: chunked" >>, << "19\r\nbody\r\n\r\nnewline in body\r\n\r\n" >>}, ts_http:split_body(Data)). decode_buffer_test() -> Data = << "HTTP header\r\nHeader: value\r\nTransfer-Encoding: chunked\r\n\r\n19\r\nbody\r\n\r\nnewline in body\r\n0\r\n\r\n" >>, ?assertEqual(<< "HTTP header\r\nHeader: value\r\nTransfer-Encoding: chunked\r\n\r\nbody\r\n\r\nnewline in body\r\n" >>, ts_http:decode_buffer(Data, #http{chunk_toread=-2})). decode_buffer2_test() -> Data = << "HTTP header\r\nHeader: value\r\n\r\nbody\r\n\r\nnewline in body\r\n" >>, ?assertEqual(<< "HTTP header\r\nHeader: value\r\n\r\nbody\r\n\r\nnewline in body\r\n" >>, ts_http:decode_buffer(Data, #http{chunk_toread=-1}) ). decode_buffer3_test() -> Data = << "HTTP header\r\nHeader: value\r\nTransfer-Encoding: chunked\r\n\r\n17\r\nbody\r\n\r\nnewline in body\r\n3\r\nabc\r\n0\r\n\r\n" >>, ?assertEqual(<< "HTTP header\r\nHeader: value\r\nTransfer-Encoding: chunked\r\n\r\nbody\r\n\r\nnewline in bodyabc" >>, ts_http:decode_buffer(Data, #http{chunk_toread=-2})). compress_chunk_test()-> <> = zlib:gzip("sesame ouvre toi"), Data1 = << "HTTP header\r\nHeader: value\r\nTransfer-Encoding: chunked\r\n\r\nA\r\n" >>, Data2= <<"1A\r\n" >>, Data3= <<"0\r\n\r\n" >>, Data= <>, ?assertEqual(<< "HTTP header\r\nHeader: value\r\nTransfer-Encoding: chunked\r\n\r\nsesame ouvre toi" >>, ts_http:decode_buffer(Data, #http{chunk_toread=-2, compressed={false,gzip}})). myset_env()-> myset_env(0). myset_env(N)-> application:set_env(stdlib,debug_level,N). tsung-1.4.2/src/test/ts_test_mon.erl0000644000201100017670000000643011701017117017152 0ustar nniclausdream%%%------------------------------------------------------------------- %%% File : ts_test_mon.erl %%% Author : Nicolas Niclausse %%% Description : %%% %%% Created : 24 August 2007 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_test_mon). -compile(export_all). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("ts_os_mon.hrl"). -include_lib("eunit/include/eunit.hrl"). test()-> ok. munin_data_ok_test()-> myset_env(7), %% error because of empty socket in gen_tcp:recv %% FIXME: start a fake tcp server ?assertError(function_clause, ts_os_mon_munin:read_munin_data(undefined,{ok,"glop 100"},[300])). munin_data_nok_test()-> myset_env(7), %% error because of empty socket in gen_tcp:recv %% FIXME: start a fake tcp server ?assertError(function_clause, ts_os_mon_munin:read_munin_data(undefined,{ok,"glop %"},[300])). sample_update_test()-> myset_env(), Val=ts_stats_mon:update_stats(sample,[],50), Val2=ts_stats_mon:update_stats(sample,Val,20), ?assertMatch([35.0,450.0,50,20,2,0,0,0],Val2). sample_update_reset_test()-> myset_env(), Val=ts_stats_mon:update_stats(sample,[],50), Val2=ts_stats_mon:update_stats(sample,Val,20), ?assertMatch([0,0,50,20,0,35.0,2,0],ts_stats_mon:reset_stats(Val2)). sample_counter_update_test()-> myset_env(), Val=ts_stats_mon:update_stats(sample_counter,[],10), Val2=ts_stats_mon:update_stats(sample_counter,Val,60), Val3=ts_stats_mon:update_stats(sample_counter,Val2,80), ?assertMatch([35.0,450.0,50,20,2,0,0,80],Val3). sample_counter_reset_test()-> myset_env(), Val=ts_stats_mon:update_stats(sample_counter,[],10), Val2=ts_stats_mon:update_stats(sample_counter,Val,60), Val3=ts_stats_mon:update_stats(sample_counter,Val2,80), ?assertMatch([0,0,50,20,0,35.0,2,80],ts_stats_mon:reset_stats(Val3)). sample_counter_update2_test()-> myset_env(), Val=ts_stats_mon:update_stats(sample_counter,[],10), Val2=ts_stats_mon:update_stats(sample_counter,Val,30), Val3=ts_stats_mon:update_stats(sample_counter,Val2,80), Val4=ts_stats_mon:update_stats(sample_counter,Val3,202), ?assertMatch([64.0,5496.0,122,20,3,0,0,202],Val4). sample_counter_cycle_update_test()-> myset_env(), Val=ts_stats_mon:update_stats(sample_counter,[],10), Val2=ts_stats_mon:update_stats(sample_counter,Val,60), Val3=ts_stats_mon:update_stats(sample_counter,Val2,40), ?assertMatch([50,0,50,50,1,0,0,40],Val3). sample_counter_zero_update_test()-> myset_env(), Val=ts_stats_mon:update_stats(sample_counter,[],10), Val2=ts_stats_mon:update_stats(sample_counter,Val,60), Val3=ts_stats_mon:update_stats(sample_counter,Val2,0), ?assertMatch([50,0,50,50,1,0,0,60],Val3). procnet_test()-> myset_env(), ?assertMatch({10106167,2609645}, ts_os_mon_erlang:get_os_data(packets, {unix, linux}, "./src/test/procnetdev_test.txt")). procnet_7chars_test()-> myset_env(), ?assertMatch({10106167,2609645}, ts_os_mon_erlang:get_os_data(packets, {unix, linux}, "./src/test/procnetdev_test7chars.txt")). myset_env()-> myset_env(0). myset_env(V)-> application:set_env(stdlib,debug_level,V). tsung-1.4.2/src/test/badpop.xml.in0000644000201100017670000000306311701017117016503 0ustar nniclausdream tsung-1.4.2/src/test/ts_test_search.erl0000644000201100017670000004056011701017117017630 0ustar nniclausdream%%%------------------------------------------------------------------- %%% File : ts_test_search.erl %%% Author : Nicolas Niclausse %%% Description : unit tests for ts_search module %%% %%% $Id$ %%%------------------------------------------------------------------- -module(ts_test_search). -compile(export_all). -export([marketplace/1,namespace/1,sessionBucket/1, new/1]). -include_lib("eunit/include/eunit.hrl"). -include_lib("ts_profile.hrl"). -include_lib("ts_config.hrl"). -define(MANY,20). -define(FORMDATA,""). test()-> ok. parse_dyn_var_jsonpath_test() -> myset_env(), Data="\r\n\r\n{\"titi\": [23,45]}", JSONPath = "titi[1]", ?assertEqual([{'myvar',45}], ts_search:parse_dynvar([{jsonpath,'myvar', JSONPath} ],list_to_binary(Data))). parse_dyn_var_jsonpath2_test() -> myset_env(), Data="\r\n\r\n{\"titi\": [23,45]}", JSONPath = "titi[3]", ?assertEqual([{'myvar',undefined}], ts_search:parse_dynvar([{jsonpath,'myvar', JSONPath} ],list_to_binary(Data))). parse_dyn_var_jsonpath3_test() -> myset_env(), Data="\r\n\r\n{\"titi\": [{\"val\": 123, \"name\": \"foo\"}, {\"val\": 42, \"name\": \"bar\"}]}", JSONPath = "titi[?name=bar].val", ?assertEqual([{'myvar',42}], ts_search:parse_dynvar([{jsonpath,'myvar', JSONPath} ],list_to_binary(Data))). parse_dyn_var_jsonpath4_test() -> myset_env(), Data="\r\n\r\n{\"titi\": [{\"val\": 123, \"name\": \"foo\"}, {\"val\": 42, \"name\": \"bar\"}]}", JSONPath = "titi[?name=void].val", ?assertEqual([{'myvar',undefined}], ts_search:parse_dynvar([{jsonpath,'myvar', JSONPath} ],list_to_binary(Data))). parse_dyn_var_jsonpath5_test() -> myset_env(), Data="\r\n\r\n{\"titi\": [{\"val\": 123, \"status\": \"foo\"}, {\"val\": 42, \"status\": \"OK\"}, {\"val\": 48, \"status\": \"OK\"}]}", JSONPath = "titi[?status=OK].val", ?assertEqual([{'myvar',[42,48]}], ts_search:parse_dynvar([{jsonpath,'myvar', JSONPath} ],list_to_binary(Data))). parse_dyn_var_jsonpath6_test() -> myset_env(), Data="\r\n\r\n{\"titi\": [{\"val\": 123, \"name\": \"foo\"}, {\"val\": 42, \"name\": \"bar\"}]}", JSONPath = "titi[*].val", ?assertEqual([{'myvar',[123,42]}], ts_search:parse_dynvar([{jsonpath,'myvar', JSONPath} ],list_to_binary(Data))). parse_dyn_var_jsonpath_int_test() -> myset_env(), Data="\r\n\r\n{\"titi\": [{\"val\": 123, \"name\": \"foo\"}, {\"val\": 42, \"name\": \"bar\"}]}", JSONPath = "titi[?val=123].name", ?assertEqual([{'myvar',<<"foo">>}], ts_search:parse_dynvar([{jsonpath,'myvar', JSONPath} ],list_to_binary(Data))). parse_dyn_var_jsonpath_xmpp_test() -> myset_env(), Data="{\n \"status\": \"terminated\",\n \"uid\": \"944370dc04adbee1792732e01097e618af97cc27\",\n \"updated_at\": 1282660758,\n \"nodes\": [\n \"suno-12\",\n \"suno-13\"\n ],\n \"created_at\": 1282660398,\n \"environment\": \"lenny-x64-big\",\n \"result\": {\n \"suno-13\": {\n \"last_cmd_stdout\": \"\",\n \"last_cmd_stderr\": \"\",\n \"cluster\": \"suno\",\n \"ip\": \"192.168.1.113\",\n \"last_cmd_exit_status\": 0,\n \"current_step\": null,\n \"state\": \"OK\"\n },\n \"suno-12\": {\n \"last_cmd_stdout\": \"\",\n \"last_cmd_stderr\": \"\",\n \"cluster\": \"suno\",\n \"ip\": \"192.168.1.112\",\n \"last_cmd_exit_status\": 0,\n \"current_step\": null,\n \"state\": \"OK\"\n }\n },\n \"site_uid\": \"sophia\",\n \"notifications\": [\n \"xmpp:joe@foo.bar/tsung\"\n ],\n \"user_uid\": \"joe\"\n}", JSONPath = "nodes", ?assertMatch([{'nodes',[<<"suno-12">>,<<"suno-13">>]}], ts_search:parse_dynvar([{jsonpath,'nodes', JSONPath} ],list_to_binary(Data))). parse_dyn_var_xpath_test() -> myset_env(), Data="\r\n\r\n"++?FORMDATA++"", XPath = "//input[@name='jsf_tree_64']/@value", ?assertMatch([{'jsf_tree_64',[<< "H4sIAAAAAAAAAK1VS2/TQBBeo+kalCKAA" >>]}], ts_search:parse_dynvar([{xpath,'jsf_tree_64', XPath} ],list_to_binary(Data))). parse_dyn_var_xpath_with_scripttag_test() -> myset_env(), Data= "\r\n\r\n" ""++?FORMDATA++"", XPath = "//input[@name='jsf_tree_64']/@value", ?assertMatch([{'jsf_tree_64', [<< "H4sIAAAAAAAAAK1VS2/TQBBeo+kalCKAA" >>] }], ts_search:parse_dynvar([{xpath,'jsf_tree_64', XPath} ],list_to_binary(Data))). parse_dyn_var_xpath2_test() -> myset_env(), Data="\r\n\r\n", XPath = "//input[@name='tree64']/@value", ?assertMatch([{tree64,[<< "H4sIAAAAAAAAAK1VS2/TQBBeo+kalCKAA" >>]}], ts_search:parse_dynvar([{xpath,tree64, XPath }],list_to_binary(Data))). parse_dyn_var_xpath3_test() -> myset_env(), Data="\r\n\r\n", XPath = "//hidden[@name='random']/@value", ?assertMatch([{random,[<<"42">>]}], ts_search:parse_dynvar([{xpath, random, XPath }],list_to_binary(Data))). parse_dyn_var_xpath4_test() -> myset_env(), Data="\r\n\r\n/", XPath = "//hidden[@name='random']/@value", ?assertMatch([{random,[<<"42">>]}], ts_search:parse_dynvar([{xpath, random, XPath }],list_to_binary(Data))). parse_dyn_var_many_re_test() -> myset_env(), {Data, Res}= setdata(?MANY,binary), RegexpFun = fun(A) -> {re,list_to_atom(A), ?DEF_RE_DYNVAR_BEGIN++ A ++?DEF_RE_DYNVAR_END} end,%' B=lists:map(fun(A)->"random"++integer_to_list(A) end, lists:seq(1,?MANY)), C=lists:map(RegexpFun, B), {Time, Out}=timer:tc( ts_search,parse_dynvar,[C,list_to_binary(Data)]), erlang:display([?MANY," re:", Time]), ?assertEqual(Res, Out). parse_dyn_var_many_xpath_test() -> myset_env(), {Data, Res}= setdata(?MANY,binarylist), B=lists:map(fun(A)->{xpath, list_to_atom("random"++integer_to_list(A)), "//input[@type='hidden'][@name='random"++integer_to_list(A)++"']/@value"} end, lists:seq(1,?MANY)), {Time, Out}=timer:tc( ts_search,parse_dynvar,[B,list_to_binary(Data)]), erlang:display([?MANY," xpath:", Time]), ?assertMatch(Res, Out). parse_dyn_var_many_xpath_explicit_test() -> myset_env(), {Data, Res}= setdata(?MANY,binarylist), B=lists:map(fun(A)->{xpath, list_to_atom("random"++integer_to_list(A)), "/html/body/form/input[@type='hidden'][@name='random"++integer_to_list(A)++"']/@value"} end, lists:seq(1,?MANY)), {Time, Out}=timer:tc( ts_search,parse_dynvar,[B,list_to_binary(Data)]), erlang:display([?MANY," xpath_explicit:", Time]), ?assertMatch(Res, Out). parse_dyn_var_many_big_re_test() -> myset_env(), {Data, Res}= setdata_big(?MANY,binary), RegexpFun = fun(A) -> {re,list_to_atom(A), ?DEF_RE_DYNVAR_BEGIN++ A ++?DEF_RE_DYNVAR_END} end,%' B=lists:map(fun(A)->"random"++integer_to_list(A) end, lists:seq(1,?MANY)), C=lists:map(RegexpFun, B), {Time, Out}=timer:tc( ts_search,parse_dynvar,[C,list_to_binary(Data)]), erlang:display([?MANY," re_big:", Time]), ?assertMatch(Res, Out). parse_dyn_var_many_big_xpath_test() -> myset_env(), {Data, Res}= setdata_big(?MANY,binarylist), B=lists:map(fun(A)->{xpath, list_to_atom("random"++integer_to_list(A)), "//input[@type='hidden'][@name='random"++integer_to_list(A)++"']/@value"} end, lists:seq(1,?MANY)), {Time, Out}=timer:tc( ts_search,parse_dynvar,[B,list_to_binary(Data)]), erlang:display([?MANY," xpath_big:", Time]), ?assertMatch(Res, Out). parse_dyn_var_many_big_xpath_explicit_test() -> myset_env(), {Data, Res}= setdata_big(?MANY,binarylist), B=lists:map(fun(A)->{xpath, list_to_atom("random"++integer_to_list(A)), "/html/body/form/input[@type='hidden'][@name='random"++integer_to_list(A)++"']/@value"} end, lists:seq(1,?MANY)), {Time, Out}=timer:tc( ts_search,parse_dynvar,[B,list_to_binary(Data)]), erlang:display([?MANY," xpath_explicit_big:", Time]), ?assertMatch(Res, Out). setdata(N) -> setdata(N,list). setdata(N,Type) -> {"\r\n\r\n
"++lists:flatmap(fun(A)-> AI=integer_to_list(A),[""] end,lists:seq(1,N)) ++"
/", lists:reverse(lists:map(fun(A)->{list_to_atom("random"++integer_to_list(A)) , format_result("value"++integer_to_list(A),Type)} end, lists:seq(1,N)))}. setdata_big(N) -> setdata_big(N, list). setdata_big(N, Type) -> Head = "ABCDERFDJSJS", Fields = lists:flatmap(fun(A)-> AI=integer_to_list(A), [""] end,lists:seq(1,N)), Form = "
" ++ Fields ++ "
", Content = "

This is a some random content

" "
  • item1
  • item2
" "

Some more text... not too big really...

" "

More text inside a paragraph element

" "
", HTML ="\r\n\r\n" ++ Head ++ "" ++ Content ++ Content ++ Form ++ Content ++ "", {HTML,lists:reverse(lists:map(fun(A)->{list_to_atom("random"++integer_to_list(A)) , format_result("value"++integer_to_list(A),Type)} end, lists:seq(1,N)))}. format_result(Data,binarylist) -> [list_to_binary(Data)]; format_result(Data,binary) -> list_to_binary(Data); format_result(Data,_) -> Data. parse_subst1_re_test() -> myset_env(), Data=?FORMDATA, StrName="jsf_tree_64", Regexp = ?DEF_RE_DYNVAR_BEGIN++ StrName ++?DEF_RE_DYNVAR_END,%' [{Name,Value}] = ts_search:parse_dynvar([{re, 'jsf_tree_64', Regexp }],list_to_binary(Data)), ?assertMatch("H4sIAAAAAAAAAK1VS2/TQBBeo+kalCKAA", ts_search:subst("%%_jsf_tree_64%%",[{Name,Value}])). parse_subst2_re_test() -> myset_env(), Data=" 4DOM version 0.10.2

4DOM version 0.10.2

4Suite http://FourThought.com/4Suite< /A>

", Regexp = "(.*)", [{Name,Value}] = ts_search:parse_dynvar([{re, 'title', Regexp }],list_to_binary(Data)), ?assertMatch("4DOM version 0.10.2", ts_search:subst("%%_title%%",[{Name,Value}])). parse_extract_fun1_test() -> myset_env(), Data="/echo?symbol=%%ts_test_search:new%%", ?assertMatch("/echo?symbol=MSFT", ts_search:subst(Data,[])). parse_extract_fun2_test() -> myset_env(), Data="/stuff/%%ts_test_search:namespace%%/%%ts_test_search:marketplace%%/%%ts_test_search:sessionBucket%%/01/2000?keyA1=dataA1&keyB1=dataB1", ?assertMatch("/stuff/namespace1/5/58/01/2000?keyA1=dataA1&keyB1=dataB1", ts_search:subst(Data,[])). parse_subst_var_fun_test() -> myset_env(), Data=?FORMDATA, StrName="jsf_tree_64", Regexp = ?DEF_RE_DYNVAR_BEGIN++ StrName ++?DEF_RE_DYNVAR_END,%' [{Name,Value}] = ts_search:parse_dynvar([{re, 'jsf_tree_64', Regexp }],list_to_binary(Data)), ?assertMatch("H4sIAAAAAAAAAK1VS2/TQBBeo+kalCKAA-MSFT", ts_search:subst("%%_jsf_tree_64%%-%%ts_test_search:new%%",[{Name,Value}])). parse_subst_badregexp_sid_test() -> myset_env(), Data="HTTP/1.1 200 OK\r\nServer: nginx/0.7.65\r\nDate: Fri, 05 Feb 2010 08:13:29 GMT\r\nContent-Type: text/xml; charset=utf-8\r\nConnection: keep-alive\r\nContent-Length: 373\r\n\r\n", Regexp = "sid=\".*?\"", [{Name,Value}] = ts_search:parse_dynvar([{re, sid, Regexp }],list_to_binary(Data)), ?assertEqual({sid,<<"">>},{Name,Value}). parse_subst_regexp_sid_test() -> myset_env(), Data="HTTP/1.1 200 OK\r\nServer: nginx/0.7.65\r\nDate: Fri, 05 Feb 2010 08:13:29 GMT\r\nContent-Type: text/xml; charset=utf-8\r\nConnection: keep-alive\r\nContent-Length: 373\r\n\r\n", Regexp = "sid=\"([^\"]*)\"", [{Name,Value}] = ts_search:parse_dynvar([{re, sid, Regexp }],list_to_binary(Data)), ?assertEqual({sid,<<"5bfd2b59-3144-4e62-993b-d05d2ae3bee9">>},{Name,Value}). dynvars_urandom_test() -> myset_env(), ?assertMatch(["qxvmvtglimieyhemzlxc"],ts_client:set_dynvars(urandom,{string,20},[toto],[])). dynvars_urandom_neg_test() -> myset_env(), ?assertError(function_clause,ts_client:set_dynvars(urandom,{string,-3},[toto],[])). dynvars_urandom2_test() -> myset_env(), ?assertMatch(["qxvmvtglimieyhemzlxc","qxvmvtglimieyhemzlxc"],ts_client:set_dynvars(urandom,{string,20},[toto,tutu],[])). dynvars_random_test() -> myset_env(), [String] = ts_client:set_dynvars(random,{string,20},[toto],[]), ?assertMatch(20,length(String)). dynvars_random2_test() -> myset_env(), [String,String2] = ts_client:set_dynvars(random,{string,20},[toto,titi],[]), ?assertMatch({20,20},{length(String),length(String2)}). dynvars_jsonpath_test() -> myset_env(), Data="\r\n\r\n{\"titi\": [{\"val\": 123, \"name\": \"foo\"}, {\"val\": 42, \"name\": \"bar\"}]}", JSONPath = "titi[?name=bar].val", Dynvars=ts_dynvars:new(data,Data), ?assertEqual(42,ts_client:set_dynvars(jsonpath,{JSONPath,data},[toto],#dyndata{dynvars=Dynvars})). %%TODO: out of order.. %parse_dynvar_xpath_collection_test() -> % myset_env(), % Data="" % " " % "
" % " " % "
" % " " % " ", % XPath = "//img/@src", % Tree = mochiweb_html:parse(list_to_binary(Data)), % R = mochiweb_xpath:execute(XPath,Tree), % erlang:display(R), % Expected = [<<"img0">>,<<"img1">>,<<"img2">>,<<"img3">>,<<"img4">>], % ?assertMatch(Expected, R). parse_dynvar_xpath_single_test() -> myset_env(), Data="" "
" " " "
" " " "
", XPath = "//a/@href", Tree = mochiweb_html:parse(list_to_binary(Data)), R = mochiweb_xpath:execute(XPath,Tree), erlang:display(R), Expected = [<<"/index.html">>], ?assertEqual(Expected, R). filter_re_include_test() -> ?assertEqual(["/toto"], ts_client:filter({ok,["http://toto/", "/toto", "mailto:bidule"]}, {true, "^/.*"})). filter_re_exclude_test() -> ?assertEqual(["http://toto/", "mailto:bidule"], ts_client:filter({ok,["http://toto/", "/toto", "mailto:bidule"]}, {false,"^/.*"})). extract_body_test() -> Data = << "HTTP header\r\nHeader: value\r\n\r\nbody\r\n" >>, ?assertEqual(<< "body\r\n" >>, ts_search:extract_body(Data)). extract_body_nohttp_test() -> Data = << "random\r\nstuff" >>, ?assertEqual(Data, ts_search:extract_body(Data)). badarg_re_test() -> Data = << "Below this line, is 1000 repeated lines">>, Regexp = "is (\\d+) repeated lines", {ok,Regexp2}=re:compile(Regexp), ?assertEqual([{lines, <<"1000">>}], ts_search:parse_dynvar([{re, 'lines', Regexp2 }],Data)). myset_env()-> myset_env(0). myset_env(Level)-> application:set_env(stdlib,debug_level,Level). new({Pid, DynData}) -> "MSFT". marketplace({Pid,DynData}) -> "5". namespace({Pid,DynData}) -> "namespace1". sessionBucket({Pid,DynData}) -> "58". tsung-1.4.2/src/test/ts_test_all.erl0000644000201100017670000000160311701017117017126 0ustar nniclausdream%%%------------------------------------------------------------------- %%% File : ts_test_all.erl %%% Author : Nicolas Niclausse %%% Description : run all test functions %%% %%% Created : 17 Mar 2007 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_test_all). -compile(export_all). -include_lib("eunit/include/eunit.hrl"). test() -> ok. all_test_() -> [ts_test_recorder, ts_test_config, ts_test_dynvars_api, ts_test_file_server, ts_test_options, ts_test_pgsql, ts_test_proxy, ts_test_http, ts_test_jabber, ts_test_match, ts_test_mon, ts_test_user_server, ts_test_search, ts_test_stats].