tsung-1.5.1/0000755000175000017500000000000012321173206013745 5ustar nniclaussenniclaussetsung-1.5.1/examples/0000755000175000017500000000000012321173206015563 5ustar nniclaussenniclaussetsung-1.5.1/examples/jabber_roster.xml.in0000644000175000017500000000530312104023217021531 0ustar nniclaussenniclausse tsung-1.5.1/examples/jabber_node.xml.in0000644000175000017500000000665412104023217021152 0ustar nniclaussenniclausse tsung-1.5.1/examples/mqtt.xml.in0000644000175000017500000000361412236145741017713 0ustar nniclaussenniclausse test_message tsung-1.5.1/examples/jabber.xml.in0000644000175000017500000001017212104023217020133 0ustar nniclaussenniclausse tsung-1.5.1/examples/jabber_muc.xml.in0000644000175000017500000000607512104023217021006 0ustar nniclaussenniclausse tsung-1.5.1/examples/mysql.xml.in0000644000175000017500000000216512104023217020056 0ustar nniclaussenniclausse SHOW TABLES SELECT * FROM gens SELECT * FROM te tsung-1.5.1/examples/ldap.xml.in0000644000175000017500000000470312104023217017631 0ustar nniclaussenniclausse organizationalPerson inetOrgPerson person %%_new_user_cn%% fffs SomeSN some@mail.com tsung-1.5.1/examples/http-digest.xml.in0000644000175000017500000000414212104023217021142 0ustar nniclaussenniclausse tsung-1.5.1/examples/websocket.xml.in0000644000175000017500000000237212236145741020714 0ustar nniclaussenniclausse {"user":"user", "password":"password"} ok {"uid":"%%_uid%%", "data":"data"} {"key":"value"} tsung-1.5.1/examples/pgsql.xml.in0000644000175000017500000000344212104023217020036 0ustar nniclaussenniclausse SELECT * from accounts; SELECT * from users; tsung-1.5.1/examples/amqp.xml.in0000644000175000017500000001075612236145741017671 0ustar nniclaussenniclausse tsung-1.5.1/examples/fs-nfs.xml.in0000644000175000017500000000736312104023217020112 0ustar nniclaussenniclausse tsung-1.5.1/examples/http_setdynvars.xml.in0000644000175000017500000000564612212102345022161 0ustar nniclaussenniclausse tsung-1.5.1/examples/jabber_register.xml.in0000644000175000017500000000245512104023217022044 0ustar nniclaussenniclausse error tsung-1.5.1/examples/http_distributed.xml.in0000644000175000017500000001436012104023217022272 0ustar nniclaussenniclausse tsung-1.5.1/examples/thinks2.xml.in0000644000175000017500000000226412104023217020273 0ustar nniclaussenniclausse tsung-1.5.1/examples/jabber_privacy.xml.in0000644000175000017500000000317612212102345021676 0ustar nniclaussenniclausse tsung-1.5.1/examples/http-oauth.xml.in0000644000175000017500000000472312104023217021010 0ustar nniclaussenniclausse tsung-1.5.1/examples/http_simple.xml.in0000644000175000017500000000424012104023217021235 0ustar nniclaussenniclausse tsung-1.5.1/examples/http_tag.xml.in0000644000175000017500000000241112236145741020532 0ustar nniclaussenniclausse tsung-1.5.1/examples/thinks.xml.in0000644000175000017500000000227612104023217020214 0ustar nniclaussenniclausse tsung-1.5.1/man/0000755000175000017500000000000012321173206014520 5ustar nniclaussenniclaussetsung-1.5.1/man/tsplot.10000644000175000017500000001375012236145741016145 0ustar nniclaussenniclausse.\" auto-generated by docbook2man-spec from docbook-utils package .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 one 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 overridden 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.5.1/man/tsung.10000644000175000017500000000523412236145741015756 0ustar nniclaussenniclausse.\" 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.5.1/man/tsung-recorder.1.sgml0000644000175000017500000001240612236145741020521 0ustar nniclaussenniclausse
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 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.5.1/man/Design_fr.txt0000644000175000017500000000602212236145741017171 0ustar nniclaussenniclausse 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 définition 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 utilisés 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 simultanément * ts_launcher (gen_fsm) lance les clients simulés (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 maître qui va lancer les autres processus (un par beam) 1.1/ processus clients initiant les connexions TCP (module ts_client). Ces processus sont lancés par le processus maître avec un intervalle de temps suivant une distribution de probabilité exponentielle d'intensité paramétrable au démarrage (unité = sec). Le nombre de clients total est également paramétrable au démarrage du processus maître (il s'agit bien du nombre de client total et non du nombre de clients simultanés) 1.2/ Le processus de monitoring (module ts_mon) 2/ Un module pour gérer les échantillons aléatoires (module ts_stats) 4/ Les modules spécifiques pour gérer les différents 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 Références: ========== - 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 - modélisation stochastiques : Plus de détails sur ce type de modélisation sont disponibles dans les documents suivants (dans le contexte du protocole HTTP) Nicolas Niclausse. Modélisation, analyse de performance et dimensionnement du World Wide Web. Thèse 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.5.1/man/tsung-recorder.10000644000175000017500000000507612236145741017565 0ustar nniclaussenniclausse.\" 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 \fB-p\fR 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.5.1/man/Jabber.txt0000644000175000017500000000137212236145741016461 0ustar nniclaussenniclausseRequirements: ============ 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.5.1/man/tsplot.1.sgml0000644000175000017500000002554312236145741017111 0ustar nniclaussenniclausse
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 one 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 overridden 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.5.1/man/tsung-inside.png0000644000175000017500000010401412236145741017647 0ustar nniclaussenniclausse‰PNG  IHDRõú‘|B“sBITÛáOà IDATxœìy\TUûÀŸÙØÅaßDA\5TÄ •E—pCSÃ~.¸¥fbîk¹”¾j&©$fjfQ`¨ ¥Eîb†J*(¢àÈ.Ìòûãâõ:wöíù~ø¼ïsÏ=çLÂwžyî¹ç0D" ‚ F[×@}!*2J×C@UIÏH'Ñï•””¤ëQ ˆªDEF‘Šgêv(¢ Ü£!))‰ü&Š~GL”;bdŠG¿#‚'˜G%)ÈIÑõõàÕm”®‡ Ðï¢ „Ü]¸º¢*¼ÒÊ‚œ£T<ægDaPîÆñïh”߯Ðï¢ (wcÂXÿ51?ƒ ˆîqû`¥ùEǶ4µsvIy›¡sg& ¸­À2ë7)Åì^¡ˆÍzÙó­Êøñ<__x+D–æMîÎõ}ºWM‰ÚZ‹%ÚXþ±¯UÊï6¿üa–WØÐµ/q…¶ßªA¿#ˆò45ñ—¬ßûäi©¹ÇÅÉîóeÿ7uÞ&ÈÍ+ôtw²±²4aTÿ¥öå=|rdÏ É³á}ºÏZ²=åÛµ7oçoÚušø‚Ð`ÿ9q#©ñJʉjÔB¢Ù÷­€9y_'ÿúÍÖEP]S7æÃÕ§ØÌ+):g÷¦†ôï }£çe¥î±¾b†…PŽšs1}¯¤éÑ­CEemXÿ…3ƨ÷?¦°••ÝîJ6}$Vn}ú‚ÃÆ}åŸ|ð¢ofCc«£§\§­zúíÚ¦ömˆ å ߯ž0„ñ²Áì~!÷›ŸÜ§,/N^O*¾b~lõØAÔö­«¦F›çÜ7¿¯Þ· o ßDy2Î_³0çûf%¼¨{Éf³íþ¦üÅÜi£ºù¶€¦&~îýGnÎöÅÏžKžå•”@EeÍüå»’w&´kã×nÞk±k²Ù¢’¶βªy·uK<˜68<Á`%ôûêàíA ˜ÄÇË=yG_ è=_í~¯7ÈöÐoæwò^}¨ö_~W?®vh?XšWÎÃyTl¿óȳm‹©—‹,̺u,ùòc÷q‹m®üpts9‹%2ãPkÖ…§è™zǯ‡`þA”ÇÚÊânÞãÿîŠD"k+ ©u~ÿûÆÀ~=G ùåd–¬v2Î_ëÓ.ôòïÔb×tšgG®ŸÓ箩җõõ  e ö­«' ±ÛõµÐì^!«¼êED0µðEä»WnPÊ Dv}H€Ev®úÇg€`üŽ Êön÷ü‚â%ë÷óÊâ& ™55Z²Nêé ËæOtv´K<˜6ïUP)FiY…«“½B]Ói– ~ꈙŸlÖKN_÷<?cq, Å ¹¶6{¶,€ü‚âÉs?Ϲó`ÁÌ…FK“ê÷‡ÙŽ˜oy9§ÉÛƒ(aUÖƒ!°·¥VÚÙ2›˜uõB+ÉF\[fU-ùÒîëcÜýÍsc^úw.ùb‘&F®Ÿ ßD%¦ÅFM‹*¯¨Žž²’89ÚåÞD¿SÉfÙl–@Ðìe>ŸÏf³ÈÊ.Nvoûµ'Cx©}ulßæè«;ÁQs$ |¼ÜîLÈ/(ž¿|×ä1‘ôL¡µeå#í¾:Z²e!Q"à¶‘ˆU^MU<³¢ZdÆZYJm„UQ-lmC¾¬zXíˆpâXÄ1-ãa~A”§ôyeSZ·¶±¶´àóbÒÏ^™9yxòŽ„ä _¬ž•"#—pþÂÍb^ñòÊ”©H6ëÝÖ5¿ ¸®¾.ßøÏ·S;jýYS¢÷$§*×—$>^îÄÍMP3&‚YQcuæ2ñ²©c[½­uæEjëŒ /»“!y9£‰où×õ—=}É¡ßÙžøØÙJ^bĘ֧‚¨—‚BÞæÝ?X˜›U×Ô êèãå.Vá×SYë¦ÇowZ²îþ‚I’íØq[m[¿xm"“É EA½üºwí V'ïaѤøÀd1ù|¾X³ËLZ8sLì¬õV–æ,kËÊÔk]íýý|ÎfeKí+fXˆÔwGöÛ×ϦžŠ‹Bã?2ˆÌ8•Óß³ÿß÷Í/Ù¬Š&9|¶_hcEΟ±úûŸ§û×¼¾F(€ÙÐĹ_ÈÝ÷3C ¬ûú»C `46QÛ¡Á_"£±  cë¸bâ(±~dANб>£+Ü>XY;,¤æ½B¡Çø%œ‡Eäüw«?¯·þöW³{DlVCOߊø±Û‘6Ï·0ç»;Õóß_ågÄæ¿@áù¤V?Ÿ±ûßa²äeO_^â ^i¥1-Q—ž‘Ž~GL=ô{MmÝš/’/ßéé;fx¨æºCÀ(ýnœßJÄ iecµuÕL]1xðþ*‚ ˆq‚ñ;‚è¼ÒJ±Ìõ#Ê~G}AÒìÔr´<¢(èwÑ dÉZÁp¯ô—ü6£ èwÑ=-ʬfpvSúK ~›Q¼¿Š ˆ¦ ó¥D½"TÐï¢cR•yþ—u]ˆˆùA¤ãÝ+–8xxí°üšˆ~‚~G¤HÑÐѽ¢_JÈ|ºÒ"’ ßÄx Š˜jaUÊé÷‚èèw1dÙV¹r9–G­èwAè‚Z7,Ðï¢c\¸ô“ÎÚO7£Ó ô;‚ ÒA³:8ÿAtͨ܀æŠ(4Tje¥/D$A¿#ˆ^ТªÐeˆ¢ ßD_puâJ•¸¬r=Gé/%Æ÷mFW`þAô cÒV‹·Že½Y¥/D¨ ßÑ „ˆ•XæWé ÌÏ ˆá•”ú`¥XáÒ ûÆ|¸š8¾‘“7ýã/ˆãꚺAã>!®òŒ=ùû¢¼oô<âàæíüØYëcg­ókv%ý"ÖxpÔâÚnaÓ&Åo$~›x%åo‡8eÞçÑ“—™ø£æÞ¬ˆõGÓ"€ñ;‚h™¦&~îýGnÎö…E%m=œeUónë–x0mpx ƒÁ J**kæ/ß•¼3¡]¸vóž¬k;x{Úý)µÄÇË=yG_ è=áŒ1jz+ˆ¾ƒñ;‚h•ßÿ¾1°_ÏÑCC~9™%§š³#׿‹Ïés×È’Œó×Ãút'ä½ü;)Úu}}ˆ½1`0~G­’zú²ùí¦Íûp´œšñSGÌüdÛ °^ÄËÒ² W'{±:÷<?cq, Å ¹¶6{¶,€ü‚âÉs?Ϲó`ÁÌõ¾šàþ|:ýŽ Ú£¦¶îúÍ{K7î^iùÍÛùl6K hö2ŸÏg³Yde'»·ýÚ“!¼“£]îýGb vlßæhâ â˜È¿‹øx¹Ü™_P<ù®Éc"5ðÎd‚ûóéÌÏ ˆöH?{eæäáÉ;’w$|±zVÊÉ,ï¶®ùÅuõ pùƾÚQëÏš½'9•8Ž 8áf1¯ŒxùÏ­ƒùAãýŽ bœ`~A´ÚoŠêŠ÷çÓ èwAháÝ+–8xxí0ý«p>‚~GD1HÑÈÑ=mö¤šZé *èw1f¨.¦ŠX•rš]àþ|:ýŽ ÆŒ¬àZ‰r9ŠW(cƒh ô;‚ J¢JfF¬2‰+}!" úAÀPÝ€@¿#B 4»ÁÏ7!ˆ¶Q{JsˆTÐï‚ Æ úAÄ8A¿#ˆPcFE?“3¸?Ÿ>€~G1NÐï¢Ô{ês‹ûóéô;‚ š÷çÓ-8ÿAt†«W•µ‚ Âq¸?ŸA¿#ˆ.QZñ†¥9ÜŸO'`~AtŒ Cë!tÀøAt¬\„¬š&¦h”ýŽ ú‚œ5 ÚhjÜœZnÐÿM´úAô£1—*‚Æ-œTóï‚h:‚VúZ…ª™,èwAÔ Z@¿#¢_(º…“æFbè ßQ3(h=ýŽ bœ ßAŒœ‰ ˆðœ@<¼zD·#AHÐï¢_èÿÃM¤Ê© Öõô;‚è zø¸¦*÷œp1ýkU:UhÍ5}ûðÓ+Ðï¢èüqMÕ£r¢0 œ£ õô;"“ÚZèÚ ^ ‚þÓ@òmH¶E)«7ÁB¶&†r-HB3„ÇÏù ßĪlYåêÊ•‹™½àêa¯ÀX¢DuA·ØʽEÐïºæåKøæ˜7Oû=> 6‡ööðûïƒAYÔÔ@\$$hD&Š¢OQ½&UßÔBMÜö¤N•!„N˜Ý+0¶àêa²šê‚Æ-œTý®k^¾„;´ï÷û÷aɸv \\ òÕŸÏ?— з/ŒNNZ¢²v Íf7{ B'-/V_-‚F›+ü^ãa<x,„…ÑÝ zõ‡þPQ5Gàˆ8À Te5Pq °¶A¸–¬€'àÄ ¸qîN‡é?ÁO½¡÷`üVCu*¤:€ƒößÔkZŒŠ+*`üxàñ@ €… áæM(*‚°0€/¾ÐÚ0σèhpqàrD"رNž33xüîÞE¿ë)òÍ®ÑN‰ :f§‚‚Ö:ð{¤ù‚ïi8 ÕPÝàÑXÐ º}ß| Ÿî…½ðüÀn#4ö…¾ã`\„¬…µK`ɸÀvT‡ó!…Pø|Ô :­€ß·ÃÇÚSÍЉŠÿú |}áôi€êj=~û ÎÓòHwÂNŸ†¿þ‚?ÿÆ>_Ë#Bè"›kÎø’;Pl.–Aô ø½ôZk¬Àj ìýË ¬ ´éÝ ¢¾€/@¢°ã$œ4³Çðø.ÜoÂÍz¨ /£ * ²þ„?§Àhí:A'ðßKpIûïè5t¢â^½`Ͱ²‚¡ÿ×Ú%<Ö®…•+ÁÅž?¨ªooàp ª þøÞ_'ãB”AsIvjÀošZ Û£¿û߸rNo†Í'áäX‡æX± šˆƒÓpú/øëOø“œq0Ž|6°}Áwì „ÀÉ€Œ‹p1롞â*&0… Ôþ;z ¨ØÏ®\Ó§aóf8y–-ÓÅ@ÁÇ6m‚ÐP`³ÁÙ~ÿ† ƒýûaäH°° Ñ=bË óaÐ솈ü^ENà ±žà¹Vxçà\„†Ã¡ UPå ÞàTAÕðÇûð>„@ÈØò?ø_0O‡éŽàh ÖõP¯ý· :QqQ89Al,xzŠ`auu:ì„ 0òµÞÚ22ÄësÞmlpò»Ñ“Ç5¥š]Ìã(wÃB~¿ W—ÃrpXÀ"²1 Ã80浆Ö?ÃÏ0 †í‡ý#a¤XøCs0!«`U?ègVÎàÜújð-@'*¾z–/X,øâ °°€¨(xë- ƒ={týSDì©(4»ÑÀ‰DºA”õ…¾ÿÁºb²DEF%%%)tIANŠÚãhí?®)¹â£)›WZéÕm”®G¡6âââÒ3Òqþ;‚èÚ|\SrY7¹+º÷»#8bðŽ˜8Þ^=¢éÇ5eì€f7RtïwaD ã,“J³nt(“!ŒÑP!äN¾ÔÄTY;È08ÊÝ80x¿0( תë©s+õÕ£†0FCLîjoœ8ЉÙoÞÎß´ë4ñ¡Áþ1ÃBf-ÙžòíZâ,¯¤œxÉ+)»Øßχ(ß¿}qEeMðÐ9»7}4¤oŠšÝÁËró =Ýl¬,‡FÅ Y²~ï“§¥æf'»­«fª>`ãÃHüN…t½¡Š(Õc‰Âõí?&Ïý|ÌðPjüN=58¡ï§óSnß-pu¶Q÷’Åb—0€1zh¿ã'þ êéKváÓÎ-'÷¡«³=ðù‚™—ž•VlÚuäè/gà‡_š3ìDhOìçný•]ºaî(“šž!°'9µ˜WæîêÿÜÊ#Þ‚BÌš=ó“mRO•>¯äÚÚp8ìÖ­m¬-1?#ƒ÷;‚虯޸ÛÄç÷îÞ^ÅïNܦ¿G=E=ž·ìÝÝŸg…õ‰X³5ùÏKÿVTÖ,ŽG6ön÷=ÉiÔ.&ÅD¬ÜôíÙ¬l›3,ô¬ç/þ‰[=}öüÖ6°ˆSNËvϼ=ËËóŸØ½9²AíÌ}´ã¶Ú¶6~ñÚD&“)Š‚zùÅ É{X4)~#0YÌÍ+¦“•Érؾ~6Yîêlïïçs6+[²ý‚BÞæÝ?X˜›U×Ô ê/=Gè~}Ñ-Ú\Ÿ@•ÙR§ÄÐW6Nl—®O€ ˆò(-w4;¢èwÑÊÉ]ò¹S…|-™&èwÑ8Š>Ç$'`ÅÍN¿2bd ßD³È‰Á­ŒfGýŽ „¦ÜéìÐÒcJÔ³(wÐï¢iäËNÀ-™š¨F(ÍŽ ßDSȹ§*?`Å'ÆÇhv„ýŽ A–ÜÕhvxSî"úAÔT¹·˜‹W4µ‚rGäƒ~G5#&÷v0;-údÉS4 úAÔ Uît&Ï´hvjy‹‹" Ù*èwQ„Ðéìð¦‘eÉZP¼XðŽZG¤‚~Gõ@Gë {Êc‹r'«EÅK^Ž ’ ßDU2»ê^F³#4A¿#ˆòP–Qeb ÍବÜêĈ©~Ge Þ;•ó>MŠèô;‚(€›§+Ì¢N’Q}•GÑèw¡aö§yP“ÀrG³#zúAäñ*`o6;1¹Ó\ A´úA¤C Ø%¡ÊÍŽè'èwy9;IpTsþ]]©W'.ý)48y¡ úAš¡cv²&ÙýýŽ -¤bÄjŠ€j5{szçêa:!<ï}ÐïˆéÂ`ÿŸNÇìàêé&à=~Z“⪦1P—{l1KƒrGýŽ˜"„ÙE"€¨È(€$9•]=Ýš¯ÍO:HÍð§³>0‚´ú1!^ìÍfoÒì¼ÇOÝ<]Õ+w9´9¢ÐïˆI ŠÙ@rdzˆÖ@¿#ÆŒ¢Z‡Wf'´N ^¹£Ù­~GŒµ˜Ô'w Ûíƒ~GŒ ê½SšH5;¨IîhvDW¼á÷¨È(]A =#]•Ë•0;0éÒÌ”ÇT»_Çvéuu/ûFÏ;š¸¢m—Õ[p8ìŠÊÚÙq#ò'LKûnÃåìÜGŸN^þ²¡qÍÖƒ,S Ž^ÿ²aÕæ§Žnb0b§ ‹JØ,æï\º~ç^þ;n«c©çÚz8·õpž1y¸êo1D^û=*2*)IÞ,1Ñ4Q‘QJ+^1¹SÒ7Q‘Q’¿÷ôŸx’¶§e\ô:{ùŸ[yyÅÁ]àÐñ3úD„Ô¾¨Ÿ³tÇ{ÃB:z{œÈ¼ääÐZ¬‘ï~Ì ëã?(,ø|Áê­ÉcG„]ÎÎ ð;UXT"vmì¨þC¼£ôø#€IüÊÑ’’’TùIKî Æë¨QîÄËAá§þ¸zûnA×ÎÞWPÔÍ×l¬-ù Ð;óü5@(ÖNþ£b?⸡±©©‰?fxد§.ˆb³Yp8å÷eŸíß÷}ó$ñòûŸÎ(ý.C‡ (wDŸ ¯hÏ`Ð;½[®*Ê]j¶ÝÒ¼¡±ÉÓÝ©¡¡ |ڹݾ[àêlÿ¢î%‹Å0FíwüÄŸA=}©ú´sËÉ}èêl¿œÌzVZ±iבìœû/ê^ROñùx°ùÀø‘µ¢¢"555''§ªªÊÚÚÚÏÏoذaîîîPZZš’’rûöíúúz‡ààਨ(6»¹µ¯¾úêúõë|ðA¿~ýˆ’_ýõæÍ›K–,™5k–Ô¾æÌ™Ó³gOâB`2™­[· Œ‰‰!›m±S˜={6µY©…ä©%ýû÷Ÿ4i’ü †Š‚‰yõÊ`ù‚I `$L€I1k¶&ÿyéߊʚÅñãò ìÝî{’ÓÄ®š±rÓ·g³²9lÖ™óÙ¥þÍfeœ»v"óõT̰Pɧüžuå–“÷£éï)÷vCG¦¹JJJ6nÜèèè8a—ÚÚÚìììãÇÏ›7Çãmذ¡M›6Ó§Oçr¹ÇÏËË[°`ãUˆÄápRSSƒƒƒ©r433[½z5q|æÌ™¼¼¼™3g/œœˆƒ.]ºÄÆÆ ‚üüüï¿ÿÞÚÚzøðá@§S…ðóó;v,ù²U«Vò€è-ò‚wÍ®ô„9fLÏšM|¾|:YØõ-oâàhâ ±k--Ì·¬jþY·$Ž8ˆ ëE§À¿Ks®&(À/(ÀO¬_Ä4‘é÷ï¿ÿžËå&$$‚~ë­·jkkàðáË-"Nyxx´mÛvÍš5YYYdÀÞ£Gû÷ïŸ;wnàÀd› £m۶ı­­-‡Ã!_’XXX¸¹¹@›6m®^½ZXXH”ÓéT!,--%{—3ÄÀP|2&äŽ :„)µ´®®îÖ­[b© ›/^ܾ}{àÀÔSžžž]ºt¹rå YÂáp†~âĉ††%†% óòò ¼½½€f§jDlˆÞ"%x'î Ê¾}*åäîK<’ŠrGôéñ{II‰H$òðð==ýßÿ5j”™™™&†'–wppÐòµÐ,semjæd&ÝõßI‚¢âÍŽè=Òó3NNN £¨¨ˆþ©'Ož¸¸¸ˆ¾óÎ;ööö'Ož¤? :̘1ãÅ‹gΜQ´Sšùwkkk9@ôæx]©lŒx#Š@,f€rGôé~·¶¶îҥ˙3gµ¼¶¶–˜()vª¨¨èÖ­[½{÷k‡Á`Œ5êÌ™3UUUŠŽÌÜÜ|àÀ™™™|>_¡NÕuêQ(ovx%÷!tŸ¨rõtsõtã=~ÿ€P: IDATz)}·r="ˆ6‘îw˜8qbyyù¦M›²³³Ÿ|˜x *66¶¬¬ìË/¿¼sçNqqñ… ¶nÝêëëKä¯ÅèÑ£‡‡‡Çßÿ­ÄàBCC_¼xqõêUšÖ××R¨®®–U(Y^VV&ˆ^Á`¼’» -€Ê«K"ˆ>#3ÿîââ²jÕªÔÔÔï¿ÿ¾ªªÊÆÆ¦K—.cÆŒ77·+V¤¤¤ìÙ³‡xÔ(,,lèСL¦ôO‹Ñ£GoݺU‰ÁµjÕêwÞÉÌÌ ¦Óé;wÈùõ#µ0**J²¼GsçΕ3%Æh @ù°D¡½>ÄÌÎ+­Ä]–Œ:;›" ±¾®O€è qqq2×'`0€"ÝNU)ÿ÷_VØ^“¸‘žQ@ÈÝ«Û(]DDøä=b8VVæe)ÍÈG~Bƫۨ‚œcûL #“; ú1(Éršv–Ó’êr'0V) FúÑ{Þ”»Š-µ(w¼Š èwD‘6ÇEéà厘èwD_‘»*™™4;b| ß½#=ó¤ªYö7iqª;ñÔ’¢ÍSh#ÀXo¥ ßý‚ˆ£%e¬´ð[»ìýµåƒó#^ieANŠQ*^æó«¢eȧÿ£"†¨·ey»ˆDJt‡r7&ˆG£ü6†~GôùéoU‚wé’«’) Êݘ0ÖMò3Fùù†h:ßy5wcSžÜÕ—ÜGTÇ탕æwÛÒÔÎ Ø%åm†Îyœ™(à¶ˬܤ³{…"6ëeÏ·*ãÇ7vð|}á­<Yš7¹;×÷é^5%Zhk-v–@hcUxö‡Í,/Þd=+8rkÆDVM1æÝ7éú¿"ŠB'­IGîÊÙXúU*,/•¦&þ’õ{Ÿ<-57ã¸8Ù}¾ìÿ¦ÎÛ¹y…žîN6V–C#‚&Œê¿tþ¼‡OŽìY!y6¼O÷YK¶§|»öæíüM»Ž@_ì?'n$µ#^I9QZH4ûã¾Õp#'ïëä_¿ÙºªkêÆ|¸úô›y%åÁCçìÞôÑþ½ oô¼¬Ô ÖW̰jãÁQs.¦ïâ•”GŒ]ìï×¼­ëþí‹+*k"Ç}Ò£[‡ŠÊÚ°>þ gŒQ×F¶²²ÛýCɦÄÊ­O_pظ¯ü“^ôïÍlhluô”ë´UO¿]ÛÔ¾y·Ÿò…ïWOÂxÙ`v¿ûÍOîS–'¯'_1?¶zì ²5†@(â°K?ŸßäéjþÏ]§e;ŽÜÚ¡ÊlðiÐò;ÊQW'®ÅÓ ÛU| I¼-u‡íç¯Y˜sŽ}³^Ô½d³Y‡v Ó?þbî´QÝ|Û@S?÷þ#7gûâgÏ%ÏòJÊ ¢²fþò]É;Úµq€k7ïµØ5ÙlaQI[gYÕ¼Ûº%LHnCO¿¯ÞÄ€I|¼Ü“w$ð‚~ÑóÕë÷êqƒlýf~'¿áÕ' €@`ÿåw•ñã ,Í+gŽá<*¶ßyäÙ¶ÅÔËEæ Ý:–|ù±û¸Å¶ÇNW~8º¹œÅ™q¨5ËM&êûö¨×ßüVžûnþåŽ(¬_…r2úœJ±¶²¸›÷ø¿û…"‘ÈÚÊBjßÿ¾1°_ÏÑCC~9™%«Œó×Ãút'„ ½ü;µØ5fÀÙ‘ëßÅçô¹kªô%F}}¨ûE`ߺz»]?P Íî²Ê«^D¼±zë‹Èw-®Ü¡”ˆ8ìú‹ì\:=2›Ìï!9|žÄwq¨ê«ö7¢? ßía”ψN‹šU^Q=eyÔÀwȸ˜ ¦¶îúÍ{K7î^iùÍÛùþ]|$qr´Ë½ÿˆ~§’ͲÙ, ÙË|>ŸÍf‘•]œìÞökO†ðRûêØ¾ÍÑÄÄqpÔÉB/÷ƒ;ò Šç/ß5yL$ýÓAhmYùÁH»¯Ž–lYH”¸­@$b•WSϬ¨™q„V–RaUT [Û/«ÞV;"œ8q^éN$rØ|€ó°èÙ®¥ÀbI6b4 ßm „Ù b’KéóJ®­ ‡ÃnÝÚÆÚ҂ψUH?{eæäáSÇ€ËÙ¹)'³¤ú=24`Orj1¯ÌÝÕþ¹•×½k9ýJ6ûñ¬±ùÅuõ V–æ—oüçÛ©µþ¬)Ñ3?Ù&«/Wgž:øx¹7ÔN͘Û£§¬Î\&^6ul+°·µÎ¼X=îõ=RëŒ /»SÊFßò¯ë/¢^çÓ…6V|±w'9|ždv·àÙ®¥BkéFúÑ8F¶ò6ïþÁÂܬº¦nPÿ@/w± ¿žÊZŸ08x»Ó’ußðL’lÇŽÛjÛÚøÅk™L¦H( êå'é÷¼‡E“â7“ÅäóùbÍ._0iáÌ1±³Ö[Yš³X¬-+gP¯uu¶÷÷ó9›•-µ¯˜a!RßÙ#l_?›z*.VÍÏ ˆÌ8•Óß³ÿß÷Í/Ù¬Š&9|¶_hcEΟ±úûŸ§û×¼¾F(€ÙÐĹ_ÈÝ÷3C ¬ûú‹C `46½nŸÃvܰ×ìÎÃg»DfFc0™"¶Ñ†ð´öo*ÈI»Q¦½ùªìc44:l>`uö2°X5Ñas'HýèFô¯ÀXEÍw23]ý‹¼ËøF Äþe’ˆ*¸}°²vXHÍ{„BñK8‹HŸXýy½õ·¿šÝ{$b³zúVÄmìØŽ¼°Ù'æ|w§:Â'¯ò3b>€¢Ÿ·yŒ^@-y\ºq.ðJ+i‰U÷oÒÎ|U°Ûu”ó°¨(e£±ÉeöF#·z"Ý ïâ —Òw{i·_…䮟ÔÔÖ­ùâ ùòž¾c†‡êp<šæ)u^?“Ytl õl]H@]H@ËÒ;[põ°2C4L”÷»–æ« E6içK?›'°³€ª÷‡·þþ7ô»þãÛü‡¤øv*ïF@+«­«fêzˆÁ£üú3Ú™¯Êæ•1_Ô“_Ç;·ã<*fHÜÅBô¯ÀØ×rW7OWõŽAL•î¯ja¾j嬱 ²4'JDV 1êD­¤4…èªÍU1;Ɉ(€t•Çe¨HîÙ¹~D9Tò»æ«2ê€QßÖ–À¨{ L©{DçIvâAå䦛§ëÓǼ¸8å[PçzZGÒìÔr´<¢(ªÎÔø|ÕÖ"¡µ¥ÙýÂzG.˜Ý{ÔÔÎ݈ç3¤Ö ôäΕæîeÉZÁp¯ô—ü6£ ªú]ãóUÍ8µÃB¸ûnèÒžÑÐÔúÐo5¯¢{D·ˆÉ]uˆà]½m -ʬfpvSúK ~›Q5<ßT;´_ëïN9ôÚ!}…Ö–­¿ýÕaã>b¾êÓý«;¶%ëÛo?d¿ý9_µtý¡íëüŒÝÿÛýïuXx>©bî‡Mß¶‰ž,VmthõФF¨"¦ZX•rú½ úúÝQ4x}•fR1ÞM&q#˶ʕ˱ïL,}^ _¹å[W÷~9™5vúÚØYë7í:"‰nää½ÕgJEe |¼fZ©Jð®®H0x×s^;Œr7h0~W‡v Ñ“—eD„¼!Š›šÄ*ûuò:ûWöø‘ýÏü•Ý¥³¯¤œÉdX˜›‰ ð½t=ÊžW9ØÛ@æùk1ÃCÙ,Lzoàɳ— ßç/Þä Œp=d ÞÅhQUè2DQÐïšbÄ wíÆÏX7núÚòŠj±³l6‹Ãf]ÎÎíú–Qò¼¢ÚÙ¡ùØÙ‘[VQ L&cpx`ú«åÛãÆÕ‰+Uâ²Êõšc–¬¦ô…ˆèwMÁá°Ïw.e[xŸ‡~:#Y!$ØõÖäˆÐ^ÄK{®-‘‘€ÒçUÜæ–ÇF‡ý˜v^;cÖjÞ ÷æª$„Í©?º‘ò(ý¥¿Í¨ô»¦()«‰Dàæbß$méð>Ý{ùwjßÎxpüÄŸD*æðÏg‡ èM”ÛX[vôö¸y;_[×8ø@“I¡ô—#û6£ðþª¦ÈÉ}øÝ 3[(~¹&^²‚µåº%¯w²ðñr2aæz›õ¶ŸOô wÿ¹Õìô©ã';­¥qkÓɼóJÊg-ÙžòæÂyK7ìË{øäÇ}«àFNÞ×É¿~³uT×Ôùpõé6óJʃ‡ÎÙ½é£!ý{@ßèyY©;àæíüM»Ž@_ì3,„ÚxpÔœ‹é»x%åcû¿ÚyÿöÅ•5‘ã>éÑ­CEemXÿ…3Æhïý¿Bi#£ÊUý®fRn ôë9 _O©uztëУ[òeòŽâà½a!ï ‘¬ÖÖÃ9ÿò÷š±âH®Iż­ÚÔÄϽÿÈÍÙ¾°¨¤­‡³¬jÞmݦ d¼Ú’ª¢²fþò]É;Úµq€k7ïɺ¶ƒ·1‰‹ÄÇË=yG_ è=_'~GtægL Õ£!W'®¤Í‰=µÉ»0n~ÿûÆÀ~=G ùåd–œjÎŽ\ÿ.>§Ï]#K2Î_ëÓ;ôòï¤h×õõ `,w):`üŽ(¡xEU®¡à½…›«zvï5õô…eó':;Ú%L›÷áh95ã§Ž˜ùɶAaÍwàKË*\ìÅêÜðdüŒuı@(+äÚÚìÙ²ò Š'Ïý<ç΃3cÔûvh‚ûóéô»É¡Ðó#’×R_\=ŒÑºBÔÔÖ]¿yoéÆ}À+-¿y;ŸÍf Í^æóùlÊÞÂ.Nvoûµ'Cx'G»ÜûÄìØ¾ÍÑÄÄqpÔÉB/÷ƒ;ò Šç/ß5yL$hÜŸO‡ ßMå/õ/J2§JŸšÉÁÌ;¤Ÿ½2sòð©ãÀåìÜ”“YÏ›_P\Wß`ei~ùƾÚQëÏš=ó“mÄqdhÀžäÔb^™»«#üs+ÏÕY<œ——{7ßöjz+´Àýùt úQ¤å‰Rë^±^ñ¼ÇOu68]“÷°hRüF`²˜|>}Â4¢<àíNKÖ}³|Á¤…3ÇÄÎZoeiÎb±¶¼¹¥«³½¿ŸÏÙ¬l°ã¶Ú¶6~ñÚD&“)Š‚zùÅPîÆKí¶¯ŸM=«½í‹q>ÉDQ‘QIIIr*ä¤àGãC¡^é_^i¥W·Qnž®"hžÒ¢ëãââÒ3Òi¶ßrv½¥-þþK‚tPz¬?Cü¢ª«5CüáüÓ…Îs"AQñª?NB<ÐÄ{ü”0»«§ñ£J›ˆž£è6{ª_ˆH‚~7uäè[lÃUU 3ï„åQô¢0ÿŽÈþžKÜ,%ëJ5KêmUj–†T¼)§é©à¤@D- ßZ¨¢ø!µNˆžnêÝè“m O¡èú`~¡‹‰ïÀ Ó¿¦_ŸHÝDE £ùG.zöè’òðJ+éOÁ¤3Bô;¢Ú{šI$jþ¡'zƒF _£â: ß‘P:-ã8ááÕ#ªvoì¢WÚÔz®x…òHbûói¨óïˆtSÈúËÑh"^d"æ Å·”Ñï ŽŠŽÆ|ù`ünÒÈIæÊ9E'K£žà]*dD ã êÜŸOç ßMš·é¤Vk1~÷œ üÈhÀע7ÄìZ,†ž¥Áýù4 úÝQT —Òw“Ç4“3š Þ¥BFôÆ›¦7\p>‚ùw„.4ŸuÒ`f†ÒÓôz‡ãnƒÈÂãþ|:ãw“C9³!¼nî¬*‡dêF¿ jãw%!Ö_ýçv^÷.\œíV.œŸt7ÿÉì¥ÿkßÖ ¦Œähßzî²ÞmÝ^¾lسy¡ƒ½­êÃV%l$ãD9!¼Žƒw©PçÏP¯ÇójD- ß•„Ø¿8zò2âàౌˆÐ€÷c"„BQcSYÉdˆÕ¼‘“Gœòëäuö¯ìñ#ûŸù+»Kg/¢ppxàÊE“‰ã9y‘¡½V.šœüÃéÔŒ Œ¬½·'=Ù³IÉyÒ\ŸžnÚ\úFí7Eõ?EƒûóéÌϨ++‹oçóJÊ™L†…¹K‚|/]Ï€²çUòcóšÚ:K ZmÊGu­ÈoAƒw9¼º%1„÷ø)¹˜%®g) ï^±ÄBWÉš©Õâ .¥/DH0~W#½ûðÑÓñ3Ö¹8Ù}½é#{»–s)l6‹Ãf]ÎÎíú–×…k·‰ÂŒó×òÀÂcˆ—9¹ªkêÒ¾Û Ññ+ÂR.žR׳”<…€˜â^“ù›€ûóéô»zàpØ‹g[<{Üžä´C?™÷áh:W…û¯ÞšüÕgóI¿ â˜ÌÏ$lØ{óv~`·45zu``Á»\Ä„NÕ½Á¹žêbªˆU)§ÙîϧsÐïꡤ¬ÒÉ¡5ƒÁps±Ï+(¦yUxŸîWnä¶o×BBàÿ&ý2ñG½ò»X¯šH´<F2´w}õRÿ¿ÄÈ ®•(—£x9!<¢CÐïê!'÷Aâw'Ì8l¡PøåšxšWÙX[®[G-9õÇÕ{ùO &:´‡ Qèãå^^Q£Þ«-ï´n®j`ñÂõäþ«Ô[Íúïzµ£JfF¬2‰+}!" ú]%R6§Åôë9 _O:5{tëУ[â€<›¼#(¹pb'õ*²Î‘=ËÕ7jõ@†ðÆ”™QªÓŦ«î1T7 Ðï¢Ä„n|¡=šÝà@¿#ÊcâÁ»|ä„öpõ°zgøaŽ‘ úÝ„pu⪨}ðˆ~¯è.ÉÐÞ‹ò’º|‚¨ô;¢Îª+¨BÇÐQô;¢ ¼«N‹´tB{}`iFR÷çSîBD ô»)¢h/'x'ÐZoˆ7WÕ…œÐ^o¿<µø›&g>å.D¨ ßMâÏ£EËKý+Â9‘êBéÛ!bÁ»>?Z%ë7櫸>°* ßM9–Wô¯³ðÊ¡´â©ÿ@úÿhîϧÐïˆbB¼N*G ÅËÿ'3ÁU© ßµ¡éÞpŒ­04Óe Ô÷*êK= í[S4Jƒ~GÀ`‚wÃDΊê2šNB{5nÎG-GË·úiú³!5·¼K, /‰eâ %ü׎¹´Ú«"hÜÂIuÐïˆÁC˜]ÜÝÒ>dl ’îæI·¯§yŠ ÎpP{h¯Š q 'µ€~GZ@,x'^ÊÉÒhs"t³ËFjͨȨ¤¤$š-¸y{7Í¢Û¥‘àR=´GAëèwDa´o(±Xœ õŸo!âwrÿ&%PýÑ_íÿ÷×rÖ·pRèwD1èÈE£Yx0œ4ºTT·³n?!è„ö(h=ýŽÈCL%:É-Á»˜]]èÕ'„dho(Ó.Mô;B…´¢®žÐºÏ|× û„˜Ó¿V±}D- ßZè$çN‚r×7$Hã?¼z„Wªõ!Ò@¿#-£œÜ‰ž§øªå'3ÓÉ€Ý#wý\Sjðnó‚L õû¤øðÏí¼î]:¸8Û­\8yî²B¡°±‰¿aé´ŽÞdÍŒs×ò ŠgM€é±aé43‡Z¹¶¶~öÒÿµoëSÆ r´o=wÙï¶n/_6ìÙ¼ÐÁÞ–lªªúý ã¶'mÿÄÚÊ>^³§g·Ž»’~¡Ó‹¢Oš'Y·hL…T/¯¦ˆ¨ôŒt•Ú2Ì=|\S'*Ç-œÔ…‰úýÐîO zò2âàౌˆÐ€÷c"„BQcS“ük=õ7µrî½ÂÁá+M&ÎÞÈÉ‹ íµrÑääN§f\ø`ü`å. ïÓãÜ…† …÷M=f/ô!ÌÞwøZY§T] àêá ÀXÞãQò«‘f'fÆÅ5—¦¥•DçkªQå(h=ÁDý.†••Ååëwx!®Îöæfj©\S[çèÐZé CƒýwìûyèÀ kÿÜ ìÞ™~/tcvâÔp:žÔËkÂìFüP(MÔõ4ͯ\úŸ`Á-œÔú`Ä w>z:~Æ:'»¯7}do'/Ý!V2Î_ËT gŒ!^æä>¨®©KûnƒÒš™q•ðù‚Ìó×…õ¢ßK‹´(w’¾Ã×f¥­<ž4OÅ_JßäéÆ{ü”Z(°#ª#kÊ£–UNN‘T]и…“ê ß8öâÙãÏ·'9íÐOgæ}8š<Åf±q,Ù,–Xå~ï¼M¤Jˆ:dæ$aÃÞ›·ó{¼%«—/ ð»týÎõï}:âÍÛù4{¡¹“5³ÒV*Ô¸|è›Ýt’3ª< $UßÔB­Eå’“ßU4ná¤"L]@/()«‰DàæbßÄPOy·sý7÷446=+­à¶¶‘S™ÊÿMzàØiš½H½pPx¯ûS:û´a2¥/‹%µùOšG_î}‡¯•zV!\=ÝÜ<]Ý<]Ÿ>æ?*6ˆxNó˜ÒëGˆí FÖ“M®N\©.–U.¢2õGÅÑš¿ää>Hü- ¿\óÆ.õÞmÝ:µo3æÃÕ|0~êƒ!Vùé³òS\½—ÿb¢CÛy¸úx¹—WÔhßîuĪKŒe¥­Tb <ÂëÕä<£¸A­‡kbBÆ A¿+¶Ö˜_'¯³eÙÿÌ_Ù]:{Īa Ÿü1ãðžåæfœšÚºŠªZêå:_bL ¹ŠW}‘…—È@‘ë‹™äì#½z\Ín ßÅ‘¿ XP€ï¥ë¹ãGö/{^E„Ìbõ3Î]fnÆ€V6V­l¬è7N¢¡%ÆT_i@¯– 2Ħ–‚®ï  Üô»8ò×c³Y6ërvn×·¼.\»-YÿyEµoÇvpøç³¿œú{xdðû1²-.1·CÖxšóßAîxúÈ áMðΪ¤ÜuŽÍ~óvþ¦]G ‰/ ö2kÉö”o›¯x%åÄK^IyÄØÅþ~>Dùþí‹+*k‚‡ÎÙ½é£!ý{@PÔì^›Wèéîdce94"(fXÈ’õ{Ÿ<-57ã¸8Ùm]5Sõèwqä¬5Fì¿zkòWŸÍ'ü.Vßžk[V^±£¸:Ùß¹W §qí/1&fmúóßAkIbRëÒè›ÜÕ³WTÖÌ_¾+ygB»6.píæ=9•;x{ÙQï¶n‰Ó‡2 6›Eœþñs§êæÛ~;sÉœs웕ð¢î¥ZÆl| ßÅ))«trhÍ`0Ü\ìó Š%+„÷é~åF.y×T¬þ°ˆàeŸíÌá°…"¡¢üßÄ¡_&þØã­Aá½6lÿ¾Å%ÆˆÊ ½MµÌWùYx"'÷ÁÞåüh IDATC¿qmmºtö²²²8–z®­‡s[瓇SOùunG­Æf1ã>7"|Å&vEeí츾ۥe\L<˜–ö݆ËÙ¹?72œèâeCãš­Y,¦@ ?2Ü¿‹Ï¥ëwVm>pê覆Æ&ê©Â¢6‹9dÀ;—®ß¹—ÿÄŽÛŠ:ùoDí ™Œó×Ãút'ä½ü;ñJÊé_îìÈíÜÁóô¹kƒÃ¥V°¶²¸›÷ø¿û…;xsIÐïâÈYkŒÀÆÚrÝ’8Yõ]ícGˆµÞÚÊÍ#§q5.1¦Ð{Thþ{VÚJ¥ïÊÒ‡¸³ªÑ.4Áÿ3-6Ê¿‹¤e\ŒÕÈ€w$OíØ÷3µZ|BßeMo×& "4 öEýœ¥;ìX½=Nd^rzónÊw?f†õñ|¾Nd^;"ìrvnNîCê©Â¢±áQÇ# ¥ÚKË*\ìiV¾ÿàÉøë€kk³gË¢0~ꈙŸl#2“’„½Û=¿ xÉú½Å¼²¸ Cˆ41ÐïͤlNaè×s@¿žRëôèÖ¡G·äËä Rë ,µ±Ê®ÎöNìë‚88²g9qpñ·]ä©+ÓG‡KÀGÿ~LÄ×É©ßχS~Ϻr˯S»‰ï ¤ž¢Ï[öîîϳÎ_*6Ö–üW Ðû·ÌKãF„S»ÈTLþ:±Ù¬u/›šøc†‡}¶ã°DÔSäž•V„½-6©ã×Ü}T'G»ÜûhVîØ¾ÍÑÄb….Nvoûµ?}«¦ÅFM‹*¯¨Žž²ý.ô»É¡ôüwÍ…ð†{gÕŽÛêÓù`òÜÏÇ ¥ÆËÔSƒÃ‰cïÀ;6ð˜>íÜnß-pu¶Q÷’Åj~ÌŒÑCû?ñgPO_² Ÿvn9¹]í€ÏœÈ¼ô¬´bÓ®#Ù9÷‡ROÁ«€ÈÏ€Üø]Ó3d"Cö$§óÊÜ]àŸ[yÄ8b֔號l“zªôy%×Ö†Ãa·nmcm‰ùé ßM ç¿«w0ÔÞ“3p"óâÕw›øüÞÝ;ëxÙÉûÑô÷¨§ˆãƒ?z-šÙœ{™±fkòŸ—þ­¨¬Y?Žl0ìÝî{’Ó¨]LЉX¹éÛ³YÙ6+fXèY7ö}ù1›ÍÊ8wíé³çç®’§$‡GY¨¹vÜVÛÖÆ/^›Èd2EBQP/¿˜a!y‹ˆÇ ™,ææÓÉÊd9l_?›,wu¶÷÷ó9›•-Ù~A!oóî,Ìͪkêõ—ž£Gpýw‚þΫR!ü®èüHùËj~K¾KW+7H…×'ÐÎúïz2['¶Ë×G5#)wUÑËÅgôAîhvýÞŒB«Ð‹Æ|8)êÏ‹ÿ®\4ùFN^ZÆbr:µÂ”qƒ"BÈku¾þŒ*9t-Ì7JôDîhvýÞŒB«ÐëÀÜÈÉ“Úu¡*:_Fß î¬ÁDYè\î¶›2¸>°t¬¬,þ½Ï+)g2rŠÑP³5µu–f‘a½2Ï_€ן±´PÛµÞY¥ƒnåŽëú"¿KGþ*4'Ï^¹sï‘»«#um*Ô…bˆ§Z¤6 Z\FŸ1޹ðbhYîb ÛÀø]ÄB1çR¶…÷éqè§3bg‡ è}4qÅ—kfɺ<2´WòŽ„ä T¹KmV¬fdh¯÷­îñvÇ›·ó€\&À¿s‹• }f•Ú—;õ1lGÐïÒ))«‰DàæbßÄh¹Ùÿ›8ôÀ±Ó0(¼×Îý)-®?CT6hŒiwVÈ™=¾º¦NVå9y1®º”þ•“÷ï+·&ÍÞxû|’••…X ÿÜÊŸ0sÝÅßvÙq[}¼fÏÖU3%G¨Š¦~—5bv ¯T­j`ß“™éZ–{ZÆÅ¯¤¦þ¬®îeßèyGW´mã²zˇ]QY;;nDÞÃâăiißm¸œûèñ³q#ÃÉF^64®ÙzÅb Âñ#Ãë_6¬Ú|àÔÑM CìTaQ ›Å2àK×ïÜËbÇmu,õ\[ç¶Î3&×ÂûEtŽ^ûýÐîO zò2âàౌˆÐ€÷c"„BQcSµæƒGOþòÇᯗ±Y¬5_L˸XYU+«2øuò:ûWöø‘ýÏü•Ý¥³—Ô<Ý==œ§ü>ûƒZzà Ââ–F  ‰ˆHÒtGb‘»—§Ë?·òò Šƒ»À¡ãgô ˆ ¨}Q?géŽ÷†…tôö8‘yÉÉ¡µX;ßý˜ÖÇPX ðù‚Õ[“ÇŽ»œà'vª°¨DìÚØQý‡ xG³ïÑ' )?ceeñïí|^I9“ɰ07£žÊ<-fx(›Å€Iï ($^~ÿÓͽSD¯0$¿ô®³£ÝøëÆM_[^QM=õ¼¢ÚÙ¡9×áìÈ-«¨–SØl‡ÍºœÛõ-/Y-“ɘ~æ²FߢWµå–æ MžîNÄKŸvn·ïÀ‹º—,À=´ßñŠ]èÓÎ-'÷!qüËɬg¥›vÉιÿ¢î%õŸ/€ØQý7,öáÄ(¢x9ñ½šx›ˆbH~çpØ‹g;—²-¼OCoÆ ö\ÛÒçͱpéó*®­œÊ!Áþ«·&G„ö’Õq<6:ìÇ´óšzKÆ…ön´ªÂëVîËLš:n0q<)&"óüµU[,ݰwqü8¢0ìÝîOŸ=»jRLDƹ« ö®Ø”´kÿ/û¾üxÃÒi‹fŽ9‘y‰zŠø´ƒˆß·ó“ÚÞ¢ßèuþ]Œ’²J'‡Ö ÃÍÅ>¯ ˜z*"4`ùçI#†ôa³X‡>;d@o9• Âût¿r#·};7Y-å6Ö–½=þºœ£éwg4Š×ç\¼Îå><2˜<ž55š8ø|ùt²°ë[ÞÄÁÑÄb×ZZ˜oyu«Ý’8â 2¬9LÙB™àߥ9Wàà'Ö/b ’ßsr$~wΈ …_®‰§žòñr2aæz›õ¶ŸOô wϺ!«2µ%ùç!µ…nå§¦Žœ|ì´FߚѠí^qOkGî^±€S!Ýb~O=¸8Яç€~=eU{oXÈ{ÃBÈ—r*÷èÖ¡G·äËä R[ «µõpοü½ ïÑPîˆIaHùwÄ€ÐF¯`厘èwDýèaò厘 èwåQýéSã{~•œö®¥D<½^r—|vån¸Ó“(TÐïˆ1Ã`hDî^ÝFÁ›R@¹.Ä¿#ñojdÀýU}ÆÕ‰«ô'¿QïÔ—D¯ñõe‡ðD±†Ò2^ÝFä¤ÿúAQñp)}7i¨öþx®C[—ðw|uÒ;!÷òŠòŸS¾™s³²ªÒÆÚ¦«_בÃFz¸{lÿj;|4û#±«¨åÛ¿Ú~åúêÙÈþ‘S'M%OMÿ`zX¿0âÔO¿þ”}3{Å’q³â@ ç,d±Y§3O?(xPû¢ö³ÕŸµkÛN‰÷…~Wåo|r'̼«sÿ&EÐ¨Ü )¸zºïñS/ veüX¤ßâºøê0ˆ~VòlÕÆUΎΓ'Lvuq­©­¹š}õèñ£‹æ-¢ÙBW¿®ÇN$_¶jÕŠ<æp8?§þÜ7¸/›ýZ¹æf柭þŒ8>uæÔ½¼{ófÎ#^:99ý{ëߎ:î?¸_é7…~G4ˆ®²ðZ{‚‰”»6:3^¾úæ«Ü»¹¹wsLù‘ÉdÚwèïKÿœúó³’gæí½Ûúñ§°èÓE#†ŽéÓ<‰yA‚÷F¼×7¸/¬þlµ_g¿g¥ÏnݾÅd2ÇŒÓ?´?Q­¾¾~ï½Ù7³m¬mbFƤL£6Bràûö\û• +Iû½åWS[Cÿ]XYZÉŠ²{õèu÷þÝ3çÎ 8˜,d0d}[[[3Žõò À xVòŒþ$A¿«ECx£ Þe-(v1ýk7O ‡ð(wgöôÙåå]ýºŽ> *«*÷ìÛ3e┞Ý{666ÞʽE§‘Œß3ÎY8wÆÜoý»yûæ®~]œ`_ò¾§¼§«?]mnf~èè¡òòrÉkëêêþ½õÔøZÙ´’¬¬gÔðQÇ9Þ/ÜÜÜ\-mÒﯪW'.kÓ¬f|¸yºj¶‘ ÝM• Ê]sTUW EÂÞ½ííì]]\†ÑZ-¸w°ß[~ðv×·]œ]þ»÷Ô¾¨½|íò”‰S¼Úz¹¹ºýßÿרÔ(yí³’g"‘¨GU†}åú•ظXòçÏ¿ßX.,$ÌÂÂâTæ)UºPŒßÕ án©±¼qk]~æécžÆýÀhin”»fñôðôíì»ðÓ…Ý»u÷{Ë/¨w•¥U‹W¹8»ÇÖVÖuuuÀãñ„B¡wóR<ÜÖ\{¸tõÒ®Ä]DáÊ%+9ŽêÃË¿;88Pϲ˜¬˜‘1ßú6¢„ê}Ñý®~Œ[å²ÿL¡xÍeišï¦@ã‚G¹k&“¹lñ²Ü»¹9wrNfžüéן6®ÞØÚ¶5ãÍDœHHîoB÷nÝ7¯ÛL;:86551Œ'EOÚ{µWzØròïï¾ónjzjÚÉ4±,æÀü bð0@$aänØ0™Lª¯ †ß[~ãFûlõgMMM·îÜK K"*€¦¦¦Êª–oz¹ºº2Œü‡ÍkVVU>/înîÄ™™™µµu·.ÝNŸ9-xs3…ƒÁ;jì©3§ªªªÔجÐÐܪICYƒÛ@[;Iå®9ìòäUTVÔÖÖœ8uâIÑ“ŠÊŠ«ÙWëêëˆÜ‹ß[~._hhhGð¥o¬FÅÆÚ&(0(ùpò£ÂG¼g¼½öšqÄws#˜:qêóòçë6­»š}õñ“Çwþ»“|8ù›¤oˆ³uõu ‘?ÕÕâ[IÖ)-“²õp@OÏó·¼¥ÑZQqóŠ>ª}QÛâUb`~ÑjÏÒ4ßMÕ Ò_ƒr×(C ýjïWs?ž [7lý÷Ö¿¿þök}}½“£Ó“>èоŒ6¢øiñìE³[Û¶9ÄÑÑ‘NËÓ¦LÛw`ߪ«ˆù‘ÅO‹¥fÛ]]\7¬ÚðsêÏÉß'WUUÙØØtëÒm˜æK·îÜZºz)Yy|Ìøè¨h±ÄêôX4WÊÜù±£Çnܺ±Åaßɽóå®/‰ã{vÀÌi3%§uʇ!‰¢"£’’äí/\“bš9e¤EZÜ̓WZI}h…ŽßãââÒ3Òå×yãñ%±I3 Ρiñ÷PîÆB]}ÝŒy3Ö,[£JžýÿÛ;ï¸(®®Ÿ-ô" QŠhÀ(6bTPk°c ¦ˆMì¾1vóiÔhШ5±5!‚AÅ}_k°lD¥* Åmߣã¸uvw¶ŸçÇ»3÷Þ™öÙ³gîœko"ŒßuQªZ$#!¼îM¥‚r7h ‹ |Ûù6°–×θ•Õm†&?<‘*p°«s×v¥›ß+q9ÍÝߪ6þE{ï×o€ÈÊ‚çéÚԧ볆 ímÄöm­KþÚá´qŸMö%ÎÓZ¡­USXpõìIG{wcR<ú¡#ÉY9I¤,±§1¹fW­20ÊA„vÖŽ[~­\ó¹Øv›ãZ½£zÞ‡Ïôb·¼°ûå˜û”%e?.çùµ!TÏz¿nB «¹Å<¿Äaûož,*ݽ’T|ÍÌ„º±ƒ©> «›#°·åVV;/Ûæøý/O}„â5ÿBµ^_E´}¹KAÍ« ´?¨–GtBݸÁ–—þ±¸]øÆVÀéÛ½µÓÆ5 í'²²8ØÕNÓôN°Ó&ñ˜CdiÑÒ9 òÛ9"Ø8þz;‡#27# ¥“?ß­µÈÊ‚ïê$´µ8ÚkþÅiŒßŨ¼« wñ^åà]ÉÀ_@žÇãÏ_ùã²* s37ÇÕ_~Ð úŸq.#ÄŽן:xXlòŬÍå•ÕQcçùwnœ[S[=n^·Îíkj"úÏútŒR¿+YœZÕMˆqÜükù–ÿÍï•pªŸ= £¶|ýŽË¢Í [üã_dÆmêßÃòÚùDzß´ÕO‡ÙÏž7F…ÖLeæüõ ô;¢qT¿šJÈÔĘ̀šÕ!-¯”â³Ï^±´0;°}1™³þ³)£:úÇ¿“ÿÀÃÕ©´â©äÞòÊj¨©­Ÿ¹hóîM Úµq€+¹÷š¶äqe[/WYÍ|Ûz¤îÉÂzõ•ˆþ±Úûzí£8ü}uÜzÀaçË9NÍÁ+×Ï€ú‘žG…™=r^ñƒýo§êÆF3õôÌÏ š%,6Ie¹‹Dº‘; yÝ•f{kË ÞÍ/‰D6Ö–RÛœ>}P¿î£‡öÿãè9YãdŸ½ѧ+!\èÜAá¡é ®ÎÁüŸ¹¢Î±ÄhjjFïÚXÕ~8Òñû_È?ŸÀÁD"Nuµ»¦Ndn&´¶’:§¦NØÊ–|úìý¸Ò´ÕÄÏÓEEVg‡æ·k§Œ²ûõ¸Ôq ô;¢u’3¾!.fmUýØ,¨œ{g¨äoùÃ2ÂòtG¼Ó5vPïù+è5dÚÖŸ2¤¶É8~aôÐ~‘}ºþ÷â?²Æ©zRãîâ¤ÔyÒ–`Úä©{2E¯~9R•ÿÑøOW?¡PlãÔ¹ˆ-…Å¥“>[ÝÄçIWêlR?&Š]So}ò2ñ”ÐVàdosâ"µMö…æN’É`ñøVÿ»ÚÜ=Ü"´µæ»:?’©v–‘ÞÒŒùD#k€åì/¯Ro ‘XÊ»šézî¤â¦k¦$ÄNIˆ­®©þÁ¢ØA½É¸˜ ¾¡ñjî½…_ï€òªêÜ[…Áü%qqv¼“ÿ€þéIËår‚—^æóù\.‡lìæâØ%È á¥+À¯Í/©_Ãb“%7øûxîÙ´ °¸tæ¢Í“Æ0™Ü™›Õ~ò®Ów?¿|ÊåÔ|>±õÿíÚZ“óg¬Ïß(Û¹ìu¡v Ï,¿ÄaÇï,šoa ¬<ê!ìÒO7†÷ÚÛšß{Ðê§ÃbÉ}ãýŽH‡\¤I…à”»ZgðÊÑ4—èìÈ,t®»V=­u°·53ã¶jekceÉç Ädú{ê¤a“Ç€Ë×î¤='ÕïÑá=¶íÎ(-âéî 7nt}»½œs“vNÒØÂâÒÆ¦k+‹Ë×ïvhGmŸôÁð©ó6È:–»«r_ü}<‰‹ÌÒ0´_«½GÈzCL_¡U«·þz‡ˆËiéX¶sé‹€¶d{§ûœ6îYZð=]ût­Z™,´Ÿqü.Íñ»4òiÉ©íVço8l?Änhâ»95Äö«ýx4ã/Aç ß(›ŸaVîR¦ÃÓëÈ8òï„*.)ÿf˯–æuõƒ„øûxŠ58|ìÜÊSˆÇ=ºt˜¿b;ÿ‹‰’ã8:ØmX>mîòT6›-ŠB{Iú½ èñÄi_›ÃæóùbÃ.úb⬩c’VZ[Yp8œµ‹?¥öuwu ò?uîšÔcÅÇõ—úÚÉ#Àƕө»b¤vQ–2êŒ 6ûñµÔ½ý{4ö#½½)óU9Eƒ‚%‰ÖG-ÎKÇ;8L ÉEVé([ò༪VÙ[_&»ßÔ4-o«$wÕê“<¾;ŒþiõÄÄĬì,ŒßZ¨&wՑдâ^»k¨’|qÖM«¾¡qÙú=äÓÞÝÇ ×ôA#ýŽ039p÷öˆŠ‘U@R¦Ãu±@6‘¢ õö€âœ4EÍUÇÎÖzÝ’©š1Vp~$¢ùâfVîr&¨È¸.äNr)k ø„$hhüòªZ± 1>0~G¤@M¾KŠ›š‡aVî Û0_wŒ ˆàP<ƒ¼,•Û1é(ýŽ(!tBñŒ/×G³Àk¥ëÜIŠsÒ|B|BQ¼Â8½¼ªÖp/ùêh¾•;š&èwDbú¦ÆõÌÊ~¥—×ÓáõIîLò4“0†¨x•¿”à·Àü;"…ú–œC©Ê–ñ‰ôQî$Å9iD,¯ëÑ;è|)a¶£‰ƒ~Gè"Õæê+^•U“X,³­4™®Q¶£Rª2 ¯ÑÿRÂTGó3&Ô·ñUWNf†DýüŒ*+i°X Òóþ%dºF£³'µ†oÏ(ºb ¯ÅD@¿›(r‚òªÚ‹Y[^'4U»—•î™(¼S¤®DÑBæjŒÉòô]¯ì—2Ÿ®rGÐï&ˆRßvÅÞ-ÌΖQ.3C\TÕKÃÈyÒõÔë'èwÓBÙeyUmXl0mv`Hî†Âh?—k«³]þPôzúQãfeÓîŠ"wÃR7ØÙ©Õg_¦ø¶õhnnÙöͬi 6îÚ8X±sβmSbš›yrÚ·v_`L!êÌcVñJdfèåÜ +KC å@žPë‡ñøXÙ}ø¤/‰{dG…÷x?>J(½à½±.WQIÙϿܿm‘¹¹YQIYþýG¿üñWÚÖ/¹βõ{2³/z{ºz{¹¦¥Ÿžþá²×Èų'‰³ç`vÚ¶Eæfõ 5”ÅÚàð±óÔ¸s¯„:Âõ¼‚èðž‹gOÚýëñŒì ‘}º¹pcè PP˜ÿq`@»ëyrÚ8~ˆ~…Ú€q¹S›–âAv O3„7 ¹"*)Ñÿo3úŒÑÞßdmmùÏ­ÂòÊj6›eiaNÝ•}æÊØáææfàÛÖãBέøaá\&¾;èè©Ë0 o·³sùñõÕ$DZ07;[ë¶^®4O€J}C£•¥ytDÏg¯À•ÿ†tí(ç D{…/_ õoý`äæ%ÒîJÊÝàÌNBÞì*v'”BU¡Ë…Oü.ƈÁï=(ÿé 7Ç­k>w¢¬˜þ´¦.0 Ô§®ÎOjê€Íf ‰ Ézµ|;=õ÷í{<Ý¿]–$Ö1í÷S;?,:ìýø(Y'Ùg¯>(€YŸŽ!žæÝ¹_Wߘ¹w•¹¹ÙÃÇ•|¾àÄÙ«ƒ#z#Èi¯_˜– ¼g8  ´³ 1KC"5' n4µTþRb|ßf´†ÑÆïffܹÓÇIßÙ§Û¾ßNRwµv´RýŒ|êä`_õôåOÕÓg­^~Œq0ó,Ù,f`¯_R¿"åNt$ÆI=pêûÞ½™Ÿ‘<èðž»SìNY@,©ÞóàŽ¥ÝºäÞ*€ÐA—®Þ¾úϽÁ/ãwùí š™©‹ó™ry±]¡ú¨ü¥¿Í¨†Ñú½òI­H$7'Þ›ËØG…÷<˜y–XÛþÁÊ>½::ò_"“öû©˜½ˆf¶6V¾^rdÑó`æYB‘þ Pùø½¡?8ƒ#{nÚ™ÞÑ¿ ›-o:ÙÞ°PJî±Q*.ÖL„ð7† õº«Q"ë#JáG—ÊM£ÍÏäݹŸº÷ˆ¹W(~»lu—_; £LHZieiace±aÅôø¸þ¦®4ãrºùüΛ/>yüݲeê×Î#aôÀ„¤•6ÖV0;iŒœ(«¨>öWνÂG?<¼—ÑÌßdzº¦þiu]ç@¿’Ç•Ÿ¾?ŒAN{õ?Ú„fÚ\±U>÷“Š×·¯å•ÕIó7¦ÿ¸œºqáªEîX ×ó > ï±}ÝlŸ„Z€Þ~mŽÿúMyeuØÐä-k>Ð úŸq.#ro®Ù¼x|AxXp|\êàa±É³6—WVGäOlܹqnMm}ô¸yÝ:·¯©mˆèLdÿ´ŒÊFF•+…±ù=cÏËÜôÀ~Ýöë.«ÙðÁï üùôݸþïÆõ'Ÿvëܾ[çöÐÖ˵ðòÏÐÎÛØ"Ö`Xtذè0©‡;wW§ G6Qîß¶ˆxpñÏÍÔ½ Û ƒ÷×rgRëz+z'ÿ‡«SÉãJê%ú✴ºúF³O}Ûz¤îÉÂzõ’jjëg.Ú¼{Ó‚vmÜàJî=Y‡hïëEL*#ñ÷ñܲ€/ô>S'~G´ƒÑæg=!4vš–åNE$zùCämô0usúüõAýºÚÿ£ç$÷òüÚø„$´šìêìÜÉÿø™+ä®ì³W#út%ä=ƒ;({覦½¯²Œ¨…±Åïˆ^á’p)k‹Ü6š“;2o£oeÊ2Ž_øræ{®ÎŽ©{2g|4Z²AqNZyeµåÐäi+“§ÎÛ@N¯ªzRãîâ$Ö8ÿþ£ñŸ®  „B±ö¶ÛÖ~…Å¥“>[wûþSã5ôºäƒëóiô»I ÔM"²FP¶ËËÙ~r«¹“è[Þ¦¾¡ñjî½…_ï€òªêÜ[…\.G xée>ŸÏårˆÇÍÝ{ÇNï @†ð.ÎŽwòˆ à׿—Ô¯ˆÇa±É’ ü}<÷lZPX\:sÑæIc¢5ó⤃ëóiô;B ²<Írctf€hYîTÞ}–‡7”=,×þidú{ê¤a“Ç€Ë×î¤=7'ilaqicS‹µ•Ååëw;¼¾Q£8'í½Êê™C“‰7mtxm»3JËŸxº;À›î®âá¼|ü}<;ú1ør¢ò¢áƽڸæ@¿› ê„ðî.E9û Å‹­õ!G÷ò˪èPîTD"ˆŽÝµk—‡·;±EÓ¢/(zLÔJbsØ|>å‚)Äö]:Ì_±}ÑgM“´ÒÚÊ‚Ãá¬]ü)µ¯»«Ó[c¢+fû„$@NÚ†åÓæ.Oe³Ù"¡(´gP¡ø‹Y[IîTˆø½8/Ý7ä¥â™Z‹œþ5±»{0G¨ ßf´É+õOc”;±4POô*L›úMZ^ëóéô;¢.r<ò²þLçQDÎZßæŸ0Žú¢Wy†ŸäÝ=Äj!€¼ ƒùwD-äËÜEMXkñìtFQÎ~Y—"ä æômÉîäšPê Ë4ƒk©ëó18¾Iñ;¢:rÄ!Õûä,C0X˜ÎÛ¨Œþò “-rÖçS­£‰ƒ~GÔBª2äg~MÍò@OôŒÜ{)«‹þdäU^4ÜÈV×èwDEdÉ‚¦DLÐò ëˆ^¯y-w4MÐïˆ*¨)wÓ´@ä”I­«/z:•ÐuÈãú|ÚçG"J#©#Á²‡å1Q±ú¹¶v¸˜µ•0{Xl’抨SÒÀ·g‚oO¥;Ê*º ¿ƒ:MŒß%êqM|Í'—Ã]/‘ª+ çå£f OU|ÑÝq}>-ƒ~Gè¢5¹“ åA[¢g$#Oº^ªèq}>íƒ~Gh!õ+¼v.Сå Ô½¬Xûåv€ˆõ:WОÆFôˆFA¿#ò \LåZž}–'QYô²lKÝ^œd /¿½,Ë«¼“ÉH\厠ß91»Ô̬®¦Ö¡å©h(u£lFƒt½ýŽÐ‚*tÏ›FË‹!)zõo‹UXÒµ®ÿàüHD:’ w2œ×“›bD"‰À”gRJBέTªn¥Èë®ê…hŒß%й“Pcy0Ìp^©%,èÀl•ý©M†( ú‘‚ÁÅk¤å1iC…AÑëOm2„>èwD9ô9Ž3\Ë3ÂK@ˆèÕ¹ ×çÓ èwãçЮR·Ç'¦HÝNïR—þÑ[¹“®åµ€ú¢ÇÚdúÝh!µÞwØrù ¨¢—#wÃz?“Z7”Ô<#!<ýVÑ«ÈÓ|R×çS­#‚~7BqËÒ: Ù€hOZ^²æ”a™] ƒ½–‘\'–¦èUäq}>-c~ÿòÿv>ol¾y·¨w÷À}»{º·þî‡ß\ËæN&›ÝÍ/!¶‹D"PHíòV€÷àñó³ÒV{¹;µf¹ËÓÝùæû’CÀÙ‹¹³–lýß­­-ç,Û«}¼býÞ<>Ï»£ÏäñCÎ^̽të_¿}kgk=勵;7ÌÕî/†®ÙÅ Ú¿´ü›ïUƒ6»úŸ·Q3„WÇqÔpž=Ë«Èãú|ÚÄ ý¾já”GeU›w¦¯Z8¶íÎÛwpDˆX³3r©Û©]Öo=°lîä´ßOÎOž@MÖPýzw>|üÂàˆžlK(8|ƯÇãÀç_}7¿ôé¶y× gè`þ‰jr'é;lù¹ÌŇv͈OL1â™’á|L”®ÎE *+ž)Í©·Q-WíôÐæJa~#aôÀöùëü 7gÇÏ?y—õê+¶lÏnÞ-ž4ö£Yëx<¾™WáPÝ;\ýç^]ýóøaá2Îü[øðÝ¡ýˆ]ÝÞnÿoáC‡V¶o¿åûoáÃ’Ç•šÝoph× •ÍNBòñÆhv1(¢ÏòðЛ¥£TP¼&¬§”èqÙ?ýÄî_µ·³ž4võ—×>k¸y·Háög®4·¼X¾~OsË‹¬S—é EÒ%Ðïz^o[wðmsãV!±ýÆ­ÂþmˆÇ3>²ãwÆ_¦‘;IßaËeM¹1Jb¢bË–—=,÷ðv'~t}Fàîâ@SÙô[ªLQÎ~â‡HÝȹ-VÕBM` ñû±¿rÎÿÇápjëÚûzÉÚþ´¦ŽØþÛŸÿK];ËÞκ±±yú”Cú(Šdb|Ô„Qjë`üÈÈåë÷|µf׋ü·ßò hWù¤Ü\}Ú¸=ÔV¯ÐÅç2KÝ.ÿ#HÔ¨~Z¹@ ž¬++ãLÝ«MèDôty±W¤ì Q³»IÁ‰D±Ñ±»ví’Ó¨8/‰z‹¬àÔº,Ëop.s1#~/¯ªõé{1wÓŽôþmjëž§¬L>ŸssÓŽôŽí½`~òkk ji_‡V¶02¦/|0cõî”Dw¢ýˆ!}~Ü”8Ä’ÙPë ìÛ}@¿näµ\@X2¾VŠä¹ÌÅä°˜¥a ÕD/kÞ±]'–WMåô+¦I}Qjv7e Ïï4‹@ì Þ‰b>ûÏ¦Šªò)±+í÷SÔÒ¾n.Žm¥v—<„Ø PÑmaœ"©ÿнB£Ñ™ ¨rvHÖÔuŒÊ Ãó»r*úÿ+'÷V¡P$ôòp.(~œuòraq)¬Z8E¬´ïÙ‹¹’~'ÛÏOž §h0Æ)’†…ü=ÍpUŽâéß[¤Ñì YŸ€N .çãJÍî&‹Áû¨è K¾ùéæÝ¢Î~ä®Á‘!‰bf|¹éÁ£ x3~ïà×æÆ­Â.AþpãVa÷ÎOªë ¶®ÁÊ‚hCm/ë’t ô»skôÐ~ðª€0y”OÞJ˜œñÑèµ[~Uÿµ‹]•ƒj%$ ÅcŠFs0>ßF¾Öµ™(—,!)ßÑtÖoÒZ}1£Áàý®°¢ïÌßݼ3=.:ŒŒÇg|4z܈—¥}+ŸÔöéõöØá Vþ°xÍOªŸ}üþP¢#Ù¾£¿w~Ñ#9‡ ¢«ÂŒ—¤š]aEo\ÒAM¨¢¿˜µ•~G2„—cvíOe‘UXjÅcújV³» bêõ÷:ÑÔÔòÉûqº>¥¡Þߤr I…í‰íäŠ}²ãwíLÆßÊ.Þ›¤°ÎÍnX`}`½Cµr¾ïÇ«¾ø¦~ÖÐüH\šGo‘t·® {‡Ü ãñ»öËùêCa±àÙù‘ 1ER–å•Z[Ñ4ÚŸå‚f×OŒÇïˆÖæGŠY^Rúø>7Ðìú úÝHÐþüHruò)ù˜º½°F!îîíAºEOE¡âu"w4»aax~Wª~d€_›†çMVL«¬ª<~~VÚjoO¾@°jãϵÏl¬-­,ÍûöîL--iogMDË%!•…©’²æ¿kº>Ô ^{·6é7Ro× ÛÚžß•­9}áw•U5ûÓO/›;9í÷“ó“'Ì8àë•0z ð‚óߤ–šC·%!•…ñùïÚ„t}bbbµˆ›H¤ý“Ñt{ë8SfϽU¸fó~àñáaÁñqý“æoLÿñå¿Yye5ñ´¼²:jìÜà bûÎskjëÆ&oYóyÌ€^;½½Ü)(ñöt±µ¶×þÊ•UY˜›¹¹8®[2Uͳ5 Ïïbȯù°´*°}[WÇ›w‹g'ýhÖ:· d̰pؾ÷Èåëw†G¿C--)6¾KB*‹QÕ&Îbù75mÑk¦ä^S[?sÑæÝ›´kãWrïÉiÜÞ×kß–ÿP·ø¶õHÝ“9$2„Åbq¹bï'sÖ6eQìïÏ“—,-Ìl_ Ï›Õ<[cÂàý®°~$=u¹¹åÅòõ{š[^dºÜÁ¯Í›…o¿åûÉûq÷î?âñrâwÐnIHe¡&Ð54ÿ]Ç%$©N×û >ïÎýöýé`oÛ©£µµåŒ3m½\Ûz¹~:iuWPÇvÔf\;²o·¥k23ãÖÔ6LOÐ.3ûbêžÌ̽«._»óàaŸ‘‘Ä!š[^,[·‡Ãa Âñ##ƒ;ù_ºz{É7?ûeMË uWÉãJ.‡3°÷¥«·ï>rt°£žüÂlB&ûìÕˆ>] ¹@Ïàå•Õô»»:;tlï}üÌ•!‘R¾¦€µå¿ïæ—tlïmcmÉÀ ïw…õ#à·?ÿ—ºv–½uccóô…)?¬Ÿ½rã¾¹Ë¶ÙØX€­Õ/é§ÉÒ’n.ŽbÝuU’>ÿ®‰‘UDªëõIô¿1%!6¸“?df_L5 f`oÉ]);~§6€}‡Nì×#*¼GÃó¦ä…)?¥Ì€_¯#'.¹´nE=ÄÞƒ'"ú I>_GN\;"âòµ;ywЍ»$¿PRÏGŒ_G­zRãîâD³qþýGã?]ö¶ÛÖ~Alœ6yÄÔyGô”Ú%â®…Å¥óWþPZþ$qBLÒäጜ¶`~oãá²zÑ'Äã!‘!R?ÕÃÂÉÇ;¾C<°¶¶üñ»y°tÎÔÆ²â‚ð°``³¹.­ˆ¼ÞòùJm“RÏ_C×Q]œïä? Ù8À¯Í/©_‰mtsqìäwüÌY½¦$ÄNIˆ­®©þÁ"ô;‰Aú]*ŒTsÔÏ’òÑ\”m«8éSÇÑÁî?3߀IŸ­3,œ/Sw ‰ ¡6ÿv·þ-vwuzÞØÌá°‰.,`ÚïБÿ†v$áßÎ#ïN‘»«ðù‚#'.UTÕ¬Ù¼ÿZ^þÈê.x°ù¿kt†Ltxm»3JËŸxº;À›ÄI*EÒçÎÛ uWÕÓZ{[33n«V¶6V˜Ÿyñø‘jŽúPQ]'pŽœ¸˜sý_Ÿß«kGx/»´vøü“w©»ÄšÀÄø¨eëvÿ÷Ò?5µõs§#Œx§ë¶Ý™ÔCLŒZ¼æÇSç®™q9ñqỾãÛ9\.'ûÌ•²Š§ÙgrÈ]’§G=êvMOtt°Û°|ÚÜå©l6[$…ö Šë_Pôxâ´¯€ÍaóÕ'dcr;l\9ÜîîêäêÜ5Éñ‹KÊ¿Ùò«¥…y]}ãàÒ¿‹›&¦¾¾‡ACÔ'Ðt|M.ñ¡Zwݯï¡(¨×·õ=´ Nl— ®ï ‚>%pô ”»Ñƒ~GL ©®’95ÖXA³›èwÃF ?õ}"ʼr}‹&SÍnR ßS'6*†Ì¿waK”»©~G×H­wf¢G³›&†çwšõ#ùÁÊ ûêê­,Íý}<'–_÷qÒØh±2DJ¢®äœ¤±ßíøÍ·­GQIõôëFWÿKN"4¡1–ܨçPïAE¹›,†çwšõ#fœõõv' =òx|:u%Ë@’uiö§Ÿ¦ŽffÆ¥žƒ†UrQˆÁ­LB]ltÄ1<¿‹!«~äÝ‚²Ð£™—NÝGÉ2d]I.—CMþ)PÉIDY$W&q×3R7=;7D˰u}êBÔ\ýåǵÏnÞ-"·wðksãV!ñ˜Ïu‰§7nvðoC<žñÑ蔿“½ÄžÆê½já”U §ˆ¦ð¬ºú]Ï+ðmë¯JN*<4bX”?,#~.emñ I t{V:?D¯0øø]VýÈq#"W|»wî²mÖV–í¼Ý&‰¦S÷Qì)¿O›ash× -Ô'P¹8èC}Eh¨>ƒ¢§³DŸT¹£ÙUëèº*ýhˆ%'-ÀHP/k‰mb;iy̹#R1¿ëªô#–œD"Õõ -,KîÔî.8ý‘…ñøÝ4‰OLÑhŠFÍä "‰Ø´t±$ åþºš‘úAtƒž\•EŒƒŸ‰Ä'¦hnýU ÞµCqNùã’@3x'Pª1bR ßÆoœ5# Œß¦@¿в1xGƒÆðòïtê‹ …¢¯Öì"›q9œ[ÿwðoS[÷?“0zàÍ;E Vý°!õHöŠšÇÿÊ™¹h³P$ôòpæ 7ïÚ/ÿþco¿åÛ𼉬öE“Ï£ñÝ‚’nÛÍdû·ð!Ù†((&y*D=²ƒ™g㇅«Ð áe•Sç(w=A©ÛÅñÞrDïwYõÅÄòÝÊd›ýàQʼn3Wš[^,_¿§¹åEÖ©ËDjµ/ê˜ë· 6¦ShL…‚b Ö#‹OL!fÔ(ey¢=Ñ—~/AôÃËψ!«¾˜Tf~üîæé5ÏR×β·³nllž¾0eòøÁðfµ/ê˜…Å¥ÔÆ?¬ŸM-[&µÐ˜ ůGFhšH×€ìŒ ù€Z×7Ü]èL|Äà‘Ö3 HÑ‹¡­›l}1F¯x|Wj¬/¦wJ/œ'†ç ap:õ#Dãñ»¡Ôù2”óDô´9¢}A‘ úAÄ8A¿#‚'èwAãýŽ bœÏüÑ2³/ný)#+íÿ›ûŸñKêWmÛ¸-]û“™·¦¶az∂¢ÒÔ=™™{W]¾vçÁÊq##ɾÍ-/–­ÛÃá°áø‘‘MÍ-K¾ùéØ/kX,–Ø®’Ç•\;f`ïKWoß+|äè`w ãL[/×¶^®ŸN¦Ã—¯2~È~«½GLdgžCMMMFFF^^Þ³gÏlll‚‚‚âââ<==¿ÿþ{˜>}ºX{êöï¿ÿþêÕ«Ô½ ˜8q"¹ëÃ?ìׯ±ëðáù¹¹óçÏOJJ’z&ÉÉÉí°p”IDAT999=233 œ0aBëÖ­%[¢ßD«øx»Ý¸YPP\Ò ö:9°_¨ð Ï›’¦¼×?À×ëȉK.­[‰uÜ{ðDDŸ`¢ìŸ/Xºn÷Ø—¯Ý í$¶K²NQ¨1{kåõ'•••_ýµ³³ó„ ÜÜÜ®]»vèС3¤ß9(IPPÐØ±cɧvvväc33³ŒŒŒ°°0.÷µÍÍÍ—.]J<>yòdAAÁÔ©S‰§...Ç óó󫯯OOO_·nݪU«Ølñ| úA´ÊàÈcå´¼à½ÝÑ ŠÇE…€­_ €!{ýyâÒ¸‘b ”‹#·¼àñxü1Ã"þ/%-´Gu—Ë€´ôÓçþ¾YQUÓ?´ ù4¨C»÷Þ¤­Ê?8—_T‘_T‘yâ›ÍÚ¼rbNnÑŸ§þyR]oaÎmëÕzæ”(XöíáÁo‡v÷'z-Y÷ÇÐA]zuõ€õ©ÇüÜž<­¿[XÆf±âuíÛ+€hÖÜÂûù÷‹ÿÜ}dcm70øÄo ŽxÛÇ[<þùçŸ,X@*ø­·Þjhh ÿ*¬¬¬Ú¶m+uW·nÝòóóÏœ93hÐë¿‹Å"ÛÛÛÛ›™™Q»/\¸|ܪU«+V”——{zzŠŒ~G­beiÑò‚çíéÒÒÂÿv·þ-vwuzÞØÌá°€¬ÑCû:òßÐîÔŽþí<òî¹»:ÀGÏUTÕ¬Ù¼ÿZ^þóÆfê.¢ì°ù0ðø}òؾ5ÏÉüL]}ÓîƒçÇëÕ%°Í ÿß‚r:ƒœ½øï§ÃÇ÷»_ºe÷é·Ú{8;ÙÀÏé—*žÔÍùtˆ…9÷ПWjž5Jömll¼yófbb"5¾[[[&^˜™™ 6ì?þèׯŸ………²Ý[ZZX,–Ô“¡ë÷òªZ¼‰Q\T*‹¾˜ÈVêžL˜µlÝîÿ^ú§¦¶~î´qE%åñN×m»3ÅzMŒZ¼æÇSç®™q9'Ï^û_Æw\.'ûÌ•#'.QwÅÇ…K‘ˆß]Z;Á½Óu Í"‘¨[§¶v¶–àÚÚžN¯ž]|:ø¹@P€§³“]Aq…³“íó¦–ë7|þQ´·§L¶põo’}+++E"‘——‚ò…ò¹zõjbb"ùtÊ”)}úô!Ÿöïßÿرc'Nœˆ‹‹SjX>ŸðàÁÐÐP{{)¿Z~÷é<ª8/(!wC¯ÓÄ,d’&'¬^ô ¹ñí·|‰¿¤~%Ö×ÊÒbí’—Øó_š":¢'ñ€ÜÁ^æ(B{…ö;®¡ãéæàë¶ôÛÃ:zvôsïÞÙÇÊÒLa/çÖ¯Ã[k+ó¦fT>© EíÚ¼ÌÆØÛY9¶²€Û÷JW~÷±qþüùffŠÇWˆXþ]ìr(›Í9rä¾}û @L¡P˜šš*ˆKµ’ÐßIÅÓ?6‚ ÜÆa³Y3§Dç•ß-(;}þΑS¹ “ãìm-Y,µ™ÞXí‡o´÷q]±bñ¸uëÖ<Åb=~üØÇÇGåÓ–“'èÝ»wVVÖÑ£GŲ@² …Û¶m«¬¬œ;w®•••Ô6Jäßñ½Š ˆN`³YT#³XÐÁϽƒŸ{ìÀà…_º[PÖ««¯¥…•/xVߤpXWg;‹õàÑÓö>®PWßDäßÍ͹d3ssóN:ù¿ÛeµÏêšro•4µ¼pmm~n9¹E/^ðááãסÂam¬,ºwnw 3çQYuåÓºŸÓ/™q9R[¾÷Þ{ÕÕÕkÖ¬¹víÚ£GîÞ½›––F.ÐÔÔTB¡®®Nr±6Ož<‘lÓ­[7//¯óçÏ+<ó­[·Þ»woܸqÕÕÕÄ€---’Í^ÆïYÙYj®r€ êƒrG¤2¨oÐÎ}ùÍo°ø‹á·óKÉknáµv´7¼—·3 ‰è\Qõláêßìl-ö lí@+²Nš–~qí¶cÄüÈòÊgRïææ¶dÉ’ŒŒŒŸþùÙ³g¶¶¶:u3f ±÷öíÛä\uˆA¬M·nÝ>ûì3É=zݺu O›¸[jýúõä–9sæ‰5{ã[Ol´ø9!ˆ6щÜõyý&D›45óæ­:0gê sŽ¡§£¥¬ß„¡‚ &ŃGOŸ7¶x{:=ojùãøu×Övm=[W<1’‰$x‚¨ N6\ø|ÁÁ#9Oj8v_·¤IŒFî€~G5Á»C ×%³FOì¦ ô;‚¨ ÞbLÜAìú*‚˜&8y 1&Èyh¸¾‚à- ˆñ@dŒñ;‚¼ç#Fu$úAÄ8Áü ‚ ˆqòÿU3ºÀ¼«†IEND®B`‚tsung-1.5.1/man/Design.txt0000644000175000017500000000662712236145741016515 0ustar nniclaussenniclausse 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. Modélisation, analyse de performance et dimensionnement du World Wide Web. Thèse 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.5.1/man/tsung-inside.dia0000644000175000017500000001641712236145741017631 0ustar nniclaussenniclausse‹€É(Ktsung-inside.figì]Ûr7¶}Ÿ¯P)¯4Œûe4ΔG£™¸Ê‘çDÊyUQ-ó„&]Ç/ó=ç?νÑÍ‹D¶ÔÂn´¤X•(TZ½ˆ&ÖÂÞÀÆÂ_þúÇçÉÞï£ùõx6}³/ßßM/f—ãéÕ›ý_OÿñÊïÿõÇ?ýår<ü3üs5~Þƒ¿˜^ã»7ûŸ‹/~ýúÛ·olòýz¸˜ÍÙdü•]^ÿ{8™ _ÃE¯÷üÓÞÞæ .‡‹!þ®þíp±˜Ï¿.F{ÓáçÑ›ýóáÅoWóÙ×éå~uU}ÝÅl2›ïý>œ¼Ùÿác|í¿®oóúÆ}î¸÷—áÕè|>þÖ|k¯Rnýe4¿}ÛÏ_f×c¸dñýËÖ% ÷Áo\S_u M¯~üá­þ¡úHõ/Ö÷ÚõAAŸ‡ó«ñtžÍ¤z’y)%< |àN;±|&‡;ïnÒ/ܼ_¸ñõÙ—Ù|1ŽÛç³Ùd4œV¨‹ù×Q:ÎõÅp]ì®fZñq¼XÌîùü‡“ë6 ¨~½bÛC™{5oIÍMâÞ¸¢á.߯—‹Ogdz\ÕÝ¿gºûïãëñùd´ëÓ§‹În¿ãã§Üþö·Õ{ýçÄÞPÝ­qx¸ô#32®¾Ž/G×÷t³›×4ÜéS}ÙëûžúíëÚ>˜êW·ë1~ÍëÛ¿ÏÇÿ÷¿óÑ«/“át¯þŽkÑÙ^,Æ¿/ßm<–ÙùÿŒ.u{OÃéåp~¹÷jïo³?ö×a!ãË7ûøÍçu»…p3­öe¶îUŠnÍ@hæ¬r·ŸÅ¶’4ÁœŸßF™C;†Ó«Éh…äA×#’’Ò2®Ì@J„6i¸£ÉèóÙÅl>ݺnb„гÝÌ•¾Hƃu:,_ž€õi4¾ú´hóLë|6¿ÍïkgÜ*Ã7_‚„wSCv¨ˆÚè†1žNÛ@ŒÜHŽ»Ãõ§Ù·³qøýAÆCPª®}6^Ž¿n‘xóÛ‘Þ¸û8Tý²’—d½d½aÜ93Ð,h!ißNnצBâÁ 7­B¹16³ÜtÐÂVjÃY0jCmÙÒJm„ctze‹Ü¹É.7’.7V‡eÂ…¼bc¹P”ðî@&¥°(7ÊWn¨-l#6©7£Dþ·hô’Gµ›`¤éQl.ܹMЖbsùñâ|˜ñÔÄ^½ˆ"‹ ŒùF«0{äÕŒj¬H\„Ð/¤F½q¹õ¦‹F¶‘M⛚£2JŽcÖøMH¨‹äÉÙ)9G“ÉøËõh‡ìhªì„*ƒ (gdFÙ$£‡AgZ.ÎDI™W¹Wt:ib«¤ ˜¹ùrY“*¶™Q¥UO5©êAtZAtFUCŽ,“ÁÊ€šn¶ Èj„“¿s§:4”iÉûãƒ𨆩œ9m Z¿(±È}Tí ­pµ'®Ú¸Ê5¤Ö@©sŽª$”ˆ@‚G®ÆqUx悦D½mÈJnc»aUiî×äQ9cyÁŒäF;çÜ —V¨ÚU™ª9'|dŒKDÛqtÄÑâFS£½Å¡ÎÐRáû‰ÚE ˰Z¸Jåªï€«^ú8äxɳޫÀ™à$"9ï ÒÕBâŠ2!kºÚI [ [©l ±Õ2o]î‘5²”]“Õ1«3/ØwÑÆÂÖÂV*[½šoÙ• "ëTðŠ®†y¯6WËl ŒxíéJic¡k¡+™®äb8 Ç2ÌêŒÈ¹` H·‘DðR@ ìãü°‘>ë‚q',|-|%ó•\MY$t¹+"éàÖ¹+ðÕxÙCîÚI ] ]Ét%×c­ºröäuE×:y]²5òÚI ] ]Ét%×1­ºröäuE×eòºäkö䵓6ºº¶£ë¿f“ïïÇÓ|¥3a…®ˆë)œËÄEöv“MLjTi{€SOFÆUXaSw=}'s“ÔÄ—f¥0´§1ïêþžÂùÇœz£WD»^|ŸŒn£¦_?×.íÖ—ÃëO“ÑôêÀSuozy6œÏgßš?yjËòÎg÷~Ã}ßÃòÞ¯^Þ»} C "iéâÂõ6±ª±eá³å¼Zœ²|ø©d¨fÏ\âwÒNv7p‡ü(&-JusÛéŽKánÖ»A­Xy• µê9(Õõb8_d"üƽû£ü&h¤_}¹Ì\,€Å×[_ûòÿì}î£ÑÐ|1òiøõÿ~³¿Ý‘wü±\þqãßVŸsë=PžNG,6¤I,¥‰\”†óÞFâ–,íuNqZ ø¡ý4&Eg «SøT¶¾¤›f,·®iì‡óÛIõ•KÇ®Åõ×éÕ«Ã÷OeƒyWã§oÄý8›.v¡âï÷>?'ßáà §×û{1@‰ÝµúÓŸF“ßG‹ñÅp³Ÿ>ül%M_àšz^Û/O½Ù'Ò/SÐn}¥yo} Ðp2¾š~íþJ×Í[¢,¥ã†ÑÏÃò{üHécEG2E¯Çc*n­2 FÒÌŽK$ÎéÀ°`½Ä÷.$#÷)T‡ŽOùðþýÑ/E¥r«Tc·L+2õØ2E.D4L=«¬ ¯+a¥;ðÌVï¬L¬íS NOÎ~=9úå þùï(R:(/¼‡o/[¤:e X+‰ªÂ"Q9$J’‹/-3†Ã嘵9÷ö®q4JÄ‘Ò1g ¹zÖ¨Ÿ?mÊ­M ½1¬hÓck¹Òíô†Àx²ÛjKqª€ rÒ.j“ް‚§nìYœNßý\B§>äiw‡LA+úôØúÔO#t>´÷qYËà—0*è š¤p|"1ZëS™Ž?üýˆ¿8YrΊ1Çj3ë´«¦`µÒ$,ã(š”I“Èåà —ɵÅ”ç9Wð6¸PêÀ2nyˆï!zzKx7}(y]/Sc¯L+‘Óc«¹jZ£YV9-rÖ`®p”S;å—{õQ{Ö§ÃÇÿx÷Ï25Þ“L5t˰"R-RôRñ€þÔh?©¼ÊšàU@ð#Øjc‡ quH¬£ëµÀ Ö@¼8u2VßxåÏóúc Z‘§Ç–'r9ê.¡ ÃlîmÝK .µAß~[½uÉ‚}ÏB©'O}ÏB5uÇ´2õØêDw75Lh«ÔÝníb§÷]ˆƒõ&ÆN©.ø}JÓÉÑÉÉ»Çg‡o:zq¥¥ºñÊžß5tʰ@õ%QwlÅ“äBrÉ‚3A t £³­vQ Zãéj€èœ9Ð,k +¡R'êÛmÇklä®çµ´çQ6·¾uÙœÙÿ†à¦@‰\#ä´Â¢.x<‚5¯ÕH w'€™*E’ÉÈ}FK×_†ß¦/.J²*ž·äŒ±’c³GI=2®eœts(HzñqR³Ë‘¢[ôZÐ ˆ r*¯E/ q§#W3:!LÀÂNäµèí¢Å娸‘ E?¬Þ [nÜ+-E^÷û%’®J‡\õNWÈ!¹Ï€âç2õÒãÔËî^™W&_žÀä‹"×O Σ#‘t*§RH¡µŽ‡^Iå}´BBã!P.™VCØT_¹Öl¥7²°µ°•ÈVr•%väP ;Þe„5_"é*–Âd«5Ù#az# [ [ «rš\f¨u/6:w¹ÏIr£Œ¯õ{«žI¹Ï/GÿõëÑÉi)øé©àgw×L+«r·XÒ·k†îÀ½5Ä<òf™Wª–Hh:Æce¢R…ÞÏEªþõöŸGE§úЩÆ~™Wtê±uŠ\•¸ÑB?:H¶ ©bEf?ü¹èÔá‡ãã£ÃRõ-U¡HÕ³—*rMæª?fònöØ@’¸ÙÃ3%ë÷ö¹lö8ýåíñÉÛÃS4%+rÕ§\Ýîž)pE®ú’«;ê2uήBk—4uªp´ÝóaÐó‘¬ÖX—é\ˆVÔNèDä–¦dMܾw X¬L=j©m]&Vf>‡ºÌRÜxFÒŽXʆX“y?ì]šC.ÃìUsl½!^ 'âºVµú¬’mÈrhNÙ‹_4ç©kÎcmÂ×äJRÀ‰Ò „`.¯ïá’•ÙP¿óN>×CÈÊþñîýQ9¨§C€;f \ÉÈúÊÈî(ö!Ò¢k‰çC+ï²Úbá€Fcp›gÊÆó† ¤ú›{K}:ib»R6_¤7÷—ú½ æs—ú” ø4®r-†ö¸ƒS‘s²«Í=Ž–ñª&ÏáÑqŽÉzVX[¢RÛWXZXJd)¹|Ö2éN4Jæ´ÊIT@2©)Ñ@Kò¤°O¢ Òdej'm,d-d%äé†\=k˜Z¦C¤æË-óô‰ã!$˜¨»Xêçp<ºä”ÃzûËÓ:f \ÉÓûÊÓïXÅ0äúÙÀ|ÌbÑnFfNÔ]‘´T::Úxƒ[ƃõ‰¸mý„š¸})Ä8Êr?Cs×¥,ÿü§-wëá|‹:aÑCÞ»`’}¡ÕKgñgY`@㜦[ QVr×=‚ˆ&7åã&}£r‹ÔîFî)‚mF½¸sM¶G‘z>u E¤ò‰”)k³OA¦Èe³–I …¡µÍy~¯e".ÇJ¼á²>a«(•ä©3©íDª¡‰ÛzP(È@iOã?-8*µ MÍÜËH-35µ¼É»j¹FR‘ÄŽYéκMEîsêæbx½xq36½,ÙØ!SàZÍØÜ–Ó 2csGœÑýg Ž¬-]T‚ÞŒ3gATÇoçŽ3š¸#‰oíûˆ3žO~S⌎hÜg+9ÓÖGj¹ÌqÆI.I¬4ãÆüs‰3&“gäŽ3;d \ë•¡M9MzñqÆßfì 1ÈÕ›BáaknðJb)ˆÌº· ŒŽH> -ã§\5ž÷ ¥NZÙªÜD2!7k@rž¡„ûp´qk¸Ä*اZn¢ƒ6:1ÔkYn2r#9J|lן V9^üv5Ÿ}^Þ†9ŸÍ&£á´ZÌ¿ŽÒPªÎ}6^Ž¿n1ùFP„‰cªcÉu¨¸è*¥¼þK³Ä  ¬3©Ï%R2ãyænœìœAt:idÑqÌ ¾):»Otpþ ˆµä`¤éQtz¨q»üxq>L„xj¢¯ŽE§¹´Ö’Kk±"ÝjLФ´©g[·ÝÉË• ýWã¡Ú^ œéà\ù¬Æ´´±YŠkÉt%××â™§BéêLN®B0à…à)*0´áé®ÂÛîq$å ÷S•ܾv%ðJs¿æÊf$7Ú9ï¬àV8š¡saiv–’ K£A/WÈÃ]Ö£N6D>„4dNÀPg¤3_ôû©ÚE#˨ZøJæ+ýØô€vj8ê¸à¥NüZÖ‡PÖê "cµ‹µ<;céÍ,”-”%S–~xzÝ—_Aˆj´4Y3ך³ˆåÑjEZŽÇc“Õk¡£–ÚÚ’iK?D|Ù™ƒ\Èå<·lÅZ€RÞ¬GZ ’Â-‡yÙNZH[HK&mn&p Q ©ƒ]+Î"’ñlp^ Ÿ¢Å‰[èr*ëQƒ´±ÐµÐ•LWr…veBÙ,B /7²Y ¬´¾l¶›fÊÊ’)K.:[öå>²Ù%g7²Ùš´}d³µ´Ð¶Ð–J[G®ÚZuæüÙ습«l¶&m/Ùl7 -¤-¤%o"sô’'‡´ø‘wy ´7å…´0Ôâ´Äã ˜Ð.$B·ÛHÖÜÌצñ´ÝJ†»,ŸÃV²ˆ·Â4ïiH¼õåðúÓý»ÈDà©ÚWvÁu£C »à½Ž+ 9±úQY‘uÜI²  ãm "kø3ØwøþÝÑñéÉ‹Ûg¬¾ñÊ~¸\cŸL+‰ÉÒÛ‘FÑ«Ø ÓFa© çÎHå³®±×`Îh!µ³Xs´Gx! —‰QIŸJõþí¯Ç‡?½@7×þ¥êŽ®™‚XÔê±ÕŠ\Ç«ûÒâ …²!äÜ@·F Úi7ÐieðR&okëS¨Ž?üýH¼8•rΊWv‘jì‘)pÅÀ¨/…ºc˜^µh™àý ƒP©!MëÝv΄îó:Äív^ëfõe.3e®¸ÌÓ" z­"™Þ¹Á«©‡º¶N~IG9žäæ˜Õ‘¥KFî3 øùÃñÙá[È}^\P¡¥ºñÊnWÔØ+SàJÞó¢ r}fÜߪZcïòš‡à&~‹ª¥RUÛø­¬æm¸%Õs´ÛÈOnh -JhA -È%šBCl,#TyMÆÖP¾bl`ÂÔÉ»ÑûŒ.NNßž¾;<+Ó«½M¯6vϼdôdÜU6C7FtL‰÷!:gͺKÚ1‰A.`èï2G#-ø˽O5ök]:ÓÔÔƒžÊ ©Ï¥”Ï´¾u)Ÿy&å3wè‘§[&¤<ŠI•zÜ}Û¤Gá„& i©M,âÓ·¥YÃÚ–JÔÔÈÉ‘V ÖL©%EÅþ¥Pyõµ&œteUÂAWbùÇâñNºòôâá^U';$¤i¦:Ï ­š)cRÏ"Ï¢:Xe¬£Ûnâó(§]Õɬ:ÎdVÝÎО^)\›&ÊéX2†$‰Vrµ/´PÌú~Œ¡Im,¾ÐÅúƼâ ö…öôâßÚ3Y@"wí»²…FmäÊOÜ2вÂÓÚšÒIJ–TÖ’Èd¥ûWÖ®É:rÞ2•è íq òÚ:ààÝ‹+tzûŠ+ta)‘¥XV¢a2ð…÷ã ˜µ›®Ð0ÒAëÅšÔÈ2ª¾’ùÚ™W¥‡ïÞç-Ö®­*IX½¶—E³€ž<¡I,|-|%ó•^YweÇ\0ÙÇWä+ YÖ|õÌ8™ÿÔr# _ _É|¥AÖ]g|2o†ªù HBlŒ¯ŽY™{3T,|-|%óµ“ÊÀf•®h@Ò6И¾Þ8¼{ºvÒÆB×BW*]Cgæ”ùÓ×¥7å*}Ö”®ôµ“F¾¾’ùJ®,[våüéë’¯«ôµækék',|-|%ó•\˜µìÊùÓ×%_WékÍ×Ò×NYøZøJ.Ýô’¦ÚÙ³ xÞÒíÚö¢R+­X¹>ÆCO®Ï[Üqi4}&=²i­õ­Ë¦µÇª9 5lúœ2Œþº†o!¿æy> Î|fåù ÈZ$:tËgÊ'xªý›ºd \Ù矬¼I½D­öÕu ²ó¼›MVHðo¬û®Ýž=Œåæ9ØÒ7’¾Íž·:e \©Ç)r]ž„ ÏÙÅ¡+U)ÚλT@ƒ'gH¦ƒ2ðÞÉdqìÛæY¾8}z›çÝ1­¸<÷¥NwÌ wáÇ !ªöVe¢j—gDRè-]ž…dV''ßry&5²Ì —™aZ4A¯BĽeÒá6wLVïÔ%’Äsö|Ly¬‹È=ƒc#ŠËsŸ.Ï»{e \Éyž@TA÷_¬Í-.*囩MžIE;úÚãçjBf'.Y¢ŠU¢ Áé….¤…†ç5ÐX!‰Š­µ¿³ö3ÏàðˆbïüHöÎ[}3®}wÇÞÅ©èÑóØ1á”Î|(z4wvŒsS V´v† Eâ8õ0_ç­6vu&=ŽRÓúÖ¥<æ™”ÇÜ©B]œ‹GtZ—jÀó SgD R¯Lá½Õ©•®³WÝjäÎ|(š:‹X‡›Ø÷‹©óKáòêkM°WõÍ©ÿßÞÕô¶ ÃÐ{K&HÔ7 ô¶ÃÎý0ô²íÐv‡ýû’rÒ¦h”¥z©§/ªÍç'ê‰\þð±¼jZ®ÍzZÝŠ:Ýn J;”à@$(õU q÷Þt¡rã·ËMMKUg¡Î´“cgÚYL­Ò¯ÜÓ¦v§Œ=)çÉ*ÁÞ‚WÖâÔ‘­á"L=?ýúùði2ÿmË“ ï^‹Au}ËgïL\UzÈçi’ôÐ%…Ôì Î ²¦jðøK´=óÙyê,JÚÃ!­> †­c²U@Z£¤*´§çýãs'‰tò·Ç‰¤SЖIvYél@&]äžfÃïPîìýÎÑ~)(Õè©Ñpá<Þ ùµ±(è¨Ãå‰j>:9ÔéäÓÂBi[ 4;ªŸÏEJk¶g0Ôƒ )ô´"Œ·‰:&oCñC±•cîºÖ6?ÂY2C™WjGx¿»g†Ì ¨™Ê•S¥¢ÙH2óºÌ>²%¢ÑÍꬒõÑ“?Ðôu&f©9PDÊH‡ºÞMmàf²jélaŒ³4åð¿ºKÈ}æj ŽöuÑ\BSg—}‘¦ƒ¥àz¦~K5û§‡²”w†úEƒ71šJ9rH©kçú?¡)…8ÑÛyåôµ©áMo…¦„¦Î.{šºÐ&¶ïš YÇ4îüŽ6„Å®[Ûß0öu)¿mÄCçÙÀC=ãŸïï¿Èv´aÛÑ–nLœ8ÆÙ‹•ivŒËaçJ󺘒-9ùì ä°ü6RXH›¨ºŠÁâZ>¾ä¥@üœ¢¤6œ—‚Q®ÎE)Õn$?<±¨²]ßZ¯o@Ú¼ )ª´Èí÷.:êŸ×Qçw%MdÔ&dT³÷<£¬¡” Þ&@ϲ°¤g\é×Òòç¥xhÓÕz¾8ÆY1¥É±@ÙNö\Ä”ˆ)Šöð·æÏM³a²Ú JQé˜#óbÖæÏ Y¡È[é\*Ds.嬓eÎ5«‰j~ D…ô‰të¯n&•?¢¢¢pç쪖A_.“D_¼™§@Búò”‰$“ÁÜ⻵ ¦É¹®NÒ*ƒ¬©“„³Ú|úŠÌöUe’PºS°Ä¥ÕU’üǧ”Q 3馶JRúpúÇÃØd-DíáZV¬)#šbßéè#¾—p¥®Ð®1p‘«Ãµ}®®­áº‚q‚Ëå‡J©g™ Š™‚¤UŽÚ•pÍ”®@¸ÈÕáÚ>H W צ55J‡äñ6ŽJw±±¶´ñ„TЭ‰b´ î†-iæ2`ä#š¹Ìß“8Ya'NÎIj:þ¾ÿýíñîf:ÀŸ‡Çý»›|\Á þtsung-1.5.1/man/Makefile0000644000175000017500000001536612236145741016203 0ustar nniclaussenniclausse# $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'interprétation/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.5.1/man/ts_template.erl0000644000175000017500000000753512236145741017567 0ustar nniclaussenniclausse%%% 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.5.1/man/tsung.1.sgml0000644000175000017500000001404112236145741016713 0ustar nniclaussenniclausse
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.5.1/configure0000755000175000017500000037727112321170241015671 0ustar nniclaussenniclausse#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.69 for tsung 1.5.1. # # Report bugs to . # # # Copyright (C) 1992-1996, 1998-2012 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 # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # 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 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+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. $as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 as_fn_exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} 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 test -x / || 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 : export CONFIG_SHELL # 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 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+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. $as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 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_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_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; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # 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 -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' 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 as_test_x='test -x' as_executable_p=as_fn_executable_p # 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.5.1' PACKAGE_STRING='tsung 1.5.1' 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 DIALYZER 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 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.5.1 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.5.1:";; 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.5.1 generated by GNU Autoconf 2.69 Copyright (C) 2012 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.5.1, which was generated by GNU Autoconf 2.69. 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 as_fn_executable_p "$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 as_fn_executable_p "$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 as_fn_executable_p "$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 as_fn_executable_p "$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 as_fn_executable_p "$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 # Extract the first word of "dialyzer", so it can be a program name with args. set dummy dialyzer; 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_DIALYZER+:} false; then : $as_echo_n "(cached) " >&6 else case $DIALYZER in [\\/]* | ?:[\\/]*) ac_cv_path_DIALYZER="$DIALYZER" # 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 as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_DIALYZER="$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 test -z "$ac_cv_path_DIALYZER" && ac_cv_path_DIALYZER="/usr/bin/dializer" ;; esac fi DIALYZER=$ac_cv_path_DIALYZER if test -n "$DIALYZER"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DIALYZER" >&5 $as_echo "$DIALYZER" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } 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 as_fn_executable_p "$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 as_fn_executable_p "$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 as_fn_executable_p "$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 as_fn_executable_p "$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 as_fn_executable_p "$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 '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(asn1), 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,asn1,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 as_fn_executable_p "$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/tsung_recorder/tsung_recorder.app src/tsung/tsung.app" 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 -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' 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 # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # 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.5.1, which was generated by GNU Autoconf 2.69. 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.5.1 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" Copyright (C) 2012 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") CONFIG_FILES="$CONFIG_FILES src/tsung_controller/tsung_controller.app" ;; "src/tsung_recorder/tsung_recorder.app") CONFIG_FILES="$CONFIG_FILES src/tsung_recorder/tsung_recorder.app" ;; "src/tsung/tsung.app") CONFIG_FILES="$CONFIG_FILES src/tsung/tsung.app" ;; *) 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.5.1/config.guess0000644000175000017500000012450212104023217016261 0ustar nniclaussenniclausse#! /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.5.1/README.md0000644000175000017500000000131312317102151015216 0ustar nniclaussenniclausse# Tsung README ## Introduction This document gives pointers for information on this package which is distributed under the GNU General Public License version 2 (see file COPYING). ## 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, MQTT, AMQP and Jabber/XMPP) A User's manual is available : http://tsung.erlang-projects.org/user_manual/ ## 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.5.1/Makefile.in0000644000175000017500000002753412320752470016032 0ustar nniclaussenniclausse#### 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 EBIN = ./ebin EBIN_TEST = ./ebin-test ifeq ($(TYPE),test) OPT =+export_all EBIN = $(EBIN_TEST) endif INC = ./include CC = $(ERLC) ESRC = ./src 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 $(EBIN)/gen_ts_transport.beam 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) MOCHI = $(wildcard $(ESRC)/lib/mochi*.erl) LIBSRC = $(filter-out $(MOCHI), $(wildcard $(ESRC)/lib/*.erl)) ifeq ($(USEMOCHIWEBLIBS),yes) LIBSRC += $(MOCHI) 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 = docs/_build/latex/Tsung.pdf USERMANUAL_IMG = $(wildcard docs/images/*.png) USERMANUAL_SRC = $(wildcard docs/*.rst docs/*.txt) MANPAGES = $(wildcard man/*.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 APPFILES = $(EBIN)/$(APPLICATION).app APPFILES_IN = $(ESRC)/$(APPLICATION)/$(APPLICATION).app.in CONTROLLER_APPFILES = $(EBIN)/$(CONTROLLER_APPLICATION).app CONTROLLER_APPFILES_IN = $(ESRC)/$(CONTROLLER_APPLICATION)/$(CONTROLLER_APPLICATION).app.in RECORDER_APPFILES = $(EBIN)/$(RECORDER_APPLICATION).app RECORDER_APPFILES_IN = $(ESRC)/$(RECORDER_APPLICATION)/$(RECORDER_APPLICATION).app.in SCRIPT = $(BINDIR)/tsung REC_SCRIPT = $(BINDIR)/tsung-recorder PWD = $(shell pwd) DIST_COMMON=Makefile.in $(CONFFILE_SRC) $(PERL_SCRIPTS_SRC) $(TSPLOT_SRC) tsung.sh.in tsung-recorder.sh.in tsung.spec.in $(CONTROLLER_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_APPFILES) $(APPFILES) $(RECORDER_APPFILES) buildtest: $(TEST_TARGET) fulltest: clean test test: @mkdir -p $(EBIN_TEST) $(MAKE) TYPE=test dotest dotest: tsung buildtest $(CONFFILE) $(TEST_CONFFILE) $(ERL) -noshell -pa $(EBIN_TEST) -s ts_test_all run -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 # TODO: remove -Wno_behaviours, but only if R15B became a requirement. # see http://erlang.org/pipermail/erlang-questions/2012-January/063608.html dialyzer: $(DIALYZER) -r ebin -I ./include/ -Wno_undefined_callbacks 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 show: @echo $(LIBSRC) clean: -rm -fr $(EBIN_TEST) -rm -f $(TARGET) $(TMP) -rm -f $(RECORDER_APPFILES) $(CONTROLLER_APPFILES) $(APPFILES) -rm -f $(EBIN)/*.app $(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 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 $(APPFILES) $(DESTDIR)$(TARGETDIR)/ebin/ install -m 0644 $(SRC) $(DESTDIR)$(TARGETDIR)/src/ # install the man page install -d $(DESTDIR)$(MAN_DIR) install -m 0644 $(MANPAGES) $(DESTDIR)$(MAN_DIR) # 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: 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_APPFILES) $(DESTDIR)$(RECORDER_TARGETDIR)/ebin install -m 0644 $(RECORDER_SRC) $(DESTDIR)$(RECORDER_TARGETDIR)/src install_controller: 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_APPFILES) $(DESTDIR)$(CONTROLLER_TARGETDIR)/ebin install -m 0644 $(CONTROLLER_SRC) $(DESTDIR)$(CONTROLLER_TARGETDIR)/src uninstall: rm -rf $(TARGETDIR) $(SCRIPT) 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=$@ $(APPFILES): $(APPFILES_IN) @$(SHELL) ./config.status --file=$@:$< $(CONTROLLER_APPFILES): $(CONTROLLER_APPFILES_IN) @$(SHELL) ./config.status --file=$@:$< $(RECORDER_APPFILES): $(RECORDER_APPFILES_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 man man user_guide: $(MAKE) -C docs latexpdf release: Makefile tsung.spec doc user_guide rm -fr $(distdir) mkdir -p $(distdir) tar zcf tmp.tgz $(SRC) $(APPFILES_IN) $(INC_FILES) $(LIBSRC) \ $(CONTROLLER_SRC) $(CONTROLLER_APPFILES_IN) $(TESTSRC) \ $(RECORDER_APPFILES_IN) \ $(RECORDER_SRC) $(TEMPLATES) \ man/*.erl man/*.txt man/*.dia man/*.png man/Makefile man/*.sgml man/*.1 \ docs/*.rst docs/Makefile docs/*.txt docs/README docs/*.py docs/_static docs/_templates \ $(USERMANUAL) $(USERMANUAL_SRC) $(USERMANUAL_IMG) $(DTD) \ COPYING README.md LISEZMOI TODO $(CONFFILE_SRC) $(TEST_CONFFILE_SRC) \ 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 $(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) $< # to avoid circular dependency $(EBIN)/gen_ts_transport.beam: src/$(APPLICATION)/gen_ts_transport.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.5.1/tsung.sh.in0000755000175000017500000001273012317474675016077 0ustar nniclaussenniclausse#!/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=1 MAX_PROCESS=250000 EXCLUDE_TAG_LIST="" TSUNGPATH=$INSTALL_DIR/lib/tsung-$VERSION/ebin CONTROLLERPATH=$INSTALL_DIR/lib/tsung_controller-$VERSION/ebin CONF_OPT_FILE="$HOME/.tsung/tsung.xml" 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' 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 } start() { echo "Starting Tsung" $ERL $ERL_OPTS $ERL_RSH -noshell $PROTO_DIST $NAMETYPE $CONTROLLER -setcookie $COOKIE \ -s tsung_controller \ -pa $TSUNGPATH -pa $CONTROLLERPATH \ -tsung_controller smp_disable $SMP_DISABLE \ -tsung_controller debug_level $DEBUG_LEVEL \ -tsung_controller warm_time $WARM_TIME \ -tsung_controller exclude_tag \"$EXCLUDE_TAG_LIST\" \ -tsung_controller config_file \"$CONF_OPT_FILE\" -tsung_controller $LOG_OPT -tsung_controller $MON_FILE } debug() { $ERL $ERL_OPTS $ERL_RSH $NAMETYPE $CONTROLLER $PROTO_DIST -setcookie $COOKIE \ -s tsung_controller \ -pa $TSUNGPATH -pa $CONTROLLERPATH \ -tsung_controller warm_time $WARM_TIME \ -tsung_controller config_file \"$CONF_OPT_FILE\" \ -tsung_controller exclude_tag \"$EXCLUDE_TAG_LIST\" \ -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 where YYYYMMDD-HHMM dirs are created (default is ~/.tsung/log/) " 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 1 sec)" echo " -v print version information and exit" echo " -6 use IPv6 for Tsung internal communications" echo " -x list of requests tag to be excluded from the run (separated by comma)" echo " -h display this help and exit" exit } while getopts "6vhf:l:d:r:i:Fsw:m:p:x:" 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 ;; x) EXCLUDE_TAG_LIST=$OPTARG;; h) usage;; *) usage ;; esac done shift $(($OPTIND - 1)) case $1 in start) checkconfig maindir logdir start ;; debug) checkconfig maindir logdir debug ;; stop) stop ;; status) status ;; *) usage $0 ;; esac tsung-1.5.1/configure.in0000644000175000017500000000760112317474675016305 0ustar nniclaussenniclaussednl 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_PATH_PROG(DIALYZER,dialyzer, /usr/bin/dializer, $with_erlang:$with_erlang/bin:$PATH) AC_PREFIX_PROGRAM(erl) AC_ERLANG_SUBST_ROOT_DIR() 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(asn1), 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,asn1,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/tsung_recorder/tsung_recorder.app \ src/tsung/tsung.app \ ]) AC_OUTPUT tsung-1.5.1/CONTRIBUTORS0000644000175000017500000000305712212102345015625 0ustar nniclaussenniclausse$Id$ AUTHOR: ====================== o Nicolas Niclausse : Maintainer; CONTRIBUTORS: ====================== o Jean François Lecomte : several enhancements for Jabber o Mickaël Rémond : erlang server monitoring; various patches for HTTP; configure support; SOAP support; initial dynamic substitution implementation. o Jérome 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 New contributors since the migration to git are available here: https://github.com/processone/tsung/graphs/contributors tsung-1.5.1/include/0000755000175000017500000000000012321173206015370 5ustar nniclaussenniclaussetsung-1.5.1/include/ts_job.hrl0000644000175000017500000000360412147621622017370 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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.5.1/include/rabbit_framing.hrl0000644000175000017500000002122012147621622021050 0ustar nniclaussenniclausse%% Autogenerated code. Do not edit. %% %% The contents of this file are subject to the Mozilla Public License %% Version 1.1 (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License %% at http://www.mozilla.org/MPL/ %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and %% limitations under the License. %% %% The Original Code is RabbitMQ. %% %% The Initial Developer of the Original Code is VMware, Inc. %% Copyright (c) 2007-2013 VMware, Inc. All rights reserved. %% -define(PROTOCOL_PORT, 5672). -define(FRAME_METHOD, 1). -define(FRAME_HEADER, 2). -define(FRAME_BODY, 3). -define(FRAME_HEARTBEAT, 8). -define(FRAME_MIN_SIZE, 4096). -define(FRAME_END, 206). -define(REPLY_SUCCESS, 200). -define(CONTENT_TOO_LARGE, 311). -define(NO_ROUTE, 312). -define(NO_CONSUMERS, 313). -define(ACCESS_REFUSED, 403). -define(NOT_FOUND, 404). -define(RESOURCE_LOCKED, 405). -define(PRECONDITION_FAILED, 406). -define(CONNECTION_FORCED, 320). -define(INVALID_PATH, 402). -define(FRAME_ERROR, 501). -define(SYNTAX_ERROR, 502). -define(COMMAND_INVALID, 503). -define(CHANNEL_ERROR, 504). -define(UNEXPECTED_FRAME, 505). -define(RESOURCE_ERROR, 506). -define(NOT_ALLOWED, 530). -define(NOT_IMPLEMENTED, 540). -define(INTERNAL_ERROR, 541). -define(FRAME_OOB_METHOD, 4). -define(FRAME_OOB_HEADER, 5). -define(FRAME_OOB_BODY, 6). -define(FRAME_TRACE, 7). -define(NOT_DELIVERED, 310). %% Method field records. -record('connection.start', {version_major = 0, version_minor = 9, server_properties, mechanisms = <<"PLAIN">>, locales = <<"en_US">>}). -record('connection.start_ok', {client_properties, mechanism = <<"PLAIN">>, response, locale = <<"en_US">>}). -record('connection.secure', {challenge}). -record('connection.secure_ok', {response}). -record('connection.tune', {channel_max = 0, frame_max = 0, heartbeat = 0}). -record('connection.tune_ok', {channel_max = 0, frame_max = 0, heartbeat = 0}). -record('connection.open', {virtual_host = <<"/">>, capabilities = <<"">>, insist = false}). -record('connection.open_ok', {known_hosts = <<"">>}). -record('connection.close', {reply_code, reply_text = <<"">>, class_id, method_id}). -record('connection.close_ok', {}). -record('connection.redirect', {host, known_hosts = <<"">>}). -record('channel.open', {out_of_band = <<"">>}). -record('channel.open_ok', {channel_id = <<"">>}). -record('channel.flow', {active}). -record('channel.flow_ok', {active}). -record('channel.close', {reply_code, reply_text = <<"">>, class_id, method_id}). -record('channel.close_ok', {}). -record('channel.alert', {reply_code, reply_text = <<"">>, details = []}). -record('access.request', {realm = <<"/data">>, exclusive = false, passive = true, active = true, write = true, read = true}). -record('access.request_ok', {ticket = 1}). -record('exchange.declare', {ticket = 0, exchange, type = <<"direct">>, passive = false, durable = false, auto_delete = false, internal = false, nowait = false, arguments = []}). -record('exchange.declare_ok', {}). -record('exchange.delete', {ticket = 0, exchange, if_unused = false, nowait = false}). -record('exchange.delete_ok', {}). -record('exchange.bind', {ticket = 0, destination, source, routing_key = <<"">>, nowait = false, arguments = []}). -record('exchange.bind_ok', {}). -record('exchange.unbind', {ticket = 0, destination, source, routing_key = <<"">>, nowait = false, arguments = []}). -record('exchange.unbind_ok', {}). -record('queue.declare', {ticket = 0, queue = <<"">>, passive = false, durable = false, exclusive = false, auto_delete = false, nowait = false, arguments = []}). -record('queue.declare_ok', {queue, message_count, consumer_count}). -record('queue.bind', {ticket = 0, queue = <<"">>, exchange, routing_key = <<"">>, nowait = false, arguments = []}). -record('queue.bind_ok', {}). -record('queue.purge', {ticket = 0, queue = <<"">>, nowait = false}). -record('queue.purge_ok', {message_count}). -record('queue.delete', {ticket = 0, queue = <<"">>, if_unused = false, if_empty = false, nowait = false}). -record('queue.delete_ok', {message_count}). -record('queue.unbind', {ticket = 0, queue = <<"">>, exchange, routing_key = <<"">>, arguments = []}). -record('queue.unbind_ok', {}). -record('basic.qos', {prefetch_size = 0, prefetch_count = 0, global = false}). -record('basic.qos_ok', {}). -record('basic.consume', {ticket = 0, queue = <<"">>, consumer_tag = <<"">>, no_local = false, no_ack = false, exclusive = false, nowait = false, arguments = []}). -record('basic.consume_ok', {consumer_tag}). -record('basic.cancel', {consumer_tag, nowait = false}). -record('basic.cancel_ok', {consumer_tag}). -record('basic.publish', {ticket = 0, exchange = <<"">>, routing_key = <<"">>, mandatory = false, immediate = false}). -record('basic.return', {reply_code, reply_text = <<"">>, exchange, routing_key}). -record('basic.deliver', {consumer_tag, delivery_tag, redelivered = false, exchange, routing_key}). -record('basic.get', {ticket = 0, queue = <<"">>, no_ack = false}). -record('basic.get_ok', {delivery_tag, redelivered = false, exchange, routing_key, message_count}). -record('basic.get_empty', {cluster_id = <<"">>}). -record('basic.ack', {delivery_tag = 0, multiple = false}). -record('basic.reject', {delivery_tag, requeue = true}). -record('basic.recover_async', {requeue = false}). -record('basic.recover', {requeue = false}). -record('basic.recover_ok', {}). -record('basic.nack', {delivery_tag = 0, multiple = false, requeue = true}). -record('tx.select', {}). -record('tx.select_ok', {}). -record('tx.commit', {}). -record('tx.commit_ok', {}). -record('tx.rollback', {}). -record('tx.rollback_ok', {}). -record('confirm.select', {nowait = false}). -record('confirm.select_ok', {}). -record('file.qos', {prefetch_size = 0, prefetch_count = 0, global = false}). -record('file.qos_ok', {}). -record('file.consume', {ticket = 1, queue = <<"">>, consumer_tag = <<"">>, no_local = false, no_ack = false, exclusive = false, nowait = false}). -record('file.consume_ok', {consumer_tag}). -record('file.cancel', {consumer_tag, nowait = false}). -record('file.cancel_ok', {consumer_tag}). -record('file.open', {identifier, content_size}). -record('file.open_ok', {staged_size}). -record('file.stage', {}). -record('file.publish', {ticket = 1, exchange = <<"">>, routing_key = <<"">>, mandatory = false, immediate = false, identifier}). -record('file.return', {reply_code = 200, reply_text = <<"">>, exchange, routing_key}). -record('file.deliver', {consumer_tag, delivery_tag, redelivered = false, exchange, routing_key, identifier}). -record('file.ack', {delivery_tag = 0, multiple = false}). -record('file.reject', {delivery_tag, requeue = true}). -record('stream.qos', {prefetch_size = 0, prefetch_count = 0, consume_rate = 0, global = false}). -record('stream.qos_ok', {}). -record('stream.consume', {ticket = 1, queue = <<"">>, consumer_tag = <<"">>, no_local = false, exclusive = false, nowait = false}). -record('stream.consume_ok', {consumer_tag}). -record('stream.cancel', {consumer_tag, nowait = false}). -record('stream.cancel_ok', {consumer_tag}). -record('stream.publish', {ticket = 1, exchange = <<"">>, routing_key = <<"">>, mandatory = false, immediate = false}). -record('stream.return', {reply_code = 200, reply_text = <<"">>, exchange, routing_key}). -record('stream.deliver', {consumer_tag, delivery_tag, exchange, queue}). -record('dtx.select', {}). -record('dtx.select_ok', {}). -record('dtx.start', {dtx_identifier}). -record('dtx.start_ok', {}). -record('tunnel.request', {meta_data}). -record('test.integer', {integer_1, integer_2, integer_3, integer_4, operation}). -record('test.integer_ok', {result}). -record('test.string', {string_1, string_2, operation}). -record('test.string_ok', {result}). -record('test.table', {table, integer_op, string_op}). -record('test.table_ok', {integer_result, string_result}). -record('test.content', {}). -record('test.content_ok', {content_checksum}). %% Class property records. -record('P_connection', {}). -record('P_channel', {}). -record('P_access', {}). -record('P_exchange', {}). -record('P_queue', {}). -record('P_basic', {content_type, content_encoding, headers, delivery_mode, priority, correlation_id, reply_to, expiration, message_id, timestamp, type, user_id, app_id, cluster_id}). -record('P_tx', {}). -record('P_confirm', {}). -record('P_file', {content_type, content_encoding, headers, priority, reply_to, message_id, filename, timestamp, cluster_id}). -record('P_stream', {content_type, content_encoding, headers, priority, timestamp}). -record('P_dtx', {}). -record('P_tunnel', {headers, proxy_name, data_name, durable, broadcast}). -record('P_test', {}). tsung-1.5.1/include/ELDAPv3.hrl0000644000175000017500000000352612104023217017176 0ustar nniclaussenniclausse%% 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.5.1/include/ts_os_mon.hrl0000644000175000017500000000222612147621622020107 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -define(INTERVAL, 10000). -define(INIT_WAIT, 1000). tsung-1.5.1/include/ts_macros.hrl0000644000175000017500000000501712321167730020101 0ustar nniclaussenniclausse%%% %%% Copyright 2012 © nicolas niclausse %%% %%% Author : Nicolas Niclausse %%% Created: 22 août 2012 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. %%% -vc('$Id: ts_macros.hrl,v 0.0 2012/08/22 09:07:50 nniclaus Exp $ '). -author('nniclaus@sophia.inria.fr'). -define(NOW, now()). -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)). %% 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 -define(TIMEOUT_PARALLEL_SPAWN, 60000). -define(MAX_PHASE_EXCEED_PERCENT, 20). -define(MAX_PHASE_EXCEED_NUSERS, 10). -define(restart_sleep, 2000). -define(infinity_timeout, 15000). -define(config_timeout, 60000). -define(check_noclient_timeout, 60000). -define(fast_check_noclient_timeout, 5000). -define(retries, 4). tsung-1.5.1/include/ts_fs.hrl0000644000175000017500000000307512147621622017230 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). %% use by the client to create the request -record(fs_session, { position=0, iodev } ). -record(fs, { command, mode, path, iodev, size, dest, position }). tsung-1.5.1/include/ts_shell.hrl0000644000175000017500000000266112147621622017727 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). %% use by the client to create the request -record(shell, { command, args }). -record(shell_sess, { fixme }). tsung-1.5.1/include/ts_recorder.hrl0000644000175000017500000000213212104023217020403 0ustar nniclaussenniclausse -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.5.1/include/ts_mysql.hrl0000644000175000017500000000347012320752470017763 0ustar nniclaussenniclausse%%% Created : July 2008 by Grégoire 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_session, { salt } ). %% 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.5.1/include/ts_config.hrl0000644000175000017500000001054412320752470020063 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -include("ts_macros.hrl"). -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, total_popularity = 0, % should be 100 if we use probabilites; sum of all weights if we use weights use_weights , % true if we use weights instead of probabilities total_server_weights=0, job_notify_port, tag }). -record(client, {host, weight = 1, maxusers, ip = [] }). -record(server, {host, port, type, weight }). -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, popularities = [], id }). tsung-1.5.1/include/ts_amqp.hrl0000644000175000017500000000520312236145741017553 0ustar nniclaussenniclausse%%% This code was developped by Zhihui Jiao(jzhihui521@gmail.com). %%% %%% Copyright (C) 2013 Zhihui Jiao %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -vc('$Id$ '). -author('jzhihui521@gmail.com'). -define(AMQP_USER, "guest"). -define(AMQP_PASSWORD, "guest"). -define(PROTOCOL_VERSION_MAJOR, 0). -define(PROTOCOL_VERSION_MINOR, 9). -define(PROTOCOL_HEADER, <<"AMQP", 0, 0, 9, 1>>). -define(PROTOCOL, rabbit_framing_amqp_0_9_1). -define(MAX_CHANNEL_NUMBER, 65535). -define(CLIENT_CAPABILITIES, [{<<"publisher_confirms">>, bool, true}, {<<"exchange_exchange_bindings">>, bool, true}, {<<"basic.nack">>, bool, true}, {<<"consumer_cancel_notify">>, bool, true}]). %% use by the client to create the request -record(amqp_request, { version = "0_9_1", % default is 0.9.1 type, username, password, heartbeat, vhost, channel = "1", exchange, routing_key, confirm, prefetch_size, prefetch_count, persistent, payload, payload_size, queue, ack }). -record(amqp_dyndata, { none } ). -record(ch, { ack, next_pub_seqno, unconfirmed_set } ). -record(amqp_session, { vhost, protocol, channel_max = 65535, map_num_pa, ack_buf, status, % status of handshake waiting = none % waiting state: {Channel, Expecting} }). tsung-1.5.1/include/ts_profile.hrl0000644000175000017500000001243712317102151020250 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -record(match, { regexp, subst = false, 'when' = false, name, 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 bosh_path = "/http-bind/", % for bash only websocket_path = "/chat", % for websocket only websocket_frame = "binary", % for websocket only retry_timeout = 10, % retry sending in microsec idle_timeout = 600000, % timeout for local ack global_ack_timeout = infinity, % timeout for global ack tcp_rcv_size = 32768, % tcp buffers size tcp_snd_size = 32768, udp_rcv_size, % udp buffers size udp_snd_size, certificate = [] }). -record(token_bucket, {rate, burst, last_packet_date = 0, current_size = 0 }). -define(size_mon_thresh, 524288). % 512KB -define(short_timeout, 1). % 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' (cas be used to store data dynamically generated during the % session (Cookies for examples)) 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) dynvars=[], % clienttype, % module name (ts_jabber, etc.) transactions=[], % current transactions rate_limit, % rate limiting parameters dump % type of dump (full, light, none) }). -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 phase_id = 1, start_date, short_timeout = ?short_timeout, maxusers %% if maxusers are currently active, launch a %% new beam to handle the new users }). tsung-1.5.1/include/rabbit.hrl0000644000175000017500000001044712147621622017356 0ustar nniclaussenniclausse%% The contents of this file are subject to the Mozilla Public License %% Version 1.1 (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License %% at http://www.mozilla.org/MPL/ %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and %% limitations under the License. %% %% The Original Code is RabbitMQ. %% %% The Initial Developer of the Original Code is VMware, Inc. %% Copyright (c) 2007-2012 VMware, Inc. All rights reserved. %% -record(user, {username, tags, auth_backend, %% Module this user came from impl %% Scratch space for that module }). -record(internal_user, {username, password_hash, tags}). -record(permission, {configure, write, read}). -record(user_vhost, {username, virtual_host}). -record(user_permission, {user_vhost, permission}). -record(vhost, {virtual_host, dummy}). -record(connection, {protocol, user, timeout_sec, frame_max, vhost, client_properties, capabilities}). -record(content, {class_id, properties, %% either 'none', or a decoded record/tuple properties_bin, %% either 'none', or an encoded properties binary %% Note: at most one of properties and properties_bin can be %% 'none' at once. protocol, %% The protocol under which properties_bin was encoded payload_fragments_rev %% list of binaries, in reverse order (!) }). -record(resource, {virtual_host, kind, name}). -record(exchange, {name, type, durable, auto_delete, internal, arguments, scratches, policy}). -record(exchange_serial, {name, next}). -record(amqqueue, {name, durable, auto_delete, exclusive_owner = none, arguments, pid, slave_pids, sync_slave_pids, policy, gm_pids}). %% mnesia doesn't like unary records, so we add a dummy 'value' field -record(route, {binding, value = const}). -record(reverse_route, {reverse_binding, value = const}). -record(binding, {source, key, destination, args = []}). -record(reverse_binding, {destination, key, source, args = []}). -record(topic_trie_node, {trie_node, edge_count, binding_count}). -record(topic_trie_edge, {trie_edge, node_id}). -record(topic_trie_binding, {trie_binding, value = const}). -record(trie_node, {exchange_name, node_id}). -record(trie_edge, {exchange_name, node_id, word}). -record(trie_binding, {exchange_name, node_id, destination}). -record(listener, {node, protocol, host, ip_address, port}). -record(runtime_parameters, {key, value}). -record(basic_message, {exchange_name, routing_keys = [], content, id, is_persistent}). -record(ssl_socket, {tcp, ssl}). -record(delivery, {mandatory, sender, message, msg_seq_no}). -record(amqp_error, {name, explanation = "", method = none}). -record(event, {type, props, timestamp}). -record(message_properties, {expiry, needs_confirming = false, delivered = false}). -record(plugin, {name, %% atom() version, %% string() description, %% string() type, %% 'ez' or 'dir' dependencies, %% [{atom(), string()}] location}). %% string() %%---------------------------------------------------------------------------- -define(COPYRIGHT_MESSAGE, "Copyright (C) 2007-2012 VMware, Inc."). -define(INFORMATION_MESSAGE, "Licensed under the MPL. See http://www.rabbitmq.com/"). -define(PROTOCOL_VERSION, "AMQP 0-9-1 / 0-9 / 0-8"). -define(ERTS_MINIMUM, "5.6.3"). %% EMPTY_FRAME_SIZE, 8 = 1 + 2 + 4 + 1 %% - 1 byte of frame type %% - 2 bytes of channel number %% - 4 bytes of frame payload length %% - 1 byte of payload trailer FRAME_END byte %% See rabbit_binary_generator:check_empty_frame_size/0, an assertion %% called at startup. -define(EMPTY_FRAME_SIZE, 8). -define(MAX_WAIT, 16#ffffffff). -define(HIBERNATE_AFTER_MIN, 1000). -define(DESIRED_HIBERNATE, 10000). -define(CREDIT_DISC_BOUND, {2000, 500}). -define(INVALID_HEADERS_KEY, <<"x-invalid-headers">>). -define(ROUTING_HEADERS, [<<"CC">>, <<"BCC">>]). -define(DELETED_HEADER, <<"BCC">>). tsung-1.5.1/include/ts_mqtt.hrl0000644000175000017500000000342712236145741017610 0ustar nniclaussenniclausse%%% This code was developped by Zhihui Jiao(jzhihui521@gmail.com). %%% %%% Copyright (C) 2013 Zhihui Jiao %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -vc('$Id$ '). -author('jzhihui521@gmail.com'). %% use by the client to create the request -record(mqtt_request, { type, clean_start = true, keepalive = 10, % 10s will_topic, will_qos, will_msg, will_retain, topic, qos = 0, retained = false, payload }). -record(mqtt_dyndata, { none } ). -record(mqtt_session, { ack_buf = <<>>, ping_pid, keepalive, curr_id = 0, wait, % wait code status % connection status }). tsung-1.5.1/include/mqtt.hrl0000644000175000017500000000457212236145741017104 0ustar nniclaussenniclausse%% The MIT License (MIT) %% %% Copyright (c) <2013> %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal %% in the Software without restriction, including without limitation the rights %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %% copies of the Software, and to permit persons to whom the Software is %% furnished to do so, subject to the following conditions: %% %% The above copyright notice and this permission notice shall be included in %% all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %% THE SOFTWARE. %% %% An erlang client for MQTT (http://www.mqtt.org/) %% -define(LOG(Msg), io:format("{~p:~p ~p}: ~p~n", [?MODULE, ?LINE, self(), Msg])). -define(MQTT_PORT, 1883). -define(PROTOCOL_NAME, "MQIsdp"). -define(PROTOCOL_VERSION, 3). -define(UNUSED, 0). -define(USERNAME, undefined). -define(PASSWORD, undefined). -define(DEFAULT_KEEPALIVE, 120). -define(DEFAULT_RETRY, 120). -define(DEFAULT_CONNECT_TIMEOUT, 5). -define(CONNECT, 1). -define(CONNACK, 2). -define(PUBLISH, 3). -define(PUBACK, 4). -define(PUBREC, 5). -define(PUBREL, 6). -define(PUBCOMP, 7). -define(SUBSCRIBE, 8). -define(SUBACK, 9). -define(UNSUBSCRIBE, 10). -define(UNSUBACK, 11). -define(PINGREQ, 12). -define(PINGRESP, 13). -define(DISCONNECT, 14). -record(connect_options, { protocol_name = ?PROTOCOL_NAME, protocol_version = ?PROTOCOL_VERSION, client_id, clean_start = true, will, keepalive = ?DEFAULT_KEEPALIVE, username = ?USERNAME, password = ?PASSWORD, retry = ?DEFAULT_RETRY, connect_timeout = ?DEFAULT_CONNECT_TIMEOUT }). -record(mqtt, { id, type, dup = 0, qos = 0, retain = 0, arg }). -record(sub, { topic, qos = 0 }). -record(publish_options, { qos = 0, retain = 0 }). -record(will, { topic, message, publish_options = #publish_options{} }). tsung-1.5.1/include/ts_websocket.hrl0000644000175000017500000000355212301350731020576 0ustar nniclaussenniclausse%%% This code was developped by Zhihui Jiao(jzhihui521@gmail.com). %%% %%% Copyright (C) 2013 Zhihui Jiao %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -vc('$Id$ '). -author('jzhihui521@gmail.com'). %% use by the client to create the request -record(websocket_request, { version = "13", % default is 13(rfc6455) type, % connect or message path, % connection path frame = "binary", subprotos = [], % subprotocols data % websocket data }). -record(websocket_dyndata, { none } ). -record(websocket_session, { status, % status of handshake accept % Sec-Websocket-Accept header value }). %% opcode of websocket -define(OP_CONT, 0). -define(OP_TEXT, 1). -define(OP_BIN, 2). -define(OP_CLOSE, 8). -define(OP_PING, 9). -define(OP_PONG, 10). tsung-1.5.1/include/ts_raw.hrl0000644000175000017500000000275512147621622017415 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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.5.1/include/ts_jabber.hrl0000644000175000017500000000654612317102151020041 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -record(jabber_session, {id, regexp, user_server, username, passwd, domain}). -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 resource, node_type, subid, version ="1.0", %% 1.0 or "legacy", used by type=connect cacertfile, %% PEM encoded CA certificates file, used by type=starttls keyfile, %% user's private PEM encoded key file, used by type=starttls keypass, %% passphase of user's private PEM encoded key file, used by type=starttls certfile, %% the user's certificate file, userd by type=starttls prefix %% username prefix }). -define(setroster_intensity, 1/(ts_utils:get_val(setroster)*1000)). -define(xmpp_username, "tsunguser" ). -define(xmpp_passwd, "sesame"). -define(xmpp_domain, "erlang-projects.org"). -define(xmpp_userid_max, 10000 ). -define(xmpp_global_number,100). tsung-1.5.1/include/ts_ldap.hrl0000644000175000017500000000267312147621622017543 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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.5.1/include/ts_pgsql.hrl0000644000175000017500000000414312320752470017742 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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_session, { auth_method, username, salt } ). %%% 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.5.1/include/eldap.hrl0000644000175000017500000000136212104023217017161 0ustar nniclaussenniclausse-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.5.1/include/ts_http.hrl0000644000175000017500000000767012317102151017572 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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, oauth_consumer, oauth_access_token, oauth_access_secret, oauth_url, userid, % for www_authentication passwd, % for www_authentication auth_type, digest_nonce, digest_opaque, digest_cnonce, digest_nc, digest_qop, realm, soap_action, % for SOAP support tag, % for tagged requests use_proxy = false }). -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, and for the whole session (user_agent and cookies) -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=[], %cookies of the current request user_agent, session_cookies = [] % Cookies of the session }). -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(PATCH, "PATCH"). -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.5.1/debian/0000755000175000017500000000000012321173206015167 5ustar nniclaussenniclaussetsung-1.5.1/debian/compat0000644000175000017500000000000212104023217016360 0ustar nniclaussenniclausse4 tsung-1.5.1/debian/copyright0000644000175000017500000000143112104023217017114 0ustar nniclaussenniclausseThis 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.5.1/debian/control0000644000175000017500000000203612317102151016567 0ustar nniclaussenniclausseSource: tsung Section: net Priority: optional Maintainer: Nicolas Niclausse Build-Depends: debhelper (>= 4.0.0), erlang-nox (>= 10.b.5-1) , python-sphinx, 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.5.1/debian/rules0000755000175000017500000000237012301354513016251 0ustar nniclaussenniclausse#!/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) $(MAKE) -C docs singlehtml 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.5.1/debian/tsung.dirs0000644000175000017500000000011012104023217017175 0ustar nniclaussenniclausseusr/bin/ usr/lib/erlang/lib usr/lib/tsung/bin usr/share/tsung/templates tsung-1.5.1/debian/docs0000644000175000017500000000007312301354513016042 0ustar nniclaussenniclausseCHANGES docs/_build/singlehtml CONTRIBUTORS README.md TODO tsung-1.5.1/debian/changelog0000644000175000017500000001077312321170241017045 0ustar nniclaussenniclaussetsung (1.5.1-1) unstable; urgency=low * 1.5.1 -- Nicolas Niclausse Wed, 09 Apr 2014 08:53:05 +0200 tsung (1.5.1a-1) unstable; urgency=low * fix make deb * prepare for new release -- Nicolas Niclausse Thu, 20 Feb 2014 10:53:05 +0200 tsung (1.5.0-1.2+nmu) unstable; urgency=low * Adding 'all_except_body' option to ts_http request subst. ** Using " will run substitutions on everything except body contents. * Adding 'mysqladmin' monitoring options to erlang monitors. ** Collects statistics on threads/questions on a mysql server. -- Don Kjer Wed, 31 Jul 2013 22:21:32 +0000 tsung (1.5.0-1.1+nmu) unstable; urgency=low * Fixing issue with attempting to set_opts on closed socket * Updating mochiweb for better xpath support * Adding mean rate calculation to tsung_stats reports. * Adding --title option to set header of report * Applying debian spelling correction patch -- Don Kjer Tue, 23 Jul 2013 06:00:53 +0000 tsung (1.5.0-1) unstable; urgency=low * New upstream release -- Nicolas Niclausse Fri, 24 May 2013 09:53:05 +0200 tsung (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.5.1/docs/0000755000175000017500000000000012321173206014675 5ustar nniclaussenniclaussetsung-1.5.1/docs/_templates/0000755000175000017500000000000012236145741017042 5ustar nniclaussenniclaussetsung-1.5.1/docs/_templates/README0000644000175000017500000000001712236145741017720 0ustar nniclaussenniclausseHTML Templates tsung-1.5.1/docs/conf-file.rst0000644000175000017500000000443212236716142017303 0ustar nniclaussenniclausse.. _sec-file-structure-label: File structure ============== The default :index:`encoding` is utf-8. You can use a different encoding, like in: .. code-block:: xml Scenarios are enclosed into **tsung** tags: .. code-block:: xml ... .. index:: dumptraffic If you add the attribute **dumptraffic="true"**, all the traffic will be logged to a file. .. warning:: 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: .. code-block:: text #date;pid;id;http method;host;URL;HTTP status;size;duration;transaction;match;error;tag Where: =========== ===================================================================================== field description =========== ===================================================================================== date timestamp at the end of the request (seconds since 1970-01-01 00:00:00 UTC) pid erlang process id id tsung user id host server hostname url URL (relative) HTTP status HTTP reponse status (200, 304, etc.) size reponse size (in bytes) duration request duration (msec) transaction name of the transaction (if any) this request was made in match if a match is defined in the request: match|nomatch (last if several are defined) error name of http error (or empty) tag tag name if the request was tagged; empty otherwise =========== ===================================================================================== .. index:: loglevel 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 :command:`make debug` and set **loglevel** to **debug**. tsung-1.5.1/docs/faq.rst0000644000175000017500000002745612236145741016224 0ustar nniclaussenniclausse.. index:: faq .. _faq: ========================== Frequently Asked Questions ========================== 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: * ssh in not working (you must have a key without passphrase, or use an agent) * Tsung and Erlang are not installed on all clients nodes * Erlang version or location (install path) is not the same on all clients nodes * A firewall is dropping Erlang packets: Erlang virtual machines use several TCP ports (dynamically generated) to communicate (if you are using EC2, you may have to change the Security Group that is applied on the VMs used for Tsung: open port range 0 - 65535) * SELinux: You should disable SELinux on all clients. * Bad :file:`/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 * 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:: ssh myhostname erl 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). * 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. **Warning** 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:: 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' .. _faq-emfile-label: Why do i have error_connect_emfile errors? ========================================== :index:`emfile` error means : **too many open files** This happens usually when you set a high value for :ref:`maxusers-label` (in the ```` section) (the default value is 800). The errors means that you are running out of file descriptors; you must check that :ref:`maxusers-label` 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 :ref:`maxusers-label` Tsung will have to start several virtual machine on the same host to bypass the maxusers limit. It could be good if you want to test a large number of users to make some modifications to your system before launching Tsung: * Put the domain name into :file:`/etc/hosts` if you don't want the DNS overhead and you only want to test the target server * Increase the maximum number of open files and customize TCP settings in :file:`/etc/sysctl.conf`. For example:: net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.ip_local_port_range = 1024 65000 fs.file-max = 65000 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 Tsung with full debugging: recompile with :command:`make debug`, and don't forget to set the loglevel to ``debug`` in the XML file (see :ref:`tsung.xml log levels `). 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 :command:`toolbar:start().` to launch the graphical tools provided by Erlang. Can I dynamically follow redirect with HTTP? ============================================ If your HTTP server sends 30X responses (:index:`redirect`) with dynamic URLs, you can handle this situation using a dynamic variable: .. code-block:: xml 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): .. code-block:: xml .. _what-format-stats: What is the format of the stats file tsung.log? =============================================== Sample 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_stddev, max, min, mean, count or for HTTP returns codes, ``size_sent`` and ``size_rcv``:: stats: name, count(during the last 10sec), totalcount(since the beginning) 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 ```` section of your configuration file (also see :ref:`sec-file-structure-label`). This will print every statistics data in a raw format in a file named :file:`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}] Since version **1.5.0**, a script :command:`tsung_percentile.pl` is provided to compute the percentiles from this file. 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 1,000 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`` (e.g. ```` in the ``arrivalphase`` node in the XML config file). .. _sec-faq-snmp-label: 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 :command:`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). This is fixed in Erlang R9C-1 and up, but you can apply this patch to make it work on earlier version:: --- 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. 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 ````. .. code-block:: xml tsung-1.5.1/docs/conf-options.rst0000644000175000017500000001323512317102151020045 0ustar nniclaussenniclausse.. index:: options .. _sec-options-label: Setting options =============== .. index:: override .. index:: thinktime .. index:: ssl_ciphers .. index:: tcp_snd_buffer .. index:: tcp_rcv_buffer .. index:: udp_snd_buffer .. index:: udp_rcv_buffer 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. .. code-block:: xml .. index:: idle_timeout .. index:: global_ack_timeout Timeout for acknowledgments of messages --------------------------------------- This is used to set the idle timeout(used for 'parse' and 'local' ack) and global ack timeout(used for 'global' ack). By default, idle timeout will be 10min(600000) and global ack timeout will be ``infinity``. This value can be changed like this: .. code-block:: xml .. index:: hibernate Hibernate --------- .. versionadded:: 1.3.1 The option ``hibernate`` 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: .. code-block:: xml To disable hibernation, you must set the value to ``infinity``. .. index:: rate_limit Rate_limit ---------- .. versionadded:: 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. .. code-block:: xml 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: .. code-block:: xml AMQP options ------------ You can set the AMQP heartbeat timeout; for example to set it to 30s (default is 600s), add: .. code-block:: xml 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): .. code-block:: xml 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: .. code-block:: xml Much simpler than the old method! In case you have several arrival phases programmed and if you use file with ``order="iter"`` the position in the file will not be reset between different arrival phase. You will not be returned to the first line when changing phase. .. code-block:: xml In this example phase 1 will read about 10 lines and phase 2 will read the next 20 lines. .. TODO explain, that file servers are synchronized between tsung nodes in a distributed setup. .. index:: dyn_variable .. _sec-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 **dynamically generated** by the server for each user. For this, you can use ```` in the scenario Let's take an example with HTTP. You can easily grab a value in a HTML form like: .. code-block:: html
with: .. code-block:: xml Now ``random_num`` will be set to 42 during the users session. Its 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: .. code-block:: xml 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. .. code-block:: xml Previously (before 1.4.0), Tsung uses the old ``regexp`` module from Erlang. This is now deprecated. The syntax was: .. code-block:: xml .. index:: xpath 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: .. code-block:: xml 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 ```` 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: .. code-block:: xml .. index:: jsonpath .. _sec-jsonpath-label: 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 ``>``, instead of ``re``, like: .. code-block:: xml You can also use expressions ``Key=Val``, e.g.: .. code-block:: xml PostgreSQL """""""""" .. versionadded:: 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``. .. code-block:: xml SELECT * from pgbench_history LIMIT 20; 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". Decoding variables """""""""""""""""" It's possible to decode variable that contains html entities encoded, this is done with **decode** attribute set to **html_entities**. .. code-block:: xml .. index:: setdynvars 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: Several types of dynamic variables are implemented (``sourcetype`` attribute): .. index:: callback * Dynamic variables defined by calling an Erlang function: .. code-block:: xml .. index:: delimiter .. index:: fileid .. index:: iter * Dynamic variables defined by parsing an external file: .. code-block:: xml *delimiter* can be any string, and *order* can be **iter** or **random** * A dynamic variable can be a random number (uniform distribution) .. code-block:: xml * A dynamic variable can be a random string .. code-block:: xml * 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. * A dynamic variable can be generated by dynamic evaluation of erlang code: .. code-block:: xml 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 :ref:`sec-dynamic-variables-label`. * A dynamic variable can be generated by applying a JSONPath specification (see :ref:`sec-jsonpath-label`) to an existing dynamic variable: .. code-block:: xml * You can create dynamic variables to get the hostname and port of the current server .. code-block:: xml * You can define a dynamic variable as constant value to use it in a plugin (since version **1.5.0**) .. code-block:: xml A **setdynvars** can be defined anywhere in a session. .. index:: match Checking the server's response ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ With the tag ``match`` in a ```` 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: .. code-block:: xml Welcome ! 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, name in a file (in :file:`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 :file:`match----.dump` You can mixed several match tag in a single request: .. code-block:: xml Retry Error You can also do the action on **nomatch** instead of **match**. .. index:: skip_headers .. index:: apply_to_content 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: .. code-block:: xml 01441debe3d7cc65ba843eee1acff89d You can also use dynamic variables, using the **subst** attribute: .. code-block:: xml %%_myvar%% **Since 1.5.0**, it's now possible to add **name** attribute in **match** tag to name a record printed in match.log as follow: .. code-block:: xml 200OK 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. .. index:: 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 .. code-block:: xml ... ... .. index:: repeat .. index:: while .. index:: until """""""" Repeat the enclosing action (while or until) some condition. This is intended to be used together with ```` 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 ```` or ```` example: .. code-block:: xml ... ... **Since 1.3.1**, it's also possible to add if statements based on dynamic variables: .. index:: if """" .. code-block:: xml You can use ``eq`` or ``neq`` to check the variable. **Since 1.5.1** you can also use the comparison operators ``gt``, ``gte``, ``lt`` and ``lte`` to do respectively ``greater than``, ``greater than or equal to``, ``less than`` and ``less than or equal to``. If the dynamic variable is a list (output from XPath for example), you can access to the n-th element of a list like this: .. code-block:: xml Here we compare the first element of the list to 3. .. index:: foreach """"""""" Repeat the enclosing actions for all the elements contained in the list specified. The basic syntax is as follows: .. code-block:: xml 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: .. code-block:: xml If you want to exclude all the elements from a specific URI, you would write: .. code-block:: xml You can combine this with a XPath query. For instance the following scenario will retrieve all the images specified on a web page: .. code-block:: xml Rate limiting ^^^^^^^^^^^^^ Since version **1.4.0**, rate limiting can be enabled, either globally (see :ref:`sec-options-label`), or for each session separately. For example, to limit the rate to 64KB/sec for a given session: .. code-block:: xml ... Only the incoming traffic is rate limited currently. .. index:: tag Requests exclusion ^^^^^^^^^^^^^^^^^^ .. versionadded:: 1.5.1 It is possible to exclude some request for a special run. To do this you have to tag them and use the option ``-x`` when launching the run. For example, to exclude the GET of foo.png, add a ``tag`` to the respective request: .. code-block:: xml Then launch the run with:: tsung -f SCENARIO.xml -x image start Only the GET to ``/`` will be performed. Note that request tags also get logged on **dumptraffic="protocol"** (see :ref:`sec-file-structure-label`) Client certificate ^^^^^^^^^^^^^^^^^^ .. versionadded:: 1.5.1 It is possible to use a client certificate for ssl authentication. You can use dynamic variables to set some parameters of the certificate (and the key password is optional). .. code-block:: xml tsung-1.5.1/docs/Makefile0000644000175000017500000001267012236145741016353 0ustar nniclaussenniclausse# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Tsung.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Tsung.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Tsung" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Tsung" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." tsung-1.5.1/docs/errorslist.rst0000644000175000017500000000405612236145741017654 0ustar nniclaussenniclausse=========== Errors list =========== error_closed ------------ Only for non persistent session (XMPP); the server unexpectedly closed the connection; the session is aborted. error_inet_ ---------------------- 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_ ------------------------- Error in a repeat loop (undefined dynamic variable usually). error_send_ ---------------------- 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_ ------------------------- 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 --------------- XMPP: No online user available (usually for a chat message destinated to a online user) error_no_offline ---------------- 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_ ------------------- 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. tsung-1.5.1/docs/benchmark.rst0000644000175000017500000002127712236145741017402 0ustar nniclaussenniclausse================== Benchmark Approach ================== HTTP/WebDAV =========== Benchmarking a Web server ------------------------- #. 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 ``http://-`` instead of ``https://`` in your browser. #. Edit / organize scenario, by adding recorded sessions in the configuration file. #. Write small code for dynamic parts if needed and place dynamic mark-up in the scenario. #. 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. #. Launch benchmark with your first application parameters setup: :command:`tsung start` (run :command:`man tsung` for more options). #. 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:`statistics-reports`): the statistics are updated every 10 seconds). For a brief summary of the current activity, use :command:`tsung status`. #. Analyze results, change parameters and relaunch another benchmark. WebDAV ------ It's the same approach as HTTP: first you start to record one or more sessions with the :ref:`recorder `: :command:`tsung-recorder -p webdav start`. 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: .. index:: ts_http, http_use_server_as_proxy .. code-block:: xml LDAP ==== 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-label` for more information. PostgreSQL ========== 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``. MySQL ===== A MySQL plugin for the recorder is not yet implemented, so you have to write the session by yourself; see section :ref:`session-mysql-label` for more information. Jabber/XMPP =========== Overview -------- This paragraph explains how to write a session for Jabber/XMPP. There are two differences between HTTP and Jabber testing: * There is no recorder for Jabber, so you have to write your sessions by hand. An example is provided in :ref:`sec-session-jabber-label`. * The Jabber plugin does not parse XML; instead it uses packet acknowledgments. 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 using the ``ack`` attribute: * ``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: .. index:: presence .. code-block:: xml You also have to specify the number of users to be connected: .. index:: ts_jabber, global_number .. code-block:: xml To be sure that exactly ``global_number`` users are started, add the ``maxnumber`` attribute to ``users``: .. index:: maxnumber, interarrival .. code-block:: xml If you do not specify ``maxnumber``, the global ack will be reset every ``global_number`` users. .. _bidi-presence-label: Bidirectional Presence ^^^^^^^^^^^^^^^^^^^^^^ **New in 1.2.2**: This version adds an new option for a session. if you set the attribute ``bidi`` (for bidirectional) in the ```` tag: ````, then incoming messages from the server will be analyzed. Currently, only roster subscription requests are handled: if a user received a subscription request (````), it will respond with a ```` message. 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: * It removes the client from the list of Online users, and moves them into the list of Connected users. * It sends a broadcast presence update of ``type="unavailable"``. **presence:final** is optional. **Warning: 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. 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:`jabber-options-label` for password settings). * **plain authentication** - sends clear-text passwords: .. code-block:: xml ... * **digest authentication** as described in XMPP JEP-0078: Non-SASL Authentication http://www.jabber.org/jeps/jep-0078.html .. code-block:: xml ... * **sip-digest authentication** .. code-block:: xml ... 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. tsung-1.5.1/config.sub0000644000175000017500000007511312104023217015727 0ustar nniclaussenniclausse#! /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.5.1/tsung-1.0.dtd0000644000175000017500000003372612317102151016105 0ustar nniclaussenniclausse tsung-1.5.1/LISEZMOI0000644000175000017500000000540612104023217015123 0ustar nniclaussenniclausse# $Id$ Tsung LISEZMOI 1. Introduction 1.1. Généralités 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 montée en charge et les performances d'applications client/serveur (basées sur IP). Actuellement, les protocoles HTTP, Jabber, PostgreSQL, WEBDAV et LDAP sont implémentés, et Tsung est très facilement extensible (voir le fichier doc/Design.txt pour une description de l'implémentation et des possibilités d'extensions). Tsung utilise le langage Erlang. Ce logiciel est capable de simuler plusieurs milliers d'utilisateurs simultanément, et ceux-ci peuvent être répartis sur plusieurs machines. Plus de 10000 utilisateurs peuvent être simulés sur une seule machine; la limite supérieure dépend du type de hardware et également de l'activité des clients simulés. L'idée est de simuler le comportement d'un client réel en utilisant un modèle de type stochastique, ceci afin de reproduire le trafic plus fidèlement que peuvent le faire de simple modèles déterministes. Un utilisateur est caractérisé par une une suite d'actions (requetes, thinktime) faites au cours d'une session. Plusieurs sessions peuvent être définies, chacune avec une popularité donnée. De cette façon, lors de l'injection, chaque nouvel utilisateur utilisera un type de session en tirant aléatoirement une session (en fonction de la popularité de chaque session). Un paramètre important est le l'inter-arrivée des clients qui détermine le taux d'arrivée des clients sur le système (ie. le nombre de clients arrivant sur le système -- démarrant leur session -- par unité de temps). Plusieurs phases peuvent être définies pour un tests, chaque phase injectant des utilisateurs à un taux donné. Dans l'implémentation actuelle, la taux d'arrivée des clients et le temps entre message d'un même client ("think time") sont modélisés par une distribution exponentielle (par conséquent, le processus d'arrivée 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. Problèmes/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.5.1/vsn.mk0000644000175000017500000000000612321170241015074 0ustar nniclaussenniclausse1.5.1 tsung-1.5.1/acinclude.m40000755000175000017500000000255312104023217016141 0ustar nniclaussenniclaussednl 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.5.1/tsung.spec.in0000644000175000017500000000465712236145741016412 0ustar nniclaussenniclausse%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 doxygen-latex python-sphinx texlive-titlesec texlive-framed texlive-threeparttable texlive-wrapfig 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.md 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.5.1/ebin/0000755000175000017500000000000012321173206014662 5ustar nniclaussenniclaussetsung-1.5.1/src/0000755000175000017500000000000012321173206014534 5ustar nniclaussenniclaussetsung-1.5.1/src/log2tsung.pl.in0000644000175000017500000002467612104023217017434 0ustar nniclaussenniclausse#!/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.5.1/src/tsung_stats.pl.in0000644000175000017500000007322612301350731020064 0ustar nniclaussenniclausse#!/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 $report_title ); 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, "title=s",\$report_title ); &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; $report_title = "Tsung" unless $report_title; $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 $bosh; # true if bosh. add bosh specific 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; } } sub gnuplot_has_cairo { return (system("$gnuplot -e 'set term pngcairo' 2> /dev/null ") == 0) } # plot stats from file with gnuplot sub plot_stats { # args: my $names = shift; # arrayref contaning data names my $titles = 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; } my $has_pngcairo = &gnuplot_has_cairo(); print STDERR "Using pngcairo support in gnuplot\n" if $verbose and $has_pngcairo; $datatype = "unknown" unless $datatype; open(GP,">$gplotdir/graphes-$datatype.gplot") or die "can't open graphes-$datatype.gplot: $!"; select GP; foreach my $output_fmt ($imgfmt, "tn.png") { my $output_ext; my $output_type; if (($output_fmt eq "png") || ($output_fmt eq "tn.png")) { $output_ext = "png"; if ($has_pngcairo) { $output_type = "pngcairo "; } else { $output_type = "png "; } } elsif ($output_fmt eq "ps") { $output_type = "postscript color "; $output_ext = "ps"; } elsif ($output_fmt eq "pdf") { $output_type = "pdf color "; $output_ext = "pdf"; } elsif ($output_fmt eq "svg") { $output_type = "svg "; $output_ext = "svg"; } # gnuplot styles and options print "set size $thumbnail_size,$thumbnail_size\n" if (($output_fmt eq "tn.png") and $oldgnuplot); print "set xtics rotate\n" if (($output_ext eq "png") and ($rotate_xtics)); print "set style data $style\n"; if (($output_fmt eq "tn.png") and not $oldgnuplot and ($output_type !~ /pngcairo/)) { print "set terminal $output_type tiny size 320,240\n"; } elsif (($output_fmt eq "tn.png") and not $oldgnuplot) { print "set terminal $output_type size 320,240 font 'Verdana,7' \n"; } elsif ((($output_ext eq "svg") or (($output_ext 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"; # remove border on top and right and set color to gray print "set style line 11 lc rgb '#808080' lt 1\n"; print "set border 3 back ls 11\n"; print "set tics nomirror\n"; print "set style line 1 lc rgb '#8b1a0e' pt 1 ps 1 lt 2 lw 2\n"; # --- red print "set style line 2 lc rgb '#5e9c36' pt 6 ps 1 lt 2 lw 2\n"; # --- green my $d; # temporary var (title) foreach $d (0..$#{$names}) { # gnuplot headings if ($output_fmt eq "tn.png") { print "set output \"$imgdir/graphes-$datatype-@{$names}[$d]_$output_fmt\"\n"; } else { print "set output \"$imgdir/graphes-$datatype-@{$names}[$d].$output_ext\"\n"; } print "set title \" @{$titles}[$d]\"\n"; if ($timestamp) { print "set xlabel \"unit = $timestamp sec \"\n"; } else { print "set xlabel \"time (sec of the running test)\"\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 "; my $lineindex=0; foreach $filename (@{$files}) { $lineindex++; 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\" ls $lineindex" ; 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"; @rowdata = (); #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,$stddev,$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->{'rate_total'}->{$type} = 0 if not defined $maxval->{'rate_total'}->{$type}; $maxval->{'rate_count'}->{$type} = 0 if not defined $maxval->{'rate_count'}->{$type}; $maxval->{'rate_total'}->{$type} = $maxval->{'rate_total'}->{$type} + $rate; $maxval->{'rate_count'}->{$type} = $maxval->{'rate_count'}->{$type} + 1; $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 @bosh_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","stddev","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 eq "bosh_http_conn" or $key eq "bosh_http_req") { $bosh = 1; push @bosh_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,\@col,"Session",undef,\@session,["sessions/sec"],$logy) unless $noplot; plot_stats(\@colcount,["HTTP Code Response rate","total"], "HTTP_CODE",undef,\@code,["number/sec","total"],$logy) if not $noplot and @code; plot_stats(\@colcount,\@colcount,"Bosh",undef,\@bosh_tps,["rate"],$logy) unless $noplot; plot_stats(\@col,["Request and tcp/udp connection rate","Mean request (and connection) duration","stddev","max_sample","min_sample"],"Perfs",undef,\@tps,["Requests rate (r/sec)","Requests duration (msec)"],$logy) unless $noplot; plot_stats(\@col,["Transaction and page rate", "Mean transaction and page duration","stddev","max_sample","min_sample"],"Transactions",undef,\@transactions,["transactions/sec","Transaction duration (msec)"],$logy) unless $noplot; plot_stats(\@colcount,\@colcount,"Match",undef,\@match,["rate","rate"],$logy) unless $noplot; plot_stats(\@colcount,\@colcount,"Event",undef,\@time,["rate","msec"],$logy) unless $noplot; plot_stats(\@colasync,\@colasync,"Async",undef,\@async,["rate","rate"],$logy) unless $noplot; plot_stats(\@colcount,\@colcount,"Errors",undef,\@errors,["errors/sec","total"],$logy) unless $noplot; plot_stats(\@colusers,["Simultaneous users and opened TCP/UDP connections","maximum_simultaneous"],"Users",undef,\@users,["value", "total"],$logy) unless $noplot; plot_stats(\@colcount,["Users arrival/departure rate", "Total users"],"Users_Arrival",undef,\@users_rate,["number of users/sec", "total"],$logy) unless $noplot; plot_stats(\@colcount,["Network throughput","Total send/receive bytes"],"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(\@colcount,"Bosh",undef,\@bosh_tps,["rate"],$logy) if not $noplot and @bosh_tps; 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,\@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 = $report_title; 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 $data (keys % {$maxval->{'rate_count'}} ) { next if ($data =~ m/^size/); $maxval->{'rate_mean'}->{$data} = sprintf "%.2f",$maxval->{'rate_total'}->{$data} / $maxval->{'rate_count'}->{$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, bosh => $bosh, 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", "[--title ] (report title, default=Tsung)\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 François 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.\n"; exit; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tsung-1.5.1/src/test/�������������������������������������������������������������������������������0000755�0001750�0001750�00000000000�12321173206�015513� 5����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tsung-1.5.1/src/test/ts_test_http.erl���������������������������������������������������������������0000644�0001750�0001750�00000040372�12320752470�020756� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%%% %%% Copyright © Nicolas Niclausse 2007 %%% %%% Author : Nicolas Niclausse <Nicolas.Niclausse@niclux.org> %%% Created: 17 Mar 2007 by Nicolas Niclausse <Nicolas.Niclausse@niclux.org> %%% %%% 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(true,#http_request{url="%%_image%%"}, DynVars), ?assertEqual("/images/my%20image%20with%20spaces.png", Req#http_request.url). subst_full_url_test() -> myset_env(), URL="http://myserver/%%_path%%", Proto=#http{user_agent="Firefox"}, DynVars=ts_dynvars:new(path,"bidule/truc"), {Req,_}=ts_http:add_dynparams(true,{DynVars, Proto} , #http_request{url=URL}, {"erlang.org",80,ts_tcp}), Str="GET /bidule/truc HTTP/1.1\r\nHost: myserver\r\nUser-Agent: Firefox\r\n\r\n", {Res,_}=ts_http:get_message(Req,#state_rcv{}), ?assertEqual(Str, binary_to_list(Res)). 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{session_cookies=Cookies,user_agent="Firefox"}, DynVars=ts_dynvars:new(redirect,"http://erlang.org/bidule/truc"), {Req,_}=ts_http:add_dynparams(true,{DynVars, Proto} , #http_request{url=URL}, {"erlang.org",80,ts_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{}), ?assertEqual(Str, binary_to_list(Res)). subst_ipv6_host_test()-> URL="/%%_dynpath%%", Proto=#http{user_agent="Firefox"}, DynVars=ts_dynvars:new(dynpath,"bidule/truc"), Rep=ts_http:add_dynparams(true,{DynVars, Proto}, #http_request{url=URL}, {"2178:2:5:0:28f:0:3",80,ts_tcp6}), {Res,_}=ts_http:get_message(Rep,#state_rcv{}), OK = << "GET /bidule/truc HTTP/1.1\r\nHost: [2178:2:5:0:28f:0:3]\r\nUser-Agent: Firefox\r\n\r\n" >>, ?assertEqual(OK, Res). subst_ipv6_host2_test()-> URL="/%%_dynpath%%", Proto=#http{user_agent="Firefox"}, DynVars=ts_dynvars:new(dynpath,"bidule/truc"), Rep=ts_http:add_dynparams(true,{DynVars, Proto}, #http_request{url=URL}, {"::1",8080,ts_tcp6}), {Res,_}=ts_http:get_message(Rep,#state_rcv{}), OK = << "GET /bidule/truc HTTP/1.1\r\nHost: [::1]:8080\r\nUser-Agent: Firefox\r\n\r\n" >>, ?assertEqual(OK, 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{session_cookies=Cookies,user_agent="Firefox"}, DynVars=ts_dynvars:new(redirect,"http://erlang.org/bidule/truc"), Rep=ts_http:add_dynparams(true,{DynVars, Proto}, #http_request{url=URL}, {"erlang.org",80,ts_tcp6}), ?assertMatch({_,{"erlang.org",80,ts_tcp6}}, Rep). subst_cookie_test()-> myset_env(), URL="/bidule/truc", Cookie="bar=%%_foovar%%; path=/; domain=erlang.org", Cookies=ts_http_common:add_new_cookie(Cookie,"erlang.org",[]), Proto=#http{user_agent="Firefox"}, DynVars=ts_dynvars:new(foovar,"foo"), Req=ts_http:add_dynparams(true,{DynVars, Proto}, #http_request{url=URL,cookie=Cookies}, {"erlang.org",80,ts_tcp}), Str="GET /bidule/truc HTTP/1.1\r\nHost: erlang.org\r\nUser-Agent: Firefox\r\nCookie: bar=foo\r\n\r\n", {Res,_}=ts_http:get_message(Req,#state_rcv{}), ?assertEqual(Str, binary_to_list(Res)). 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{session_cookies=Cookies,user_agent="Firefox"}, DynVars=ts_dynvars:new(), Req=ts_http:add_dynparams(false,{DynVars, Proto}, #http_request{url=URL}, {"www.domain.org",80,ts_tcp}), Str="GET /bidule/truc HTTP/1.1\r\nHost: www.domain.org\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{session_cookies=Cookies,user_agent="Firefox"}, DynVars=ts_dynvars:new(), Req=ts_http:add_dynparams(false,{DynVars, Proto}, #http_request{url=URL}, {"www.domain.org",80, ts_tcp}), Str="GET /bidule/truc HTTP/1.1\r\nHost: www.domain.org\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{session_cookies=Cookies,user_agent="Firefox"}, DynVars=ts_dynvars:new(), Req=ts_http:add_dynparams(false,{DynVars, Proto}, #http_request{url=URL}, {"www.foobar.net",80, ts_tcp}), Str="GET /bidule/truc HTTP/1.1\r\nHost: www.foobar.net\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). parse_304_test() -> Res = <<"HTTP/1.1 304 Not Modified\r\nDate: Fri, 24 Aug 2012 07:49:37 GMT\r\nServer: Apache/2.2.16 (Debian)\r\nETag: \"201ad-10fb-473ae23fb0600\"\r\nVary: Accept-Encoding\r\n\r\n">>, State=#state_rcv{session=#http{user_agent="Firefox"}}, {Rep, [], false } =ts_http:parse(Res,State), ?assertMatch(#http{user_agent="Firefox",status={none,304}, partial=false}, Rep#state_rcv.session). 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()-> <<A:10/binary, B/binary>> = 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= <<Data1/binary, A/binary, Data2/binary, B/binary, Data3/binary>>, ?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}})). authentication_basic_test()-> Base="QWxhZGRpbjpvcGVuIHNlc2FtZQ==", ?assertEqual(["Authorization: Basic ",Base,?CRLF], ts_http_common:authenticate(#http_request{userid="Aladdin", auth_type="basic",passwd="open sesame"})). authentication_digest1_test()-> OK="Authorization: Digest username=\"Mufasa\", realm=\"testrealm@host.com\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", uri=\"/dir/index.html\", response=\"6629fae49393a05397450978507c4ef1\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\", qop=\"auth\", nc=00000001, cnonce=\"0a4f113b\"\r\n", Req=#http_request{userid="Mufasa", auth_type="digest",passwd="Circle Of Life", realm ="testrealm@host.com", url="/dir/index.html", digest_qop = "auth", digest_nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093", digest_nc = "00000001", digest_cnonce = "0a4f113b", digest_opaque = "5ccc069c403ebaf9f0171e9517f40e41"}, ?assertEqual(OK, lists:flatten(ts_http_common:authenticate(Req))). oauth_test()-> myset_env(), Data = <<"HTTP/1.1 200 OK\r\nDate: Mon, 10 Sep 2012 12:26:35 GMT\r\nServer: Apache/2.2.17 (Debian)\r\nX-Powered-By: PHP/5.3.3-7\r\nContent-Length: 55\r\nContent-Type: text/html\r\n\r\noauth_token=requestkey&oauth_token_secret=requestsecret">>, ?assertMatch([{'token',<< "requestsecret" >>}], ts_search:parse_dynvar([{re,'token', "oauth_token_secret=([^&]*)"} ],Data)), ?assertMatch([{'token',<< "requestkey" >>}], ts_search:parse_dynvar([{re,'token', "oauth_token=([^&]*)"} ],Data)). set_msg_dyn_test() -> URL = "http://jm-11:%%_myport%%/%%_myurl%%", Subst =true, Res = ts_config_http:set_msg(#http_request{url= URL}, {Subst, undefined, false, [#server{host="myserver", port=99, type="tcp"}], "myserver", ets:new(fake,[]), 1}), ?assertMatch(#http_request{url=URL}, Res#ts_request.param). set_msg_test() -> URL = "http://server:8080/path%%bla%%", Subst = false, Res = ts_config_http:set_msg(#http_request{url= URL}, {Subst, undefined, false, [#server{host="myserver", port=99, type="tcp"}], "myserver", ets:new(fake,[]), 1}), ?assertMatch(#http_request{url="/path%%bla%%",host_header= "server:8080"}, Res#ts_request.param), ?assertMatch(#ts_request{host="server", port=8080, scheme = ts_tcp}, Res). set_msg2_test() -> URL = "http://server:8080/path%%", Subst = true, Res = ts_config_http:set_msg(#http_request{url= URL}, {Subst, undefined, false, [#server{host="myserver", port=99, type="tcp"}], "myserver", ets:new(fake,[]), 1}), ?assertMatch(#http_request{url="/path%%",host_header= "server:8080"}, Res#ts_request.param), ?assertMatch(#ts_request{host="server", port=8080, scheme = ts_tcp}, Res). myset_env()-> myset_env(0). myset_env(N)-> application:set_env(stdlib,debug_level,N). ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tsung-1.5.1/src/test/ts_test_options.erl������������������������������������������������������������0000644�0001750�0001750�00000001275�12104023217�021457� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%% 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.5.1/src/test/netstat_test.txt���������������������������������������������������������������0000644�0001750�0001750�00000000552�12104023217�020772� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Table d'interfaces noyau Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg wlan0 1500 0 0 0 0 0 0 0 0 0 BMU lo 16436 0 13186 0 0 0 13186 0 0 0 LRU eth0 1500 0 7823989 0 2 0 4272908 0 0 0 BMRU ������������������������������������������������������������������������������������������������������������������������������������������������������tsung-1.5.1/src/test/ts_test_websocket.erl����������������������������������������������������������0000644�0001750�0001750�00000010777�12236145741�021776� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%%% Author: Zhihui Jiao <jzhihui521@gmail.com> %%% %%% 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_websocket). -vc('$Id$ '). -author('jzhihui521@gmail.com'). -compile(export_all). -include("ts_profile.hrl"). -include("ts_websocket.hrl"). -include("ts_config.hrl"). -include_lib("eunit/include/eunit.hrl"). test()->ok. handshake_test() -> {_, Accept} = websocket:get_handshake("127.0.0.1", "/chat", [], 13), Response1 = ["HTTP/1.1 101 Switching Protocols\r\n", "Upgrade: websocket\r\n", "Connection: Upgrade\r\n", "Sec-WebSocket-Accept: " ++ Accept ++ "\r\n\r\n"], ?assertEqual(ok, websocket:check_handshake(list_to_binary(Response1), Accept)), Response2 = ["HTTP/1.1 201 Switching Protocols\r\n", "Upgrade: websocket\r\n", "Connection: Upgrade\r\n", "Sec-WebSocket-Accept: " ++ Accept ++ "\r\n\r\n"], ?assertEqual({error, {mismatch, "Status", "101", "201"}}, websocket:check_handshake(list_to_binary(Response2), Accept)), Response3 = ["HTTP/1.1 101 Switching Protocols\r\n", "Upgrade: websocket\r\n", "Connection: Upgrade\r\n", "Sec-WebSocket-Accept: anything\r\n\r\n"], ?assertEqual({error, {mismatch, "Sec-WebSocket-Accept", Accept, "anything"}}, websocket:check_handshake(list_to_binary(Response3), Accept)), Response4 = ["HTTP/1.1 101 Switching Protocols\r\n", "Upgrade: anything\r\n", "Connection: Upgrade\r\n", "Sec-WebSocket-Accept: " ++ Accept ++ "\r\n\r\n"], ?assertEqual({error, {mismatch, "Upgrade", "websocket", "anything"}}, websocket:check_handshake(list_to_binary(Response4), Accept)), Response5 = ["HTTP/1.1 101 Switching Protocols\r\n", "Upgrade: websocket\r\n", "Connection: anything\r\n", "Sec-WebSocket-Accept: " ++ Accept ++ "\r\n\r\n"], ?assertEqual({error, {mismatch, "Connection", "Upgrade", "anything"}}, websocket:check_handshake(list_to_binary(Response5), Accept)), Response6 = ["HTTP/1.1 101 Switching Protocols\r\n", "Connection: Upgrade\r\n", "Sec-WebSocket-Accept: " ++ Accept ++ "\r\n\r\n"], ?assertEqual({error, {miss_headers, "Upgrade"}}, websocket:check_handshake(list_to_binary(Response6), Accept)), Response7 = ["HTTP/1.1 101 Switching Protocols\r\n", "Upgrade: websocket\r\n" "Sec-WebSocket-Accept: " ++ Accept ++ "\r\n\r\n"], ?assertEqual({error, {miss_headers, "Connection"}}, websocket:check_handshake(list_to_binary(Response7), Accept)), Response8 = ["HTTP/1.1 101 Switching Protocols\r\n", "Upgrade: websocket\r\n", "Connection: Upgrade\r\n\r\n"], ?assertEqual({error, {miss_headers, "Sec-WebSocket-Accept"}}, websocket:check_handshake(list_to_binary(Response8), Accept)), Response9 = ["HTTP/1.0 101 Switching Protocols\r\n", "Upgrade: websocket\r\n", "Connection: Upgrade\r\n", "Sec-WebSocket-Accept: " ++ Accept ++ "\r\n\r\n"], ?assertEqual({error, {mismatch, "Version", "HTTP/1.1", "http/1.0"}}, websocket:check_handshake(list_to_binary(Response9), Accept)). decode_test() -> Data1 = <<16#81,16#05,16#48,16#65,16#6c,16#6c,16#6f>>, {Opcode, Payload, Left} = websocket:decode(Data1), ?assertEqual(?OP_TEXT, Opcode), ?assertEqual(<<"Hello">>, Payload), ?assertEqual(<<>>, Left), Data2 = <<16#81,16#05,16#48,16#65,16#6c,16#6c>>, Result = websocket:decode(Data2), ?assertEqual(more, Result). myset_env()-> myset_env(0). myset_env(N)-> application:set_env(stdlib, debug_level, N). �tsung-1.5.1/src/test/ts_test_pgsql.erl��������������������������������������������������������������0000644�0001750�0001750�00000014773�12104023217�021121� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%%%------------------------------------------------------------------- %%% File : ts_test_pgsql.erl %%% Author : Nicolas Niclausse <nicolas@niclux.org> %%% Description : %%% %%% Created : 10 Apr 2008 by Nicolas Niclausse <nicolas@niclux.org> %%%------------------------------------------------------------------- -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). subst_parameters_test()-> myset_env(), Proto=#pgsql_session{}, DynVars=ts_dynvars:new(param,"42"), Params=["%%_param%%","1"], Req=ts_pgsql:add_dynparams(true,{DynVars,Proto}, #pgsql_request{type=bind,name_portal="",name_prepared="P0_10", formats=none,formats_results=[text],parameters=Params}, {"pgsql.org",5432,gen_tcp}), Str=[66,0,0,0,30,0,80,48,95,49,48,0,0,0,0,2,0,0,0,2,52,50,0,0,0,1,49,0,1,0,0], {Res,_}=ts_pgsql:get_message(Req,#state_rcv{}), ?assertEqual(Str, binary_to_list(Res)). subst_parameters2_test()-> myset_env(), Proto=#pgsql_session{}, Params=[42,1], Req=ts_pgsql:add_dynparams(true,{[], Proto}, #pgsql_request{type=bind,name_portal="",name_prepared="P0_10", formats=none,formats_results=[text],parameters=Params}, {"pgsql.org",5432,gen_tcp}), Str=[66,0,0,0,30,0,80,48,95,49,48,0,0,0,0,2,0,0,0,2,52,50,0,0,0,1,49,0,1,0,0], {Res,_}=ts_pgsql:get_message(Req,#state_rcv{}), ?assertEqual(Str, binary_to_list(Res)). myset_env()-> myset_env(0). myset_env(Val)-> application:set_env(stdlib,debug_level,Val). �����tsung-1.5.1/src/test/test_file_server2.csv����������������������������������������������������������0000644�0001750�0001750�00000000015�12147621622�021660� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������user1;sesame �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tsung-1.5.1/src/test/test_file_server_pipe.csv������������������������������������������������������0000644�0001750�0001750�00000000104�12147621622�022612� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������conv%2F99%2F589%2Finfo.txt|99|589 conv%2F99%2F938%2Finfo.txt|99|938 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tsung-1.5.1/src/test/ts_test_stats.erl��������������������������������������������������������������0000644�0001750�0001750�00000001234�12104023217�021115� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%%%------------------------------------------------------------------- %%% File : ts_test_stats.erl %%% Author : Nicolas Niclausse <nicolas@niclux.org> %%% Description : %%% %%% Created : 12 Jul 2011 by Nicolas Niclausse <nicolas@niclux.org> %%%------------------------------------------------------------------- -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.5.1/src/test/ts_test_mqtt.erl���������������������������������������������������������������0000644�0001750�0001750�00000013554�12236145741�020771� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%%% This code was developped by Zhihui Jiao(jzhihui521@gmail.com). %%% %%% Copyright (C) 2013 Zhihui Jiao %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_test_mqtt). -vc('$Id$ '). -author('jzhihui521@gmail.com'). -compile(export_all). -include("ts_profile.hrl"). -include("mqtt.hrl"). -include("ts_config.hrl"). -include_lib("eunit/include/eunit.hrl"). encode_connect_test() -> ClientId = "tsung-test-id", PublishOptions = mqtt_frame:set_publish_options([{qos, 0}, {retain, false}]), Will = #will{topic = "will_topic", message = "will_message", publish_options = PublishOptions}, Options = mqtt_frame:set_connect_options([{client_id, ClientId}, {clean_start, true}, {keepalive, 10}, Will]), Message = #mqtt{type = ?CONNECT, arg = Options}, EncodedData = mqtt_frame:encode(Message), ?assertEqual(<<16,53,0,6,77,81,73,115,100,112,3,6,0,10,0,13,116,115,117,110, 103,45,116,101,115,116,45,105,100,0,10,119,105,108,108,95, 116,111,112,105,99,0,12,119,105,108,108,95,109,101,115,115, 97,103,101>>, EncodedData). decode_connect_test() -> Data = <<16,53,0,6,77,81,73,115,100,112,3,6,0,10,0,13,116,115,117,110, 103,45,116,101,115,116,45,105,100,0,10,119,105,108,108,95, 116,111,112,105,99,0,12,119,105,108,108,95,109,101,115,115, 97,103,101>>, {#mqtt{type = Type}, Left} = mqtt_frame:decode(Data), ?assertEqual(<<>>, Left), ?assertEqual(?CONNECT, Type). encode_disconnect_test() -> Message = #mqtt{type = ?DISCONNECT}, EncodedData = mqtt_frame:encode(Message), ?assertEqual(<<224,0>>, EncodedData). decode_disconnect_test() -> Data = <<224,0>>, {#mqtt{type = Type}, Left} = mqtt_frame:decode(Data), ?assertEqual(<<>>, Left), ?assertEqual(?DISCONNECT, Type). encode_publish_test() -> Message = #mqtt{id = 1, type = ?PUBLISH, qos = 0, retain = 0, arg = {"test_topic", "test_message"}}, EncodedData = mqtt_frame:encode(Message), ?assertEqual(<<48,24,0,10,116,101,115,116,95,116,111,112,105,99,116,101,115,116,95,109,101,115,115,97,103,101>>, EncodedData). decode_publish_test() -> Data = <<48,24,0,10,116,101,115,116,95,116,111,112,105,99,116,101,115,116,95,109,101,115,115,97,103,101>>, {#mqtt{type = Type}, Left} = mqtt_frame:decode(Data), ?assertEqual(<<>>, Left), ?assertEqual(?PUBLISH, Type). encode_subscribe_test() -> Arg = [#sub{topic = "test_topic", qos = 0}], Message = #mqtt{id = 1, type = ?SUBSCRIBE, arg = Arg}, EncodedData = mqtt_frame:encode(Message), ?assertEqual(<<128,15,0,1,0,10,116,101,115,116,95,116,111,112,105,99,0>>, EncodedData). decode_subscribe_test() -> Data = <<128,15,0,1,0,10,116,101,115,116,95,116,111,112,105,99,0>>, {#mqtt{type = Type}, Left} = mqtt_frame:decode(Data), ?assertEqual(<<>>, Left), ?assertEqual(?SUBSCRIBE, Type). encode_unsubscribe_test() -> Arg = [#sub{topic = "test_topic"}], Message = #mqtt{id = 1, type = ?UNSUBSCRIBE, arg = Arg}, EncodedData = mqtt_frame:encode(Message), ?assertEqual(<<160,14,0,1,0,10,116,101,115,116,95,116,111,112,105,99>>, EncodedData). decode_unsubscribe_test() -> Data = <<160,14,0,1,0,10,116,101,115,116,95,116,111,112,105,99>>, {#mqtt{type = Type}, Left} = mqtt_frame:decode(Data), ?assertEqual(<<>>, Left), ?assertEqual(?UNSUBSCRIBE, Type). encode_puback_test() -> Message = #mqtt{type = ?PUBACK, arg = 1}, EncodedData = mqtt_frame:encode(Message), ?assertEqual(<<64,2,0,1>>, EncodedData). decode_puback_test() -> Data = <<64,2,0,1>>, {#mqtt{type = Type}, Left} = mqtt_frame:decode(Data), ?assertEqual(<<>>, Left), ?assertEqual(?PUBACK, Type). encode_ping_test() -> Message = #mqtt{type = ?PINGREQ}, EncodedData = mqtt_frame:encode(Message), ?assertEqual(<<192,0>>, EncodedData). decode_ping_test() -> Data = <<192,0>>, {#mqtt{type = Type}, Left} = mqtt_frame:decode(Data), ?assertEqual(<<>>, Left), ?assertEqual(?PINGREQ, Type). encode_pong_test() -> Message = #mqtt{type = ?PINGRESP}, EncodedData = mqtt_frame:encode(Message), ?assertEqual(<<208,0>>, EncodedData). decode_pong_test() -> Data = <<208,0>>, {#mqtt{type = Type}, Left} = mqtt_frame:decode(Data), ?assertEqual(<<>>, Left), ?assertEqual(?PINGRESP, Type). more_test() -> Data = <<64,2,0>>, Result = mqtt_frame:decode(Data), ?assertEqual(more, Result). left_test() -> Data = <<64,2,0,1,2,3>>, {#mqtt{type = Type}, Left} = mqtt_frame:decode(Data), ?assertEqual(<<2,3>>, Left), ?assertEqual(?PUBACK, Type). myset_env()-> myset_env(0). myset_env(N)-> application:set_env(stdlib, debug_level, N). ����������������������������������������������������������������������������������������������������������������������������������������������������tsung-1.5.1/src/test/ts_test_jabber.erl�������������������������������������������������������������0000644�0001750�0001750�00000030173�12320752470�021222� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%%% %%% Copyright © Nicolas Niclausse 2007 %%% %%% Author : Nicolas Niclausse <Nicolas.Niclausse@niclux.org> %%% Created: 17 Mar 2007 by Nicolas Niclausse <Nicolas.Niclausse@niclux.org> %%% %%% 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_macros.hrl"). -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("<presence type='subscribe' to='toto@im.apinc.org' from='tintin@jabber.org'> <status>Hi dude.</status> </presence>"), Resp=list_to_binary("<presence to='tintin@jabber.org' type='subscribed'/>"), State=#state_rcv{}, ?assertMatch({Resp,State}, ts_jabber:parse_bidi(Req,State)). bidi_multisubscribeok_test()-> myset_env(), Req=list_to_binary("<presence type='subscribe' to='toto@im.apinc.org' from='tintin@jabber.org'> <status>Hi dude.</status></presence><presence type='subscribe' to='toto@im.apinc.org' from='glop@jabber.org'> <status>Copaing?.</status></presence>"), Resp=list_to_binary("<presence to='tintin@jabber.org' type='subscribed'/><presence to='glop@jabber.org' type='subscribed'/>"), State=#state_rcv{}, ?assertMatch({Resp,State}, ts_jabber:parse_bidi(Req,State)). bidi_multisubscribe_nok_test()-> myset_env(), Req=list_to_binary("<presence type='subscribe' to='toto@im.apinc.org' from='tintin@jabber.org'> <status>Hi dude.</status></presence><presence type='subscribed' from='glop@jabber.org'> <status>Copaing?.</status></presence>"), Resp=list_to_binary("<presence to='tintin@jabber.org' type='subscribed'/>"), State=#state_rcv{}, ?assertMatch({Resp,State}, ts_jabber:parse_bidi(Req,State)). bidi_subscribe_nok_test()-> myset_env(), Req=list_to_binary("<presence type='subscribed' from='tintin@jabber.org'> <status>Hi dude.</status> </presence>"), State=#state_rcv{}, ?assertMatch({nodata,State}, ts_jabber:parse_bidi(Req,State)). bidi_nok_test()-> myset_env(), Req=list_to_binary("<presence from='tintin@jabber.org'><status>Alive.</status></presence>"), State=#state_rcv{}, ?assertMatch({nodata,State}, ts_jabber:parse_bidi(Req,State)). auth_sasl_test()-> myset_env(), Res = << "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN' >AGp1bGlldAByMG0zMG15cjBtMzA=</auth>" >>, ?assertMatch(Res, ts_jabber_common:auth_sasl("juliet","r0m30myr0m30","PLAIN")). add_dynparams_test()-> ts_user_server:start(), ts_user_server:reset(100), ts_msg_server:start(), Req = #jabber{id=0,prefix="foo",username="foo",passwd="bar",type='connect',domain={domain,"localdomain"}}, {_,Session} = ts_jabber:get_message(Req,#state_rcv{session=#jabber_session{}}), ?assertEqual(Req#jabber{id=1,username="foo1",passwd="bar1",user_server=default,domain="localdomain"}, ts_jabber:add_dynparams(true,{[],Session},Req,"localhost")). add_dynparams2_test()-> Req = #jabber{id=0,prefix="foo",username="foo",passwd="bar",type='connect', domain={domain,"localdomain"}}, {_,Session} = ts_jabber:get_message(Req,#state_rcv{session=#jabber_session{}}), ?assertEqual(Req#jabber{id=2,username="foo2",passwd="bar2",user_server=default,domain="localdomain"}, ts_jabber:add_dynparams(true,{[],Session},Req,"localhost")). get_message_test()-> Req = #jabber{id=0,prefix="foo",username="foo",type='auth_set_plain',passwd="bar",domain={domain,"localdomain"},resource="tsung"}, RepOK = <<"<iq id='3' type='set' ><query xmlns='jabber:iq:auth'><username>foo3</username><resource>tsung</resource><password>bar3</password></query></iq>" >>, {Rep,_}=ts_jabber:get_message(Req,#state_rcv{session=#jabber_session{}}), ?assertEqual(RepOK,Rep ). get_message2_test()-> Req = #jabber{id=user_defined,username="foo",type='auth_set_plain',passwd="bar",domain={domain,"localdomain"},resource="tsung"}, RepOK = <<"<iq id='4' type='set' ><query xmlns='jabber:iq:auth'><username>foo</username><resource>tsung</resource><password>bar</password></query></iq>" >>, {Rep,_} = ts_jabber:get_message(Req,#state_rcv{session=#jabber_session{}}), ?assertEqual(RepOK,Rep). pubsub_unsubscribe_test()-> ts_user_server:reset(1), Req = #jabber{id=0,prefix="foo",username="foo",type='pubsub:unsubscribe',passwd="bar",domain={domain,"localdomain"},dest=random, node="node", pubsub_service="mypubsub", subid="myid",resource="tsung"}, RepOK= << "<iq to='mypubsub' type='set' id='5'><pubsub xmlns='http://jabber.org/protocol/pubsub'><unsubscribe node='/home/localdomain/foo1/node' jid='foo1@localdomain' subid='myid'/></pubsub></iq>" >>, {Rep,_}=ts_jabber:get_message(Req,#state_rcv{session=#jabber_session{}}), ?assertEqual(RepOK,Rep ). connect_legacy_test()-> ts_user_server:reset(1), Req = #jabber{id=0,prefix="foo",username="foo",type='connect',passwd="bar",domain={domain,"localdomain"},resource="tsung", version="legacy"}, RepOK= <<"<?xml version='1.0'?><stream:stream id='6' to='localdomain' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>" >>, {Rep,_} = ts_jabber:get_message(Req,#state_rcv{session=#jabber_session{}}), ?assertEqual(RepOK,Rep). connect_xmpp_test()-> ts_user_server:reset(1), Req = #jabber{id=0,prefix="foo",username="foo",type='connect',passwd="bar",domain={domain,"localdomain"},resource="tsung", version="1.0"}, RepOK= <<"<?xml version='1.0'?><stream:stream id='7' to='localdomain' xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams'>" >>, {Rep,_} = ts_jabber:get_message(Req,#state_rcv{session=#jabber_session{}}), ?assertEqual(RepOK,Rep). pubsub_subscribe_test()-> ts_user_server:reset(1), Req = #jabber{id=0,prefix="foo",dest="foo2",username="foo",type='pubsub:subscribe',passwd="bar",domain={domain,"localdomain"},node="node", pubsub_service="mypubsub", resource="tsung"}, RepOK= << "<iq to='mypubsub' type='set' id='8'><pubsub xmlns='http://jabber.org/protocol/pubsub'><subscribe node='/home/localdomain/foo2/node' jid='foo1@localdomain'/></pubsub></iq>" >>, {Rep,_}=ts_jabber:get_message(Req,#state_rcv{session=#jabber_session{}}), ?assertEqual(RepOK,Rep ). get_online_test()-> ts_user_server:reset(100), Id=ts_user_server:get_idle(), IdOther = ts_user_server:get_idle(), RealId = ts_jabber_common:set_id(IdOther,"tsung","tsung"), ts_user_server:add_to_online(default,RealId), {ok,Offline} = ts_user_server:get_offline(), {ok,Online} = ts_user_server:get_online(Id), ?assertEqual({1,3,2},{Id,Offline,Online} ). get_online_user_defined_test()-> ts_user_server:reset(0), ts_msg_server:stop(), ts_msg_server:start(), User1 = "tsung1", User2 = "tsung2", User3 = "tsung3", Pwd = "sesame", ts_user_server:add_to_connected({User1,Pwd}), ts_user_server:add_to_online(default, ts_jabber_common:set_id(user_defined,User1, Pwd) ), ts_user_server:add_to_connected({User2,Pwd}), ts_user_server:add_to_online(default, ts_jabber_common:set_id(user_defined,User2, Pwd) ), ts_user_server:add_to_connected({User3,Pwd}), ts_user_server:add_to_online(default, ts_jabber_common:set_id(user_defined,User3, Pwd) ), ts_user_server:remove_connected(default, ts_jabber_common:set_id(user_defined,User3, Pwd) ), {ok,Offline}=ts_user_server:get_offline(), Msg = ts_jabber_common:get_message(#jabber{type = 'presence:directed', id=user_defined,username=User1,passwd=Pwd,prefix="prefix", show = "foo", status="mystatus",user_server=default, domain="domain.org"}), Res = "<presence id='1' to='tsung2@domain.org'><show>foo</show><status>mystatus</status></presence>", ?assertEqual(Res, binary_to_list(Msg) ). get_offline_user_defined_test()-> ts_user_server:reset(0), User1 = "tsung1", User3 = "tsung3", Pwd = "sesame", ts_user_server:add_to_connected({User1,Pwd}), ts_user_server:add_to_online(default, ts_jabber_common:set_id(user_defined,User1, Pwd) ), ts_user_server:add_to_connected({User3,Pwd}), ts_user_server:add_to_online(default, ts_jabber_common:set_id(user_defined,User3, Pwd) ), ts_user_server:remove_connected(default, ts_jabber_common:set_id(user_defined,User3, Pwd) ), Msg = ts_jabber_common:get_message(#jabber{type = 'chat', prefix="prefix", data="hello", dest = offline, user_server=default, domain="domain.org"}), Res = "<message id='2' to='tsung3@domain.org' type='chat'><body>hello</body></message>", ?assertEqual(Res, binary_to_list(Msg) ). get_unique_user_defined_test()-> % this test must be runned just after get_offline_user_defined_test Msg = ts_jabber_common:get_message(#jabber{type = 'chat', prefix="prefix", data="hello", dest = unique, user_server=default, domain="domain.org"}), Res = "<message id='3' to='tsung1@domain.org' type='chat'><body>hello</body></message>", ?assertEqual(Res, binary_to_list(Msg) ). get_unique_test()-> ts_user_server:reset(2), Id=ts_user_server:get_idle(), Msg = ts_jabber_common:get_message(#jabber{type = 'chat', prefix="prefix", data="hello", dest = unique, user_server=default, domain="domain.org"}), Res = "<message id='4' to='prefix1@domain.org' type='chat'><body>hello</body></message>", ?assertEqual(Res, binary_to_list(Msg) ). get_random_test()-> ts_user_server:reset(1), Msg = ts_jabber_common:get_message(#jabber{type = 'chat', prefix="prefix", data="hello", dest = random, user_server=default, domain="domain.org"}), Res = "<message id='5' to='prefix1@domain.org' type='chat'><body>hello</body></message>", ?assertEqual(Res, binary_to_list(Msg) ). get_random_user_defined_test()-> ts_user_server:reset(0), Id = xmpp, ts_user_server:set_random_fileid(Id), ts_file_server:start(), ts_file_server:read([{default,"./src/test/test_file_server.csv"}, {Id,"./src/test/test_file_server2.csv"} ]), Msg = ts_jabber_common:get_message(#jabber{type = 'chat', prefix="prefix", data="hello", dest = random, user_server=default, domain="domain.org"}), Res = "<message id='6' to='user1@domain.org' type='chat'><body>hello</body></message>", ?assertEqual(Res, binary_to_list(Msg) ). get_offline_user_defined_offline_test()-> Id = xmpp, ts_user_server:set_offline_fileid(Id), ts_user_server:reset(0), User1 = "tsung1", Pwd = "sesame", ts_user_server:add_to_connected({User1,Pwd}), ts_user_server:add_to_online(default, ts_jabber_common:set_id(user_defined,User1, Pwd) ), Msg = ts_jabber_common:get_message(#jabber{type = 'chat', prefix="prefix", data="hello", dest = offline, user_server=default, domain="domain.org"}), Res = "<message id='7' to='user1@domain.org' type='chat'><body>hello</body></message>", ?assertEqual(Res, binary_to_list(Msg) ). get_offline_user_defined_no_offline_test()-> ts_user_server:reset(0), User1 = "user1", Pwd = "sesame", ts_user_server:add_to_connected({User1,Pwd}), ts_user_server:add_to_online(default, ts_jabber_common:set_id(user_defined,User1, Pwd) ), Msg = ts_jabber_common:get_message(#jabber{type = 'chat', prefix="prefix", data="hello", dest = offline, user_server=default, domain="domain.org"}), %% Res = "<message id='8' to='user1@domain.org' type='chat'><body>hello</body></message>", Res = "", ts_user_server:set_offline_fileid(undefined), ?assertEqual(Res, binary_to_list(Msg) ). myset_env()-> myset_env(0). myset_env(Val)-> application:set_env(stdlib,debug_level,Val). �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tsung-1.5.1/src/test/ts_test_user_server.erl��������������������������������������������������������0000644�0001750�0001750�00000006346�12147621622�022347� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%%%------------------------------------------------------------------- %%% File : ts_test_user_server.erl %%% Author : Nicolas Niclausse <nicolas@niclux.org> %%% Description : %%% %%% Created : 20 Mar 2005 by Nicolas Niclausse <nicolas@niclux.org> %%%------------------------------------------------------------------- -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(), ?assertEqual(2,B). 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(), ?assertEqual(3,C). 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), B=ts_user_server:get_idle(), ?assertEqual(A,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()-> myset_env(0). myset_env(A)-> application:set_env(stdlib,debug_level,A). ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tsung-1.5.1/src/test/ts_test_mon.erl����������������������������������������������������������������0000644�0001750�0001750�00000007117�12236145741�020573� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%%%------------------------------------------------------------------- %%% File : ts_test_mon.erl %%% Author : Nicolas Niclausse <nicolas@niclux.org> %%% Description : %%% %%% Created : 24 August 2007 by Nicolas Niclausse <nicolas@niclux.org> %%%------------------------------------------------------------------- -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). netstat_test()-> myset_env(), {ok, Lines} = ts_utils:file_to_list("./src/test/netstat_test.txt"), ?assertMatch({7823989,4272908}, ts_os_mon_erlang:get_os_data(packets, {unix, linux},Lines )). netstat2_test()-> myset_env(), {ok, Lines} = ts_utils:file_to_list("./src/test/netstat_test2.txt"), ?assertMatch({41687492504,56858242340}, ts_os_mon_erlang:get_os_data(packets, {unix, linux}, Lines)). netstat3_test()-> myset_env(), {ok, Lines} = ts_utils:file_to_list("./src/test/netstat_test3.txt"), ?assertMatch({58334153,45308889}, ts_os_mon_erlang:get_os_data(packets, {unix, linux}, Lines)). myset_env()-> myset_env(0). myset_env(V)-> application:set_env(stdlib,debug_level,V). �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tsung-1.5.1/src/test/netstat_test3.txt��������������������������������������������������������������0000644�0001750�0001750�00000002547�12236145741�021100� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Table d'interfaces noyau Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg eth0 1500 0 11887768 0 0 0 833748 0 0 0 BMRU eth0:0 1500 0 - no statistics available - BMRU eth0:1 1500 0 - no statistics available - BMRU eth0:3 1500 0 - no statistics available - BMRU eth0:4 1500 0 - no statistics available - BMRU eth0:8 1500 0 - no statistics available - BMRU eth1 1500 0 40621236 0 0 0 40325942 0 0 0 BMRU eth1:0 1500 0 - no statistics available - BMRU eth1:1 1500 0 - no statistics available - BMRU eth1:2 1500 0 - no statistics available - BMRU eth1:3 1500 0 - no statistics available - BMRU eth1:4 1500 0 - no statistics available - BMRU eth2 1500 0 5825149 0 0 0 4149195 0 0 0 BMRU eth3 1500 0 0 0 0 0 4 0 0 0 BMRU lo 16436 0 10530106 0 0 0 10530106 0 0 0 LRU ���������������������������������������������������������������������������������������������������������������������������������������������������������tsung-1.5.1/src/test/ipcfg.out����������������������������������������������������������������������0000644�0001750�0001750�00000001066�12104023217�017332� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������5: 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.5.1/src/test/thinkfirst.xml.in��������������������������������������������������������������0000644�0001750�0001750�00000003713�12212102345�021026� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0"?> <!DOCTYPE tsung SYSTEM "@prefix@/share/@PACKAGE_NAME@/@DTD@"> <tsung loglevel="notice" version="1.0"> <clients> <client host="client1"></client> <client host="client2"></client> <client host="client3"></client> <client host="client4"></client> <client host="client5"></client> <client host="client6"></client> <client host="client7"></client> <client host="client8"></client> <client host="client9"></client> <client host="client10"></client> </clients> <servers> <server host="127.0.0.1" port="5222" type="tcp"></server> </servers> <monitoring> <monitor host="foo" type="snmp"> <snmp version="v2" community="public"/> </monitor> </monitoring> <load> <user session="example" start_time="1" unit="second"/> <user session="example" start_time="2" unit="second"/> <user session="example" start_time="4" unit="second"/> <user session="example*" start_time="10" unit="second"/> <arrivalphase phase="1" duration="1" unit="minute"> <users maxnumber="5" interarrival="1" unit="second"></users> </arrivalphase> <arrivalphase phase="2" duration="1" unit="minute"> <users maxnumber="15" interarrival="1" unit="second"></users> </arrivalphase> </load> <options> <option name="ports_range" value="on" min="10000"></option> <option name="hibernate" value="3"></option> </options> <sessions> <session probability="90" name="example" type="ts_http"> <thinktime value="30"></thinktime> <transaction name="offline"> <request> <http url="/" method="GET"></http> </request> </transaction> <thinktime min="3" max="4" random="false"></thinktime> </session> <session probability="5" name="example2" type="ts_http"> <request> <http url="/" method="GET"></http> </request> </session> <session probability="5" name="foo" type="ts_http"> <request> <http url="/" method="GET"></http> </request> </session> </sessions> </tsung> �����������������������������������������������������tsung-1.5.1/src/test/procnetdev_test7chars.txt������������������������������������������������������0000644�0001750�0001750�00000001271�12104023217�022570� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Inter-| 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.5.1/src/test/ts_test_search.erl�������������������������������������������������������������0000644�0001750�0001750�00000047173�12157674022�021256� 0����������������������������������������������������������������������������������������������������ustar �nniclausse����������������������nniclausse�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%%%------------------------------------------------------------------- %%% File : ts_test_search.erl %%% Author : Nicolas Niclausse <nicolas@niclux.org> %%% 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,"<input type=\"hidden\" name=\"jsf_tree_64\" id=\"jsf_tree_64\" value=\"H4sIAAAAAAAAAK1VS2/TQBBeo+kalCKAA\">"). 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',<< >>}], 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', << >>}], 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_jsonpath_struct_test() -> myset_env(), Data="{\"accessToken\":{\"id\":\"78548a96-cadd-48c0-b7d6-4ff3b81f10cc\",\"lists\":[\"testlist1\"],\"token\":\"rTgdC3f7uJ/Smg3s4b9va2KW5GdPkRHtwYNgWbvwhensgOSf2/wan95VPDiXKnAAGilsZlpw/Td4bs/OPeVeYg==\",\"scope\":[\"GET_ME\",\"WRITE_ACCESS\"]},\"accessTokenSignature\":\"gWAL+zvDcQjqLmNdSwcG/TOWyta5g==\"}", JSONPath = "accessToken", ?assertMatch([{'nodes', << "{\"id\":\"78548a96-cadd-48c0-b7d6-4ff3b81f10cc\",\"lists\":[\"testlist1\"],\"token\":\"rTgdC3f7uJ/Smg3s4b9va2KW5GdPkRHtwYNgWbvwhensgOSf2/wan95VPDiXKnAAGilsZlpw/Td4bs/OPeVeYg==\",\"scope\":[\"GET_ME\",\"WRITE_ACCESS\"]}" >> }], ts_search:parse_dynvar([{jsonpath,'nodes', JSONPath} ],list_to_binary(Data))). parse_dyn_var_xpath_test() -> myset_env(), Data="\r\n\r\n<html><body>"++?FORMDATA++"</body></html>", 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<html><head><script type=\"text/javascript\"> " " A = B <= C </script>" "</head><body>"++?FORMDATA++"</body></html>", 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<html><body><input type=\"hidden\" name=\"tree64\" id=\"tree64\" value=\"H4sIAAAAAAAAAK1VS2/TQBBeo+kalCKAA\"></body></html>", 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<html><body><hidden name=\"random\" value=\"42\"></form></body></html>", 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<html><body><hidden name='random' value='42'></form></body>/<html>", 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<html><body><form>"++lists:flatmap(fun(A)-> AI=integer_to_list(A),["<input type='hidden' name='random",AI,"'"," value='value",AI,"'>"] end,lists:seq(1,N)) ++"</form></body>/<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)))}. setdata_big(N) -> setdata_big(N, list). setdata_big(N, Type) -> Head = "<head><title>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_re_decode_test() -> myset_env(), Data= << "" >>, 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, fun ts_utils:conv_entities/1 }],Data), ?assertEqual("'foo&bar'", ts_search:subst("%%_jsf_tree_64%%",[{Name,Value}])). 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(binary_to_list(String))). dynvars_random2_test() -> myset_env(), [String,String2] = ts_client:set_dynvars(random,{string,20},[toto,titi],[],{},[]), ?assertMatch({20,20},{length(binary_to_list(String)),length(binary_to_list(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],Dynvars,{},[])). dynvars_jsonpath2_test() -> myset_env(), Data="{\"accessToken\":{\"id\":\"78548a96-cadd-48c0-b7d6-4ff3b81f10cc\",\"lists\":[\"testlist1\"],\"token\":\"rTgdC3f7uJ/Smg3s4b9va2KW5GdPkRHtwYNgWbvwhensgOSf2/wan95VPDiXKnAAGilsZlpw/Td4bs/OPeVeYg==\",\"scope\":[\"GET_ME\",\"WRITE_ACCESS\"]},\"accessTokenSignature\":\"gWAL+zvDcQjqLmNdSwcG/TOWyta5g==\"}", JSONPath = "accessToken", JSONPath2 = "accessTokenSignature", Dynvars=ts_dynvars:new(data,Data), Res = << "{\"id\":\"78548a96-cadd-48c0-b7d6-4ff3b81f10cc\",\"lists\":[\"testlist1\"],\"token\":\"rTgdC3f7uJ/Smg3s4b9va2KW5GdPkRHtwYNgWbvwhensgOSf2/wan95VPDiXKnAAGilsZlpw/Td4bs/OPeVeYg==\",\"scope\":[\"GET_ME\",\"WRITE_ACCESS\"]}" >>, ?assertEqual(Res,ts_client:set_dynvars(jsonpath,{JSONPath,data},[toto],Dynvars,{},[])), ?assertEqual(<< "gWAL+zvDcQjqLmNdSwcG/TOWyta5g==" >>,ts_client:set_dynvars(jsonpath,{JSONPath2,data},[toto],Dynvars,{},[])). dynvars_jsonpath3_test() -> myset_env(), Data="\r\n\r\n{\"titi\": [42, 666] }", JSONPath = "titi[1]", Dynvars=ts_dynvars:new(data,Data), ?assertEqual(666,ts_client:set_dynvars(jsonpath,{JSONPath,data},[toto],Dynvars,{},[])). dynvars_jsonpath4_test() -> myset_env(), Data="\r\n\r\n{\"titi\": [{\"val\": [ 123, 231 ] , \"name\": \"foo\"}, {\"val\": [ 13, 23 ], \"name\": \"bar\"}]}", JSONPath = "titi[?name=bar].val[0]", Dynvars=ts_dynvars:new(data,Data), ?assertEqual(13,ts_client:set_dynvars(jsonpath,{JSONPath,data},[toto],Dynvars,{},[])). dynvars_file_test() -> myset_env(), ts_file_server:stop(), ts_file_server:start(), ts_file_server:read([{default,"./src/test/test_file_server.csv"}]), ?assertMatch([<<"username1">>,<<"glop">>, << >>],ts_client:set_dynvars(file,{iter,default,<< ";" >>},[],[],{},[])). dynvars_file_pipe_test() -> myset_env(), ts_file_server:stop(), ts_file_server:start(), ts_file_server:read([{default,"./src/test/test_file_server_pipe.csv"}]), ?assertMatch([<<"conv%2F99%2F589%2Finfo.txt">>,<<"99">> ,<<"589">>],ts_client:set_dynvars(file,{iter,default,<< "|" >>},[],[],{},[])). %%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.5.1/src/test/ts_test_proxy.erl0000644000175000017500000001715612104023217021152 0ustar nniclaussenniclausse%%%------------------------------------------------------------------- %%% 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_body_test()-> myset_env(), Data="sqdfqsdflqkfnmqlskfqd http://-foobar.foo42.fr\r\n", NewData="sqdfqsdflqkfnmqlskfqd https://foobar.foo42.fr\r\n", {ok, Res} = ts_utils:to_https({request,{body,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.5.1/src/test/netstat_test2.txt0000644000175000017500000000725312104023217021061 0ustar nniclaussenniclausseKernel Interface table Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg bond0 1500 0 35717377985 0 72 0 51928792541 0 0 0 BMmRU eth0 1500 0 35464245354 0 72 0 10222152685 0 0 0 BMsRU eth1 1500 0 84377542 0 0 0 19068087208 0 0 0 BMsRU eth2 1500 0 84377558 0 0 0 12129547888 0 0 0 BMsRU eth3 1500 0 84377531 0 0 0 10509004760 0 0 0 BMsRU eth4 1500 0 1376226016 0 0 0 1065638532 0 0 0 BMRU eth5 1500 0 239281824 0 0 0 159281176 0 0 0 BMRU eth6 1500 0 4354606679 0 0 0 3704530091 0 0 0 BMRU lo 16436 0 19075478 0 0 0 19075478 0 0 0 LRU vif1.0 1500 0 2449883984 0 0 0 4932787016 0 290 0 BMPRU vif1.1 1500 0 20174776 0 0 0 922457211 0 1 0 BMPRU vif11.0 1500 0 1170683857 0 0 0 3728663145 0 405 0 BMPRU vif120.0 1500 0 162318032 0 0 0 195123086 0 275 0 BMPRU vif121.0 1500 0 7035110 0 0 0 86902198 0 61700 0 BMPRU vif13.0 1500 0 189254632 0 0 0 2648494432 0 144 0 BMPRU vif131.0 1500 0 36323244 0 0 0 114665173 0 240 0 BMPRU vif132.0 1500 0 331293668 0 0 0 427660681 0 15522 0 BMPRU vif132.1 1500 0 285434 0 0 0 1572185 0 366 0 BMPRU vif133.0 1500 0 291704054 0 0 0 656316729 0 2669 0 BMPRU vif133.1 1500 0 11 0 0 0 1318276 0 205 0 BMPRU vif134.0 1500 0 1336271 0 0 0 14264970 0 14819 0 BMPRU vif134.1 1500 0 3702136 0 0 0 13180789 0 59 0 BMPRU vif136.0 1500 0 298298 0 0 0 8724792 0 1382 0 BMPRU vif20.0 1500 0 2103005514 0 0 0 4756632990 0 208 0 BMPRU vif20.1 1500 0 2302610336 0 0 0 2988962747 0 135 0 BMPRU vif28.0 1500 0 530670872 0 0 0 2722807108 0 6646 0 BMPRU vif28.1 1500 0 161486331 0 0 0 1233169592 0 46 0 BMPRU vif3.0 1500 0 941732469 0 0 0 3550602523 0 78 0 BMPRU vif30.0 1500 0 31320617 0 0 0 2301508319 0 188 0 BMPRU vif4.0 1500 0 44617622 0 0 0 2528877279 0 14 0 BMPRU vif4.1 1500 0 131 0 0 0 35164655 0 5 0 BMPRU vif5.0 1500 0 1123981715 0 0 0 3672210140 0 47 0 BMPRU vif61.0 1500 0 7886297 0 0 0 627298783 0 306 0 BMPRU vif61.1 1500 0 5595990 0 0 0 287812945 0 4 0 BMPRU vif69.0 1500 0 127685262 0 0 0 979705050 0 443 0 BMPRU vif7.0 1500 0 38828730 0 0 0 2517131705 0 538 0 BMPRU vif7.1 1500 0 1057490859 0 0 0 1368572360 0 3 0 BMPRU vif9.0 1500 0 91364037 0 0 0 2573492550 0 1356 0 BMPRU vif94.0 1500 0 14480917 0 0 0 166584483 0 1137 0 BMPRU xenbr0 1500 0 2378678882 0 0 0 131938 0 0 0 BMRU xenbr1 1500 0 34507916 0 0 0 6 0 0 0 BMRU xenbr2 1500 0 904791170 0 0 0 6 0 0 0 BMRU tsung-1.5.1/src/test/ts_test_utils.erl0000644000175000017500000000374312236145741021143 0ustar nniclaussenniclausse%% ========================================================================== %% File : ts_test_utils.erl %% Author : Rodolphe Quiédeville %% Description : %% %% Created : 17 Oct 2013 by Rodolphe Quiédeville %% ========================================================================== -module(ts_test_utils). -compile(export_all). -include_lib("eunit/include/eunit.hrl"). test()-> ok. add_time_test() -> ?assertEqual({1382,29907,875287}, ts_utils:add_time({1382,29904,875287},3)), %% have to check this second test, maybe 1383 instead of 1392 Old = {1382,999999,875287}, New = ts_utils:add_time(Old,3), ?assertEqual(3*1000*1000, timer:now_diff(New, Old)). node_to_hostname_test() -> ?assertEqual({ok, "foo"}, ts_utils:node_to_hostname('bar@foo')). to_lower_test()-> ?assertEqual("foo",ts_utils:to_lower("Foo")), ?assertEqual("foo",ts_utils:to_lower("FOO")). mkey1search_atom_test()-> Data = [{foo,bar},{foo,caps},{bar,foo},{foo,caps}], ?assertEqual([bar,caps,caps],ts_utils:mkey1search(Data,foo)). mkey1search_empty_test()-> %% the key does not exists Data = [{foo,bar},{foo,caps},{bar,foo}], ?assertEqual(undefined,ts_utils:mkey1search(Data,foobar)). mkey1search_string_test()-> Data = [{"foo","bar"},{"foo","caps"},{"bar","foo"}], ?assertEqual(["bar","caps"],ts_utils:mkey1search(Data,"foo")). datestr_test()-> ?assertEqual(["2013","10","17",45,"19","41"],ts_utils:datestr({{2013,10,17},{19,41,29}})). export_text_test()-> ?assertEqual("foo",ts_utils:export_text("foo")). export_text_bin_test()-> ?assertEqual("foo",ts_utils:export_text(<<"foo">>)). export_text_escape_test()-> ?assertEqual("fo&o",ts_utils:export_text(<<"fo&o">>)), ?assertEqual("A > B",ts_utils:export_text(<<"A > B">>)), ?assertEqual("'B'",ts_utils:export_text("'B'")), ?assertEqual(""B"",ts_utils:export_text("\"B\"")), ?assertEqual("< A",ts_utils:export_text(<<"< A">>)). tsung-1.5.1/src/test/ts_test_dynvars_api.erl0000644000175000017500000000555612104023217022311 0ustar nniclaussenniclausse%% 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.5.1/src/test/ts_test_match.erl0000644000175000017500000001151012104023217021051 0ustar nniclaussenniclausse%%%------------------------------------------------------------------- %%% 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.5.1/src/test/procnetdev_test.txt0000644000175000017500000000127012104023217021457 0ustar nniclaussenniclausseInter-| 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.5.1/src/test/xmpp-muc.xml.in0000644000175000017500000001101412104023217020400 0ustar nniclaussenniclausse tsung-1.5.1/src/test/ts_test_config.erl0000644000175000017500000002677412236145741021261 0ustar nniclaussenniclausse%%%------------------------------------------------------------------- %%% 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"). -include("ts_http.hrl"). test()-> ok. popularity_test() -> ?assertError({"can't mix probabilites and weights",10,10}, ts_config:get_popularity(10,10,undefined,100)), ?assertError({"can't use probability when using weight"}, ts_config:get_popularity(10,-1,true,100)), ?assertError({"can't use weights when using probabilities"}, ts_config:get_popularity(-1,10,false,100)), ?assertEqual({10,false,110}, ts_config:get_popularity(10,-1,false,100)), ?assertEqual({10,true,110}, ts_config:get_popularity(-1,10,true,100)), ?assertEqual({30,false,60}, ts_config:get_popularity(30,-1,false,30)), ?assertError({"must set weight or probability in session"} , ts_config:get_popularity(-1,-1,undefined,100)), ?assertError({"can't mix probabilites and weights",0,0}, ts_config:get_popularity(0,0,true,100)), ?assertError({"can't mix probabilites and weights",0,0}, ts_config:get_popularity(0,0,false,100)), ?assertEqual({0,true,100}, ts_config:get_popularity(-1,0,true,100)), ?assertEqual({0,false,100}, ts_config:get_popularity(0,-1,false,100)). 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(0), 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",1}), ?assertEqual(1, Session#session.id). config_get_session_size_test() -> myset_env(), {ok, Session=#session{userid=2} } = ts_config_server:get_next_session({"localhost",1}), ?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,{bad_sum,_,_}}, ts_config_server:check_config( ts_config_server:compute_popularities(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",1}), 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",1}), 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",1}), 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",1}), 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). read_config_tag_noexclusion_test() -> %% no exclusion all request will be played myset_env(), ok = ts_config_server:read_config("./examples/http_tag.xml"), {ok, Session=#session{userid=7} } = ts_config_server:get_next_session({"localhost",1}), Id = Session#session.id, ReqRef = #http_request{url="/img/excluded.png"}, {ts_request,parse,false,[],[],Req,_,_,_,_} = ts_config_server:get_req(Id,2), ?assertEqual(ReqRef#http_request.url, Req#http_request.url). read_config_tag_one_test() -> %% one tag defined %% exclude urls tagged as 'landing' myset_env(), application:set_env(stdlib,exclude_tag,"landing"), ok = ts_config_server:read_config("./examples/http_tag.xml"), {ok, Session=#session{userid=8} } = ts_config_server:get_next_session({"localhost",1}), Id = Session#session.id, ReqRef = #http_request{url="/img/excluded.gif"}, {ts_request,parse,false,[],[],Req,_,_,_,_} = ts_config_server:get_req(Id,2), ?assertEqual(ReqRef#http_request.url, Req#http_request.url). read_config_tag_two_test() -> %% two tag defined %% exclude urls tagged as 'landing' and 'gif' myset_env(), application:set_env(stdlib,exclude_tag,"gif,landing"), ok = ts_config_server:read_config("./examples/http_tag.xml"), {ok, Session=#session{userid=9} } = ts_config_server:get_next_session({"localhost",1}), Id = Session#session.id, ReqRef = #http_request{url="/img/not-excluded.png"}, {ts_request,parse,false,[],[],Req,_,_,_,_} = ts_config_server:get_req(Id,2), ?assertEqual(ReqRef#http_request.url, Req#http_request.url). 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) , 5). 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). wildcard_test() -> Names = ["foo1", "foo2", "bar", "barfoo", "foobar", "foo", "fof","glop"], ?assertEqual(["foo1", "foo2", "foobar", "foo"], ts_utils:wildcard("foo*",Names)), ?assertEqual(["foo1", "foo2"], ts_utils:wildcard("foo?",Names)), ?assertEqual(["foobar"], ts_utils:wildcard("foo*r",Names)). 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"), application:set_env(stdlib,exclude_tag,""). tsung-1.5.1/src/test/ifcfg.out0000644000175000017500000000425712104023217017325 0ustar nniclaussenniclausseeth0 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.5.1/src/test/badpop.xml.in0000644000175000017500000000306312104023217020104 0ustar nniclaussenniclausse tsung-1.5.1/src/test/ts_test_interaction.erl0000644000175000017500000000271412104023217022302 0ustar nniclaussenniclausse%%%------------------------------------------------------------------- %%% File : ts_test_interaction.erl %%% Author : Nicolas Niclausse %%% Description : %%% %%% Created : 28 Aug 2012 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_test_interaction). -compile(export_all). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include_lib("eunit/include/eunit.hrl"). test()-> ok. notify_test()-> myset_env(0), ts_interaction_server:start(), ts_interaction_server:send({chat,now()}), ts_interaction_server:notify({'receive',chat,self()}), ts_interaction_server:rcv({chat,now()}), Res = receive Data -> erlang:display(["received 1",Data]), ok after 1000 -> timeout end, ?assertMatch(ok, Res ). notify_to_test()-> myset_env(0), ts_interaction_server:notify({send,chat2,self()}), ts_interaction_server:send({chat2,now()}), Res = receive Data -> erlang:display(["received 2",Data]), ok after 2000 -> timeout end, ?assertMatch(ok, Res ). myset_env()-> myset_env(0). myset_env(Val)-> application:set_env(stdlib,dumpstats_interval,550), application:set_env(stdlib,mon_file,"test-mon.log"), application:set_env(stdlib,debug_level,Val). tsung-1.5.1/src/test/test_file_server.csv0000644000175000017500000000005512104023217021567 0ustar nniclaussenniclausseusername1;glop; username2;; username3;glop4; tsung-1.5.1/src/test/ts_test_recorder.erl0000644000175000017500000001006512104023217021566 0ustar nniclaussenniclausse%%%------------------------------------------------------------------- %%% 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_macros.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)). 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.5.1/src/test/ts_test_client.erl0000644000175000017500000001121112236145741021246 0ustar nniclaussenniclausse%%%------------------------------------------------------------------- %%% File : ts_test_client.erl %%% Author : Rodolphe Quiédeville %%% Description : %%% %%% Created : 7 Oct 2013 by Rodolphe Quiédeville %%%------------------------------------------------------------------- -module(ts_test_client). -compile(export_all). -include_lib("eunit/include/eunit.hrl"). test()-> ok. eq_test() -> ?assertEqual(false, ts_client:rel('eq',5,4)), ?assertEqual(true, ts_client:rel('eq',4,4)). eq_float_test() -> ?assertEqual(false, ts_client:rel('eq',5.0,4.0)), ?assertEqual(true, ts_client:rel('eq',4.0,4.0)). eq_mix_test() -> ?assertEqual(false, ts_client:rel('eq',5.0,4.0)), ?assertEqual(true, ts_client:rel('eq',4.0,4.0)). eq_alist_test() -> ?assertEqual(false, ts_client:rel('eq',"5",4)), ?assertEqual(true, ts_client:rel('eq',"4",4)). eq_blist_test() -> ?assertEqual(false, ts_client:rel('eq',<<"5">>,"4")), ?assertEqual(true, ts_client:rel('eq',<<"4">>,"4")). eq_binary_test() -> ?assertEqual(false, ts_client:rel('eq',<<"5">>,"4")), ?assertEqual(true, ts_client:rel('eq',<<"4">>,"4")), ?assertEqual(false, ts_client:rel('eq',"5",<<"4">>)), ?assertEqual(true, ts_client:rel('eq',"4",<<"4">>)). eq_aatom_test() -> ?assertEqual(false, ts_client:rel('eq', foo, <<"foobar">>)), ?assertEqual(true, ts_client:rel('eq', foo, <<"foo">>)). eq_batom_test() -> ?assertEqual(false, ts_client:rel('eq',<<"barfoo">>, foobar)), ?assertEqual(true, ts_client:rel('eq',<<"foobar">>, foobar)). neq_int_test() -> ?assertEqual(true, ts_client:rel('neq',5,4)), ?assertEqual(false, ts_client:rel('neq',4,4)). neq_list_test() -> ?assertEqual(true, ts_client:rel('neq',"e","4")), ?assertEqual(false, ts_client:rel('neq',"4","4")). neq_binary_test() -> ?assertEqual(true, ts_client:rel('neq',<<"ed">>,<<"4">>)), ?assertEqual(false, ts_client:rel('neq',<<"4">>,<<"4">>)). need_jump_while_test()-> ?assertEqual(true, ts_client:need_jump('while',true)), ?assertEqual(false, ts_client:need_jump('while',false)). need_jump_until_test()-> ?assertEqual(false, ts_client:need_jump('until',true)), ?assertEqual(true, ts_client:need_jump('until',false)). need_jump_if_test()-> ?assertEqual(false, ts_client:need_jump('if',true)), ?assertEqual(true, ts_client:need_jump('if',false)). binary_to_num_int_test()-> ?assertEqual(100, ts_client:binary_to_num(<<"100">>)). binary_to_num_float_test()-> ?assertEqual(100.1, ts_client:binary_to_num(<<"100.1">>)). binary_to_num_float_neg_test()-> ?assertEqual(-3.14, ts_client:binary_to_num(<<"-3.14">>)). gt_int_test()-> ?assertEqual(true, ts_client:rel('gt',<<"2">>,<<"3">>)), ?assertEqual(true, ts_client:rel('gt',<<"-2">>,<<"-1">>)), ?assertEqual(false, ts_client:rel('gt',<<"2">>,<<"2">>)), ?assertEqual(false, ts_client:rel('gt',<<"22">>,<<"3">>)). gt_float_test()-> ?assertEqual(true, ts_client:rel('gt',<<"2.0">>,<<"3.1">>)), ?assertEqual(false, ts_client:rel('gt',<<"2.0">>,<<"2.0">>)), ?assertEqual(false, ts_client:rel('gt',<<"22.1">>,<<"3.0">>)). lt_int_test()-> ?assertEqual(false, ts_client:rel('lt',<<"2">>,<<"3">>)), ?assertEqual(false, ts_client:rel('lt',<<"2">>,<<"2">>)), ?assertEqual(true, ts_client:rel('lt',<<"22">>,<<"3">>)). lt_float_test()-> ?assertEqual(false, ts_client:rel('lt',<<"2.0">>,<<"3.1">>)), ?assertEqual(false, ts_client:rel('lt',<<"2.0">>,<<"2.0">>)), ?assertEqual(true, ts_client:rel('lt',<<"22.1">>,<<"3.0">>)). gte_int_test()-> ?assertEqual(true, ts_client:rel('gte',<<"2">>,<<"3">>)), ?assertEqual(true, ts_client:rel('gte',<<"2">>,<<"2">>)), ?assertEqual(false, ts_client:rel('gte',<<"22">>,<<"3">>)). gte_float_test()-> ?assertEqual(true, ts_client:rel('gte',<<"2.0">>,<<"3.1">>)), ?assertEqual(true, ts_client:rel('gte',<<"-2.0">>,<<"-1.31">>)), ?assertEqual(true, ts_client:rel('gte',<<"2.0">>,<<"2.0">>)), ?assertEqual(false, ts_client:rel('gte',<<"22.1">>,<<"3.0">>)). lte_int_test()-> ?assertEqual(false, ts_client:rel('lte',<<"2">>,<<"3">>)), ?assertEqual(true, ts_client:rel('lte',<<"-2">>,<<"-3">>)), ?assertEqual(true, ts_client:rel('lte',<<"2">>,<<"2">>)), ?assertEqual(true, ts_client:rel('lte',<<"22">>,<<"3">>)). lte_float_test()-> ?assertEqual(false, ts_client:rel('lte',<<"2.0">>,<<"3.1">>)), ?assertEqual(true, ts_client:rel('lte',<<"-2.0">>,<<"-3.1">>)), ?assertEqual(true, ts_client:rel('lte',<<"2.0">>,<<"2.0">>)), ?assertEqual(true, ts_client:rel('lte',<<"-2.0">>,<<"-2.0">>)), ?assertEqual(true, ts_client:rel('lte',<<"22.1">>,<<"3.0">>)). tsung-1.5.1/src/test/ts_test_all.erl0000644000175000017500000000221312236145741020542 0ustar nniclaussenniclausse%%%------------------------------------------------------------------- %%% 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"). run() -> eunit:test([ts_test_all], [{report,{eunit_surefire,[{dir,"."}]}}]). test() -> ok. all_test_() -> [ts_test_recorder, ts_test_config, ts_test_client, 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, ts_test_interaction, ts_test_websocket, ts_test_utils, ts_test_mqtt ]. tsung-1.5.1/src/test/ts_test_file_server.erl0000644000175000017500000000672412147621622022310 0ustar nniclaussenniclausse%%%------------------------------------------------------------------- %%% 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;sesame">> }, 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)), {Time1, Out } = timer:tc(ts_file_server, read, [[{default,File}]]), erlang:display([?CSVSIZE," read_file:", Time1]), ?assertMatch(ok, Out). config_file_server_huge_get_random_test()-> {Time2, Out } = timer:tc( lists, foreach, [ fun(_)-> ts_file_server:get_random_line() end,lists:seq(1,?CSVSIZE)]), erlang:display([?CSVSIZE," get all lines (random):", Time2]), ?assertMatch(ok, Out ). config_file_server_huge_get_next_test()-> {Time2, Out } = timer:tc( lists, foreach, [ fun(_)-> ts_file_server:get_next_line() end,lists:seq(1,?CSVSIZE)]), erlang:display([?CSVSIZE," get all lines:", Time2]), ?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")). split_test()-> ?assertEqual([<<"username3" >>, <<"glop4">>, <<>>], ts_utils:split(<< "username3;glop4;">>, <<";">>)). split2_test()-> ?assertEqual([<< >>], ts_utils:split(<< "">>, <<";">>)). myset_env()-> myset_env(0). myset_env(V)-> application:set_env(stdlib,file_server_timeout,30000), application:set_env(stdlib,debug_level,V), application:set_env(stdlib,thinktime_override,"false"), application:set_env(stdlib,thinktime_random,"false"). tsung-1.5.1/src/lib/0000755000175000017500000000000012321173206015302 5ustar nniclaussenniclaussetsung-1.5.1/src/lib/mqtt_frame.erl0000644000175000017500000003264612236145741020170 0ustar nniclaussenniclausse%% The MIT License (MIT) %% %% Copyright (c) <2013> %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal %% in the Software without restriction, including without limitation the rights %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %% copies of the Software, and to permit persons to whom the Software is %% furnished to do so, subject to the following conditions: %% %% The above copyright notice and this permission notice shall be included in %% all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %% THE SOFTWARE. %% Modified from: https://code.google.com/p/my-mqtt4erl/source/browse/src/mqtt_core.erl. -module(mqtt_frame). -author('hellomatty@gmail.com'). %% %% An erlang client for MQTT (http://www.mqtt.org/) %% -include_lib("mqtt.hrl"). -export([encode/1, decode/1]). -export([set_connect_options/1, set_publish_options/1, command_for_type/1]). %%%=================================================================== %%% API functions %%%=================================================================== encode(#mqtt{} = Message) -> {VariableHeader, Payload} = encode_message(Message), FixedHeader = encode_fixed_header(Message), EncodedLength = encode_length(size(VariableHeader) + size(Payload)), <>. decode(<>) -> {RemainingLength, Rest1} = decode_length(Rest), Size = size(Rest1), if Size >= RemainingLength -> <> = Rest1, {decode_message(decode_fixed_header(<>), Body), Left}; true -> more end; decode(_Data) -> more. set_connect_options(Options) -> set_connect_options(Options, #connect_options{}). set_publish_options(Options) -> set_publish_options(Options, #publish_options{}). command_for_type(Type) -> case Type of ?CONNECT -> connect; ?CONNACK -> connack; ?PUBLISH -> publish; ?PUBACK -> puback; ?PUBREC -> pubrec; ?PUBREL -> pubrel; ?PUBCOMP -> pubcomp; ?SUBSCRIBE -> subscribe; ?SUBACK -> suback; ?UNSUBSCRIBE -> unsubscribe; ?UNSUBACK -> unsuback; ?PINGREQ -> pingreq; ?PINGRESP -> pingresp; ?DISCONNECT -> disconnect; _ -> unknown end. %%%=================================================================== %%% Internal functions %%%=================================================================== set_connect_options([], Options) -> Options; set_connect_options([{keepalive, KeepAlive}|T], Options) -> set_connect_options(T, Options#connect_options{keepalive = KeepAlive}); set_connect_options([{retry, Retry}|T], Options) -> set_connect_options(T, Options#connect_options{retry = Retry}); set_connect_options([{client_id, ClientId}|T], Options) -> set_connect_options(T, Options#connect_options{client_id = ClientId}); set_connect_options([{clean_start, Flag}|T], Options) -> set_connect_options(T, Options#connect_options{clean_start = Flag}); set_connect_options([{connect_timeout, Timeout}|T], Options) -> set_connect_options(T, Options#connect_options{connect_timeout = Timeout}); set_connect_options([{username, UserName}|T], Options) -> set_connect_options(T, Options#connect_options{username = UserName}); set_connect_options([{password, Password}|T], Options) -> set_connect_options(T, Options#connect_options{password = Password}); set_connect_options([#will{} = Will|T], Options) -> set_connect_options(T, Options#connect_options{will = Will}); set_connect_options([UnknownOption|_T], _Options) -> exit({connect, unknown_option, UnknownOption}). set_publish_options([], Options) -> Options; set_publish_options([{qos, QoS}|T], Options) when QoS >= 0, QoS =< 2 -> set_publish_options(T, Options#publish_options{qos = QoS}); set_publish_options([{retain, true}|T], Options) -> set_publish_options(T, Options#publish_options{retain = 1}); set_publish_options([{retain, false}|T], Options) -> set_publish_options(T, Options#publish_options{retain = 0}); set_publish_options([UnknownOption|_T], _Options) -> exit({unknown, publish_option, UnknownOption}). construct_will(WT, WM, WillQoS, WillRetain) -> #will{ topic = WT, message = WM, publish_options = #publish_options{qos = WillQoS, retain = WillRetain} }. decode_message(#mqtt{type = ?CONNECT} = Message, Rest) -> <> = Rest, {VariableHeader, Payload} = split_binary(Rest, 2 + ProtocolNameLength + 4), <<_:16, ProtocolName:ProtocolNameLength/binary, ProtocolVersion:8/big, UsernameFlag:1, PasswordFlag:1, WillRetain:1, WillQoS:2/big, WillFlag:1, CleanStart:1, _:1, KeepAlive:16/big>> = VariableHeader, {ClientId, Will, Username, Password} = case {WillFlag, UsernameFlag, PasswordFlag} of {1, 0, 0} -> [C, WT, WM] = decode_strings(Payload), W = construct_will(WT, WM, WillQoS, WillRetain), {C, W, undefined, undefined}; {1, 1, 0} -> [C, WT, WM, U] = decode_strings(Payload), W = construct_will(WT, WM, WillQoS, WillRetain), {C, W, U, undefined}; {1, 1, 1} -> [C, WT, WM, U, P] = decode_strings(Payload), W = construct_will(WT, WM, WillQoS, WillRetain), {C, W, U, P}; {0, 1, 0} -> [C, U] = decode_strings(Payload), {C, undefined, U, undefined}; {0, 1, 1} -> [C, U, P] = decode_strings(Payload), {C, undefined, U, P}; {0, 0, 0} -> [C] = decode_strings(Payload), {C, undefined, undefined, undefined} end, Message#mqtt{ arg = #connect_options{ client_id = ClientId, protocol_name = binary_to_list(ProtocolName), protocol_version = ProtocolVersion, clean_start = CleanStart =:= 1, will = Will, username = Username, password = Password, keepalive = KeepAlive } }; decode_message(#mqtt{type = ?CONNACK} = Message, Rest) -> <<_:8, ResponseCode:8/big>> = Rest, Message#mqtt{arg = ResponseCode}; decode_message(#mqtt{type = ?PINGRESP} = Message, _Rest) -> Message; decode_message(#mqtt{type = ?PINGREQ} = Message, _Rest) -> Message; decode_message(#mqtt{type = ?PUBLISH, qos = 0} = Message, Rest) -> {<>, _} = split_binary(Rest, 2), {<<_:16, Topic/binary>>, Payload} = split_binary(Rest, 2 + TopicLength), Message#mqtt{ arg = {binary_to_list(Topic), binary_to_list(Payload)} }; decode_message(#mqtt{type = ?PUBLISH} = Message, Rest) -> {<>, _} = split_binary(Rest, 2), {<<_:16, Topic:TopicLength/binary, MessageId:16/big>>, Payload} = split_binary(Rest, 4 + TopicLength), Message#mqtt{ id = MessageId, arg = {binary_to_list(Topic), binary_to_list(Payload)} }; decode_message(#mqtt{type = Type} = Message, Rest) when Type =:= ?PUBACK; Type =:= ?PUBREC; Type =:= ?PUBREL; Type =:= ?PUBCOMP -> <> = Rest, Message#mqtt{ arg = MessageId }; decode_message(#mqtt{type = ?SUBSCRIBE} = Message, Rest) -> {<>, Payload} = split_binary(Rest, 2), Message#mqtt{ id = MessageId, arg = decode_subs(Payload, []) }; decode_message(#mqtt{type = ?SUBACK} = Message, Rest) -> {<>, Payload} = split_binary(Rest, 2), GrantedQoS = lists:map(fun(Item) -> <<_:6, QoS:2/big>> = <>, QoS end, binary_to_list(Payload) ), Message#mqtt{ arg = {MessageId, GrantedQoS} }; decode_message(#mqtt{type = ?UNSUBSCRIBE} = Message, Rest) -> {<>, Payload} = split_binary(Rest, 2), Message#mqtt{ id = MessageId, arg = {MessageId, lists:map(fun(T) -> #sub{topic = T} end, decode_strings(Payload))} }; decode_message(#mqtt{type = ?UNSUBACK} = Message, Rest) -> <> = Rest, Message#mqtt{ arg = MessageId }; decode_message(#mqtt{type = ?DISCONNECT} = Message, _Rest) -> Message; decode_message(Message, Rest) -> exit({decode_message, unexpected_message, {Message, Rest}}). decode_subs(<<>>, Subs) -> lists:reverse(Subs); decode_subs(Bytes, Subs) -> <> = Bytes, <<_:16, Topic:TopicLength/binary, ?UNUSED:6, QoS:2/big, Rest/binary>> = Bytes, decode_subs(Rest, [#sub{topic = binary_to_list(Topic), qos = QoS}|Subs]). encode_message(#mqtt{type = ?CONNACK, arg = ReturnCode}) -> {<>,<<>>}; encode_message(#mqtt{type = ?CONNECT, arg = Options}) -> CleanStart = case Options#connect_options.clean_start of true -> 1; false -> 0 end, {UserNameFlag, UserNameValue} = case Options#connect_options.username of undefined -> {0, undefined}; UserName -> {1, UserName} end, {PasswordFlag, PasswordValue} = case Options#connect_options.password of undefined -> {0, undefined}; Password -> {1, Password} end, {WillFlag, WillQoS, WillRetain, PayloadList} = case Options#connect_options.will of {will, WillTopic, WillMessage, WillOptions} -> { 1, WillOptions#publish_options.qos, WillOptions#publish_options.retain, [encode_string(Options#connect_options.client_id), encode_string(WillTopic), encode_string(WillMessage)] }; undefined -> {0, 0, 0, [encode_string(Options#connect_options.client_id)]} end, Payload1 = case UserNameValue of undefined -> list_to_binary(PayloadList); _ -> case PasswordValue of undefined -> list_to_binary(lists:append(PayloadList, [encode_string(UserNameValue)])); _ -> list_to_binary(lists:append(PayloadList, [encode_string(UserNameValue), encode_string(PasswordValue)])) end end, { list_to_binary([ encode_string(Options#connect_options.protocol_name), <<(Options#connect_options.protocol_version)/big>>, <> ]), Payload1 }; encode_message(#mqtt{type = ?PUBLISH, arg = {Topic, Payload}} = Message) -> if Message#mqtt.qos =:= 0 -> { encode_string(Topic), list_to_binary(Payload) }; Message#mqtt.qos > 0 -> { list_to_binary([encode_string(Topic), <<(Message#mqtt.id):16/big>>]), list_to_binary(Payload) } end; encode_message(#mqtt{type = ?PUBACK, arg = MessageId}) -> { <>, <<>> }; encode_message(#mqtt{type = ?SUBSCRIBE, arg = Subs} = Message) -> { <<(Message#mqtt.id):16/big>>, list_to_binary( lists:flatten( lists:map(fun({sub, Topic, RequestedQoS}) -> [encode_string(Topic), <>] end, Subs))) }; encode_message(#mqtt{type = ?SUBACK, arg = {MessageId, Subs}}) -> { <>, list_to_binary(lists:map(fun(S) -> <> end, Subs)) }; encode_message(#mqtt{type = ?UNSUBSCRIBE, arg = Subs} = Message) -> { <<(Message#mqtt.id):16/big>>, list_to_binary(lists:map(fun({sub, T, _Q}) -> encode_string(T) end, Subs)) }; encode_message(#mqtt{type = ?UNSUBACK, arg = MessageId}) -> {<>, <<>>}; encode_message(#mqtt{type = ?PINGREQ}) -> {<<>>, <<>>}; encode_message(#mqtt{type = ?PINGRESP}) -> {<<>>, <<>>}; encode_message(#mqtt{type = ?PUBREC, arg = MessageId}) -> {<>, <<>>}; encode_message(#mqtt{type = ?PUBREL, arg = MessageId}) -> {<>, <<>>}; encode_message(#mqtt{type = ?PUBCOMP, arg = MessageId}) -> {<>, <<>>}; encode_message(#mqtt{type = ?DISCONNECT}) -> {<<>>, <<>>}; encode_message(#mqtt{} = Message) -> exit({encode_message, unknown_type, Message}). decode_length(Data) -> decode_length(Data, 1, 0). decode_length(<<0:1, Length:7, Rest/binary>>, Multiplier, Value) -> {Value + Multiplier * Length, Rest}; decode_length(<<1:1, Length:7, Rest/binary>>, Multiplier, Value) -> decode_length(Rest, Multiplier * 128, Value + Multiplier * Length). encode_length(Length) -> encode_length(Length, <<>>). encode_length(Length, Buff) when Length div 128 > 0 -> Digit = Length rem 128, Current = <<1:1, Digit:7/big>>, encode_length(Length div 128, <>); encode_length(Length, Buff) -> Digit = Length rem 128, Current = <<0:1, Digit:7/big>>, <>. encode_fixed_header(Message) when is_record(Message, mqtt) -> <<(Message#mqtt.type):4/big, (Message#mqtt.dup):1, (Message#mqtt.qos):2/big, (Message#mqtt.retain):1>>. decode_fixed_header(Byte) -> <> = Byte, #mqtt{type = Type, dup = Dup, qos = QoS, retain = Retain}. encode_string(String) -> Bytes = list_to_binary(String), Length = size(Bytes), <>. decode_strings(Bytes) when is_binary(Bytes) -> decode_strings(Bytes, []). decode_strings(<<>>, Strings) -> lists:reverse(Strings); decode_strings(<> = Bytes, Strings) -> <<_:16, Binary:Length/binary, Rest/binary>> = Bytes, decode_strings(Rest, [binary_to_list(Binary)|Strings]). tsung-1.5.1/src/lib/mochiweb_xpath.erl0000644000175000017500000004511012301350731021006 0ustar nniclaussenniclausse%% 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]). -export_type([xpath_return/0, html_node/0]). -export_type([xpath_fun_spec/0, xpath_fun/0, xpath_func_argspec/0, xpath_func_context/0]). -export_type([indexed_xpath_return/0, indexed_html_node/0]). % internal!!! %internal data -record(ctx, { root :: indexed_html_node(), ctx :: [indexed_html_node()], functions :: [xpath_fun_spec()], position :: integer(), size :: integer() }). %% HTML tree specs -type html_comment() :: {comment, binary()}. %% -type html_doctype() :: {doctype, [binary()]}. -type html_pi() :: {pi, binary(), binary()} | {pi, binary()}. -type html_attr() :: {binary(), binary()}. -type html_node() :: {binary(), [html_attr()], [html_node() | binary() | html_comment() | html_pi()]}. -type indexed_html_node() :: {binary(), [html_attr()], [indexed_html_node() | binary() | html_comment() | html_pi()], [non_neg_integer()]}. %% XPath return results specs -type xpath_return_item() :: boolean() | number() | binary() | html_node(). -type xpath_return() :: boolean() | number() | [xpath_return_item()]. -type indexed_return_item() :: boolean() | number() | binary() | indexed_html_node(). -type indexed_xpath_return() :: boolean() | number() | [indexed_return_item()]. -type compiled_xpath() :: tuple(). %% XPath functions specs -type xpath_func_context() :: #ctx{}. -type xpath_type() :: node_set | string | number | boolean. -type xpath_func_argspec() :: [xpath_type() | {'*', xpath_type()}]. -type xpath_fun() :: fun((FuncCtx :: xpath_func_context(), FuncArgs :: indexed_xpath_return()) -> FuncReturn :: indexed_xpath_return()). -type xpath_fun_spec() :: {atom(), xpath_fun(), xpath_func_argspec()}. %% %% API %% -spec compile_xpath( 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() -spec execute(XPath, Doc) -> Results when XPath :: compiled_xpath() | string(), Doc :: html_node(), Results :: xpath_return(). 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 -spec execute(XPath, Doc, Functions) -> Result when XPath :: string() | compiled_xpath(), Doc :: html_node(), Functions :: [xpath_fun_spec()], Result :: xpath_return(). execute(XPathString,Doc,Functions) when is_list(XPathString) -> XPath = mochiweb_xpath_parser:compile_xpath(XPathString), execute(XPath,Doc,Functions); execute(XPath,Doc,Functions) -> R0 = {<<0>>,[],[Doc]}, %% TODO: set parent instead of positions list, or some lazy-positioning? R1 = add_positions(R0), Result = execute_expr(XPath,#ctx{ctx=[R1], root=R1, functions=Functions, position=0}), remove_positions(Result). %% %% XPath tree traversing, top-level XPath interpreter %% %% xmerl_xpath:match_expr/2 execute_expr({path, Type, Arg}, S) -> eval_path(Type, Arg, S); execute_expr(PrimExpr, S) -> eval_primary_expr(PrimExpr, S). eval_path(union, {PathExpr1, PathExpr2}, C) -> %% in XPath 1.0 union doesn't necessary must return nodes in document %% order (but must in XPath 2.0) S1 = execute_expr(PathExpr1, C), S2 = execute_expr(PathExpr2, C), ordsets:to_list(ordsets:union(ordsets:from_list(S1), ordsets:from_list(S2))); eval_path(abs, Path ,Ctx = #ctx{root=Root}) -> do_path_expr(Path, Ctx#ctx{ctx=[Root]}); eval_path(rel, Path, Ctx) -> do_path_expr(Path, Ctx); eval_path(filter, {_PathExpr, {pred, _Pred}}, _C) -> throw({not_implemented, "filter"}). % Who needs them? eval_primary_expr({comp,Comp,A,B},Ctx) -> %% for predicates CompFun = comp_fun(Comp), L = execute_expr(A,Ctx), R = execute_expr(B,Ctx), comp(CompFun,L,R); eval_primary_expr({arith, Op, Arg1, Arg2}, Ctx) -> %% for predicates L = execute_expr(Arg1,Ctx), R = execute_expr(Arg2,Ctx), arith(Op, L, R); eval_primary_expr({bool,Comp,A,B},Ctx) -> CompFun = bool_fun(Comp), L = execute_expr(A,Ctx), R = execute_expr(B,Ctx), comp(CompFun,L,R); eval_primary_expr({literal,L},_Ctx) -> [L]; eval_primary_expr({number,N},_Ctx) -> [N]; eval_primary_expr({negative, A}, Ctx) -> R = execute_expr(A, Ctx), [-mochiweb_xpath_utils:number_value(R)]; eval_primary_expr({function_call, Fun, Args}, Ctx=#ctx{functions=Funs}) -> %% TODO: refactor double-case case mochiweb_xpath_functions:lookup_function(Fun) of {Fun, F, FormalSignature} -> call_xpath_function(F, Args, FormalSignature, Ctx); false -> case lists:keysearch(Fun,1,Funs) of {value, {Fun, F, FormalSignature}} -> call_xpath_function(F, Args, FormalSignature, Ctx); false -> throw({efun_not_found, Fun}) end end. call_xpath_function(F, Args, FormalSignature, Ctx) -> TypedArgs = prepare_xpath_function_args(Args, FormalSignature, Ctx), F(Ctx, TypedArgs). %% execute function args expressions and convert them using formal %% signatures prepare_xpath_function_args(Args, Specs, Ctx) -> RealArgs = [execute_expr(Arg, Ctx) || Arg <- Args], convert_xpath_function_args(RealArgs, Specs, []). convert_xpath_function_args([], [], Acc) -> lists:reverse(Acc); convert_xpath_function_args(Args, [{'*', Spec}], Acc) -> NewArgs = [mochiweb_xpath_utils:convert(Arg,Spec) || Arg <- Args], lists:reverse(Acc) ++ NewArgs; convert_xpath_function_args([Arg | Args], [Spec | Specs], Acc) -> NewAcc = [mochiweb_xpath_utils:convert(Arg,Spec) | Acc], convert_xpath_function_args(Args, Specs, NewAcc). do_path_expr({step,{Axis,NodeTest,Predicates}}=_S,Ctx=#ctx{}) -> NewNodeList = axis(Axis, NodeTest, Ctx), 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}). %% %% Axes %% %% TODO: port all axes to use test_node/3 axis('self', NodeTest, #ctx{ctx=Context}) -> [N || N <- Context, test_node(NodeTest, N, Context)]; axis('descendant', NodeTest, #ctx{ctx=Context}) -> [N || {_,_,Children,_} <- Context, N <- descendant_or_self(Children, NodeTest, [], Context)]; axis('descendant_or_self', NodeTest, #ctx{ctx=Context}) -> descendant_or_self(Context, NodeTest, [], Context); axis('child', NodeTest, #ctx{ctx=Context}) -> %% Flat list of all child nodes of Context that pass NodeTest [N || {_,_,Children,_} <- Context, N <- Children, test_node(NodeTest, N, Context)]; axis('parent', NodeTest, #ctx{root=Root, ctx=Context}) -> L = lists:foldl( fun({_,_,_,Position}, Acc) -> ParentPosition = get_parent_position(Position), ParentNode = get_node_at(Root, ParentPosition), maybe_add_node(ParentNode, NodeTest, Acc, Context); (Smth, _Acc) -> throw({not_implemented, "parent for non-nodes", Smth}) end, [], Context), ordsets:to_list(ordsets:from_list(lists:reverse(L))); axis('ancestor', _Test, _Ctx) -> throw({not_implemented, "ancestor axis"}); axis('following_sibling', NodeTest, #ctx{root=Root, ctx=Context}) -> %% TODO: alerts for non-elements (like for `text()/parent::`) [N || {_,_,_,Position} <- Context, N <- begin ParentPosition = get_parent_position(Position), MyPosition = get_position_in_parent(Position), {_,_,Children,_} = get_node_at(Root, ParentPosition), lists:sublist(Children, MyPosition + 1, length(Children) - MyPosition) end, test_node(NodeTest, N, Context)]; axis('preceding_sibling', NodeTest, #ctx{root=Root, ctx=Context}) -> %% TODO: alerts for non-elements (like for `text()/parent::`) [N || {_,_,_,Position} <- Context, N <- begin ParentPosition = get_parent_position(Position), MyPosition = get_position_in_parent(Position), {_,_,Children,_} = get_node_at(Root, ParentPosition), lists:sublist(Children, MyPosition - 1) end, test_node(NodeTest, N, Context)]; axis('following', _Test, _Ctx) -> throw({not_implemented, "following axis"}); axis('preceeding', _Test, _Ctx) -> throw({not_implemented, "preceeding axis"}); axis('attribute', NodeTest, #ctx{ctx=Context}) -> %% Flat list of *attribute values* of Context, that pass NodeTest %% TODO: maybe return attribute {Name, Value} will be better then %% value only? [Value || {_,Attributes,_,_} <- Context, {_Name, Value} = A <- Attributes, test_node(NodeTest, A, Context)]; axis('namespace', _Test, _Ctx) -> throw({not_implemented, "namespace axis"}); axis('ancestor_or_self', _Test, _Ctx) -> throw({not_implemented, "ancestor-or-self axis"}). descendant_or_self(Nodes, NodeTest, Acc, Ctx) -> lists:reverse(do_descendant_or_self(Nodes, NodeTest, Acc, Ctx)). do_descendant_or_self([], _, Acc, _) -> Acc; do_descendant_or_self([Node = {_, _, Children, _} | Rest], NodeTest, Acc, Ctx) -> %% depth-first (document order) NewAcc1 = maybe_add_node(Node, NodeTest, Acc, Ctx), NewAcc2 = do_descendant_or_self(Children, NodeTest, NewAcc1, Ctx), do_descendant_or_self(Rest, NodeTest, NewAcc2, Ctx); do_descendant_or_self([_Smth | Rest], NodeTest, Acc, Ctx) -> %% NewAcc = maybe_add_node(Smth, NodeTest, Acc, Ctx), - no attribs or texts do_descendant_or_self(Rest, NodeTest, Acc, Ctx). %% Except text nodes test_node({wildcard, wildcard}, Element, _Ctx) when not is_binary(Element) -> true; test_node({prefix_test, Prefix}, {Tag, _, _, _}, _Ctx) -> test_ns_prefix(Tag, Prefix); test_node({prefix_test, Prefix}, {AttrName, _}, _Ctx) -> test_ns_prefix(AttrName, Prefix); test_node({name, {Tag, _, _}}, {Tag, _, _, _}, _Ctx) -> true; test_node({name, {AttrName, _, _}}, {AttrName, _}, _Ctx) -> %% XXX: check this! true; test_node({node_type, text}, Text, _Ctx) when is_binary(Text) -> true; test_node({node_type, node}, {_, _, _, _}, _Ctx) -> true; test_node({node_type, node}, Text, _Ctx) when is_binary(Text) -> true; test_node({node_type, node}, {_, _}, _Ctx) -> true; %% test_node({node_type, attribute}, {_, _}, _Ctx) -> %% true; [38] - attribute() not exists! test_node({node_type, comment}, {comment, _}, _Ctx) -> true; test_node({node_type, processing_instruction}, {pi, _}, _Ctx) -> true; test_node({processing_instruction, Name}, {pi, Node}, _Ctx) -> NSize = size(Name), case Node of <> -> true; _ -> false end; test_node(_Other, _N, _Ctx) -> false. test_ns_prefix(Name, Prefix) -> PSize = size(Prefix), case Name of <> -> true; _ -> false end. %% Append Node to Acc only when NodeTest passed maybe_add_node(Node, NodeTest, Acc, Ctx) -> case test_node(NodeTest, Node, Ctx) of true -> [Node | Acc]; false -> Acc end. %% used for predicate indexing %% is_reverse_axis(ancestor) -> %% true; %% is_reverse_axis(ancestor_or_self) -> %% true; %% is_reverse_axis(preceding) -> %% true; %% is_reverse_axis(preceding_sibling) -> %% true; %% is_reverse_axis(_) -> %% flase. %% %% Predicates %% apply_predicates(Predicates,NodeList,Ctx) -> lists:foldl(fun({pred, Pred} ,Nodes) -> apply_predicate(Pred,Nodes,Ctx) end, NodeList,Predicates). % special case: indexing apply_predicate({number,N}, NodeList, _Ctx) when length(NodeList) >= N -> [lists:nth(N,NodeList)]; apply_predicate(Pred, NodeList,Ctx) -> Size = length(NodeList), Filter = fun(Node, {AccPosition, AccNodes0}) -> Predicate = mochiweb_xpath_utils:boolean_value( execute_expr(Pred,Ctx#ctx{ctx=[Node], position=AccPosition, size = Size})), AccNodes1 = if Predicate -> [Node|AccNodes0]; true -> AccNodes0 end, {AccPosition+1, AccNodes1} end, {_, L} = lists:foldl(Filter,{1,[]},NodeList), lists:reverse(L). %% %% Compare functions %% %% @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). -spec comp_fun(atom()) -> fun((indexed_xpath_return(), indexed_xpath_return()) -> boolean()). 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. %% %% Boolean functions %% bool_fun('and') -> fun(A, B) -> mochiweb_xpath_utils:boolean_value(A) andalso mochiweb_xpath_utils:boolean_value(B) end; bool_fun('or') -> fun(A, B) -> mochiweb_xpath_utils:boolean_value(A) orelse mochiweb_xpath_utils:boolean_value(B) end. %% TODO more boolean operators %% %% Arithmetic functions %% -spec arith(atom(), indexed_xpath_return(), indexed_xpath_return()) -> number(). arith('+', Arg1, Arg2) -> mochiweb_xpath_utils:number_value(Arg1) + mochiweb_xpath_utils:number_value(Arg2); arith('-', Arg1, Arg2) -> mochiweb_xpath_utils:number_value(Arg1) - mochiweb_xpath_utils:number_value(Arg2); arith('*', Arg1, Arg2) -> mochiweb_xpath_utils:number_value(Arg1) * mochiweb_xpath_utils:number_value(Arg2); arith('div', Arg1, Arg2) -> mochiweb_xpath_utils:number_value(Arg1) / mochiweb_xpath_utils:number_value(Arg2); arith('mod', Arg1, Arg2) -> mochiweb_xpath_utils:number_value(Arg1) rem mochiweb_xpath_utils:number_value(Arg2). %% %% Helpers %% %% @doc Add a position to each node %% @spec add_positions(Doc) -> ExtendedDoc %% @type ExtendedDoc = {atom(), [{binary(), any()}], [extended_node()], [non_neg_integer()]} -spec add_positions(html_node()) -> indexed_html_node(). add_positions(Node) -> R = add_positions_aux(Node, []), R. add_positions_aux({Tag,Attrs,Children}, Position) -> {_, NewChildren} = lists:foldl(fun(Child, {Count, AccChildren}) -> NewChild = add_positions_aux(Child, [Count | Position]), {Count+1, [NewChild|AccChildren]} end, {1, []}, Children), {Tag, Attrs, lists:reverse(NewChildren), Position}; add_positions_aux(Data, _) -> Data. %% @doc Remove position from each node %% @spec remove_positions(ExtendedDoc) -> Doc %% @type ExtendedDoc = {atom(), [{binary(), any()}], [extended_node()], [non_neg_integer()]} -spec remove_positions(indexed_xpath_return()) -> xpath_return(). remove_positions(Nodes) when is_list(Nodes) -> [ remove_positions(SubNode) || SubNode <- Nodes ]; remove_positions({Tag, Attrs, Children, _}) -> {Tag, Attrs, remove_positions(Children)}; remove_positions(Data) -> Data. %% @doc Get node according to a position relative to root node %% @spec get_node_at(ExtendedDoc, Position) -> ExtendedDoc %% @type Position = [non_neg_integer()] %% @type ExtendedDoc = {atom(), [{binary(), any()}], [extended_node()], [non_neg_integer()]} get_node_at(Node, Position) -> get_node_at_aux(Node, lists:reverse(Position)). get_node_at_aux(Node, []) -> Node; get_node_at_aux({_,_,Children,_}, [Pos|Next]) -> get_node_at_aux(lists:nth(Pos, Children), Next). %% @doc Get parent position %% @spec get_parent_position(Position) -> Position %% @type Position = [non_neg_integer()] get_parent_position([_|ParentPosition]) -> ParentPosition. %% @doc Get position relative to my parent %% @spec get_self_position(Position) -> non_neg_integer() %% @type Position = [non_neg_integer()] get_position_in_parent([MyPosition|_]) -> MyPosition. tsung-1.5.1/src/lib/rabbit_binary_parser.erl0000644000175000017500000000736112147621622022206 0ustar nniclaussenniclausse%% The contents of this file are subject to the Mozilla Public License %% Version 1.1 (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License %% at http://www.mozilla.org/MPL/ %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and %% limitations under the License. %% %% The Original Code is RabbitMQ. %% %% The Initial Developer of the Original Code is VMware, Inc. %% Copyright (c) 2007-2012 VMware, Inc. All rights reserved. %% -module(rabbit_binary_parser). -include("rabbit.hrl"). -export([parse_table/1]). -export([ensure_content_decoded/1, clear_decoded_content/1]). %%---------------------------------------------------------------------------- -ifdef(use_specs). -spec(parse_table/1 :: (binary()) -> rabbit_framing:amqp_table()). -spec(ensure_content_decoded/1 :: (rabbit_types:content()) -> rabbit_types:decoded_content()). -spec(clear_decoded_content/1 :: (rabbit_types:content()) -> rabbit_types:undecoded_content()). -endif. %%---------------------------------------------------------------------------- %% parse_table supports the AMQP 0-8/0-9 standard types, S, I, D, T %% and F, as well as the QPid extensions b, d, f, l, s, t, x, and V. parse_table(<<>>) -> []; parse_table(<>) -> {Type, Value, Rest} = parse_field_value(ValueAndRest), [{NameString, Type, Value} | parse_table(Rest)]. parse_array(<<>>) -> []; parse_array(<>) -> {Type, Value, Rest} = parse_field_value(ValueAndRest), [{Type, Value} | parse_array(Rest)]. parse_field_value(<<"S", VLen:32/unsigned, V:VLen/binary, R/binary>>) -> {longstr, V, R}; parse_field_value(<<"I", V:32/signed, R/binary>>) -> {signedint, V, R}; parse_field_value(<<"D", Before:8/unsigned, After:32/unsigned, R/binary>>) -> {decimal, {Before, After}, R}; parse_field_value(<<"T", V:64/unsigned, R/binary>>) -> {timestamp, V, R}; parse_field_value(<<"F", VLen:32/unsigned, Table:VLen/binary, R/binary>>) -> {table, parse_table(Table), R}; parse_field_value(<<"A", VLen:32/unsigned, Array:VLen/binary, R/binary>>) -> {array, parse_array(Array), R}; parse_field_value(<<"b", V:8/unsigned, R/binary>>) -> {byte, V, R}; parse_field_value(<<"d", V:64/float, R/binary>>) -> {double, V, R}; parse_field_value(<<"f", V:32/float, R/binary>>) -> {float, V, R}; parse_field_value(<<"l", V:64/signed, R/binary>>) -> {long, V, R}; parse_field_value(<<"s", V:16/signed, R/binary>>) -> {short, V, R}; parse_field_value(<<"t", V:8/unsigned, R/binary>>) -> {bool, (V /= 0), R}; parse_field_value(<<"x", VLen:32/unsigned, V:VLen/binary, R/binary>>) -> {binary, V, R}; parse_field_value(<<"V", R/binary>>) -> {void, undefined, R}. ensure_content_decoded(Content = #content{properties = Props}) when Props =/= none -> Content; ensure_content_decoded(Content = #content{properties_bin = PropBin, protocol = Protocol}) when PropBin =/= none -> Content#content{properties = Protocol:decode_properties( Content#content.class_id, PropBin)}. clear_decoded_content(Content = #content{properties = none}) -> Content; clear_decoded_content(Content = #content{properties_bin = none}) -> %% Only clear when we can rebuild the properties later in %% accordance to the content record definition comment - maximum %% one of properties and properties_bin can be 'none' Content; clear_decoded_content(Content = #content{}) -> Content#content{properties = none}. tsung-1.5.1/src/lib/websocket.erl0000644000175000017500000001715212236145741020012 0ustar nniclaussenniclausse%%% This code was developped by Zhihui Jiao(jzhihui521@gmail.com). %%% %%% Copyright (C) 2013 Zhihui Jiao %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(websocket). -vc('$Id$ '). -author('jzhihui521@gmail.com'). -export([get_handshake/4, check_handshake/2, encode_binary/1, encode_text/1, encode_close/1, encode/2, decode/1]). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("ts_websocket.hrl"). %%%=================================================================== %%% API functions %%%=================================================================== get_handshake(Host, Path, SubProtocol, Version) -> {Key, Accept} = gen_accept_key(), Req = list_to_binary(["GET ", Path, " HTTP/1.1\r\n", "Host: ", Host ,"\r\n", "Upgrade: websocket\r\n", "Connection: Upgrade\r\n", "Sec-WebSocket-Key: ", Key, "\r\n", "Origin: http://", Host, "\r\n", "Sec-WebSocket-Version: ", Version, "\r\n"]), SubProHeader = case SubProtocol of [] -> []; _ -> "Sec-WebSocket-Protocol: " ++ SubProtocol ++ "\r\n" end, Handshake = list_to_binary([Req, SubProHeader, "\r\n" ]), {Handshake, Accept}. check_handshake(Response, Accept) -> ?DebugF("check handshake, response is : ~p~n",[Response]), [HeaderPart, _] = binary:split(Response, <<"\r\n\r\n">>), [StatusLine | Headers] = binary:split(HeaderPart, <<"\r\n">>, [global, trim]), Map = dict:new(), {Prefix, _} = split_binary(StatusLine, 12), [Version, Code] = binary:split(Prefix, <<" ">>), Map1 = dict:store("version", string:to_lower(binary_to_list(Version)), Map), Map2 = dict:store("status", binary_to_list(Code), Map1), MapFun = fun(HeaderLine, Acc) -> [Header, Value] = binary:split(HeaderLine, <<": ">>), HeaderStr = string:to_lower(binary_to_list(Header)), ValueStr = case HeaderStr of "sec-websocket-accept" -> binary_to_list(Value); _ -> string:to_lower(binary_to_list(Value)) end, dict:store(HeaderStr, ValueStr, Acc) end, HeaderMap = lists:foldl(MapFun, Map2, Headers), RequiredHeaders = [{"Version", "HTTP/1.1"}, {"Status", "101"}, {"Upgrade", "websocket"}, {"Connection", "Upgrade"}, {"Sec-WebSocket-Accept", Accept}], lists:foldl(fun(_, Acc = {error, _}) -> Acc; ({Key, Value}, ok) -> TargetKey = string:to_lower(Key), TargetValue = case TargetKey of "sec-websocket-accept" -> Value; _ -> string:to_lower(Value) end, case dict:is_key(TargetKey, HeaderMap) of true -> case dict:find(TargetKey, HeaderMap) of {ok, TargetValue} -> ok; {ok, Other} -> {error, {mismatch, Key, Value, Other}} end; _ -> {error, {miss_headers, Key}} end end, ok, RequiredHeaders). encode_binary(Data) -> encode(Data, ?OP_BIN). encode_text(Data) -> encode(Data, ?OP_TEXT). encode_close(Reason) -> %% According RFC6455, we shoud add a status code for close frame, %% check here: http://tools.ietf.org/html/rfc6455#section-7.4, %% we add a normal closure status code 1000 here. StatusCode = <<3, 232>>, Data = <>, encode(Data, ?OP_CLOSE). encode(Data, Opcode) -> Key = crypto:rand_bytes(4), PayloadLen = erlang:size(Data), MaskedData = mask(Data, Key), Length = if PayloadLen < 126 -> <>; PayloadLen < 65536 -> <<126:7, PayloadLen:16>>; true -> <<127:7, PayloadLen:64>> end, <<1:1, 0:3, Opcode:4, 1:1, Length/bitstring, Key/binary, MaskedData/bitstring>>. decode(Data) -> parse_frame(Data). %%%=================================================================== %%% Internal functions %%%=================================================================== gen_accept_key() -> random:seed(erlang:now()), Key = crypto:rand_bytes(16), KeyStr = base64:encode_to_string(Key), Accept = binary:list_to_bin(KeyStr ++ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"), AcceptStr = base64:encode_to_string(crypto:sha(Accept)), {KeyStr, AcceptStr}. %%%=================================================================== %% NOTE: The code of the following two functions are borrowed from %% https://github.com/wulczer/tsung_ws/blob/master/src/tsung/ts_websocket.erl. % mask(binary, binary) -> binary % % Mask the given payload using a 4 byte masking key. mask(Payload, MaskingKey) -> % create a mask with the same length as the payload by repeating % the masking key Div = size(Payload) div size(MaskingKey), Rem = size(Payload) rem size(MaskingKey), LongPart = binary:copy(MaskingKey, Div), Rest = binary:part(MaskingKey, {0, Rem}), Mask = << LongPart/bitstring, Rest/bitstring >>, % xor the payload and the mask crypto:exor(Payload, Mask). % parse_payload(integer, binary) -> {integer, binary, binary} | more % % Try to parse out a frame payload from binary data. Gets passed an % opcode and returns a tuple of opcode, payload and remaining data. If % not enough data is available, return a more atom. parse_payload(Opcode, << 0:1, % MASK Length:7, Payload:Length/binary, Rest/bitstring >>) when Length < 126 -> {Opcode, Payload, Rest}; parse_payload(Opcode, << 0:1, % MASK 126:7, Length:16, Payload:Length/binary, Rest/bitstring >>) when Length < 65536 -> {Opcode, Payload, Rest}; parse_payload(Opcode, << 0:1, % MASK 127:7, 0:1, Length:63, Payload:Length/binary, Rest/bitstring >>) -> {Opcode, Payload, Rest}; parse_payload(_Opcode, _Data) -> more. % parse_frame(binary) -> {integer, binary, binary} | more % % Try to parse out a WebSocket frame from binary data. Returns a tuple % of opcode, payload and remaining data or a more atom if not enough % data is available. parse_frame(<< 1:1, % FIN 0:3, % RSV Opcode:4, % OPCODE MaskLengthAndPayload/bitstring >>) -> parse_payload(Opcode, MaskLengthAndPayload); parse_frame(_Data) -> more. tsung-1.5.1/src/lib/oauth_hmac_sha1.erl0000644000175000017500000000303312104023217021024 0ustar nniclaussenniclausse%% Copyright (c) 2008-2009 Tim Fletcher %% %% Permission is hereby granted, free of charge, to any person %% obtaining a copy of this software and associated documentation %% files (the "Software"), to deal in the Software without %% restriction, including without limitation the rights to use, %% copy, modify, merge, publish, distribute, sublicense, and/or sell %% copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following %% conditions: %% %% The above copyright notice and this permission notice shall be %% included in all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR %% OTHER DEALINGS IN THE SOFTWARE. -module(oauth_hmac_sha1). -export([signature/3, verify/4]). -spec signature(string(), string(), string()) -> string(). signature(BaseString, CS, TS) -> Key = oauth_uri:calate("&", [CS, TS]), base64:encode_to_string(crypto:sha_mac(Key, BaseString)). -spec verify(string(), string(), string(), string()) -> boolean(). verify(Signature, BaseString, CS, TS) -> Signature =:= signature(BaseString, CS, TS). tsung-1.5.1/src/lib/mochiweb_xpath_utils.erl0000644000175000017500000000472512301350731022235 0ustar nniclaussenniclausse%% 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]). -spec string_value(mochiweb_xpath:indexed_xpath_return()) -> binary(). string_value(N) when is_list(N)-> case N of [X|_] -> string_value(X); [] -> <<>> end; string_value({_,_,Contents,_}) -> %% Node L = lists:filter(fun ({_,_,_,_}) ->false; (B) when is_binary(B) -> true end,Contents), list_to_binary(L); string_value({_Name, Value}) -> %% attribute Value; 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)); string_value(Expr) -> %% string_value(mochiweb_xpath:execute_expr(Expr, Ctx)). throw({not_implemented, "String from expression", Expr}). -spec node_set_value(mochiweb_xpath:indexed_xpath_return()) -> [mochiweb_xpath:indexed_html_node()]. node_set_value(List) when is_list(List) -> List; node_set_value(N) -> throw({node_set_expected,N}). -spec number_value(mochiweb_xpath:indexed_xpath_return() | binary()) -> number(). number_value(N) when is_integer(N) or is_float(N) -> N; number_value({number, N}) when is_integer(N) or is_float(N) -> N; number_value({negative, Exp}) -> N = number_value(Exp), - 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)). -spec boolean_value(mochiweb_xpath:indexed_xpath_return()) -> boolean(). 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 is_boolean(B) -> B; boolean_value({_, _, _Contents, _}) -> true; % TODO: rly? boolean_value(_Expr) -> throw({not_implemented, "Boolean from expression"}). convert(Value,number) -> number_value(Value); convert(Value,string) -> string_value(Value); convert(Value,node_set) -> node_set_value(Value); convert(Value, boolean) -> boolean_value(Value). tsung-1.5.1/src/lib/pgsql_util.erl0000644000175000017500000001754612104023217020201 0ustar nniclaussenniclausse%%% 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.5.1/src/lib/mochiweb_util.erl0000644000175000017500000003637612104023217020652 0ustar nniclaussenniclausse%% @author Bob Ippolito %% @copyright 2007 Mochi Media, Inc. %% @doc Utilities for parsing and quoting. -module(mochiweb_util). -author('bob@mochimedia.com'). -export([join/2, quote_plus/1, urlencode/1, parse_qs/1, unquote/1]). -export([path_split/1]). -export([urlsplit/1, urlsplit_path/1, urlunsplit/1, urlunsplit_path/1]). -export([guess_mime/1, parse_header/1]). -export([shell_quote/1, cmd/1, cmd_string/1, cmd_port/2]). -export([record_to_proplist/2, record_to_proplist/3]). -export([test/0]). -define(PERCENT, 37). % $\% -define(FULLSTOP, 46). % $\. -define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse (C >= $a andalso C =< $f) orelse (C >= $A andalso C =< $F))). -define(QS_SAFE(C), ((C >= $a andalso C =< $z) orelse (C >= $A andalso C =< $Z) orelse (C >= $0 andalso C =< $9) orelse (C =:= ?FULLSTOP orelse C =:= $- orelse C =:= $~ orelse C =:= $_))). hexdigit(C) when C < 10 -> $0 + C; hexdigit(C) when C < 16 -> $A + (C - 10). unhexdigit(C) when C >= $0, C =< $9 -> C - $0; unhexdigit(C) when C >= $a, C =< $f -> C - $a + 10; unhexdigit(C) when C >= $A, C =< $F -> C - $A + 10. %% @spec shell_quote(string()) -> string() %% @doc Quote a string according to UNIX shell quoting rules, returns a string %% surrounded by double quotes. shell_quote(L) -> shell_quote(L, [$\"]). %% @spec cmd_port([string()], Options) -> port() %% @doc open_port({spawn, mochiweb_util:cmd_string(Argv)}, Options). cmd_port(Argv, Options) -> open_port({spawn, cmd_string(Argv)}, Options). %% @spec cmd([string()]) -> string() %% @doc os:cmd(cmd_string(Argv)). cmd(Argv) -> os:cmd(cmd_string(Argv)). %% @spec cmd_string([string()]) -> string() %% @doc Create a shell quoted command string from a list of arguments. cmd_string(Argv) -> join([shell_quote(X) || X <- Argv], " "). %% @spec join([string()], Separator) -> string() %% @doc Join a list of strings together with the given separator %% string or char. join([], _Separator) -> []; join([S], _Separator) -> lists:flatten(S); join(Strings, Separator) -> lists:flatten(revjoin(lists:reverse(Strings), Separator, [])). revjoin([], _Separator, Acc) -> Acc; revjoin([S | Rest], Separator, []) -> revjoin(Rest, Separator, [S]); revjoin([S | Rest], Separator, Acc) -> revjoin(Rest, Separator, [S, Separator | Acc]). %% @spec quote_plus(atom() | integer() | string()) -> string() %% @doc URL safe encoding of the given term. quote_plus(Atom) when is_atom(Atom) -> quote_plus(atom_to_list(Atom)); quote_plus(Int) when is_integer(Int) -> quote_plus(integer_to_list(Int)); quote_plus(String) -> quote_plus(String, []). quote_plus([], Acc) -> lists:reverse(Acc); quote_plus([C | Rest], Acc) when ?QS_SAFE(C) -> quote_plus(Rest, [C | Acc]); quote_plus([$\s | Rest], Acc) -> quote_plus(Rest, [$+ | Acc]); quote_plus([C | Rest], Acc) -> <> = <>, quote_plus(Rest, [hexdigit(Lo), hexdigit(Hi), ?PERCENT | Acc]). %% @spec urlencode([{Key, Value}]) -> string() %% @doc URL encode the property list. urlencode(Props) -> RevPairs = lists:foldl(fun ({K, V}, Acc) -> [[quote_plus(K), $=, quote_plus(V)] | Acc] end, [], Props), lists:flatten(revjoin(RevPairs, $&, [])). %% @spec parse_qs(string() | binary()) -> [{Key, Value}] %% @doc Parse a query string or application/x-www-form-urlencoded. parse_qs(Binary) when is_binary(Binary) -> parse_qs(binary_to_list(Binary)); parse_qs(String) -> parse_qs(String, []). parse_qs([], Acc) -> lists:reverse(Acc); parse_qs(String, Acc) -> {Key, Rest} = parse_qs_key(String), {Value, Rest1} = parse_qs_value(Rest), parse_qs(Rest1, [{Key, Value} | Acc]). parse_qs_key(String) -> parse_qs_key(String, []). parse_qs_key([], Acc) -> {qs_revdecode(Acc), ""}; parse_qs_key([$= | Rest], Acc) -> {qs_revdecode(Acc), Rest}; parse_qs_key(Rest=[$; | _], Acc) -> {qs_revdecode(Acc), Rest}; parse_qs_key(Rest=[$& | _], Acc) -> {qs_revdecode(Acc), Rest}; parse_qs_key([C | Rest], Acc) -> parse_qs_key(Rest, [C | Acc]). parse_qs_value(String) -> parse_qs_value(String, []). parse_qs_value([], Acc) -> {qs_revdecode(Acc), ""}; parse_qs_value([$; | Rest], Acc) -> {qs_revdecode(Acc), Rest}; parse_qs_value([$& | Rest], Acc) -> {qs_revdecode(Acc), Rest}; parse_qs_value([C | Rest], Acc) -> parse_qs_value(Rest, [C | Acc]). %% @spec unquote(string() | binary()) -> string() %% @doc Unquote a URL encoded string. unquote(Binary) when is_binary(Binary) -> unquote(binary_to_list(Binary)); unquote(String) -> qs_revdecode(lists:reverse(String)). qs_revdecode(S) -> qs_revdecode(S, []). qs_revdecode([], Acc) -> Acc; qs_revdecode([$+ | Rest], Acc) -> qs_revdecode(Rest, [$\s | Acc]); qs_revdecode([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) -> qs_revdecode(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]); qs_revdecode([C | Rest], Acc) -> qs_revdecode(Rest, [C | Acc]). %% @spec urlsplit(Url) -> {Scheme, Netloc, Path, Query, Fragment} %% @doc Return a 5-tuple, does not expand % escapes. Only supports HTTP style %% URLs. urlsplit(Url) -> {Scheme, Url1} = urlsplit_scheme(Url), {Netloc, Url2} = urlsplit_netloc(Url1), {Path, Query, Fragment} = urlsplit_path(Url2), {Scheme, Netloc, Path, Query, Fragment}. urlsplit_scheme(Url) -> urlsplit_scheme(Url, []). urlsplit_scheme([], Acc) -> {"", lists:reverse(Acc)}; urlsplit_scheme(":" ++ Rest, Acc) -> {string:to_lower(lists:reverse(Acc)), Rest}; urlsplit_scheme([C | Rest], Acc) -> urlsplit_scheme(Rest, [C | Acc]). urlsplit_netloc("//" ++ Rest) -> urlsplit_netloc(Rest, []); urlsplit_netloc(Path) -> {"", Path}. urlsplit_netloc(Rest=[C | _], Acc) when C =:= $/; C =:= $?; C =:= $# -> {lists:reverse(Acc), Rest}; urlsplit_netloc([C | Rest], Acc) -> urlsplit_netloc(Rest, [C | Acc]). %% @spec path_split(string()) -> {Part, Rest} %% @doc Split a path starting from the left, as in URL traversal. %% path_split("foo/bar") = {"foo", "bar"}, %% path_split("/foo/bar") = {"", "foo/bar"}. path_split(S) -> path_split(S, []). path_split("", Acc) -> {lists:reverse(Acc), ""}; path_split("/" ++ Rest, Acc) -> {lists:reverse(Acc), Rest}; path_split([C | Rest], Acc) -> path_split(Rest, [C | Acc]). %% @spec urlunsplit({Scheme, Netloc, Path, Query, Fragment}) -> string() %% @doc Assemble a URL from the 5-tuple. Path must be absolute. urlunsplit({Scheme, Netloc, Path, Query, Fragment}) -> lists:flatten([case Scheme of "" -> ""; _ -> [Scheme, "://"] end, Netloc, urlunsplit_path({Path, Query, Fragment})]). %% @spec urlunsplit_path({Path, Query, Fragment}) -> string() %% @doc Assemble a URL path from the 3-tuple. urlunsplit_path({Path, Query, Fragment}) -> lists:flatten([Path, case Query of "" -> ""; _ -> [$? | Query] end, case Fragment of "" -> ""; _ -> [$# | Fragment] end]). %% @spec urlsplit_path(Url) -> {Path, Query, Fragment} %% @doc Return a 3-tuple, does not expand % escapes. Only supports HTTP style %% paths. urlsplit_path(Path) -> urlsplit_path(Path, []). urlsplit_path("", Acc) -> {lists:reverse(Acc), "", ""}; urlsplit_path("?" ++ Rest, Acc) -> {Query, Fragment} = urlsplit_query(Rest), {lists:reverse(Acc), Query, Fragment}; urlsplit_path("#" ++ Rest, Acc) -> {lists:reverse(Acc), "", Rest}; urlsplit_path([C | Rest], Acc) -> urlsplit_path(Rest, [C | Acc]). urlsplit_query(Query) -> urlsplit_query(Query, []). urlsplit_query("", Acc) -> {lists:reverse(Acc), ""}; urlsplit_query("#" ++ Rest, Acc) -> {lists:reverse(Acc), Rest}; urlsplit_query([C | Rest], Acc) -> urlsplit_query(Rest, [C | Acc]). %% @spec guess_mime(string()) -> string() %% @doc Guess the mime type of a file by the extension of its filename. guess_mime(File) -> case filename:extension(File) of ".html" -> "text/html"; ".xhtml" -> "application/xhtml+xml"; ".xml" -> "application/xml"; ".css" -> "text/css"; ".js" -> "application/x-javascript"; ".jpg" -> "image/jpeg"; ".gif" -> "image/gif"; ".png" -> "image/png"; ".swf" -> "application/x-shockwave-flash"; ".zip" -> "application/zip"; ".bz2" -> "application/x-bzip2"; ".gz" -> "application/x-gzip"; ".tar" -> "application/x-tar"; ".tgz" -> "application/x-gzip"; ".txt" -> "text/plain"; ".doc" -> "application/msword"; ".pdf" -> "application/pdf"; ".xls" -> "application/vnd.ms-excel"; ".rtf" -> "application/rtf"; ".mov" -> "video/quicktime"; ".mp3" -> "audio/mpeg"; ".z" -> "application/x-compress"; ".wav" -> "audio/x-wav"; ".ico" -> "image/x-icon"; ".bmp" -> "image/bmp"; ".m4a" -> "audio/mpeg"; ".m3u" -> "audio/x-mpegurl"; ".exe" -> "application/octet-stream"; ".csv" -> "text/csv"; _ -> "text/plain" end. %% @spec parse_header(string()) -> {Type, [{K, V}]} %% @doc Parse a Content-Type like header, return the main Content-Type %% and a property list of options. parse_header(String) -> %% TODO: This is exactly as broken as Python's cgi module. %% Should parse properly like mochiweb_cookies. [Type | Parts] = [string:strip(S) || S <- string:tokens(String, ";")], F = fun (S, Acc) -> case lists:splitwith(fun (C) -> C =/= $= end, S) of {"", _} -> %% Skip anything with no name Acc; {_, ""} -> %% Skip anything with no value Acc; {Name, [$\= | Value]} -> [{string:to_lower(string:strip(Name)), unquote_header(string:strip(Value))} | Acc] end end, {string:to_lower(Type), lists:foldr(F, [], Parts)}. unquote_header("\"" ++ Rest) -> unquote_header(Rest, []); unquote_header(S) -> S. unquote_header("", Acc) -> lists:reverse(Acc); unquote_header("\"", Acc) -> lists:reverse(Acc); unquote_header([$\\, C | Rest], Acc) -> unquote_header(Rest, [C | Acc]); unquote_header([C | Rest], Acc) -> unquote_header(Rest, [C | Acc]). %% @spec record_to_proplist(Record, Fields) -> proplist() %% @doc calls record_to_proplist/3 with a default TypeKey of '__record' record_to_proplist(Record, Fields) -> record_to_proplist(Record, Fields, '__record'). %% @spec record_to_proplist(Record, Fields, TypeKey) -> proplist() %% @doc Return a proplist of the given Record with each field in the %% Fields list set as a key with the corresponding value in the Record. %% TypeKey is the key that is used to store the record type %% Fields should be obtained by calling record_info(fields, record_type) %% where record_type is the record type of Record record_to_proplist(Record, Fields, TypeKey) when is_tuple(Record), is_list(Fields), size(Record) - 1 =:= length(Fields) -> lists:zip([TypeKey | Fields], tuple_to_list(Record)). shell_quote([], Acc) -> lists:reverse([$\" | Acc]); shell_quote([C | Rest], Acc) when C =:= $\" orelse C =:= $\` orelse C =:= $\\ orelse C =:= $\$ -> shell_quote(Rest, [C, $\\ | Acc]); shell_quote([C | Rest], Acc) -> shell_quote(Rest, [C | Acc]). test() -> test_join(), test_quote_plus(), test_unquote(), test_urlencode(), test_parse_qs(), test_urlsplit_path(), test_urlunsplit_path(), test_urlsplit(), test_urlunsplit(), test_path_split(), test_guess_mime(), test_parse_header(), test_shell_quote(), test_cmd(), test_cmd_string(), ok. test_shell_quote() -> "\"foo \\$bar\\\"\\`' baz\"" = shell_quote("foo $bar\"`' baz"), ok. test_cmd() -> "$bling$ `word`!\n" = cmd(["echo", "$bling$ `word`!"]), ok. test_cmd_string() -> "\"echo\" \"\\$bling\\$ \\`word\\`!\"" = cmd_string(["echo", "$bling$ `word`!"]), ok. test_parse_header() -> {"multipart/form-data", [{"boundary", "AaB03x"}]} = parse_header("multipart/form-data; boundary=AaB03x"), ok. test_guess_mime() -> "text/plain" = guess_mime(""), "text/plain" = guess_mime(".text"), "application/zip" = guess_mime(".zip"), "application/zip" = guess_mime("x.zip"), "text/html" = guess_mime("x.html"), "application/xhtml+xml" = guess_mime("x.xhtml"), ok. test_path_split() -> {"", "foo/bar"} = path_split("/foo/bar"), {"foo", "bar"} = path_split("foo/bar"), {"bar", ""} = path_split("bar"), ok. test_urlsplit() -> {"", "", "/foo", "", "bar?baz"} = urlsplit("/foo#bar?baz"), {"http", "host:port", "/foo", "", "bar?baz"} = urlsplit("http://host:port/foo#bar?baz"), ok. test_urlsplit_path() -> {"/foo/bar", "", ""} = urlsplit_path("/foo/bar"), {"/foo", "baz", ""} = urlsplit_path("/foo?baz"), {"/foo", "", "bar?baz"} = urlsplit_path("/foo#bar?baz"), {"/foo", "", "bar?baz#wibble"} = urlsplit_path("/foo#bar?baz#wibble"), {"/foo", "bar", "baz"} = urlsplit_path("/foo?bar#baz"), {"/foo", "bar?baz", "baz"} = urlsplit_path("/foo?bar?baz#baz"), ok. test_urlunsplit() -> "/foo#bar?baz" = urlunsplit({"", "", "/foo", "", "bar?baz"}), "http://host:port/foo#bar?baz" = urlunsplit({"http", "host:port", "/foo", "", "bar?baz"}), ok. test_urlunsplit_path() -> "/foo/bar" = urlunsplit_path({"/foo/bar", "", ""}), "/foo?baz" = urlunsplit_path({"/foo", "baz", ""}), "/foo#bar?baz" = urlunsplit_path({"/foo", "", "bar?baz"}), "/foo#bar?baz#wibble" = urlunsplit_path({"/foo", "", "bar?baz#wibble"}), "/foo?bar#baz" = urlunsplit_path({"/foo", "bar", "baz"}), "/foo?bar?baz#baz" = urlunsplit_path({"/foo", "bar?baz", "baz"}), ok. test_join() -> "foo,bar,baz" = join(["foo", "bar", "baz"], $,), "foo,bar,baz" = join(["foo", "bar", "baz"], ","), "foo bar" = join([["foo", " bar"]], ","), "foo bar,baz" = join([["foo", " bar"], "baz"], ","), "foo" = join(["foo"], ","), "foobarbaz" = join(["foo", "bar", "baz"], ""), ok. test_quote_plus() -> "foo" = quote_plus(foo), "1" = quote_plus(1), "foo" = quote_plus("foo"), "foo+bar" = quote_plus("foo bar"), "foo%0A" = quote_plus("foo\n"), "foo%0A" = quote_plus("foo\n"), "foo%3B%26%3D" = quote_plus("foo;&="), ok. test_unquote() -> "foo bar" = unquote("foo+bar"), "foo bar" = unquote("foo%20bar"), "foo\r\n" = unquote("foo%0D%0A"), ok. test_urlencode() -> "foo=bar&baz=wibble+%0D%0A&z=1" = urlencode([{foo, "bar"}, {"baz", "wibble \r\n"}, {z, 1}]), ok. test_parse_qs() -> [{"foo", "bar"}, {"baz", "wibble \r\n"}, {"z", "1"}] = parse_qs("foo=bar&baz=wibble+%0D%0A&z=1"), ok. tsung-1.5.1/src/lib/oauth_unix.erl0000644000175000017500000000266712104023217020177 0ustar nniclaussenniclausse%% Copyright (c) 2008-2009 Tim Fletcher %% %% Permission is hereby granted, free of charge, to any person %% obtaining a copy of this software and associated documentation %% files (the "Software"), to deal in the Software without %% restriction, including without limitation the rights to use, %% copy, modify, merge, publish, distribute, sublicense, and/or sell %% copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following %% conditions: %% %% The above copyright notice and this permission notice shall be %% included in all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR %% OTHER DEALINGS IN THE SOFTWARE. -module(oauth_unix). -export([timestamp/0]). -spec timestamp() -> integer(). timestamp() -> timestamp(calendar:universal_time()). timestamp(DateTime) -> seconds(DateTime) - epoch(). epoch() -> seconds({{1970,1,1},{00,00,00}}). seconds(DateTime) -> calendar:datetime_to_gregorian_seconds(DateTime). tsung-1.5.1/src/lib/oauth_uri.erl0000644000175000017500000001050512104023217020001 0ustar nniclaussenniclausse%% Copyright (c) 2008-2009 Tim Fletcher %% %% Permission is hereby granted, free of charge, to any person %% obtaining a copy of this software and associated documentation %% files (the "Software"), to deal in the Software without %% restriction, including without limitation the rights to use, %% copy, modify, merge, publish, distribute, sublicense, and/or sell %% copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following %% conditions: %% %% The above copyright notice and this permission notice shall be %% included in all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR %% OTHER DEALINGS IN THE SOFTWARE. -module(oauth_uri). -export([normalize/1, calate/2, encode/1]). -export([params_from_string/1, params_to_string/1, params_from_header_string/1, params_to_header_string/1]). -import(lists, [concat/1]). -spec normalize(iolist()) -> iolist(). normalize(URI) -> case http_uri:parse(URI) of {ok, {Scheme, UserInfo, Host, Port, Path, _Query}} -> normalize(Scheme, UserInfo, string:to_lower(Host), Port, [Path]); Else -> Else end. normalize(http, UserInfo, Host, 80, Acc) -> normalize(http, UserInfo, [Host|Acc]); normalize(https, UserInfo, Host, 443, Acc) -> normalize(https, UserInfo, [Host|Acc]); normalize(Scheme, UserInfo, Host, Port, Acc) -> normalize(Scheme, UserInfo, [Host, ":", Port|Acc]). normalize(Scheme, [], Acc) -> concat([Scheme, "://"|Acc]); normalize(Scheme, UserInfo, Acc) -> concat([Scheme, "://", UserInfo, "@"|Acc]). -spec params_to_header_string([{string(), string()}]) -> string(). params_to_header_string(Params) -> intercalate(", ", [concat([encode(K), "=\"", encode(V), "\""]) || {K, V} <- Params]). -spec params_from_header_string(string()) -> [{string(), string()}]. params_from_header_string(String) -> [param_from_header_string(Param) || Param <- re:split(String, ",\\s*", [{return, list}])]. param_from_header_string(Param) -> [Key|Rest] = string:tokens(Param, "="), QuotedValue = string:join(Rest,"="), Value = string:substr(QuotedValue, 2, length(QuotedValue) - 2), {decode(Key), decode(Value)}. -spec params_from_string(string()) -> [{string(), string()}]. params_from_string(Params) -> [param_from_string(Param) || Param <- string:tokens(Params, "&")]. param_from_string(Param) -> list_to_tuple([decode(Value) || Value <- string:tokens(Param, "=")]). -spec params_to_string([{string(), string()}]) -> string(). params_to_string(Params) -> intercalate("&", [calate("=", [K, V]) || {K, V} <- Params]). -spec calate(string(), [string()]) -> string(). calate(Sep, Xs) -> intercalate(Sep, [encode(X) || X <- Xs]). intercalate(Sep, Xs) -> concat(intersperse(Sep, Xs)). intersperse(_, []) -> []; intersperse(_, [X]) -> [X]; intersperse(Sep, [X|Xs]) -> [X, Sep|intersperse(Sep, Xs)]. -define(is_alphanum(C), C >= $A, C =< $Z; C >= $a, C =< $z; C >= $0, C =< $9). -spec encode(integer() | atom() | string()) -> string(). encode(Term) when is_integer(Term) -> integer_to_list(Term); encode(Term) when is_atom(Term) -> encode(atom_to_list(Term)); encode(Term) when is_list(Term) -> encode(lists:reverse(Term, []), []). encode([X | T], Acc) when ?is_alphanum(X); X =:= $-; X =:= $_; X =:= $.; X =:= $~ -> encode(T, [X | Acc]); encode([X | T], Acc) -> NewAcc = [$%, dec2hex(X bsr 4), dec2hex(X band 16#0f) | Acc], encode(T, NewAcc); encode([], Acc) -> Acc. decode(Str) when is_list(Str) -> decode(Str, []). decode([$%, A, B | T], Acc) -> decode(T, [(hex2dec(A) bsl 4) + hex2dec(B) | Acc]); decode([X | T], Acc) -> decode(T, [X | Acc]); decode([], Acc) -> lists:reverse(Acc, []). -compile({inline, [{dec2hex, 1}, {hex2dec, 1}]}). dec2hex(N) when N >= 10 andalso N =< 15 -> N + $A - 10; dec2hex(N) when N >= 0 andalso N =< 9 -> N + $0. hex2dec(C) when C >= $A andalso C =< $F -> C - $A + 10; hex2dec(C) when C >= $0 andalso C =< $9 -> C - $0. tsung-1.5.1/src/lib/rabbit_binary_generator.erl0000644000175000017500000002532712147621622022702 0ustar nniclaussenniclausse%% The contents of this file are subject to the Mozilla Public License %% Version 1.1 (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License %% at http://www.mozilla.org/MPL/ %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and %% limitations under the License. %% %% The Original Code is RabbitMQ. %% %% The Initial Developer of the Original Code is VMware, Inc. %% Copyright (c) 2007-2012 VMware, Inc. All rights reserved. %% -module(rabbit_binary_generator). -include("rabbit_framing.hrl"). -include("rabbit.hrl"). -export([build_simple_method_frame/3, build_simple_content_frames/4, build_heartbeat_frame/0]). -export([generate_table/1]). -export([check_empty_frame_size/0]). -export([ensure_content_encoded/2, clear_encoded_content/1]). -export([map_exception/3]). %%---------------------------------------------------------------------------- -ifdef(use_specs). -type(frame() :: [binary()]). -spec(build_simple_method_frame/3 :: (rabbit_channel:channel_number(), rabbit_framing:amqp_method_record(), rabbit_types:protocol()) -> frame()). -spec(build_simple_content_frames/4 :: (rabbit_channel:channel_number(), rabbit_types:content(), non_neg_integer(), rabbit_types:protocol()) -> [frame()]). -spec(build_heartbeat_frame/0 :: () -> frame()). -spec(generate_table/1 :: (rabbit_framing:amqp_table()) -> binary()). -spec(check_empty_frame_size/0 :: () -> 'ok'). -spec(ensure_content_encoded/2 :: (rabbit_types:content(), rabbit_types:protocol()) -> rabbit_types:encoded_content()). -spec(clear_encoded_content/1 :: (rabbit_types:content()) -> rabbit_types:unencoded_content()). -spec(map_exception/3 :: (rabbit_channel:channel_number(), rabbit_types:amqp_error() | any(), rabbit_types:protocol()) -> {rabbit_channel:channel_number(), rabbit_framing:amqp_method_record()}). -endif. %%---------------------------------------------------------------------------- build_simple_method_frame(ChannelInt, MethodRecord, Protocol) -> MethodFields = Protocol:encode_method_fields(MethodRecord), MethodName = rabbit_misc:method_record_type(MethodRecord), {ClassId, MethodId} = Protocol:method_id(MethodName), create_frame(1, ChannelInt, [<>, MethodFields]). build_simple_content_frames(ChannelInt, Content, FrameMax, Protocol) -> #content{class_id = ClassId, properties_bin = ContentPropertiesBin, payload_fragments_rev = PayloadFragmentsRev} = ensure_content_encoded(Content, Protocol), {BodySize, ContentFrames} = build_content_frames(PayloadFragmentsRev, FrameMax, ChannelInt), HeaderFrame = create_frame(2, ChannelInt, [<>, ContentPropertiesBin]), [HeaderFrame | ContentFrames]. build_content_frames(FragsRev, FrameMax, ChannelInt) -> BodyPayloadMax = if FrameMax == 0 -> iolist_size(FragsRev); true -> FrameMax - ?EMPTY_FRAME_SIZE end, build_content_frames(0, [], BodyPayloadMax, [], lists:reverse(FragsRev), BodyPayloadMax, ChannelInt). build_content_frames(SizeAcc, FramesAcc, _FragSizeRem, [], [], _BodyPayloadMax, _ChannelInt) -> {SizeAcc, lists:reverse(FramesAcc)}; build_content_frames(SizeAcc, FramesAcc, FragSizeRem, FragAcc, Frags, BodyPayloadMax, ChannelInt) when FragSizeRem == 0 orelse Frags == [] -> Frame = create_frame(3, ChannelInt, lists:reverse(FragAcc)), FrameSize = BodyPayloadMax - FragSizeRem, build_content_frames(SizeAcc + FrameSize, [Frame | FramesAcc], BodyPayloadMax, [], Frags, BodyPayloadMax, ChannelInt); build_content_frames(SizeAcc, FramesAcc, FragSizeRem, FragAcc, [Frag | Frags], BodyPayloadMax, ChannelInt) -> Size = size(Frag), {NewFragSizeRem, NewFragAcc, NewFrags} = if Size == 0 -> {FragSizeRem, FragAcc, Frags}; Size =< FragSizeRem -> {FragSizeRem - Size, [Frag | FragAcc], Frags}; true -> <> = Frag, {0, [Head | FragAcc], [Tail | Frags]} end, build_content_frames(SizeAcc, FramesAcc, NewFragSizeRem, NewFragAcc, NewFrags, BodyPayloadMax, ChannelInt). build_heartbeat_frame() -> create_frame(?FRAME_HEARTBEAT, 0, <<>>). create_frame(TypeInt, ChannelInt, Payload) -> [<>, Payload, ?FRAME_END]. %% table_field_to_binary supports the AMQP 0-8/0-9 standard types, S, %% I, D, T and F, as well as the QPid extensions b, d, f, l, s, t, x, %% and V. table_field_to_binary({FName, T, V}) -> [short_string_to_binary(FName) | field_value_to_binary(T, V)]. field_value_to_binary(longstr, V) -> ["S", long_string_to_binary(V)]; field_value_to_binary(signedint, V) -> ["I", <>]; field_value_to_binary(decimal, V) -> {Before, After} = V, ["D", Before, <>]; field_value_to_binary(timestamp, V) -> ["T", <>]; field_value_to_binary(table, V) -> ["F", table_to_binary(V)]; field_value_to_binary(array, V) -> ["A", array_to_binary(V)]; field_value_to_binary(byte, V) -> ["b", <>]; field_value_to_binary(double, V) -> ["d", <>]; field_value_to_binary(float, V) -> ["f", <>]; field_value_to_binary(long, V) -> ["l", <>]; field_value_to_binary(short, V) -> ["s", <>]; field_value_to_binary(bool, V) -> ["t", if V -> 1; true -> 0 end]; field_value_to_binary(binary, V) -> ["x", long_string_to_binary(V)]; field_value_to_binary(void, _V) -> ["V"]. table_to_binary(Table) when is_list(Table) -> BinTable = generate_table(Table), [<<(size(BinTable)):32>>, BinTable]. array_to_binary(Array) when is_list(Array) -> BinArray = generate_array(Array), [<<(size(BinArray)):32>>, BinArray]. generate_table(Table) when is_list(Table) -> list_to_binary(lists:map(fun table_field_to_binary/1, Table)). generate_array(Array) when is_list(Array) -> list_to_binary(lists:map(fun ({T, V}) -> field_value_to_binary(T, V) end, Array)). short_string_to_binary(String) when is_binary(String) -> Len = size(String), if Len < 256 -> [<>, String]; true -> exit(content_properties_shortstr_overflow) end; short_string_to_binary(String) -> Len = length(String), if Len < 256 -> [<>, String]; true -> exit(content_properties_shortstr_overflow) end. long_string_to_binary(String) when is_binary(String) -> [<<(size(String)):32>>, String]; long_string_to_binary(String) -> [<<(length(String)):32>>, String]. check_empty_frame_size() -> %% Intended to ensure that EMPTY_FRAME_SIZE is defined correctly. case iolist_size(create_frame(?FRAME_BODY, 0, <<>>)) of ?EMPTY_FRAME_SIZE -> ok; ComputedSize -> exit({incorrect_empty_frame_size, ComputedSize, ?EMPTY_FRAME_SIZE}) end. ensure_content_encoded(Content = #content{properties_bin = PropBin, protocol = Protocol}, Protocol) when PropBin =/= none -> Content; ensure_content_encoded(Content = #content{properties = none, properties_bin = PropBin, protocol = Protocol}, Protocol1) when PropBin =/= none -> Props = Protocol:decode_properties(Content#content.class_id, PropBin), Content#content{properties = Props, properties_bin = Protocol1:encode_properties(Props), protocol = Protocol1}; ensure_content_encoded(Content = #content{properties = Props}, Protocol) when Props =/= none -> Content#content{properties_bin = Protocol:encode_properties(Props), protocol = Protocol}. clear_encoded_content(Content = #content{properties_bin = none, protocol = none}) -> Content; clear_encoded_content(Content = #content{properties = none}) -> %% Only clear when we can rebuild the properties_bin later in %% accordance to the content record definition comment - maximum %% one of properties and properties_bin can be 'none' Content; clear_encoded_content(Content = #content{}) -> Content#content{properties_bin = none, protocol = none}. %% NB: this function is also used by the Erlang client map_exception(Channel, Reason, Protocol) -> {SuggestedClose, ReplyCode, ReplyText, FailedMethod} = lookup_amqp_exception(Reason, Protocol), {ClassId, MethodId} = case FailedMethod of {_, _} -> FailedMethod; none -> {0, 0}; _ -> Protocol:method_id(FailedMethod) end, case SuggestedClose orelse (Channel == 0) of true -> {0, #'connection.close'{reply_code = ReplyCode, reply_text = ReplyText, class_id = ClassId, method_id = MethodId}}; false -> {Channel, #'channel.close'{reply_code = ReplyCode, reply_text = ReplyText, class_id = ClassId, method_id = MethodId}} end. lookup_amqp_exception(#amqp_error{name = Name, explanation = Expl, method = Method}, Protocol) -> {ShouldClose, Code, Text} = Protocol:lookup_amqp_exception(Name), ExplBin = amqp_exception_explanation(Text, Expl), {ShouldClose, Code, ExplBin, Method}; lookup_amqp_exception(Other, Protocol) -> rabbit_log:warning("Non-AMQP exit reason '~p'~n", [Other]), {ShouldClose, Code, Text} = Protocol:lookup_amqp_exception(internal_error), {ShouldClose, Code, Text, none}. amqp_exception_explanation(Text, Expl) -> ExplBin = list_to_binary(Expl), CompleteTextBin = <>, if size(CompleteTextBin) > 255 -> <>; true -> CompleteTextBin end. tsung-1.5.1/src/lib/mochijson2.erl0000644000175000017500000006360512104023217020066 0ustar nniclaussenniclausse%% @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.5.1/src/lib/eldap.erl0000644000175000017500000010662612104023217017101 0ustar nniclaussenniclausse-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 -> 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.5.1/src/lib/rabbit_command_assembler.erl0000644000175000017500000001350312147621622023014 0ustar nniclaussenniclausse%% The contents of this file are subject to the Mozilla Public License %% Version 1.1 (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License %% at http://www.mozilla.org/MPL/ %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and %% limitations under the License. %% %% The Original Code is RabbitMQ. %% %% The Initial Developer of the Original Code is VMware, Inc. %% Copyright (c) 2007-2012 VMware, Inc. All rights reserved. %% -module(rabbit_command_assembler). -include("rabbit_framing.hrl"). -include("rabbit.hrl"). -export([analyze_frame/3, init/1, process/2]). %%---------------------------------------------------------------------------- %%---------------------------------------------------------------------------- -ifdef(use_specs). -export_type([frame/0]). -type(frame_type() :: ?FRAME_METHOD | ?FRAME_HEADER | ?FRAME_BODY | ?FRAME_OOB_METHOD | ?FRAME_OOB_HEADER | ?FRAME_OOB_BODY | ?FRAME_TRACE | ?FRAME_HEARTBEAT). -type(protocol() :: rabbit_framing:protocol()). -type(method() :: rabbit_framing:amqp_method_record()). -type(class_id() :: rabbit_framing:amqp_class_id()). -type(weight() :: non_neg_integer()). -type(body_size() :: non_neg_integer()). -type(content() :: rabbit_types:undecoded_content()). -type(frame() :: {'method', rabbit_framing:amqp_method_name(), binary()} | {'content_header', class_id(), weight(), body_size(), binary()} | {'content_body', binary()}). -type(state() :: {'method', protocol()} | {'content_header', method(), class_id(), protocol()} | {'content_body', method(), body_size(), class_id(), protocol()}). -spec(analyze_frame/3 :: (frame_type(), binary(), protocol()) -> frame() | 'heartbeat' | 'error'). -spec(init/1 :: (protocol()) -> {ok, state()}). -spec(process/2 :: (frame(), state()) -> {ok, state()} | {ok, method(), state()} | {ok, method(), content(), state()} | {error, rabbit_types:amqp_error()}). -endif. %%-------------------------------------------------------------------- analyze_frame(?FRAME_METHOD, <>, Protocol) -> MethodName = Protocol:lookup_method_name({ClassId, MethodId}), {method, MethodName, MethodFields}; analyze_frame(?FRAME_HEADER, <>, _Protocol) -> {content_header, ClassId, Weight, BodySize, Properties}; analyze_frame(?FRAME_BODY, Body, _Protocol) -> {content_body, Body}; analyze_frame(?FRAME_HEARTBEAT, <<>>, _Protocol) -> heartbeat; analyze_frame(_Type, _Body, _Protocol) -> error. init(Protocol) -> {ok, {method, Protocol}}. process({method, MethodName, FieldsBin}, {method, Protocol}) -> try Method = Protocol:decode_method_fields(MethodName, FieldsBin), case Protocol:method_has_content(MethodName) of true -> {ClassId, _MethodId} = Protocol:method_id(MethodName), {ok, {content_header, Method, ClassId, Protocol}}; false -> {ok, Method, {method, Protocol}} end catch exit:#amqp_error{} = Reason -> {error, Reason} end; process(_Frame, {method, _Protocol}) -> unexpected_frame("expected method frame, " "got non method frame instead", [], none); process({content_header, ClassId, 0, 0, PropertiesBin}, {content_header, Method, ClassId, Protocol}) -> Content = empty_content(ClassId, PropertiesBin, Protocol), {ok, Method, Content, {method, Protocol}}; process({content_header, ClassId, 0, BodySize, PropertiesBin}, {content_header, Method, ClassId, Protocol}) -> Content = empty_content(ClassId, PropertiesBin, Protocol), {ok, {content_body, Method, BodySize, Content, Protocol}}; process({content_header, HeaderClassId, 0, _BodySize, _PropertiesBin}, {content_header, Method, ClassId, _Protocol}) -> unexpected_frame("expected content header for class ~w, " "got one for class ~w instead", [ClassId, HeaderClassId], Method); process(_Frame, {content_header, Method, ClassId, _Protocol}) -> unexpected_frame("expected content header for class ~w, " "got non content header frame instead", [ClassId], Method); process({content_body, FragmentBin}, {content_body, Method, RemainingSize, Content = #content{payload_fragments_rev = Fragments}, Protocol}) -> NewContent = Content#content{ payload_fragments_rev = [FragmentBin | Fragments]}, case RemainingSize - size(FragmentBin) of 0 -> {ok, Method, NewContent, {method, Protocol}}; Sz -> {ok, {content_body, Method, Sz, NewContent, Protocol}} end; process(_Frame, {content_body, Method, _RemainingSize, _Content, _Protocol}) -> unexpected_frame("expected content body, " "got non content body frame instead", [], Method). %%-------------------------------------------------------------------- empty_content(ClassId, PropertiesBin, Protocol) -> #content{class_id = ClassId, properties = none, properties_bin = PropertiesBin, protocol = Protocol, payload_fragments_rev = []}. unexpected_frame(Format, Params, Method) when is_atom(Method) -> {error, rabbit_misc:amqp_error(unexpected_frame, Format, Params, Method)}; unexpected_frame(Format, Params, Method) -> unexpected_frame(Format, Params, rabbit_misc:method_record_type(Method)). tsung-1.5.1/src/lib/rfc4515_parser.erl0000644000175000017500000001207112104023217020447 0ustar nniclaussenniclausse%% 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.5.1/src/lib/oauth.erl0000644000175000017500000001350612301350731017131 0ustar nniclaussenniclausse%% Copyright (c) 2008-2009 Tim Fletcher %% %% Permission is hereby granted, free of charge, to any person %% obtaining a copy of this software and associated documentation %% files (the "Software"), to deal in the Software without %% restriction, including without limitation the rights to use, %% copy, modify, merge, publish, distribute, sublicense, and/or sell %% copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following %% conditions: %% %% The above copyright notice and this permission notice shall be %% included in all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR %% OTHER DEALINGS IN THE SOFTWARE. -module(oauth). -export( [ get/5 , header/1 , post/5 , put/5 , signature/5 , signature_base_string/3 , signed_params/6 , token/1 , token_secret/1 , uri/2 , verify/6 ]). -spec get(string(), [proplists:property()], oauth_client:consumer(), string(), string()) -> {ok, {Status::tuple(), Headers::[{string(), string()}], Body::string()}} | {error, term()}. get(URL, ExtraParams, Consumer, Token, TokenSecret) -> SignedParams = signed_params("GET", URL, ExtraParams, Consumer, Token, TokenSecret), oauth_http:get(uri(URL, SignedParams)). -spec post(string(), [proplists:property()], oauth_client:consumer(), string(), string()) -> {ok, {Status::tuple(), Headers::[{string(), string()}], Body::string()}} | {error, term()}. post(URL, ExtraParams, Consumer, Token, TokenSecret) -> SignedParams = signed_params("POST", URL, ExtraParams, Consumer, Token, TokenSecret), oauth_http:post(URL, oauth_uri:params_to_string(SignedParams)). -spec put(string(), [proplists:property()], oauth_client:consumer(), string(), string()) -> {ok, {Status::tuple(), Headers::[{string(), string()}], Body::string()}} | {error, term()}. put(URL, ExtraParams, Consumer, Token, TokenSecret) -> SignedParams = signed_params("PUT", URL, ExtraParams, Consumer, Token, TokenSecret), oauth_http:put(URL, oauth_uri:params_to_string(SignedParams)). -spec uri(string(), [proplists:property()]) -> string(). uri(Base, []) -> Base; uri(Base, Params) -> lists:concat([Base, "?", oauth_uri:params_to_string(Params)]). -spec header([{string(), string()}]) -> {string(), string()}. header(Params) -> {"Authorization", "OAuth " ++ oauth_uri:params_to_header_string(Params)}. -spec token([proplists:property()]) -> string(). token(Params) -> proplists:get_value("oauth_token", Params). -spec token_secret([proplists:property()]) -> string(). token_secret(Params) -> proplists:get_value("oauth_token_secret", Params). -spec verify(string(), string(), string(), [proplists:property()], oauth_client:consumer(), string()) -> boolean(). verify(Signature, HttpMethod, URL, Params, Consumer, TokenSecret) -> case signature_method(Consumer) of plaintext -> oauth_plaintext:verify(Signature, consumer_secret(Consumer), TokenSecret); hmac_sha1 -> BaseString = signature_base_string(HttpMethod, URL, Params), oauth_hmac_sha1:verify(Signature, BaseString, consumer_secret(Consumer), TokenSecret); rsa_sha1 -> BaseString = signature_base_string(HttpMethod, URL, Params), oauth_rsa_sha1:verify(Signature, BaseString, consumer_secret(Consumer)) end. -spec signed_params(string(), string(), [proplists:property()], oauth_client:consumer(), string(), string()) -> [{string(), string()}]. signed_params(HttpMethod, URL, ExtraParams, Consumer, Token, TokenSecret) -> Params = token_param(Token, params(Consumer, ExtraParams)), [{"oauth_signature", signature(HttpMethod, URL, Params, Consumer, TokenSecret)}|Params]. -spec signature(string(), string(), [proplists:property()], oauth_client:consumer(), string()) -> string(). signature(HttpMethod, URL, Params, Consumer, TokenSecret) -> case signature_method(Consumer) of plaintext -> oauth_plaintext:signature(consumer_secret(Consumer), TokenSecret); hmac_sha1 -> BaseString = signature_base_string(HttpMethod, URL, Params), oauth_hmac_sha1:signature(BaseString, consumer_secret(Consumer), TokenSecret); rsa_sha1 -> BaseString = signature_base_string(HttpMethod, URL, Params), oauth_rsa_sha1:signature(BaseString, consumer_secret(Consumer)) end. -spec signature_base_string(string(), string(), [proplists:property()]) -> string(). signature_base_string(HttpMethod, URL, Params) -> NormalizedURL = oauth_uri:normalize(URL), NormalizedParams = oauth_uri:params_to_string(lists:sort(Params)), oauth_uri:calate("&", [HttpMethod, NormalizedURL, NormalizedParams]). token_param("", Params) -> Params; token_param(Token, Params) -> [{"oauth_token", Token}|Params]. params(Consumer, Params) -> Nonce = base64:encode_to_string(crypto:rand_bytes(32)), % cf. ruby-oauth params(Consumer, oauth_unix:timestamp(), Nonce, Params). params(Consumer, Timestamp, Nonce, Params) -> [ {"oauth_version", "1.0"} , {"oauth_nonce", Nonce} , {"oauth_timestamp", integer_to_list(Timestamp)} , {"oauth_signature_method", signature_method_string(Consumer)} , {"oauth_consumer_key", consumer_key(Consumer)} | Params ]. signature_method_string(Consumer) -> case signature_method(Consumer) of plaintext -> "PLAINTEXT"; hmac_sha1 -> "HMAC-SHA1"; rsa_sha1 -> "RSA-SHA1" end. signature_method(_Consumer={_, _, Method}) -> Method. consumer_secret(_Consumer={_, Secret, _}) -> Secret. consumer_key(_Consumer={Key, _, _}) -> Key. tsung-1.5.1/src/lib/oauth_plaintext.erl0000644000175000017500000000264112104023217021214 0ustar nniclaussenniclausse%% Copyright (c) 2008-2009 Tim Fletcher %% %% Permission is hereby granted, free of charge, to any person %% obtaining a copy of this software and associated documentation %% files (the "Software"), to deal in the Software without %% restriction, including without limitation the rights to use, %% copy, modify, merge, publish, distribute, sublicense, and/or sell %% copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following %% conditions: %% %% The above copyright notice and this permission notice shall be %% included in all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR %% OTHER DEALINGS IN THE SOFTWARE. -module(oauth_plaintext). -export([signature/2, verify/3]). -spec signature(string(), string()) -> string(). signature(CS, TS) -> oauth_uri:calate("&", [CS, TS]). -spec verify(string(), string(), string()) -> boolean(). verify(Signature, CS, TS) -> Signature =:= signature(CS, TS). tsung-1.5.1/src/lib/pgsql_proto.erl0000644000175000017500000005232012104023217020354 0ustar nniclaussenniclausse%%% 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, auto, [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, auto, [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:list_to_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:list_to_binary(ParametersList), NResultFormats = length(ResultFormats), ResultFormatsList = lists:map( fun (binary) -> <<1:16/integer>>; (text) -> <<0:16/integer>> end, ResultFormats), ResultFormatsP = erlang:list_to_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.5.1/src/lib/mochinum.erl0000644000175000017500000001777012104023217017634 0ustar nniclaussenniclausse%% @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.5.1/src/lib/mochiweb_xpath_functions.erl0000644000175000017500000001211112301350731023071 0ustar nniclaussenniclausse%% 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([lookup_function/1]). %% Default functions. %% The format is: {FunctionName, fun(), FunctionSignature} %% WildCard argspec must be the last spec in list. %% %% @type FunctionName = atom() %% @type FunctionSignature = [XPathArgSpec] %% @type XPathArgSpec = XPathType | WildCardArgSpec %% @type WildCardArgSpec = {'*', XPathType} %% @type XPathType = node_set | string | number | boolean %% %% The engine is responsable of calling the function with %% the correct arguments, given the function signature. -spec lookup_function(atom()) -> mochiweb_xpath:xpath_fun_spec() | false. lookup_function('last') -> {'last',fun last/2,[]}; lookup_function('position') -> {'position',fun position/2,[]}; lookup_function('count') -> {'count',fun count/2,[node_set]}; lookup_function('concat') -> {'concat',fun concat/2,[{'*', string}]}; lookup_function('name') -> {'name',fun 'name'/2,[node_set]}; lookup_function('starts-with') -> {'starts-with', fun 'starts-with'/2,[string,string]}; lookup_function('contains') -> {'contains', fun 'contains'/2,[string,string]}; lookup_function('substring') -> {'substring', fun substring/2,[string,number,number]}; lookup_function('sum') -> {'sum', fun sum/2,[node_set]}; lookup_function('string-length') -> {'string-length', fun 'string-length'/2,[string]}; lookup_function('not') -> {'not', fun x_not/2, [boolean]}; lookup_function('string') -> {'string', fun 'string'/2, [node_set]}; lookup_function(_) -> false. %% @doc Function: boolean last() %% The position function returns the position of the current node last({ctx, _, _, _, Position, Size} = _Ctx, []) -> Position =:= Size. %% @doc Function: number position() %% The position function returns the position of the current node position({ctx, _, _, _, Position, _} = _Ctx, []) -> Position. %% @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: concat(binary, binary, ...) %% Concatenate string arguments (variable length) concat(_Ctx, BinariesList) -> %% list_to_binary() << <> || Str <- BinariesList>>. %% @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: checks that Where contains What contains(_Ctx,[Where, What]) -> case binary:match(Where, [What]) of nomatch -> false; {_, _} -> true 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([mochiweb_xpath_utils:number_value(V) || V <- 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). %% @doc Function: string string(node_set) %% %% The sum function returns the string representation of the %% nodes in a node-set. This is different from text() in that %% it concatenates each bit of the text in the node along with the text in %% any children nodes along the way, in order. %% Note: this differs from normal xpath in that it returns a list of strings, one %% for each node in the node set, as opposed to just the first node. 'string'(_Ctx, [NodeList]) -> lists:map(fun({_Elem, _Attr, Children,_Pos}) -> concat_child_text(Children, []) end, NodeList). concat_child_text([], Result) -> list_to_binary(lists:reverse(Result)); concat_child_text([{_,_,Children,_} | Rest], Result) -> concat_child_text(Rest, [concat_child_text(Children, []) | Result]); concat_child_text([X | Rest], Result) -> concat_child_text(Rest, [X | Result]). x_not(_Ctx, [Bool]) -> not Bool. tsung-1.5.1/src/lib/oauth_rsa_sha1.erl0000644000175000017500000000444612104023217020712 0ustar nniclaussenniclausse%% Copyright (c) 2008-2009 Tim Fletcher %% %% Permission is hereby granted, free of charge, to any person %% obtaining a copy of this software and associated documentation %% files (the "Software"), to deal in the Software without %% restriction, including without limitation the rights to use, %% copy, modify, merge, publish, distribute, sublicense, and/or sell %% copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following %% conditions: %% %% The above copyright notice and this permission notice shall be %% included in all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR %% OTHER DEALINGS IN THE SOFTWARE. -module(oauth_rsa_sha1). -export([signature/2, verify/3]). -include_lib("public_key/include/public_key.hrl"). -spec signature(string(), string()) -> string(). signature(BaseString, PrivateKeyPath) -> {ok, Contents} = file:read_file(PrivateKeyPath), [Info] = public_key:pem_decode(Contents), PrivateKey = public_key:pem_entry_decode(Info), base64:encode_to_string(public_key:sign(list_to_binary(BaseString), sha, PrivateKey)). -spec verify(string(), string(), term()) -> boolean(). verify(Signature, BaseString, PublicKey) -> public_key:verify(to_binary(BaseString), sha, base64:decode(Signature), public_key(PublicKey)). to_binary(Term) when is_list(Term) -> list_to_binary(Term); to_binary(Term) when is_binary(Term) -> Term. public_key(Path) when is_list(Path) -> {ok, Contents} = file:read_file(Path), [{'Certificate', DerCert, not_encrypted}] = public_key:pem_decode(Contents), public_key( public_key:pkix_decode_cert(DerCert, otp)); public_key(#'OTPCertificate'{tbsCertificate=Cert}) -> public_key(Cert); public_key(#'OTPTBSCertificate'{subjectPublicKeyInfo=Info}) -> public_key(Info); public_key(#'OTPSubjectPublicKeyInfo'{subjectPublicKey=Key}) -> Key. tsung-1.5.1/src/lib/mochiweb_html.erl0000644000175000017500000007500212104023217020626 0ustar nniclaussenniclausse%% @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.5.1/src/lib/rabbit_misc.erl0000644000175000017500000000167412147621622020302 0ustar nniclaussenniclausse-module(rabbit_misc). -include("rabbit.hrl"). -include("rabbit_framing.hrl"). -export([method_record_type/1]). -export([amqp_error/4]). -export([frame_error/2]). -export([protocol_error/3, protocol_error/4, protocol_error/1]). method_record_type(Record) -> element(1, Record). amqp_error(Name, ExplanationFormat, Params, Method) -> Explanation = format(ExplanationFormat, Params), #amqp_error{name = Name, explanation = Explanation, method = Method}. format(Fmt, Args) -> lists:flatten(io_lib:format(Fmt, Args)). frame_error(MethodName, BinaryFields) -> protocol_error(frame_error, "cannot decode ~w", [BinaryFields], MethodName). protocol_error(Name, ExplanationFormat, Params) -> protocol_error(Name, ExplanationFormat, Params, none). protocol_error(Name, ExplanationFormat, Params, Method) -> protocol_error(amqp_error(Name, ExplanationFormat, Params, Method)). protocol_error(#amqp_error{} = Error) -> exit(Error). tsung-1.5.1/src/lib/mochiweb_xpath_parser.erl0000644000175000017500000000445312301350731022367 0ustar nniclaussenniclausse%% @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, union, {Path1, Path2}})-> %% "expr1 | expr2 | expr3" {path, union, {simplify(Path1), simplify(Path2)}}; 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({negative, Smth}) -> {negative, simplify(Smth)}; simplify({bool, Comp, A, B}) -> {bool, Comp, simplify(A), simplify(B)}; simplify({function_call,Fun,Args}) -> {function_call,Fun,lists:map(fun simplify/1,Args)}; simplify({arith, Op, Arg1, Arg2}) -> {arith, Op, simplify(Arg1), simplify(Arg2)}. 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}) -> A; simplify_node_test({processing_instruction, Name}) -> {processing_instruction, list_to_binary(Name)}; % strictly, this must be node_type too! simplify_node_test(A={wildcard,wildcard}) -> A; simplify_node_test({prefix_test, Prefix}) -> %% [37] /prefix:*/ - namespace test {prefix_test, list_to_binary(Prefix)}. 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.5.1/src/lib/uuid.erl0000644000175000017500000001325112104023217016751 0ustar nniclaussenniclausse%% @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.5.1/src/lib/ELDAPv3.erl0000644000175000017500000035052612104023217017112 0ustar nniclaussenniclausse%% 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.5.1/src/lib/oauth_http.erl0000644000175000017500000000505112301350731020164 0ustar nniclaussenniclausse%% Copyright (c) 2008-2009 Tim Fletcher %% %% Permission is hereby granted, free of charge, to any person %% obtaining a copy of this software and associated documentation %% files (the "Software"), to deal in the Software without %% restriction, including without limitation the rights to use, %% copy, modify, merge, publish, distribute, sublicense, and/or sell %% copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following %% conditions: %% %% The above copyright notice and this permission notice shall be %% included in all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR %% OTHER DEALINGS IN THE SOFTWARE. -module(oauth_http). -export([get/1, post/2, put/2, response_params/1, response_body/1, response_code/1]). -type http_status() :: {string(), integer(), string()}. -spec get(string()) -> {ok, {Status::http_status(), Headers::[{string(), string()}], Body::string()}} | {error, term()}. get(URL) -> request(get, {URL, []}). -spec post(string(), term()) -> {ok, {Status::http_status(), Headers::[{string(), string()}], Body::string()}} | {error, term()}. post(URL, Data) -> request(post, {URL, [], "application/x-www-form-urlencoded", Data}). -spec put(string(), term()) -> {ok, {Status::http_status(), Headers::[{string(), string()}], Body::string()}} | {error, term()}. put(URL, Data) -> request(put, {URL, [], "application/x-www-form-urlencoded", Data}). -spec request(httpc:method(), tuple()) -> {ok, {Status::http_status(), Headers::[{string(), string()}], Body::string()}} | {error, term()}. request(Method, Request) -> httpc:request(Method, Request, [{autoredirect, false}], []). -spec response_params({http_status(), [{string(), string()}], string()}) -> [{string(), string()}]. response_params(Response) -> oauth_uri:params_from_string(response_body(Response)). -spec response_body({http_status(), [{string(), string()}], string()}) -> string(). response_body({{_, _, _}, _, Body}) -> Body. -spec response_code({http_status(), [{string(), string()}], string()}) -> integer(). response_code({{_, Code, _}, _, _}) -> Code. tsung-1.5.1/src/lib/mochiweb_charref.erl0000644000175000017500000001602212104023217021271 0ustar nniclaussenniclausse%% @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.5.1/src/lib/rabbit_framing_amqp_0_9_1.erl0000644000175000017500000017434712147621622022707 0ustar nniclaussenniclausse%% Autogenerated code. Do not edit. %% %% The contents of this file are subject to the Mozilla Public License %% Version 1.1 (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License %% at http://www.mozilla.org/MPL/ %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and %% limitations under the License. %% %% The Original Code is RabbitMQ. %% %% The Initial Developer of the Original Code is VMware, Inc. %% Copyright (c) 2007-2013 VMware, Inc. All rights reserved. %% -module(rabbit_framing_amqp_0_9_1). -include("rabbit_framing.hrl"). -export([version/0]). -export([lookup_method_name/1]). -export([lookup_class_name/1]). -export([method_id/1]). -export([method_has_content/1]). -export([is_method_synchronous/1]). -export([method_record/1]). -export([method_fieldnames/1]). -export([decode_method_fields/2]). -export([decode_properties/2]). -export([encode_method_fields/1]). -export([encode_properties/1]). -export([lookup_amqp_exception/1]). -export([amqp_exception/1]). %% Various types -ifdef(use_specs). -export_type([amqp_field_type/0, amqp_property_type/0, amqp_table/0, amqp_array/0, amqp_value/0, amqp_method_name/0, amqp_method/0, amqp_method_record/0, amqp_method_field_name/0, amqp_property_record/0, amqp_exception/0, amqp_exception_code/0, amqp_class_id/0]). -type(amqp_field_type() :: 'longstr' | 'signedint' | 'decimal' | 'timestamp' | 'table' | 'byte' | 'double' | 'float' | 'long' | 'short' | 'bool' | 'binary' | 'void' | 'array'). -type(amqp_property_type() :: 'shortstr' | 'longstr' | 'octet' | 'short' | 'long' | 'longlong' | 'timestamp' | 'bit' | 'table'). -type(amqp_table() :: [{binary(), amqp_field_type(), amqp_value()}]). -type(amqp_array() :: [{amqp_field_type(), amqp_value()}]). -type(amqp_value() :: binary() | % longstr integer() | % signedint {non_neg_integer(), non_neg_integer()} | % decimal amqp_table() | amqp_array() | byte() | % byte float() | % double integer() | % long integer() | % short boolean() | % bool binary() | % binary 'undefined' | % void non_neg_integer() % timestamp ). -type(amqp_method_name() :: ( 'connection.start' | 'connection.start_ok' | 'connection.secure' | 'connection.secure_ok' | 'connection.tune' | 'connection.tune_ok' | 'connection.open' | 'connection.open_ok' | 'connection.close' | 'connection.close_ok' | 'channel.open' | 'channel.open_ok' | 'channel.flow' | 'channel.flow_ok' | 'channel.close' | 'channel.close_ok' | 'access.request' | 'access.request_ok' | 'exchange.declare' | 'exchange.declare_ok' | 'exchange.delete' | 'exchange.delete_ok' | 'exchange.bind' | 'exchange.bind_ok' | 'exchange.unbind' | 'exchange.unbind_ok' | 'queue.declare' | 'queue.declare_ok' | 'queue.bind' | 'queue.bind_ok' | 'queue.purge' | 'queue.purge_ok' | 'queue.delete' | 'queue.delete_ok' | 'queue.unbind' | 'queue.unbind_ok' | 'basic.qos' | 'basic.qos_ok' | 'basic.consume' | 'basic.consume_ok' | 'basic.cancel' | 'basic.cancel_ok' | 'basic.publish' | 'basic.return' | 'basic.deliver' | 'basic.get' | 'basic.get_ok' | 'basic.get_empty' | 'basic.ack' | 'basic.reject' | 'basic.recover_async' | 'basic.recover' | 'basic.recover_ok' | 'basic.nack' | 'tx.select' | 'tx.select_ok' | 'tx.commit' | 'tx.commit_ok' | 'tx.rollback' | 'tx.rollback_ok' | 'confirm.select' | 'confirm.select_ok' )). -type(amqp_method() :: ( {10, 10} | {10, 11} | {10, 20} | {10, 21} | {10, 30} | {10, 31} | {10, 40} | {10, 41} | {10, 50} | {10, 51} | {20, 10} | {20, 11} | {20, 20} | {20, 21} | {20, 40} | {20, 41} | {30, 10} | {30, 11} | {40, 10} | {40, 11} | {40, 20} | {40, 21} | {40, 30} | {40, 31} | {40, 40} | {40, 51} | {50, 10} | {50, 11} | {50, 20} | {50, 21} | {50, 30} | {50, 31} | {50, 40} | {50, 41} | {50, 50} | {50, 51} | {60, 10} | {60, 11} | {60, 20} | {60, 21} | {60, 30} | {60, 31} | {60, 40} | {60, 50} | {60, 60} | {60, 70} | {60, 71} | {60, 72} | {60, 80} | {60, 90} | {60, 100} | {60, 110} | {60, 111} | {60, 120} | {90, 10} | {90, 11} | {90, 20} | {90, 21} | {90, 30} | {90, 31} | {85, 10} | {85, 11} )). -type(amqp_method_record() :: ( #'connection.start'{} | #'connection.start_ok'{} | #'connection.secure'{} | #'connection.secure_ok'{} | #'connection.tune'{} | #'connection.tune_ok'{} | #'connection.open'{} | #'connection.open_ok'{} | #'connection.close'{} | #'connection.close_ok'{} | #'channel.open'{} | #'channel.open_ok'{} | #'channel.flow'{} | #'channel.flow_ok'{} | #'channel.close'{} | #'channel.close_ok'{} | #'access.request'{} | #'access.request_ok'{} | #'exchange.declare'{} | #'exchange.declare_ok'{} | #'exchange.delete'{} | #'exchange.delete_ok'{} | #'exchange.bind'{} | #'exchange.bind_ok'{} | #'exchange.unbind'{} | #'exchange.unbind_ok'{} | #'queue.declare'{} | #'queue.declare_ok'{} | #'queue.bind'{} | #'queue.bind_ok'{} | #'queue.purge'{} | #'queue.purge_ok'{} | #'queue.delete'{} | #'queue.delete_ok'{} | #'queue.unbind'{} | #'queue.unbind_ok'{} | #'basic.qos'{} | #'basic.qos_ok'{} | #'basic.consume'{} | #'basic.consume_ok'{} | #'basic.cancel'{} | #'basic.cancel_ok'{} | #'basic.publish'{} | #'basic.return'{} | #'basic.deliver'{} | #'basic.get'{} | #'basic.get_ok'{} | #'basic.get_empty'{} | #'basic.ack'{} | #'basic.reject'{} | #'basic.recover_async'{} | #'basic.recover'{} | #'basic.recover_ok'{} | #'basic.nack'{} | #'tx.select'{} | #'tx.select_ok'{} | #'tx.commit'{} | #'tx.commit_ok'{} | #'tx.rollback'{} | #'tx.rollback_ok'{} | #'confirm.select'{} | #'confirm.select_ok'{} )). -type(amqp_method_field_name() :: ( queue | challenge | consumer_tag | realm | exclusive | passive | active | ticket | queue | write | exchange | read | ticket | exchange | routing_key | ticket | exchange | type | passive | durable | consumer_tag | auto_delete | no_local | internal | queue | requeue | nowait | arguments | nowait | routing_key | mandatory | exchange | ticket | no_ack | no_ack | nowait | exchange | if_unused | nowait | reply_text | exchange | ticket | frame_max | exclusive | destination | consumer_tag | source | reply_code | routing_key | nowait | ticket | arguments | exchange | nowait | routing_key | nowait | ticket | destination | source | routing_key | nowait | arguments | arguments | delivery_tag | redelivered | exchange | routing_key | ticket | ticket | queue | passive | durable | exclusive | queue | ticket | auto_delete | nowait | arguments | delivery_tag | queue | queue | if_unused | message_count | consumer_count | requeue | version_major | version_minor | routing_key | server_properties | message_count | mechanisms | routing_key | nowait | client_properties | mechanism | locales | prefetch_count | response | nowait | locale | ticket | queue | response | consumer_tag | channel_max | requeue | message_count | heartbeat | channel_max | arguments | cluster_id | frame_max | consumer_tag | heartbeat | if_empty | virtual_host | capabilities | requeue | insist | delivery_tag | delivery_tag | message_count | known_hosts | reply_code | reply_text | class_id | multiple | method_id | redelivered | arguments | out_of_band | channel_id | delivery_tag | active | prefetch_size | active | global | multiple | reply_code | ticket | reply_text | immediate | class_id | method_id | ticket )). -type(amqp_property_record() :: ( #'P_connection'{} | #'P_channel'{} | #'P_access'{} | #'P_exchange'{} | #'P_queue'{} | #'P_basic'{} | #'P_tx'{} | #'P_confirm'{} )). -type(amqp_exception() :: ( 'frame_method' | 'frame_header' | 'frame_body' | 'frame_heartbeat' | 'frame_min_size' | 'frame_end' | 'reply_success' | 'content_too_large' | 'no_route' | 'no_consumers' | 'access_refused' | 'not_found' | 'resource_locked' | 'precondition_failed' | 'connection_forced' | 'invalid_path' | 'frame_error' | 'syntax_error' | 'command_invalid' | 'channel_error' | 'unexpected_frame' | 'resource_error' | 'not_allowed' | 'not_implemented' | 'internal_error' )). -type(amqp_exception_code() :: ( 1 | 2 | 3 | 8 | 4096 | 206 | 200 | 311 | 312 | 313 | 403 | 404 | 405 | 406 | 320 | 402 | 501 | 502 | 503 | 504 | 505 | 506 | 530 | 540 | 541 )). -type(amqp_class_id() :: ( 40 | 10 | 50 | 20 | 85 | 90 | 60 | 30 )). -type(amqp_class_name() :: ( 'connection' | 'channel' | 'access' | 'exchange' | 'queue' | 'basic' | 'tx' | 'confirm' )). -endif. % use_specs %% Method signatures -ifdef(use_specs). -spec(version/0 :: () -> {non_neg_integer(), non_neg_integer(), non_neg_integer()}). -spec(lookup_method_name/1 :: (amqp_method()) -> amqp_method_name()). -spec(lookup_class_name/1 :: (amqp_class_id()) -> amqp_class_name()). -spec(method_id/1 :: (amqp_method_name()) -> amqp_method()). -spec(method_has_content/1 :: (amqp_method_name()) -> boolean()). -spec(is_method_synchronous/1 :: (amqp_method_record()) -> boolean()). -spec(method_record/1 :: (amqp_method_name()) -> amqp_method_record()). -spec(method_fieldnames/1 :: (amqp_method_name()) -> [amqp_method_field_name()]). -spec(decode_method_fields/2 :: (amqp_method_name(), binary()) -> amqp_method_record() | rabbit_types:connection_exit()). -spec(decode_properties/2 :: (non_neg_integer(), binary()) -> amqp_property_record()). -spec(encode_method_fields/1 :: (amqp_method_record()) -> binary()). -spec(encode_properties/1 :: (amqp_property_record()) -> binary()). -spec(lookup_amqp_exception/1 :: (amqp_exception()) -> {boolean(), amqp_exception_code(), binary()}). -spec(amqp_exception/1 :: (amqp_exception_code()) -> amqp_exception()). -endif. % use_specs bitvalue(true) -> 1; bitvalue(false) -> 0; bitvalue(undefined) -> 0. shortstr_size(S) -> case size(S) of Len when Len =< 255 -> Len; _ -> exit(method_field_shortstr_overflow) end. -define(SHORTSTR_VAL(R, L, V, X), begin <> = R, {V, X} end). -define(LONGSTR_VAL(R, L, V, X), begin <> = R, {V, X} end). -define(SHORT_VAL(R, L, V, X), begin <> = R, {V, X} end). -define(LONG_VAL(R, L, V, X), begin <> = R, {V, X} end). -define(LONGLONG_VAL(R, L, V, X), begin <> = R, {V, X} end). -define(OCTET_VAL(R, L, V, X), begin <> = R, {V, X} end). -define(TABLE_VAL(R, L, V, X), begin <> = R, {rabbit_binary_parser:parse_table(V), X} end). -define(TIMESTAMP_VAL(R, L, V, X), begin <> = R, {V, X} end). -define(SHORTSTR_PROP(X, L), begin L = size(X), if L < 256 -> <>; true -> exit(content_properties_shortstr_overflow) end end). -define(LONGSTR_PROP(X, L), begin L = size(X), <> end). -define(OCTET_PROP(X, L), <>). -define(SHORT_PROP(X, L), <>). -define(LONG_PROP(X, L), <>). -define(LONGLONG_PROP(X, L), <>). -define(TIMESTAMP_PROP(X, L), <>). -define(TABLE_PROP(X, T), begin T = rabbit_binary_generator:generate_table(X), <<(size(T)):32, T/binary>> end). version() -> {0, 9, 1}. lookup_method_name({10, 10}) -> 'connection.start'; lookup_method_name({10, 11}) -> 'connection.start_ok'; lookup_method_name({10, 20}) -> 'connection.secure'; lookup_method_name({10, 21}) -> 'connection.secure_ok'; lookup_method_name({10, 30}) -> 'connection.tune'; lookup_method_name({10, 31}) -> 'connection.tune_ok'; lookup_method_name({10, 40}) -> 'connection.open'; lookup_method_name({10, 41}) -> 'connection.open_ok'; lookup_method_name({10, 50}) -> 'connection.close'; lookup_method_name({10, 51}) -> 'connection.close_ok'; lookup_method_name({20, 10}) -> 'channel.open'; lookup_method_name({20, 11}) -> 'channel.open_ok'; lookup_method_name({20, 20}) -> 'channel.flow'; lookup_method_name({20, 21}) -> 'channel.flow_ok'; lookup_method_name({20, 40}) -> 'channel.close'; lookup_method_name({20, 41}) -> 'channel.close_ok'; lookup_method_name({30, 10}) -> 'access.request'; lookup_method_name({30, 11}) -> 'access.request_ok'; lookup_method_name({40, 10}) -> 'exchange.declare'; lookup_method_name({40, 11}) -> 'exchange.declare_ok'; lookup_method_name({40, 20}) -> 'exchange.delete'; lookup_method_name({40, 21}) -> 'exchange.delete_ok'; lookup_method_name({40, 30}) -> 'exchange.bind'; lookup_method_name({40, 31}) -> 'exchange.bind_ok'; lookup_method_name({40, 40}) -> 'exchange.unbind'; lookup_method_name({40, 51}) -> 'exchange.unbind_ok'; lookup_method_name({50, 10}) -> 'queue.declare'; lookup_method_name({50, 11}) -> 'queue.declare_ok'; lookup_method_name({50, 20}) -> 'queue.bind'; lookup_method_name({50, 21}) -> 'queue.bind_ok'; lookup_method_name({50, 30}) -> 'queue.purge'; lookup_method_name({50, 31}) -> 'queue.purge_ok'; lookup_method_name({50, 40}) -> 'queue.delete'; lookup_method_name({50, 41}) -> 'queue.delete_ok'; lookup_method_name({50, 50}) -> 'queue.unbind'; lookup_method_name({50, 51}) -> 'queue.unbind_ok'; lookup_method_name({60, 10}) -> 'basic.qos'; lookup_method_name({60, 11}) -> 'basic.qos_ok'; lookup_method_name({60, 20}) -> 'basic.consume'; lookup_method_name({60, 21}) -> 'basic.consume_ok'; lookup_method_name({60, 30}) -> 'basic.cancel'; lookup_method_name({60, 31}) -> 'basic.cancel_ok'; lookup_method_name({60, 40}) -> 'basic.publish'; lookup_method_name({60, 50}) -> 'basic.return'; lookup_method_name({60, 60}) -> 'basic.deliver'; lookup_method_name({60, 70}) -> 'basic.get'; lookup_method_name({60, 71}) -> 'basic.get_ok'; lookup_method_name({60, 72}) -> 'basic.get_empty'; lookup_method_name({60, 80}) -> 'basic.ack'; lookup_method_name({60, 90}) -> 'basic.reject'; lookup_method_name({60, 100}) -> 'basic.recover_async'; lookup_method_name({60, 110}) -> 'basic.recover'; lookup_method_name({60, 111}) -> 'basic.recover_ok'; lookup_method_name({60, 120}) -> 'basic.nack'; lookup_method_name({90, 10}) -> 'tx.select'; lookup_method_name({90, 11}) -> 'tx.select_ok'; lookup_method_name({90, 20}) -> 'tx.commit'; lookup_method_name({90, 21}) -> 'tx.commit_ok'; lookup_method_name({90, 30}) -> 'tx.rollback'; lookup_method_name({90, 31}) -> 'tx.rollback_ok'; lookup_method_name({85, 10}) -> 'confirm.select'; lookup_method_name({85, 11}) -> 'confirm.select_ok'; lookup_method_name({_ClassId, _MethodId} = Id) -> exit({unknown_method_id, Id}). lookup_class_name(10) -> 'connection'; lookup_class_name(20) -> 'channel'; lookup_class_name(30) -> 'access'; lookup_class_name(40) -> 'exchange'; lookup_class_name(50) -> 'queue'; lookup_class_name(60) -> 'basic'; lookup_class_name(90) -> 'tx'; lookup_class_name(85) -> 'confirm'; lookup_class_name(ClassId) -> exit({unknown_class_id, ClassId}). method_id('connection.start') -> {10, 10}; method_id('connection.start_ok') -> {10, 11}; method_id('connection.secure') -> {10, 20}; method_id('connection.secure_ok') -> {10, 21}; method_id('connection.tune') -> {10, 30}; method_id('connection.tune_ok') -> {10, 31}; method_id('connection.open') -> {10, 40}; method_id('connection.open_ok') -> {10, 41}; method_id('connection.close') -> {10, 50}; method_id('connection.close_ok') -> {10, 51}; method_id('channel.open') -> {20, 10}; method_id('channel.open_ok') -> {20, 11}; method_id('channel.flow') -> {20, 20}; method_id('channel.flow_ok') -> {20, 21}; method_id('channel.close') -> {20, 40}; method_id('channel.close_ok') -> {20, 41}; method_id('access.request') -> {30, 10}; method_id('access.request_ok') -> {30, 11}; method_id('exchange.declare') -> {40, 10}; method_id('exchange.declare_ok') -> {40, 11}; method_id('exchange.delete') -> {40, 20}; method_id('exchange.delete_ok') -> {40, 21}; method_id('exchange.bind') -> {40, 30}; method_id('exchange.bind_ok') -> {40, 31}; method_id('exchange.unbind') -> {40, 40}; method_id('exchange.unbind_ok') -> {40, 51}; method_id('queue.declare') -> {50, 10}; method_id('queue.declare_ok') -> {50, 11}; method_id('queue.bind') -> {50, 20}; method_id('queue.bind_ok') -> {50, 21}; method_id('queue.purge') -> {50, 30}; method_id('queue.purge_ok') -> {50, 31}; method_id('queue.delete') -> {50, 40}; method_id('queue.delete_ok') -> {50, 41}; method_id('queue.unbind') -> {50, 50}; method_id('queue.unbind_ok') -> {50, 51}; method_id('basic.qos') -> {60, 10}; method_id('basic.qos_ok') -> {60, 11}; method_id('basic.consume') -> {60, 20}; method_id('basic.consume_ok') -> {60, 21}; method_id('basic.cancel') -> {60, 30}; method_id('basic.cancel_ok') -> {60, 31}; method_id('basic.publish') -> {60, 40}; method_id('basic.return') -> {60, 50}; method_id('basic.deliver') -> {60, 60}; method_id('basic.get') -> {60, 70}; method_id('basic.get_ok') -> {60, 71}; method_id('basic.get_empty') -> {60, 72}; method_id('basic.ack') -> {60, 80}; method_id('basic.reject') -> {60, 90}; method_id('basic.recover_async') -> {60, 100}; method_id('basic.recover') -> {60, 110}; method_id('basic.recover_ok') -> {60, 111}; method_id('basic.nack') -> {60, 120}; method_id('tx.select') -> {90, 10}; method_id('tx.select_ok') -> {90, 11}; method_id('tx.commit') -> {90, 20}; method_id('tx.commit_ok') -> {90, 21}; method_id('tx.rollback') -> {90, 30}; method_id('tx.rollback_ok') -> {90, 31}; method_id('confirm.select') -> {85, 10}; method_id('confirm.select_ok') -> {85, 11}; method_id(Name) -> exit({unknown_method_name, Name}). method_has_content('connection.start') -> false; method_has_content('connection.start_ok') -> false; method_has_content('connection.secure') -> false; method_has_content('connection.secure_ok') -> false; method_has_content('connection.tune') -> false; method_has_content('connection.tune_ok') -> false; method_has_content('connection.open') -> false; method_has_content('connection.open_ok') -> false; method_has_content('connection.close') -> false; method_has_content('connection.close_ok') -> false; method_has_content('channel.open') -> false; method_has_content('channel.open_ok') -> false; method_has_content('channel.flow') -> false; method_has_content('channel.flow_ok') -> false; method_has_content('channel.close') -> false; method_has_content('channel.close_ok') -> false; method_has_content('access.request') -> false; method_has_content('access.request_ok') -> false; method_has_content('exchange.declare') -> false; method_has_content('exchange.declare_ok') -> false; method_has_content('exchange.delete') -> false; method_has_content('exchange.delete_ok') -> false; method_has_content('exchange.bind') -> false; method_has_content('exchange.bind_ok') -> false; method_has_content('exchange.unbind') -> false; method_has_content('exchange.unbind_ok') -> false; method_has_content('queue.declare') -> false; method_has_content('queue.declare_ok') -> false; method_has_content('queue.bind') -> false; method_has_content('queue.bind_ok') -> false; method_has_content('queue.purge') -> false; method_has_content('queue.purge_ok') -> false; method_has_content('queue.delete') -> false; method_has_content('queue.delete_ok') -> false; method_has_content('queue.unbind') -> false; method_has_content('queue.unbind_ok') -> false; method_has_content('basic.qos') -> false; method_has_content('basic.qos_ok') -> false; method_has_content('basic.consume') -> false; method_has_content('basic.consume_ok') -> false; method_has_content('basic.cancel') -> false; method_has_content('basic.cancel_ok') -> false; method_has_content('basic.publish') -> true; method_has_content('basic.return') -> true; method_has_content('basic.deliver') -> true; method_has_content('basic.get') -> false; method_has_content('basic.get_ok') -> true; method_has_content('basic.get_empty') -> false; method_has_content('basic.ack') -> false; method_has_content('basic.reject') -> false; method_has_content('basic.recover_async') -> false; method_has_content('basic.recover') -> false; method_has_content('basic.recover_ok') -> false; method_has_content('basic.nack') -> false; method_has_content('tx.select') -> false; method_has_content('tx.select_ok') -> false; method_has_content('tx.commit') -> false; method_has_content('tx.commit_ok') -> false; method_has_content('tx.rollback') -> false; method_has_content('tx.rollback_ok') -> false; method_has_content('confirm.select') -> false; method_has_content('confirm.select_ok') -> false; method_has_content(Name) -> exit({unknown_method_name, Name}). is_method_synchronous(#'connection.start'{}) -> true; is_method_synchronous(#'connection.start_ok'{}) -> false; is_method_synchronous(#'connection.secure'{}) -> true; is_method_synchronous(#'connection.secure_ok'{}) -> false; is_method_synchronous(#'connection.tune'{}) -> true; is_method_synchronous(#'connection.tune_ok'{}) -> false; is_method_synchronous(#'connection.open'{}) -> true; is_method_synchronous(#'connection.open_ok'{}) -> false; is_method_synchronous(#'connection.close'{}) -> true; is_method_synchronous(#'connection.close_ok'{}) -> false; is_method_synchronous(#'channel.open'{}) -> true; is_method_synchronous(#'channel.open_ok'{}) -> false; is_method_synchronous(#'channel.flow'{}) -> true; is_method_synchronous(#'channel.flow_ok'{}) -> false; is_method_synchronous(#'channel.close'{}) -> true; is_method_synchronous(#'channel.close_ok'{}) -> false; is_method_synchronous(#'access.request'{}) -> true; is_method_synchronous(#'access.request_ok'{}) -> false; is_method_synchronous(#'exchange.declare'{nowait = NoWait}) -> not(NoWait); is_method_synchronous(#'exchange.declare_ok'{}) -> false; is_method_synchronous(#'exchange.delete'{nowait = NoWait}) -> not(NoWait); is_method_synchronous(#'exchange.delete_ok'{}) -> false; is_method_synchronous(#'exchange.bind'{nowait = NoWait}) -> not(NoWait); is_method_synchronous(#'exchange.bind_ok'{}) -> false; is_method_synchronous(#'exchange.unbind'{nowait = NoWait}) -> not(NoWait); is_method_synchronous(#'exchange.unbind_ok'{}) -> false; is_method_synchronous(#'queue.declare'{nowait = NoWait}) -> not(NoWait); is_method_synchronous(#'queue.declare_ok'{}) -> false; is_method_synchronous(#'queue.bind'{nowait = NoWait}) -> not(NoWait); is_method_synchronous(#'queue.bind_ok'{}) -> false; is_method_synchronous(#'queue.purge'{nowait = NoWait}) -> not(NoWait); is_method_synchronous(#'queue.purge_ok'{}) -> false; is_method_synchronous(#'queue.delete'{nowait = NoWait}) -> not(NoWait); is_method_synchronous(#'queue.delete_ok'{}) -> false; is_method_synchronous(#'queue.unbind'{}) -> true; is_method_synchronous(#'queue.unbind_ok'{}) -> false; is_method_synchronous(#'basic.qos'{}) -> true; is_method_synchronous(#'basic.qos_ok'{}) -> false; is_method_synchronous(#'basic.consume'{nowait = NoWait}) -> not(NoWait); is_method_synchronous(#'basic.consume_ok'{}) -> false; is_method_synchronous(#'basic.cancel'{nowait = NoWait}) -> not(NoWait); is_method_synchronous(#'basic.cancel_ok'{}) -> false; is_method_synchronous(#'basic.publish'{}) -> false; is_method_synchronous(#'basic.return'{}) -> false; is_method_synchronous(#'basic.deliver'{}) -> false; is_method_synchronous(#'basic.get'{}) -> true; is_method_synchronous(#'basic.get_ok'{}) -> false; is_method_synchronous(#'basic.get_empty'{}) -> false; is_method_synchronous(#'basic.ack'{}) -> false; is_method_synchronous(#'basic.reject'{}) -> false; is_method_synchronous(#'basic.recover_async'{}) -> false; is_method_synchronous(#'basic.recover'{}) -> true; is_method_synchronous(#'basic.recover_ok'{}) -> false; is_method_synchronous(#'basic.nack'{}) -> false; is_method_synchronous(#'tx.select'{}) -> true; is_method_synchronous(#'tx.select_ok'{}) -> false; is_method_synchronous(#'tx.commit'{}) -> true; is_method_synchronous(#'tx.commit_ok'{}) -> false; is_method_synchronous(#'tx.rollback'{}) -> true; is_method_synchronous(#'tx.rollback_ok'{}) -> false; is_method_synchronous(#'confirm.select'{nowait = NoWait}) -> not(NoWait); is_method_synchronous(#'confirm.select_ok'{}) -> false; is_method_synchronous(Name) -> exit({unknown_method_name, Name}). method_record('connection.start') -> #'connection.start'{}; method_record('connection.start_ok') -> #'connection.start_ok'{}; method_record('connection.secure') -> #'connection.secure'{}; method_record('connection.secure_ok') -> #'connection.secure_ok'{}; method_record('connection.tune') -> #'connection.tune'{}; method_record('connection.tune_ok') -> #'connection.tune_ok'{}; method_record('connection.open') -> #'connection.open'{}; method_record('connection.open_ok') -> #'connection.open_ok'{}; method_record('connection.close') -> #'connection.close'{}; method_record('connection.close_ok') -> #'connection.close_ok'{}; method_record('channel.open') -> #'channel.open'{}; method_record('channel.open_ok') -> #'channel.open_ok'{}; method_record('channel.flow') -> #'channel.flow'{}; method_record('channel.flow_ok') -> #'channel.flow_ok'{}; method_record('channel.close') -> #'channel.close'{}; method_record('channel.close_ok') -> #'channel.close_ok'{}; method_record('access.request') -> #'access.request'{}; method_record('access.request_ok') -> #'access.request_ok'{}; method_record('exchange.declare') -> #'exchange.declare'{}; method_record('exchange.declare_ok') -> #'exchange.declare_ok'{}; method_record('exchange.delete') -> #'exchange.delete'{}; method_record('exchange.delete_ok') -> #'exchange.delete_ok'{}; method_record('exchange.bind') -> #'exchange.bind'{}; method_record('exchange.bind_ok') -> #'exchange.bind_ok'{}; method_record('exchange.unbind') -> #'exchange.unbind'{}; method_record('exchange.unbind_ok') -> #'exchange.unbind_ok'{}; method_record('queue.declare') -> #'queue.declare'{}; method_record('queue.declare_ok') -> #'queue.declare_ok'{}; method_record('queue.bind') -> #'queue.bind'{}; method_record('queue.bind_ok') -> #'queue.bind_ok'{}; method_record('queue.purge') -> #'queue.purge'{}; method_record('queue.purge_ok') -> #'queue.purge_ok'{}; method_record('queue.delete') -> #'queue.delete'{}; method_record('queue.delete_ok') -> #'queue.delete_ok'{}; method_record('queue.unbind') -> #'queue.unbind'{}; method_record('queue.unbind_ok') -> #'queue.unbind_ok'{}; method_record('basic.qos') -> #'basic.qos'{}; method_record('basic.qos_ok') -> #'basic.qos_ok'{}; method_record('basic.consume') -> #'basic.consume'{}; method_record('basic.consume_ok') -> #'basic.consume_ok'{}; method_record('basic.cancel') -> #'basic.cancel'{}; method_record('basic.cancel_ok') -> #'basic.cancel_ok'{}; method_record('basic.publish') -> #'basic.publish'{}; method_record('basic.return') -> #'basic.return'{}; method_record('basic.deliver') -> #'basic.deliver'{}; method_record('basic.get') -> #'basic.get'{}; method_record('basic.get_ok') -> #'basic.get_ok'{}; method_record('basic.get_empty') -> #'basic.get_empty'{}; method_record('basic.ack') -> #'basic.ack'{}; method_record('basic.reject') -> #'basic.reject'{}; method_record('basic.recover_async') -> #'basic.recover_async'{}; method_record('basic.recover') -> #'basic.recover'{}; method_record('basic.recover_ok') -> #'basic.recover_ok'{}; method_record('basic.nack') -> #'basic.nack'{}; method_record('tx.select') -> #'tx.select'{}; method_record('tx.select_ok') -> #'tx.select_ok'{}; method_record('tx.commit') -> #'tx.commit'{}; method_record('tx.commit_ok') -> #'tx.commit_ok'{}; method_record('tx.rollback') -> #'tx.rollback'{}; method_record('tx.rollback_ok') -> #'tx.rollback_ok'{}; method_record('confirm.select') -> #'confirm.select'{}; method_record('confirm.select_ok') -> #'confirm.select_ok'{}; method_record(Name) -> exit({unknown_method_name, Name}). method_fieldnames('connection.start') -> [version_major, version_minor, server_properties, mechanisms, locales]; method_fieldnames('connection.start_ok') -> [client_properties, mechanism, response, locale]; method_fieldnames('connection.secure') -> [challenge]; method_fieldnames('connection.secure_ok') -> [response]; method_fieldnames('connection.tune') -> [channel_max, frame_max, heartbeat]; method_fieldnames('connection.tune_ok') -> [channel_max, frame_max, heartbeat]; method_fieldnames('connection.open') -> [virtual_host, capabilities, insist]; method_fieldnames('connection.open_ok') -> [known_hosts]; method_fieldnames('connection.close') -> [reply_code, reply_text, class_id, method_id]; method_fieldnames('connection.close_ok') -> []; method_fieldnames('channel.open') -> [out_of_band]; method_fieldnames('channel.open_ok') -> [channel_id]; method_fieldnames('channel.flow') -> [active]; method_fieldnames('channel.flow_ok') -> [active]; method_fieldnames('channel.close') -> [reply_code, reply_text, class_id, method_id]; method_fieldnames('channel.close_ok') -> []; method_fieldnames('access.request') -> [realm, exclusive, passive, active, write, read]; method_fieldnames('access.request_ok') -> [ticket]; method_fieldnames('exchange.declare') -> [ticket, exchange, type, passive, durable, auto_delete, internal, nowait, arguments]; method_fieldnames('exchange.declare_ok') -> []; method_fieldnames('exchange.delete') -> [ticket, exchange, if_unused, nowait]; method_fieldnames('exchange.delete_ok') -> []; method_fieldnames('exchange.bind') -> [ticket, destination, source, routing_key, nowait, arguments]; method_fieldnames('exchange.bind_ok') -> []; method_fieldnames('exchange.unbind') -> [ticket, destination, source, routing_key, nowait, arguments]; method_fieldnames('exchange.unbind_ok') -> []; method_fieldnames('queue.declare') -> [ticket, queue, passive, durable, exclusive, auto_delete, nowait, arguments]; method_fieldnames('queue.declare_ok') -> [queue, message_count, consumer_count]; method_fieldnames('queue.bind') -> [ticket, queue, exchange, routing_key, nowait, arguments]; method_fieldnames('queue.bind_ok') -> []; method_fieldnames('queue.purge') -> [ticket, queue, nowait]; method_fieldnames('queue.purge_ok') -> [message_count]; method_fieldnames('queue.delete') -> [ticket, queue, if_unused, if_empty, nowait]; method_fieldnames('queue.delete_ok') -> [message_count]; method_fieldnames('queue.unbind') -> [ticket, queue, exchange, routing_key, arguments]; method_fieldnames('queue.unbind_ok') -> []; method_fieldnames('basic.qos') -> [prefetch_size, prefetch_count, global]; method_fieldnames('basic.qos_ok') -> []; method_fieldnames('basic.consume') -> [ticket, queue, consumer_tag, no_local, no_ack, exclusive, nowait, arguments]; method_fieldnames('basic.consume_ok') -> [consumer_tag]; method_fieldnames('basic.cancel') -> [consumer_tag, nowait]; method_fieldnames('basic.cancel_ok') -> [consumer_tag]; method_fieldnames('basic.publish') -> [ticket, exchange, routing_key, mandatory, immediate]; method_fieldnames('basic.return') -> [reply_code, reply_text, exchange, routing_key]; method_fieldnames('basic.deliver') -> [consumer_tag, delivery_tag, redelivered, exchange, routing_key]; method_fieldnames('basic.get') -> [ticket, queue, no_ack]; method_fieldnames('basic.get_ok') -> [delivery_tag, redelivered, exchange, routing_key, message_count]; method_fieldnames('basic.get_empty') -> [cluster_id]; method_fieldnames('basic.ack') -> [delivery_tag, multiple]; method_fieldnames('basic.reject') -> [delivery_tag, requeue]; method_fieldnames('basic.recover_async') -> [requeue]; method_fieldnames('basic.recover') -> [requeue]; method_fieldnames('basic.recover_ok') -> []; method_fieldnames('basic.nack') -> [delivery_tag, multiple, requeue]; method_fieldnames('tx.select') -> []; method_fieldnames('tx.select_ok') -> []; method_fieldnames('tx.commit') -> []; method_fieldnames('tx.commit_ok') -> []; method_fieldnames('tx.rollback') -> []; method_fieldnames('tx.rollback_ok') -> []; method_fieldnames('confirm.select') -> [nowait]; method_fieldnames('confirm.select_ok') -> []; method_fieldnames(Name) -> exit({unknown_method_name, Name}). decode_method_fields('connection.start', <>) -> F2 = rabbit_binary_parser:parse_table(F2Tab), #'connection.start'{version_major = F0, version_minor = F1, server_properties = F2, mechanisms = F3, locales = F4}; decode_method_fields('connection.start_ok', <>) -> F0 = rabbit_binary_parser:parse_table(F0Tab), #'connection.start_ok'{client_properties = F0, mechanism = F1, response = F2, locale = F3}; decode_method_fields('connection.secure', <>) -> #'connection.secure'{challenge = F0}; decode_method_fields('connection.secure_ok', <>) -> #'connection.secure_ok'{response = F0}; decode_method_fields('connection.tune', <>) -> #'connection.tune'{channel_max = F0, frame_max = F1, heartbeat = F2}; decode_method_fields('connection.tune_ok', <>) -> #'connection.tune_ok'{channel_max = F0, frame_max = F1, heartbeat = F2}; decode_method_fields('connection.open', <>) -> F2 = ((F2Bits band 1) /= 0), #'connection.open'{virtual_host = F0, capabilities = F1, insist = F2}; decode_method_fields('connection.open_ok', <>) -> #'connection.open_ok'{known_hosts = F0}; decode_method_fields('connection.close', <>) -> #'connection.close'{reply_code = F0, reply_text = F1, class_id = F2, method_id = F3}; decode_method_fields('connection.close_ok', <<>>) -> #'connection.close_ok'{}; decode_method_fields('channel.open', <>) -> #'channel.open'{out_of_band = F0}; decode_method_fields('channel.open_ok', <>) -> #'channel.open_ok'{channel_id = F0}; decode_method_fields('channel.flow', <>) -> F0 = ((F0Bits band 1) /= 0), #'channel.flow'{active = F0}; decode_method_fields('channel.flow_ok', <>) -> F0 = ((F0Bits band 1) /= 0), #'channel.flow_ok'{active = F0}; decode_method_fields('channel.close', <>) -> #'channel.close'{reply_code = F0, reply_text = F1, class_id = F2, method_id = F3}; decode_method_fields('channel.close_ok', <<>>) -> #'channel.close_ok'{}; decode_method_fields('access.request', <>) -> F1 = ((F1Bits band 1) /= 0), F2 = ((F1Bits band 2) /= 0), F3 = ((F1Bits band 4) /= 0), F4 = ((F1Bits band 8) /= 0), F5 = ((F1Bits band 16) /= 0), #'access.request'{realm = F0, exclusive = F1, passive = F2, active = F3, write = F4, read = F5}; decode_method_fields('access.request_ok', <>) -> #'access.request_ok'{ticket = F0}; decode_method_fields('exchange.declare', <>) -> F3 = ((F3Bits band 1) /= 0), F4 = ((F3Bits band 2) /= 0), F5 = ((F3Bits band 4) /= 0), F6 = ((F3Bits band 8) /= 0), F7 = ((F3Bits band 16) /= 0), F8 = rabbit_binary_parser:parse_table(F8Tab), #'exchange.declare'{ticket = F0, exchange = F1, type = F2, passive = F3, durable = F4, auto_delete = F5, internal = F6, nowait = F7, arguments = F8}; decode_method_fields('exchange.declare_ok', <<>>) -> #'exchange.declare_ok'{}; decode_method_fields('exchange.delete', <>) -> F2 = ((F2Bits band 1) /= 0), F3 = ((F2Bits band 2) /= 0), #'exchange.delete'{ticket = F0, exchange = F1, if_unused = F2, nowait = F3}; decode_method_fields('exchange.delete_ok', <<>>) -> #'exchange.delete_ok'{}; decode_method_fields('exchange.bind', <>) -> F4 = ((F4Bits band 1) /= 0), F5 = rabbit_binary_parser:parse_table(F5Tab), #'exchange.bind'{ticket = F0, destination = F1, source = F2, routing_key = F3, nowait = F4, arguments = F5}; decode_method_fields('exchange.bind_ok', <<>>) -> #'exchange.bind_ok'{}; decode_method_fields('exchange.unbind', <>) -> F4 = ((F4Bits band 1) /= 0), F5 = rabbit_binary_parser:parse_table(F5Tab), #'exchange.unbind'{ticket = F0, destination = F1, source = F2, routing_key = F3, nowait = F4, arguments = F5}; decode_method_fields('exchange.unbind_ok', <<>>) -> #'exchange.unbind_ok'{}; decode_method_fields('queue.declare', <>) -> F2 = ((F2Bits band 1) /= 0), F3 = ((F2Bits band 2) /= 0), F4 = ((F2Bits band 4) /= 0), F5 = ((F2Bits band 8) /= 0), F6 = ((F2Bits band 16) /= 0), F7 = rabbit_binary_parser:parse_table(F7Tab), #'queue.declare'{ticket = F0, queue = F1, passive = F2, durable = F3, exclusive = F4, auto_delete = F5, nowait = F6, arguments = F7}; decode_method_fields('queue.declare_ok', <>) -> #'queue.declare_ok'{queue = F0, message_count = F1, consumer_count = F2}; decode_method_fields('queue.bind', <>) -> F4 = ((F4Bits band 1) /= 0), F5 = rabbit_binary_parser:parse_table(F5Tab), #'queue.bind'{ticket = F0, queue = F1, exchange = F2, routing_key = F3, nowait = F4, arguments = F5}; decode_method_fields('queue.bind_ok', <<>>) -> #'queue.bind_ok'{}; decode_method_fields('queue.purge', <>) -> F2 = ((F2Bits band 1) /= 0), #'queue.purge'{ticket = F0, queue = F1, nowait = F2}; decode_method_fields('queue.purge_ok', <>) -> #'queue.purge_ok'{message_count = F0}; decode_method_fields('queue.delete', <>) -> F2 = ((F2Bits band 1) /= 0), F3 = ((F2Bits band 2) /= 0), F4 = ((F2Bits band 4) /= 0), #'queue.delete'{ticket = F0, queue = F1, if_unused = F2, if_empty = F3, nowait = F4}; decode_method_fields('queue.delete_ok', <>) -> #'queue.delete_ok'{message_count = F0}; decode_method_fields('queue.unbind', <>) -> F4 = rabbit_binary_parser:parse_table(F4Tab), #'queue.unbind'{ticket = F0, queue = F1, exchange = F2, routing_key = F3, arguments = F4}; decode_method_fields('queue.unbind_ok', <<>>) -> #'queue.unbind_ok'{}; decode_method_fields('basic.qos', <>) -> F2 = ((F2Bits band 1) /= 0), #'basic.qos'{prefetch_size = F0, prefetch_count = F1, global = F2}; decode_method_fields('basic.qos_ok', <<>>) -> #'basic.qos_ok'{}; decode_method_fields('basic.consume', <>) -> F3 = ((F3Bits band 1) /= 0), F4 = ((F3Bits band 2) /= 0), F5 = ((F3Bits band 4) /= 0), F6 = ((F3Bits band 8) /= 0), F7 = rabbit_binary_parser:parse_table(F7Tab), #'basic.consume'{ticket = F0, queue = F1, consumer_tag = F2, no_local = F3, no_ack = F4, exclusive = F5, nowait = F6, arguments = F7}; decode_method_fields('basic.consume_ok', <>) -> #'basic.consume_ok'{consumer_tag = F0}; decode_method_fields('basic.cancel', <>) -> F1 = ((F1Bits band 1) /= 0), #'basic.cancel'{consumer_tag = F0, nowait = F1}; decode_method_fields('basic.cancel_ok', <>) -> #'basic.cancel_ok'{consumer_tag = F0}; decode_method_fields('basic.publish', <>) -> F3 = ((F3Bits band 1) /= 0), F4 = ((F3Bits band 2) /= 0), #'basic.publish'{ticket = F0, exchange = F1, routing_key = F2, mandatory = F3, immediate = F4}; decode_method_fields('basic.return', <>) -> #'basic.return'{reply_code = F0, reply_text = F1, exchange = F2, routing_key = F3}; decode_method_fields('basic.deliver', <>) -> F2 = ((F2Bits band 1) /= 0), #'basic.deliver'{consumer_tag = F0, delivery_tag = F1, redelivered = F2, exchange = F3, routing_key = F4}; decode_method_fields('basic.get', <>) -> F2 = ((F2Bits band 1) /= 0), #'basic.get'{ticket = F0, queue = F1, no_ack = F2}; decode_method_fields('basic.get_ok', <>) -> F1 = ((F1Bits band 1) /= 0), #'basic.get_ok'{delivery_tag = F0, redelivered = F1, exchange = F2, routing_key = F3, message_count = F4}; decode_method_fields('basic.get_empty', <>) -> #'basic.get_empty'{cluster_id = F0}; decode_method_fields('basic.ack', <>) -> F1 = ((F1Bits band 1) /= 0), #'basic.ack'{delivery_tag = F0, multiple = F1}; decode_method_fields('basic.reject', <>) -> F1 = ((F1Bits band 1) /= 0), #'basic.reject'{delivery_tag = F0, requeue = F1}; decode_method_fields('basic.recover_async', <>) -> F0 = ((F0Bits band 1) /= 0), #'basic.recover_async'{requeue = F0}; decode_method_fields('basic.recover', <>) -> F0 = ((F0Bits band 1) /= 0), #'basic.recover'{requeue = F0}; decode_method_fields('basic.recover_ok', <<>>) -> #'basic.recover_ok'{}; decode_method_fields('basic.nack', <>) -> F1 = ((F1Bits band 1) /= 0), F2 = ((F1Bits band 2) /= 0), #'basic.nack'{delivery_tag = F0, multiple = F1, requeue = F2}; decode_method_fields('tx.select', <<>>) -> #'tx.select'{}; decode_method_fields('tx.select_ok', <<>>) -> #'tx.select_ok'{}; decode_method_fields('tx.commit', <<>>) -> #'tx.commit'{}; decode_method_fields('tx.commit_ok', <<>>) -> #'tx.commit_ok'{}; decode_method_fields('tx.rollback', <<>>) -> #'tx.rollback'{}; decode_method_fields('tx.rollback_ok', <<>>) -> #'tx.rollback_ok'{}; decode_method_fields('confirm.select', <>) -> F0 = ((F0Bits band 1) /= 0), #'confirm.select'{nowait = F0}; decode_method_fields('confirm.select_ok', <<>>) -> #'confirm.select_ok'{}; decode_method_fields(Name, BinaryFields) -> rabbit_misc:frame_error(Name, BinaryFields). decode_properties(10, <<>>) -> #'P_connection'{}; decode_properties(20, <<>>) -> #'P_channel'{}; decode_properties(30, <<>>) -> #'P_access'{}; decode_properties(40, <<>>) -> #'P_exchange'{}; decode_properties(50, <<>>) -> #'P_queue'{}; decode_properties(60, <>) -> {F0, R1} = if P0 =:= 0 -> {undefined, R0}; true -> ?SHORTSTR_VAL(R0, L0, V0, X0) end, {F1, R2} = if P1 =:= 0 -> {undefined, R1}; true -> ?SHORTSTR_VAL(R1, L1, V1, X1) end, {F2, R3} = if P2 =:= 0 -> {undefined, R2}; true -> ?TABLE_VAL(R2, L2, V2, X2) end, {F3, R4} = if P3 =:= 0 -> {undefined, R3}; true -> ?OCTET_VAL(R3, L3, V3, X3) end, {F4, R5} = if P4 =:= 0 -> {undefined, R4}; true -> ?OCTET_VAL(R4, L4, V4, X4) end, {F5, R6} = if P5 =:= 0 -> {undefined, R5}; true -> ?SHORTSTR_VAL(R5, L5, V5, X5) end, {F6, R7} = if P6 =:= 0 -> {undefined, R6}; true -> ?SHORTSTR_VAL(R6, L6, V6, X6) end, {F7, R8} = if P7 =:= 0 -> {undefined, R7}; true -> ?SHORTSTR_VAL(R7, L7, V7, X7) end, {F8, R9} = if P8 =:= 0 -> {undefined, R8}; true -> ?SHORTSTR_VAL(R8, L8, V8, X8) end, {F9, R10} = if P9 =:= 0 -> {undefined, R9}; true -> ?TIMESTAMP_VAL(R9, L9, V9, X9) end, {F10, R11} = if P10 =:= 0 -> {undefined, R10}; true -> ?SHORTSTR_VAL(R10, L10, V10, X10) end, {F11, R12} = if P11 =:= 0 -> {undefined, R11}; true -> ?SHORTSTR_VAL(R11, L11, V11, X11) end, {F12, R13} = if P12 =:= 0 -> {undefined, R12}; true -> ?SHORTSTR_VAL(R12, L12, V12, X12) end, {F13, R14} = if P13 =:= 0 -> {undefined, R13}; true -> ?SHORTSTR_VAL(R13, L13, V13, X13) end, <<>> = R14, #'P_basic'{content_type = F0, content_encoding = F1, headers = F2, delivery_mode = F3, priority = F4, correlation_id = F5, reply_to = F6, expiration = F7, message_id = F8, timestamp = F9, type = F10, user_id = F11, app_id = F12, cluster_id = F13}; decode_properties(90, <<>>) -> #'P_tx'{}; decode_properties(85, <<>>) -> #'P_confirm'{}; decode_properties(ClassId, _BinaryFields) -> exit({unknown_class_id, ClassId}). encode_method_fields(#'connection.start'{version_major = F0, version_minor = F1, server_properties = F2, mechanisms = F3, locales = F4}) -> F2Tab = rabbit_binary_generator:generate_table(F2), F2Len = size(F2Tab), F3Len = size(F3), F4Len = size(F4), <>; encode_method_fields(#'connection.start_ok'{client_properties = F0, mechanism = F1, response = F2, locale = F3}) -> F0Tab = rabbit_binary_generator:generate_table(F0), F0Len = size(F0Tab), F1Len = shortstr_size(F1), F2Len = size(F2), F3Len = shortstr_size(F3), <>; encode_method_fields(#'connection.secure'{challenge = F0}) -> F0Len = size(F0), <>; encode_method_fields(#'connection.secure_ok'{response = F0}) -> F0Len = size(F0), <>; encode_method_fields(#'connection.tune'{channel_max = F0, frame_max = F1, heartbeat = F2}) -> <>; encode_method_fields(#'connection.tune_ok'{channel_max = F0, frame_max = F1, heartbeat = F2}) -> <>; encode_method_fields(#'connection.open'{virtual_host = F0, capabilities = F1, insist = F2}) -> F0Len = shortstr_size(F0), F1Len = shortstr_size(F1), F2Bits = ((bitvalue(F2) bsl 0)), <>; encode_method_fields(#'connection.open_ok'{known_hosts = F0}) -> F0Len = shortstr_size(F0), <>; encode_method_fields(#'connection.close'{reply_code = F0, reply_text = F1, class_id = F2, method_id = F3}) -> F1Len = shortstr_size(F1), <>; encode_method_fields(#'connection.close_ok'{}) -> <<>>; encode_method_fields(#'channel.open'{out_of_band = F0}) -> F0Len = shortstr_size(F0), <>; encode_method_fields(#'channel.open_ok'{channel_id = F0}) -> F0Len = size(F0), <>; encode_method_fields(#'channel.flow'{active = F0}) -> F0Bits = ((bitvalue(F0) bsl 0)), <>; encode_method_fields(#'channel.flow_ok'{active = F0}) -> F0Bits = ((bitvalue(F0) bsl 0)), <>; encode_method_fields(#'channel.close'{reply_code = F0, reply_text = F1, class_id = F2, method_id = F3}) -> F1Len = shortstr_size(F1), <>; encode_method_fields(#'channel.close_ok'{}) -> <<>>; encode_method_fields(#'access.request'{realm = F0, exclusive = F1, passive = F2, active = F3, write = F4, read = F5}) -> F0Len = shortstr_size(F0), F1Bits = ((bitvalue(F1) bsl 0) bor (bitvalue(F2) bsl 1) bor (bitvalue(F3) bsl 2) bor (bitvalue(F4) bsl 3) bor (bitvalue(F5) bsl 4)), <>; encode_method_fields(#'access.request_ok'{ticket = F0}) -> <>; encode_method_fields(#'exchange.declare'{ticket = F0, exchange = F1, type = F2, passive = F3, durable = F4, auto_delete = F5, internal = F6, nowait = F7, arguments = F8}) -> F1Len = shortstr_size(F1), F2Len = shortstr_size(F2), F3Bits = ((bitvalue(F3) bsl 0) bor (bitvalue(F4) bsl 1) bor (bitvalue(F5) bsl 2) bor (bitvalue(F6) bsl 3) bor (bitvalue(F7) bsl 4)), F8Tab = rabbit_binary_generator:generate_table(F8), F8Len = size(F8Tab), <>; encode_method_fields(#'exchange.declare_ok'{}) -> <<>>; encode_method_fields(#'exchange.delete'{ticket = F0, exchange = F1, if_unused = F2, nowait = F3}) -> F1Len = shortstr_size(F1), F2Bits = ((bitvalue(F2) bsl 0) bor (bitvalue(F3) bsl 1)), <>; encode_method_fields(#'exchange.delete_ok'{}) -> <<>>; encode_method_fields(#'exchange.bind'{ticket = F0, destination = F1, source = F2, routing_key = F3, nowait = F4, arguments = F5}) -> F1Len = shortstr_size(F1), F2Len = shortstr_size(F2), F3Len = shortstr_size(F3), F4Bits = ((bitvalue(F4) bsl 0)), F5Tab = rabbit_binary_generator:generate_table(F5), F5Len = size(F5Tab), <>; encode_method_fields(#'exchange.bind_ok'{}) -> <<>>; encode_method_fields(#'exchange.unbind'{ticket = F0, destination = F1, source = F2, routing_key = F3, nowait = F4, arguments = F5}) -> F1Len = shortstr_size(F1), F2Len = shortstr_size(F2), F3Len = shortstr_size(F3), F4Bits = ((bitvalue(F4) bsl 0)), F5Tab = rabbit_binary_generator:generate_table(F5), F5Len = size(F5Tab), <>; encode_method_fields(#'exchange.unbind_ok'{}) -> <<>>; encode_method_fields(#'queue.declare'{ticket = F0, queue = F1, passive = F2, durable = F3, exclusive = F4, auto_delete = F5, nowait = F6, arguments = F7}) -> F1Len = shortstr_size(F1), F2Bits = ((bitvalue(F2) bsl 0) bor (bitvalue(F3) bsl 1) bor (bitvalue(F4) bsl 2) bor (bitvalue(F5) bsl 3) bor (bitvalue(F6) bsl 4)), F7Tab = rabbit_binary_generator:generate_table(F7), F7Len = size(F7Tab), <>; encode_method_fields(#'queue.declare_ok'{queue = F0, message_count = F1, consumer_count = F2}) -> F0Len = shortstr_size(F0), <>; encode_method_fields(#'queue.bind'{ticket = F0, queue = F1, exchange = F2, routing_key = F3, nowait = F4, arguments = F5}) -> F1Len = shortstr_size(F1), F2Len = shortstr_size(F2), F3Len = shortstr_size(F3), F4Bits = ((bitvalue(F4) bsl 0)), F5Tab = rabbit_binary_generator:generate_table(F5), F5Len = size(F5Tab), <>; encode_method_fields(#'queue.bind_ok'{}) -> <<>>; encode_method_fields(#'queue.purge'{ticket = F0, queue = F1, nowait = F2}) -> F1Len = shortstr_size(F1), F2Bits = ((bitvalue(F2) bsl 0)), <>; encode_method_fields(#'queue.purge_ok'{message_count = F0}) -> <>; encode_method_fields(#'queue.delete'{ticket = F0, queue = F1, if_unused = F2, if_empty = F3, nowait = F4}) -> F1Len = shortstr_size(F1), F2Bits = ((bitvalue(F2) bsl 0) bor (bitvalue(F3) bsl 1) bor (bitvalue(F4) bsl 2)), <>; encode_method_fields(#'queue.delete_ok'{message_count = F0}) -> <>; encode_method_fields(#'queue.unbind'{ticket = F0, queue = F1, exchange = F2, routing_key = F3, arguments = F4}) -> F1Len = shortstr_size(F1), F2Len = shortstr_size(F2), F3Len = shortstr_size(F3), F4Tab = rabbit_binary_generator:generate_table(F4), F4Len = size(F4Tab), <>; encode_method_fields(#'queue.unbind_ok'{}) -> <<>>; encode_method_fields(#'basic.qos'{prefetch_size = F0, prefetch_count = F1, global = F2}) -> F2Bits = ((bitvalue(F2) bsl 0)), <>; encode_method_fields(#'basic.qos_ok'{}) -> <<>>; encode_method_fields(#'basic.consume'{ticket = F0, queue = F1, consumer_tag = F2, no_local = F3, no_ack = F4, exclusive = F5, nowait = F6, arguments = F7}) -> F1Len = shortstr_size(F1), F2Len = shortstr_size(F2), F3Bits = ((bitvalue(F3) bsl 0) bor (bitvalue(F4) bsl 1) bor (bitvalue(F5) bsl 2) bor (bitvalue(F6) bsl 3)), F7Tab = rabbit_binary_generator:generate_table(F7), F7Len = size(F7Tab), <>; encode_method_fields(#'basic.consume_ok'{consumer_tag = F0}) -> F0Len = shortstr_size(F0), <>; encode_method_fields(#'basic.cancel'{consumer_tag = F0, nowait = F1}) -> F0Len = shortstr_size(F0), F1Bits = ((bitvalue(F1) bsl 0)), <>; encode_method_fields(#'basic.cancel_ok'{consumer_tag = F0}) -> F0Len = shortstr_size(F0), <>; encode_method_fields(#'basic.publish'{ticket = F0, exchange = F1, routing_key = F2, mandatory = F3, immediate = F4}) -> F1Len = shortstr_size(F1), F2Len = shortstr_size(F2), F3Bits = ((bitvalue(F3) bsl 0) bor (bitvalue(F4) bsl 1)), <>; encode_method_fields(#'basic.return'{reply_code = F0, reply_text = F1, exchange = F2, routing_key = F3}) -> F1Len = shortstr_size(F1), F2Len = shortstr_size(F2), F3Len = shortstr_size(F3), <>; encode_method_fields(#'basic.deliver'{consumer_tag = F0, delivery_tag = F1, redelivered = F2, exchange = F3, routing_key = F4}) -> F0Len = shortstr_size(F0), F2Bits = ((bitvalue(F2) bsl 0)), F3Len = shortstr_size(F3), F4Len = shortstr_size(F4), <>; encode_method_fields(#'basic.get'{ticket = F0, queue = F1, no_ack = F2}) -> F1Len = shortstr_size(F1), F2Bits = ((bitvalue(F2) bsl 0)), <>; encode_method_fields(#'basic.get_ok'{delivery_tag = F0, redelivered = F1, exchange = F2, routing_key = F3, message_count = F4}) -> F1Bits = ((bitvalue(F1) bsl 0)), F2Len = shortstr_size(F2), F3Len = shortstr_size(F3), <>; encode_method_fields(#'basic.get_empty'{cluster_id = F0}) -> F0Len = shortstr_size(F0), <>; encode_method_fields(#'basic.ack'{delivery_tag = F0, multiple = F1}) -> F1Bits = ((bitvalue(F1) bsl 0)), <>; encode_method_fields(#'basic.reject'{delivery_tag = F0, requeue = F1}) -> F1Bits = ((bitvalue(F1) bsl 0)), <>; encode_method_fields(#'basic.recover_async'{requeue = F0}) -> F0Bits = ((bitvalue(F0) bsl 0)), <>; encode_method_fields(#'basic.recover'{requeue = F0}) -> F0Bits = ((bitvalue(F0) bsl 0)), <>; encode_method_fields(#'basic.recover_ok'{}) -> <<>>; encode_method_fields(#'basic.nack'{delivery_tag = F0, multiple = F1, requeue = F2}) -> F1Bits = ((bitvalue(F1) bsl 0) bor (bitvalue(F2) bsl 1)), <>; encode_method_fields(#'tx.select'{}) -> <<>>; encode_method_fields(#'tx.select_ok'{}) -> <<>>; encode_method_fields(#'tx.commit'{}) -> <<>>; encode_method_fields(#'tx.commit_ok'{}) -> <<>>; encode_method_fields(#'tx.rollback'{}) -> <<>>; encode_method_fields(#'tx.rollback_ok'{}) -> <<>>; encode_method_fields(#'confirm.select'{nowait = F0}) -> F0Bits = ((bitvalue(F0) bsl 0)), <>; encode_method_fields(#'confirm.select_ok'{}) -> <<>>; encode_method_fields(Record) -> exit({unknown_method_name, element(1, Record)}). encode_properties(#'P_connection'{}) -> <<>>; encode_properties(#'P_channel'{}) -> <<>>; encode_properties(#'P_access'{}) -> <<>>; encode_properties(#'P_exchange'{}) -> <<>>; encode_properties(#'P_queue'{}) -> <<>>; encode_properties(#'P_basic'{content_type = F0, content_encoding = F1, headers = F2, delivery_mode = F3, priority = F4, correlation_id = F5, reply_to = F6, expiration = F7, message_id = F8, timestamp = F9, type = F10, user_id = F11, app_id = F12, cluster_id = F13}) -> R0 = [<<>>], {P0, R1} = if F0 =:= undefined -> {0, R0}; true -> {1, [?SHORTSTR_PROP(F0, L0) | R0]} end, {P1, R2} = if F1 =:= undefined -> {0, R1}; true -> {1, [?SHORTSTR_PROP(F1, L1) | R1]} end, {P2, R3} = if F2 =:= undefined -> {0, R2}; true -> {1, [?TABLE_PROP(F2, L2) | R2]} end, {P3, R4} = if F3 =:= undefined -> {0, R3}; true -> {1, [?OCTET_PROP(F3, L3) | R3]} end, {P4, R5} = if F4 =:= undefined -> {0, R4}; true -> {1, [?OCTET_PROP(F4, L4) | R4]} end, {P5, R6} = if F5 =:= undefined -> {0, R5}; true -> {1, [?SHORTSTR_PROP(F5, L5) | R5]} end, {P6, R7} = if F6 =:= undefined -> {0, R6}; true -> {1, [?SHORTSTR_PROP(F6, L6) | R6]} end, {P7, R8} = if F7 =:= undefined -> {0, R7}; true -> {1, [?SHORTSTR_PROP(F7, L7) | R7]} end, {P8, R9} = if F8 =:= undefined -> {0, R8}; true -> {1, [?SHORTSTR_PROP(F8, L8) | R8]} end, {P9, R10} = if F9 =:= undefined -> {0, R9}; true -> {1, [?TIMESTAMP_PROP(F9, L9) | R9]} end, {P10, R11} = if F10 =:= undefined -> {0, R10}; true -> {1, [?SHORTSTR_PROP(F10, L10) | R10]} end, {P11, R12} = if F11 =:= undefined -> {0, R11}; true -> {1, [?SHORTSTR_PROP(F11, L11) | R11]} end, {P12, R13} = if F12 =:= undefined -> {0, R12}; true -> {1, [?SHORTSTR_PROP(F12, L12) | R12]} end, {P13, R14} = if F13 =:= undefined -> {0, R13}; true -> {1, [?SHORTSTR_PROP(F13, L13) | R13]} end, list_to_binary([<> | lists:reverse(R14)]); encode_properties(#'P_tx'{}) -> <<>>; encode_properties(#'P_confirm'{}) -> <<>>; encode_properties(Record) -> exit({unknown_properties_record, Record}). lookup_amqp_exception(content_too_large) -> {false, ?CONTENT_TOO_LARGE, <<"CONTENT_TOO_LARGE">>}; lookup_amqp_exception(no_route) -> {false, ?NO_ROUTE, <<"NO_ROUTE">>}; lookup_amqp_exception(no_consumers) -> {false, ?NO_CONSUMERS, <<"NO_CONSUMERS">>}; lookup_amqp_exception(access_refused) -> {false, ?ACCESS_REFUSED, <<"ACCESS_REFUSED">>}; lookup_amqp_exception(not_found) -> {false, ?NOT_FOUND, <<"NOT_FOUND">>}; lookup_amqp_exception(resource_locked) -> {false, ?RESOURCE_LOCKED, <<"RESOURCE_LOCKED">>}; lookup_amqp_exception(precondition_failed) -> {false, ?PRECONDITION_FAILED, <<"PRECONDITION_FAILED">>}; lookup_amqp_exception(connection_forced) -> {true, ?CONNECTION_FORCED, <<"CONNECTION_FORCED">>}; lookup_amqp_exception(invalid_path) -> {true, ?INVALID_PATH, <<"INVALID_PATH">>}; lookup_amqp_exception(frame_error) -> {true, ?FRAME_ERROR, <<"FRAME_ERROR">>}; lookup_amqp_exception(syntax_error) -> {true, ?SYNTAX_ERROR, <<"SYNTAX_ERROR">>}; lookup_amqp_exception(command_invalid) -> {true, ?COMMAND_INVALID, <<"COMMAND_INVALID">>}; lookup_amqp_exception(channel_error) -> {true, ?CHANNEL_ERROR, <<"CHANNEL_ERROR">>}; lookup_amqp_exception(unexpected_frame) -> {true, ?UNEXPECTED_FRAME, <<"UNEXPECTED_FRAME">>}; lookup_amqp_exception(resource_error) -> {true, ?RESOURCE_ERROR, <<"RESOURCE_ERROR">>}; lookup_amqp_exception(not_allowed) -> {true, ?NOT_ALLOWED, <<"NOT_ALLOWED">>}; lookup_amqp_exception(not_implemented) -> {true, ?NOT_IMPLEMENTED, <<"NOT_IMPLEMENTED">>}; lookup_amqp_exception(internal_error) -> {true, ?INTERNAL_ERROR, <<"INTERNAL_ERROR">>}; lookup_amqp_exception(Code) -> rabbit_log:warning("Unknown AMQP error code '~p'~n", [Code]), {true, ?INTERNAL_ERROR, <<"INTERNAL_ERROR">>}. amqp_exception(?FRAME_METHOD) -> frame_method; amqp_exception(?FRAME_HEADER) -> frame_header; amqp_exception(?FRAME_BODY) -> frame_body; amqp_exception(?FRAME_HEARTBEAT) -> frame_heartbeat; amqp_exception(?FRAME_MIN_SIZE) -> frame_min_size; amqp_exception(?FRAME_END) -> frame_end; amqp_exception(?REPLY_SUCCESS) -> reply_success; amqp_exception(?CONTENT_TOO_LARGE) -> content_too_large; amqp_exception(?NO_ROUTE) -> no_route; amqp_exception(?NO_CONSUMERS) -> no_consumers; amqp_exception(?ACCESS_REFUSED) -> access_refused; amqp_exception(?NOT_FOUND) -> not_found; amqp_exception(?RESOURCE_LOCKED) -> resource_locked; amqp_exception(?PRECONDITION_FAILED) -> precondition_failed; amqp_exception(?CONNECTION_FORCED) -> connection_forced; amqp_exception(?INVALID_PATH) -> invalid_path; amqp_exception(?FRAME_ERROR) -> frame_error; amqp_exception(?SYNTAX_ERROR) -> syntax_error; amqp_exception(?COMMAND_INVALID) -> command_invalid; amqp_exception(?CHANNEL_ERROR) -> channel_error; amqp_exception(?UNEXPECTED_FRAME) -> unexpected_frame; amqp_exception(?RESOURCE_ERROR) -> resource_error; amqp_exception(?NOT_ALLOWED) -> not_allowed; amqp_exception(?NOT_IMPLEMENTED) -> not_implemented; amqp_exception(?INTERNAL_ERROR) -> internal_error; amqp_exception(_Code) -> undefined. tsung-1.5.1/src/lib/mochiweb_headers.erl0000644000175000017500000002426612104023217021303 0ustar nniclaussenniclausse%% @author Bob Ippolito %% @copyright 2007 Mochi Media, Inc. %% @doc Case preserving (but case insensitive) HTTP Header dictionary. -module(mochiweb_headers). -author('bob@mochimedia.com'). -export([empty/0, from_list/1, insert/3, enter/3, get_value/2, lookup/2]). -export([delete_any/2, get_primary_value/2]). -export([default/3, enter_from_list/2, default_from_list/2]). -export([to_list/1, make/1]). -export([from_binary/1]). %% @type headers(). %% @type key() = atom() | binary() | string(). %% @type value() = atom() | binary() | string() | integer(). %% @spec empty() -> headers() %% @doc Create an empty headers structure. empty() -> gb_trees:empty(). %% @spec make(headers() | [{key(), value()}]) -> headers() %% @doc Construct a headers() from the given list. make(L) when is_list(L) -> from_list(L); %% assume a tuple is already mochiweb_headers. make(T) when is_tuple(T) -> T. %% @spec from_binary(iolist()) -> headers() %% @doc Transforms a raw HTTP header into a mochiweb headers structure. %% %% The given raw HTTP header can be one of the following: %% %% 1) A string or a binary representing a full HTTP header ending with %% double CRLF. %% Examples: %% ``` %% "Content-Length: 47\r\nContent-Type: text/plain\r\n\r\n" %% <<"Content-Length: 47\r\nContent-Type: text/plain\r\n\r\n">>''' %% %% 2) A list of binaries or strings where each element represents a raw %% HTTP header line ending with a single CRLF. %% Examples: %% ``` %% [<<"Content-Length: 47\r\n">>, <<"Content-Type: text/plain\r\n">>] %% ["Content-Length: 47\r\n", "Content-Type: text/plain\r\n"] %% ["Content-Length: 47\r\n", <<"Content-Type: text/plain\r\n">>]''' %% from_binary(RawHttpHeader) when is_binary(RawHttpHeader) -> from_binary(RawHttpHeader, []); from_binary(RawHttpHeaderList) -> from_binary(list_to_binary([RawHttpHeaderList, "\r\n"])). from_binary(RawHttpHeader, Acc) -> case erlang:decode_packet(httph, RawHttpHeader, []) of {ok, {http_header, _, H, _, V}, Rest} -> from_binary(Rest, [{H, V} | Acc]); _ -> make(Acc) end. %% @spec from_list([{key(), value()}]) -> headers() %% @doc Construct a headers() from the given list. from_list(List) -> lists:foldl(fun ({K, V}, T) -> insert(K, V, T) end, empty(), List). %% @spec enter_from_list([{key(), value()}], headers()) -> headers() %% @doc Insert pairs into the headers, replace any values for existing keys. enter_from_list(List, T) -> lists:foldl(fun ({K, V}, T1) -> enter(K, V, T1) end, T, List). %% @spec default_from_list([{key(), value()}], headers()) -> headers() %% @doc Insert pairs into the headers for keys that do not already exist. default_from_list(List, T) -> lists:foldl(fun ({K, V}, T1) -> default(K, V, T1) end, T, List). %% @spec to_list(headers()) -> [{key(), string()}] %% @doc Return the contents of the headers. The keys will be the exact key %% that was first inserted (e.g. may be an atom or binary, case is %% preserved). to_list(T) -> F = fun ({K, {array, L}}, Acc) -> L1 = lists:reverse(L), lists:foldl(fun (V, Acc1) -> [{K, V} | Acc1] end, Acc, L1); (Pair, Acc) -> [Pair | Acc] end, lists:reverse(lists:foldl(F, [], gb_trees:values(T))). %% @spec get_value(key(), headers()) -> string() | undefined %% @doc Return the value of the given header using a case insensitive search. %% undefined will be returned for keys that are not present. get_value(K, T) -> case lookup(K, T) of {value, {_, V}} -> expand(V); none -> undefined end. %% @spec get_primary_value(key(), headers()) -> string() | undefined %% @doc Return the value of the given header up to the first semicolon using %% a case insensitive search. undefined will be returned for keys %% that are not present. get_primary_value(K, T) -> case get_value(K, T) of undefined -> undefined; V -> lists:takewhile(fun (C) -> C =/= $; end, V) end. %% @spec lookup(key(), headers()) -> {value, {key(), string()}} | none %% @doc Return the case preserved key and value for the given header using %% a case insensitive search. none will be returned for keys that are %% not present. lookup(K, T) -> case gb_trees:lookup(normalize(K), T) of {value, {K0, V}} -> {value, {K0, expand(V)}}; none -> none end. %% @spec default(key(), value(), headers()) -> headers() %% @doc Insert the pair into the headers if it does not already exist. default(K, V, T) -> K1 = normalize(K), V1 = any_to_list(V), try gb_trees:insert(K1, {K, V1}, T) catch error:{key_exists, _} -> T end. %% @spec enter(key(), value(), headers()) -> headers() %% @doc Insert the pair into the headers, replacing any pre-existing key. enter(K, V, T) -> K1 = normalize(K), V1 = any_to_list(V), gb_trees:enter(K1, {K, V1}, T). %% @spec insert(key(), value(), headers()) -> headers() %% @doc Insert the pair into the headers, merging with any pre-existing key. %% A merge is done with Value = V0 ++ ", " ++ V1. insert(K, V, T) -> K1 = normalize(K), V1 = any_to_list(V), try gb_trees:insert(K1, {K, V1}, T) catch error:{key_exists, _} -> {K0, V0} = gb_trees:get(K1, T), V2 = merge(K1, V1, V0), gb_trees:update(K1, {K0, V2}, T) end. %% @spec delete_any(key(), headers()) -> headers() %% @doc Delete the header corresponding to key if it is present. delete_any(K, T) -> K1 = normalize(K), gb_trees:delete_any(K1, T). %% Internal API expand({array, L}) -> mochiweb_util:join(lists:reverse(L), ", "); expand(V) -> V. merge("set-cookie", V1, {array, L}) -> {array, [V1 | L]}; merge("set-cookie", V1, V0) -> {array, [V1, V0]}; merge(_, V1, V0) -> V0 ++ ", " ++ V1. normalize(K) when is_list(K) -> string:to_lower(K); normalize(K) when is_atom(K) -> normalize(atom_to_list(K)); normalize(K) when is_binary(K) -> normalize(binary_to_list(K)). any_to_list(V) when is_list(V) -> V; any_to_list(V) when is_atom(V) -> atom_to_list(V); any_to_list(V) when is_binary(V) -> binary_to_list(V); any_to_list(V) when is_integer(V) -> integer_to_list(V). %% %% Tests. %% -include_lib("eunit/include/eunit.hrl"). -ifdef(TEST). make_test() -> Identity = make([{hdr, foo}]), ?assertEqual( Identity, make(Identity)). enter_from_list_test() -> H = make([{hdr, foo}]), ?assertEqual( [{baz, "wibble"}, {hdr, "foo"}], to_list(enter_from_list([{baz, wibble}], H))), ?assertEqual( [{hdr, "bar"}], to_list(enter_from_list([{hdr, bar}], H))), ok. default_from_list_test() -> H = make([{hdr, foo}]), ?assertEqual( [{baz, "wibble"}, {hdr, "foo"}], to_list(default_from_list([{baz, wibble}], H))), ?assertEqual( [{hdr, "foo"}], to_list(default_from_list([{hdr, bar}], H))), ok. get_primary_value_test() -> H = make([{hdr, foo}, {baz, <<"wibble;taco">>}]), ?assertEqual( "foo", get_primary_value(hdr, H)), ?assertEqual( undefined, get_primary_value(bar, H)), ?assertEqual( "wibble", get_primary_value(<<"baz">>, H)), ok. set_cookie_test() -> H = make([{"set-cookie", foo}, {"set-cookie", bar}, {"set-cookie", baz}]), ?assertEqual( [{"set-cookie", "foo"}, {"set-cookie", "bar"}, {"set-cookie", "baz"}], to_list(H)), ok. headers_test() -> H = ?MODULE:make([{hdr, foo}, {"Hdr", "bar"}, {'Hdr', 2}]), [{hdr, "foo, bar, 2"}] = ?MODULE:to_list(H), H1 = ?MODULE:insert(taco, grande, H), [{hdr, "foo, bar, 2"}, {taco, "grande"}] = ?MODULE:to_list(H1), H2 = ?MODULE:make([{"Set-Cookie", "foo"}]), [{"Set-Cookie", "foo"}] = ?MODULE:to_list(H2), H3 = ?MODULE:insert("Set-Cookie", "bar", H2), [{"Set-Cookie", "foo"}, {"Set-Cookie", "bar"}] = ?MODULE:to_list(H3), "foo, bar" = ?MODULE:get_value("set-cookie", H3), {value, {"Set-Cookie", "foo, bar"}} = ?MODULE:lookup("set-cookie", H3), undefined = ?MODULE:get_value("shibby", H3), none = ?MODULE:lookup("shibby", H3), H4 = ?MODULE:insert("content-type", "application/x-www-form-urlencoded; charset=utf8", H3), "application/x-www-form-urlencoded" = ?MODULE:get_primary_value( "content-type", H4), H4 = ?MODULE:delete_any("nonexistent-header", H4), H3 = ?MODULE:delete_any("content-type", H4), HB = <<"Content-Length: 47\r\nContent-Type: text/plain\r\n\r\n">>, H_HB = ?MODULE:from_binary(HB), H_HB = ?MODULE:from_binary(binary_to_list(HB)), "47" = ?MODULE:get_value("Content-Length", H_HB), "text/plain" = ?MODULE:get_value("Content-Type", H_HB), L_H_HB = ?MODULE:to_list(H_HB), 2 = length(L_H_HB), true = lists:member({'Content-Length', "47"}, L_H_HB), true = lists:member({'Content-Type', "text/plain"}, L_H_HB), HL = [ <<"Content-Length: 47\r\n">>, <<"Content-Type: text/plain\r\n">> ], HL2 = [ "Content-Length: 47\r\n", <<"Content-Type: text/plain\r\n">> ], HL3 = [ <<"Content-Length: 47\r\n">>, "Content-Type: text/plain\r\n" ], H_HL = ?MODULE:from_binary(HL), H_HL = ?MODULE:from_binary(HL2), H_HL = ?MODULE:from_binary(HL3), "47" = ?MODULE:get_value("Content-Length", H_HL), "text/plain" = ?MODULE:get_value("Content-Type", H_HL), L_H_HL = ?MODULE:to_list(H_HL), 2 = length(L_H_HL), true = lists:member({'Content-Length', "47"}, L_H_HL), true = lists:member({'Content-Type', "text/plain"}, L_H_HL), [] = ?MODULE:to_list(?MODULE:from_binary(<<>>)), [] = ?MODULE:to_list(?MODULE:from_binary(<<"">>)), [] = ?MODULE:to_list(?MODULE:from_binary(<<"\r\n">>)), [] = ?MODULE:to_list(?MODULE:from_binary(<<"\r\n\r\n">>)), [] = ?MODULE:to_list(?MODULE:from_binary("")), [] = ?MODULE:to_list(?MODULE:from_binary([<<>>])), [] = ?MODULE:to_list(?MODULE:from_binary([<<"">>])), [] = ?MODULE:to_list(?MODULE:from_binary([<<"\r\n">>])), [] = ?MODULE:to_list(?MODULE:from_binary([<<"\r\n\r\n">>])), ok. -endif. tsung-1.5.1/src/tsung-rrd.pl.in0000755000175000017500000000603012104023217017420 0ustar nniclaussenniclausse#!/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.5.1/src/templates/0000755000175000017500000000000012321173206016532 5ustar nniclaussenniclaussetsung-1.5.1/src/templates/header.thtml0000644000175000017500000001200312147621622021036 0ustar nniclaussenniclausse [% title %] - [% subtitle %]
tsung-1.5.1/src/templates/report.thtml0000644000175000017500000001267712301350731021132 0ustar nniclaussenniclausse[% 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 RateMean Rate Mean Count
$key [% data.maxmean.$key %] [% data.minmean.$key %] [% data.rate.$key %] / sec [% data.rate_mean.$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 RateMean Rate Mean Count
$key [% data.maxmean.$key %] [% data.minmean.$key %] [% data.rate.$key %] / sec [% data.rate_mean.$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 RateMean RateTotal number
$key [% data.rate.$key %] / sec [% data.rate_mean.$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 RateMean RateTotal number
$key [% data.rate.$key %] / sec [% data.rate_mean.$key %] / sec [% data.maxmean.$key %]
[% END %]
[% INCLUDE footer.thtml %] tsung-1.5.1/src/templates/graph.thtml0000644000175000017500000001111512147621622020712 0ustar nniclaussenniclausse[% INCLUDE header.thtml %]

tsung - Graphical Reports

Throughput

[% IF async %] [% IF bosh %][% END %] [% IF bosh %] [% END %] [% END %]
TransactionsRequests
transaction rate req/sec
Noack/BidiBOSH
req/sec req/sec
Network trafficNew Users
Kb/sec visit/sec
[% 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 %] [% FOREACH key = row %] [% END %] [% END %] [% END %]
CPU%Free Memory
cpu free memory
CPU Load
load
$key
[% IF key %] other [% END %]
[% END %] [% IF http %]

HTTP return code Status (rate)

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

Errors (rate)

Errors-rate
[% END %]
[% INCLUDE footer.thtml %] tsung-1.5.1/src/templates/dygraph-combined.js0000644000175000017500000041163412236145741022325 0ustar nniclaussenniclausse/*! @license Copyright 2011 Dan Vanderkam (danvdk@gmail.com) MIT-licensed (http://opensource.org/licenses/MIT) */ 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};"use strict";function RGBColorParser(f){this.ok=false;if(f.charAt(0)=="#"){f=f.substr(1,6)}f=f.replace(/ /g,"");f=f.toLowerCase();var b={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"};for(var g in b){if(f==g){f=b[g]}}var e=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(i){return[parseInt(i[1]),parseInt(i[2]),parseInt(i[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(i){return[parseInt(i[1],16),parseInt(i[2],16),parseInt(i[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(i){return[parseInt(i[1]+i[1],16),parseInt(i[2]+i[2],16),parseInt(i[3]+i[3],16)]}}];for(var c=0;c255)?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 i=this.b.toString(16);if(l.length==1){l="0"+l}if(k.length==1){k="0"+k}if(i.length==1){i="0"+i}return"#"+l+k+i}}function printStackTrace(b){b=b||{guess:true};var c=b.e||null,e=!!b.guess;var d=new printStackTrace.implementation(),a=d.run(c);return(e)?d.guessAnonymousFunctions(a):a}printStackTrace.implementation=function(){};printStackTrace.implementation.prototype={run:function(a,b){a=a||this.createException();b=b||this.mode(a);if(b==="other"){return this.other(arguments.callee)}else{return this[b](a)}},createException:function(){try{this.undef()}catch(a){return a}},mode:function(a){if(a["arguments"]&&a.stack){return"chrome"}else{if(typeof a.message==="string"&&typeof window!=="undefined"&&window.opera){if(!a.stacktrace){return"opera9"}if(a.message.indexOf("\n")>-1&&a.message.split("\n").length>a.stacktrace.split("\n").length){return"opera9"}if(!a.stack){return"opera10a"}if(a.stacktrace.indexOf("called from line")<0){return"opera10b"}return"opera11"}else{if(a.stack){return"firefox"}}}return"other"},instrumentFunction:function(b,d,e){b=b||window;var a=b[d];b[d]=function c(){e.call(this,printStackTrace().slice(4));return b[d]._instrumented.apply(this,arguments)};b[d]._instrumented=a},deinstrumentFunction:function(a,b){if(a[b].constructor===Function&&a[b]._instrumented&&a[b]._instrumented.constructor===Function){a[b]=a[b]._instrumented}},chrome:function(b){var a=(b.stack+"\n").replace(/^\S[^\(]+?[\n$]/gm,"").replace(/^\s+at\s+/gm,"").replace(/^([^\(]+?)([\n$])/gm,"{anonymous}()@$1$2").replace(/^Object.\s*\(([^\)]+)\)/gm,"{anonymous}()@$1").split("\n");a.pop();return a},firefox:function(a){return a.stack.replace(/(?:\n@:0)?\s+$/m,"").replace(/^\(/gm,"{anonymous}(").split("\n")},opera11:function(g){var a="{anonymous}",h=/^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;var k=g.stacktrace.split("\n"),l=[];for(var c=0,f=k.length;c/,"$1").replace(//,a);l.push(b+"@"+j+" -- "+k[c+1].replace(/^\s+/,""))}}return l},opera10b:function(g){var a="{anonymous}",h=/^(.*)@(.+):(\d+)$/;var j=g.stacktrace.split("\n"),k=[];for(var c=0,f=j.length;c=0){l=l.substr(0,c)}if(l){b=l+b;d=k.exec(b);if(d&&d[1]){return d[1]}d=g.exec(b);if(d&&d[1]){return d[1]}d=h.exec(b);if(d&&d[1]){return d[1]}}}return"(?)"}};CanvasRenderingContext2D.prototype.installPattern=function(e){if(typeof(this.isPatternInstalled)!=="undefined"){throw"Must un-install old line pattern before installing a new one."}this.isPatternInstalled=true;var g=[0,0];var b=[];var f=this.beginPath;var d=this.lineTo;var c=this.moveTo;var a=this.stroke;this.uninstallPattern=function(){this.beginPath=f;this.lineTo=d;this.moveTo=c;this.stroke=a;this.uninstallPattern=undefined;this.isPatternInstalled=undefined};this.beginPath=function(){b=[];f.call(this)};this.moveTo=function(h,i){b.push([[h,i]]);c.call(this,h,i)};this.lineTo=function(h,j){var i=b[b.length-1];i.push([h,j])};this.stroke=function(){if(b.length===0){a.call(this);return}for(var p=0;pt){var q=e[m];if(g[1]){t+=g[1]}else{t+=q}if(t>r){g=[m,t-r];t=r}else{g=[(m+1)%e.length,0]}if(m%2===0){d.call(this,t,0)}else{c.call(this,t,0)}m=(m+1)%e.length}this.restore();l=h,u=s}}a.call(this);b=[]}};CanvasRenderingContext2D.prototype.uninstallPattern=function(){throw"Must install a line pattern before uninstalling it."};var DygraphOptions=(function(){var a=function(b){this.dygraph_=b;this.yAxes_=[];this.xAxis_={};this.series_={};this.global_=this.dygraph_.attrs_;this.user_=this.dygraph_.user_attrs_||{};this.labels_=[];this.highlightSeries_=this.get("highlightSeriesOpts")||{};this.reparseSeries()};a.AXIS_STRING_MAPPINGS_={y:0,Y:0,y1:0,Y1:0,y2:1,Y2:1};a.axisToIndex_=function(b){if(typeof(b)=="string"){if(a.AXIS_STRING_MAPPINGS_.hasOwnProperty(b)){return a.AXIS_STRING_MAPPINGS_[b]}throw"Unknown axis : "+b}if(typeof(b)=="number"){if(b===0||b===1){return b}throw"Dygraphs only supports two y-axes, indexed from 0-1."}if(b){throw"Unknown axis : "+b}return 0};a.prototype.reparseSeries=function(){var g=this.get("labels");if(!g){return}this.labels_=g.slice(1);this.yAxes_=[{series:[],options:{}}];this.xAxis_={options:{}};this.series_={};var h=!this.user_.series;if(h){var c=0;for(var j=0;j1){Dygraph.update(this.yAxes_[1].options,f.y2||{})}Dygraph.update(this.xAxis_.options,f.x||{})};a.prototype.get=function(c){var b=this.getGlobalUser_(c);if(b!==null){return b}return this.getGlobalDefault_(c)};a.prototype.getGlobalUser_=function(b){if(this.user_.hasOwnProperty(b)){return this.user_[b]}return null};a.prototype.getGlobalDefault_=function(b){if(this.global_.hasOwnProperty(b)){return this.global_[b]}if(Dygraph.DEFAULT_ATTRS.hasOwnProperty(b)){return Dygraph.DEFAULT_ATTRS[b]}return null};a.prototype.getForAxis=function(d,e){var f;var i;if(typeof(e)=="number"){f=e;i=f===0?"y":"y2"}else{if(e=="y1"){e="y"}if(e=="y"){f=0}else{if(e=="y2"){f=1}else{if(e=="x"){f=-1}else{throw"Unknown axis "+e}}}i=e}var g=(f==-1)?this.xAxis_:this.yAxes_[f];if(g){var h=g.options;if(h.hasOwnProperty(d)){return h[d]}}var c=this.getGlobalUser_(d);if(c!==null){return c}var b=Dygraph.DEFAULT_ATTRS.axes[i];if(b.hasOwnProperty(d)){return b[d]}return this.getGlobalDefault_(d)};a.prototype.getForSeries=function(c,e){if(e===this.dygraph_.getHighlightSeries()){if(this.highlightSeries_.hasOwnProperty(c)){return this.highlightSeries_[c]}}if(!this.series_.hasOwnProperty(e)){throw"Unknown series: "+e}var d=this.series_[e];var b=d.options;if(b.hasOwnProperty(c)){return b[c]}return this.getForAxis(c,d.yAxis)};a.prototype.numAxes=function(){return this.yAxes_.length};a.prototype.axisForSeries=function(b){return this.series_[b].yAxis};a.prototype.axisOptions=function(b){return this.yAxes_[b].options};a.prototype.seriesForAxis=function(b){return this.yAxes_[b].series};a.prototype.seriesNames=function(){return this.labels_};return a})();"use strict";var DygraphLayout=function(a){this.dygraph_=a;this.points=[];this.setNames=[];this.annotations=[];this.yAxes_=null;this.xTicks_=null;this.yTicks_=null};DygraphLayout.prototype.attr_=function(a){return this.dygraph_.attr_(a)};DygraphLayout.prototype.addDataset=function(a,b){this.points.push(b);this.setNames.push(a)};DygraphLayout.prototype.getPlotArea=function(){return this.area_};DygraphLayout.prototype.computePlotArea=function(){var a={x:0,y:0};a.w=this.dygraph_.width_-a.x-this.attr_("rightGap");a.h=this.dygraph_.height_;var b={chart_div:this.dygraph_.graphDiv,reserveSpaceLeft:function(c){var d={x:a.x,y:a.y,w:c,h:a.h};a.x+=c;a.w-=c;return d},reserveSpaceRight:function(c){var d={x:a.x+a.w-c,y:a.y,w:c,h:a.h};a.w-=c;return d},reserveSpaceTop:function(c){var d={x:a.x,y:a.y,w:a.w,h:c};a.y+=c;a.h-=c;return d},reserveSpaceBottom:function(c){var d={x:a.x,y:a.y+a.h-c,w:a.w,h:c};a.h-=c;return d},chartRect:function(){return{x:a.x,y:a.y,w:a.w,h:a.h}}};this.dygraph_.cascadeEvents_("layout",b);this.area_=a};DygraphLayout.prototype.setAnnotations=function(d){this.annotations=[];var e=this.attr_("xValueParser")||function(a){return a};for(var c=0;c=0)&&(f<=1)){this.xticks.push([f,b])}}this.yticks=[];for(d=0;d=0)&&(f<=1)){this.yticks.push([d,f,b])}}}};DygraphLayout.prototype._evaluateAnnotations=function(){var d;var g={};for(d=0;d=0;e--){if(f.childNodes[e].className==g){f.removeChild(f.childNodes[e])}}var c=document.bgColor;var d=this.dygraph_.graphDiv;while(d!=document){var a=d.currentStyle.backgroundColor;if(a&&a!="transparent"){c=a;break}d=d.parentNode}function b(j){if(j.w===0||j.h===0){return}var i=document.createElement("div");i.className=g;i.style.backgroundColor=c;i.style.position="absolute";i.style.left=j.x+"px";i.style.top=j.y+"px";i.style.width=j.w+"px";i.style.height=j.h+"px";f.appendChild(i)}var h=this.area;b({x:0,y:0,w:h.x,h:this.height});b({x:h.x,y:0,w:this.width-h.x,h:h.y});b({x:h.x+h.w,y:0,w:this.width-h.x-h.w,h:this.height});b({x:h.x,y:h.y+h.h,w:this.width-h.x,h:this.height-h.h-h.y})};DygraphCanvasRenderer._getIteratorPredicate=function(a){return a?DygraphCanvasRenderer._predicateThatSkipsEmptyPoints:null};DygraphCanvasRenderer._predicateThatSkipsEmptyPoints=function(b,a){return b[a].yval!==null};DygraphCanvasRenderer._drawStyledLine=function(i,a,m,q,f,n,d){var h=i.dygraph;var c=h.getOption("stepPlot",i.setName);if(!Dygraph.isArrayLike(q)){q=null}var l=h.getOption("drawGapEdgePoints",i.setName);var o=i.points;var k=Dygraph.createIterator(o,0,o.length,DygraphCanvasRenderer._getIteratorPredicate(h.getOption("connectSeparatedPoints")));var j=q&&(q.length>=2);var p=i.drawingContext;p.save();if(j){p.installPattern(q)}var b=DygraphCanvasRenderer._drawSeries(i,k,m,d,f,l,c,a);DygraphCanvasRenderer._drawPointsOnLine(i,b,n,a,d);if(j){p.uninstallPattern()}p.restore()};DygraphCanvasRenderer._drawSeries=function(v,t,m,h,p,s,g,q){var b=null;var w=null;var k=null;var j;var o;var l=[];var f=true;var n=v.drawingContext;n.beginPath();n.strokeStyle=q;n.lineWidth=m;var c=t.array_;var u=t.end_;var a=t.predicate_;for(var r=t.start_;r=0;C--){if(!D.visibility()[C]){I.splice(C,1)}}var h=(function(){for(var e=0;e=0;u--){var n=I[u];if(!D.getOption("fillGraph",n)){continue}var p=D.getOption("stepPlot",n);var x=q[u];var f=D.axisPropertiesForSeries(n);var d=1+f.minyval*f.yscale;if(d<0){d=0}else{if(d>1){d=1}}d=E.h*d+E.y;var B=c[u];var A=Dygraph.createIterator(B,0,B.length,DygraphCanvasRenderer._getIteratorPredicate(D.getOption("connectSeparatedPoints")));var m=NaN;var l=[-1,-1];var t;var b=new RGBColorParser(x);var H="rgba("+b.r+","+b.g+","+b.b+","+y+")";w.fillStyle=H;w.beginPath();var j,G=true;while(A.hasNext){var v=A.next();if(!Dygraph.isOK(v.y)){m=NaN;if(v.y_stacked!==null&&!isNaN(v.y_stacked)){s[v.canvasx]=E.h*v.y_stacked+E.y}continue}if(k){if(!G&&j==v.xval){continue}else{G=false;j=v.xval}a=s[v.canvasx];var o;if(a===undefined){o=d}else{if(r){o=a[0]}else{o=a}}t=[v.canvasy,o];if(p){if(l[0]===-1){s[v.canvasx]=[v.canvasy,d]}else{s[v.canvasx]=[v.canvasy,l[0]]}}else{s[v.canvasx]=v.canvasy}}else{t=[v.canvasy,d]}if(!isNaN(m)){w.moveTo(m,l[0]);if(p){w.lineTo(v.canvasx,l[0])}else{w.lineTo(v.canvasx,t[0])}if(r&&a){w.lineTo(v.canvasx,a[1])}else{w.lineTo(v.canvasx,t[1])}w.lineTo(m,l[1]);w.closePath()}l=t;m=v.canvasx}r=p;w.fill()}};"use strict";var Dygraph=function(d,c,b,a){this.is_initial_draw_=true;this.readyFns_=[];if(a!==undefined){this.warn("Using deprecated four-argument dygraph constructor");this.__old_init__(d,c,b,a)}else{this.__init__(d,c,b)}};Dygraph.NAME="Dygraph";Dygraph.VERSION="1.0.1";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.ANIMATION_STEPS=12;Dygraph.ANIMATION_DURATION=200;Dygraph.KMB_LABELS=["K","M","B","T","Q"];Dygraph.KMG2_BIG_LABELS=["k","M","G","T","P","E","Z","Y"];Dygraph.KMG2_SMALL_LABELS=["m","u","n","p","f","a","z","y"];Dygraph.numberValueFormatter=function(s,a,u,l){var e=a("sigFigs");if(e!==null){return Dygraph.floatFormat(s,e)}var c=a("digitsAfterDecimal");var o=a("maxNumberWidth");var b=a("labelsKMB");var r=a("labelsKMG2");var q;if(s!==0&&(Math.abs(s)>=Math.pow(10,o)||Math.abs(s)=0;h--,d/=f){if(p>=d){q=Dygraph.round_(s/d,c)+t[h];break}}if(r){var i=String(s.toExponential()).split("e-");if(i.length===2&&i[1]>=3&&i[1]<=24){if(i[1]%3>0){q=Dygraph.round_(i[0]/Dygraph.pow(10,(i[1]%3)),c)}else{q=Number(i[0]).toFixed(2)}q+=m[Math.floor(i[1]/3)-1]}}}return q};Dygraph.numberAxisLabelFormatter=function(a,d,c,b){return Dygraph.numberValueFormatter(a,c,b)};Dygraph.dateString_=function(e){var i=Dygraph.zeropad;var h=new Date(e);var f=""+h.getFullYear();var g=i(h.getMonth()+1);var a=i(h.getDate());var c="";var b=h.getHours()*3600+h.getMinutes()*60+h.getSeconds();if(b){c=" "+Dygraph.hmsString_(e)}return f+"/"+g+"/"+a+c};Dygraph.dateAxisFormatter=function(b,c){if(c>=Dygraph.DECADAL){return b.strftime("%Y")}else{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.Plotters=DygraphCanvasRenderer._Plotters;Dygraph.DEFAULT_ATTRS={highlightCircleSize:3,highlightSeriesOpts:null,highlightSeriesBackgroundAlpha:0.5,labelsDivWidth:250,labelsDivStyles:{},labelsSeparateLines:false,labelsShowZeroValues:true,labelsKMB:false,labelsKMG2:false,showLabelsOnHighlight:true,digitsAfterDecimal:2,maxNumberWidth:6,sigFigs:null,strokeWidth:1,strokeBorderWidth:0,strokeBorderColor:"white",axisTickSize:3,axisLabelFontSize:14,xAxisLabelWidth:50,yAxisLabelWidth:50,rightGap:5,showRoller:false,xValueParser:Dygraph.dateParser,delimiter:",",sigma:2,errorBars:false,fractions:false,wilsonInterval:true,customBars:false,fillGraph:false,fillAlpha:0.15,connectSeparatedPoints:false,stackedGraph:false,stackedGraphNaNFill:"all",hideOverlayOnMouseOut:true,legend:"onmouseover",stepPlot:false,avoidMinZero:false,xRangePad:0,yRangePad:null,drawAxesAtZero:false,titleHeight:28,xLabelHeight:18,yLabelWidth:18,drawXAxis:true,drawYAxis:true,axisLineColor:"black",axisLineWidth:0.3,gridLineWidth:0.3,axisLabelColor:"black",axisLabelFont:"Arial",axisLabelWidth:50,drawYGrid:true,drawXGrid:true,gridLineColor:"rgb(128,128,128)",interactionModel:null,animatedZooms:false,showRangeSelector:false,rangeSelectorHeight:40,rangeSelectorPlotStrokeColor:"#808FAB",rangeSelectorPlotFillColor:"#A7B1C4",plotter:[Dygraph.Plotters.fillPlotter,Dygraph.Plotters.errorPlotter,Dygraph.Plotters.linePlotter],plugins:[],axes:{x:{pixelsPerLabel:60,axisLabelFormatter:Dygraph.dateAxisFormatter,valueFormatter:Dygraph.dateString_,drawGrid:true,independentTicks:true,ticker:null},y:{pixelsPerLabel:30,valueFormatter:Dygraph.numberValueFormatter,axisLabelFormatter:Dygraph.numberAxisLabelFormatter,drawGrid:true,independentTicks:true,ticker:null},y2:{pixelsPerLabel:30,valueFormatter:Dygraph.numberValueFormatter,axisLabelFormatter:Dygraph.numberAxisLabelFormatter,drawGrid:false,independentTicks:false,ticker:null}}};Dygraph.HORIZONTAL=1;Dygraph.VERTICAL=2;Dygraph.PLUGINS=[];Dygraph.addedAnnotationCSS=false;Dygraph.prototype.__old_init__=function(f,d,e,b){if(e!==null){var a=["Date"];for(var c=0;c=0;d--){var f=a[d][0];var h=a[d][1];h.call(f,g);if(g.propagationStopped){break}}}return g.defaultPrevented};Dygraph.prototype.isZoomed=function(a){if(a===null||a===undefined){return this.zoomed_x_||this.zoomed_y_}if(a==="x"){return this.zoomed_x_}if(a==="y"){return this.zoomed_y_}throw"axis parameter is ["+a+"] must be null, 'x' or 'y'."};Dygraph.prototype.toString=function(){var a=this.maindiv_;var b=(a&&a.id)?a.id:a;return"[Dygraph "+b+"]"};Dygraph.prototype.attr_=function(b,a){return a?this.attributes_.getForSeries(b,a):this.attributes_.get(b)};Dygraph.prototype.getOption=function(a,b){return this.attr_(a,b)};Dygraph.prototype.getOptionForAxis=function(a,b){return this.attributes_.getForAxis(a,b)};Dygraph.prototype.optionsViewForAxis_=function(b){var a=this;return function(c){var d=a.user_attrs_.axes;if(d&&d[b]&&d[b].hasOwnProperty(c)){return d[b][c]}if(typeof(a.user_attrs_[c])!="undefined"){return a.user_attrs_[c]}d=a.attrs_.axes;if(d&&d[b]&&d[b].hasOwnProperty(c)){return d[b][c]}if(b=="y"&&a.axes_[0].hasOwnProperty(c)){return a.axes_[0][c]}else{if(b=="y2"&&a.axes_[1].hasOwnProperty(c)){return a.axes_[1][c]}}return a.attr_(c)}};Dygraph.prototype.rollPeriod=function(){return this.rollPeriod_};Dygraph.prototype.xAxisRange=function(){return this.dateWindow_?this.dateWindow_:this.xAxisExtremes()};Dygraph.prototype.xAxisExtremes=function(){var d=this.attr_("xRangePad")/this.plotter_.area.w;if(this.numRows()===0){return[0-d,1+d]}var c=this.rawData_[0][0];var b=this.rawData_[this.rawData_.length-1][0];if(d){var a=b-c;c-=a*d;b+=a*d}return[c,b]};Dygraph.prototype.yAxisRange=function(a){if(typeof(a)=="undefined"){a=0}if(a<0||a>=this.axes_.length){return null}var b=this.axes_[a];return[b.computedValueRange[0],b.computedValueRange[1]]};Dygraph.prototype.yAxisRanges=function(){var a=[];for(var b=0;bthis.rawData_.length){return null}if(a<0||a>this.rawData_[b].length){return null}return this.rawData_[b][a]};Dygraph.prototype.createInterface_=function(){var a=this.maindiv_;this.graphDiv=document.createElement("div");this.graphDiv.style.textAlign="left";a.appendChild(this.graphDiv);this.canvas_=Dygraph.createCanvas();this.canvas_.style.position="absolute";this.hidden_=this.createPlotKitCanvas_(this.canvas_);this.resizeElements_();this.canvas_ctx_=Dygraph.getContext(this.canvas_);this.hidden_ctx_=Dygraph.getContext(this.hidden_);this.graphDiv.appendChild(this.hidden_);this.graphDiv.appendChild(this.canvas_);this.mouseEventElement_=this.createMouseEventElement_();this.layout_=new DygraphLayout(this);var b=this;this.mouseMoveHandler_=function(c){b.mouseMove_(c)};this.mouseOutHandler_=function(f){var d=f.target||f.fromElement;var c=f.relatedTarget||f.toElement;if(Dygraph.isNodeContainedBy(d,b.graphDiv)&&!Dygraph.isNodeContainedBy(c,b.graphDiv)){b.mouseOut_(f)}};this.addAndTrackEvent(window,"mouseout",this.mouseOutHandler_);this.addAndTrackEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_);if(!this.resizeHandler_){this.resizeHandler_=function(c){b.resize()};this.addAndTrackEvent(window,"resize",this.resizeHandler_)}};Dygraph.prototype.resizeElements_=function(){this.graphDiv.style.width=this.width_+"px";this.graphDiv.style.height=this.height_+"px";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_.width=this.width_;this.hidden_.height=this.height_;this.hidden_.style.width=this.width_+"px";this.hidden_.style.height=this.height_+"px"};Dygraph.prototype.destroy=function(){this.canvas_ctx_.restore();this.hidden_ctx_.restore();var a=function(c){while(c.hasChildNodes()){a(c.firstChild);c.removeChild(c.firstChild)}};this.removeTrackedEvents_();Dygraph.removeEvent(window,"mouseout",this.mouseOutHandler_);Dygraph.removeEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_);Dygraph.removeEvent(window,"resize",this.resizeHandler_);this.resizeHandler_=null;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.prototype.createMouseEventElement_=function(){if(this.isUsingExcanvas_){var a=document.createElement("div");a.style.position="absolute";a.style.backgroundColor="white";a.style.filter="alpha(opacity=0)";a.style.width=this.width_+"px";a.style.height=this.height_+"px";this.graphDiv.appendChild(a);return a}else{return this.canvas_}};Dygraph.prototype.setColors_=function(){var g=this.getLabels();var e=g.length-1;this.colors_=[];this.colorsMap_={};var a=this.attr_("colors");var d;if(!a){var c=this.attr_("colorSaturation")||1;var b=this.attr_("colorValue")||0.5;var k=Math.ceil(e/2);for(d=1;d<=e;d++){if(!this.visibility()[d-1]){continue}var h=d%2?Math.ceil(d/2):(k+d/2);var f=(1*h/(1+e));var j=Dygraph.hsvToRGB(f,c,b);this.colors_.push(j);this.colorsMap_[g[d]]=j}}else{for(d=0;d=0;--b){var m=this.layout_.points[b];for(var g=0;g=l.length){continue}var m=l[e];if(!Dygraph.isValidPoint(m)){continue}var j=m.canvasy;if(i>m.canvasx&&e+10){var a=(i-m.canvasx)/o;j+=a*(k.canvasy-m.canvasy)}}}else{if(i0){var n=l[e-1];if(Dygraph.isValidPoint(n)){var o=m.canvasx-n.canvasx;if(o>0){var a=(m.canvasx-i)/o;j+=a*(n.canvasy-m.canvasy)}}}}if(d===0||j=0){var j=0;var k=this.attr_("labels");for(h=1;hj){j=c}}var l=this.previousVerticalX_;n.clearRect(l-j-1,0,2*j+2,this.height_)}}if(this.isUsingExcanvas_&&this.currentZoomRectArgs_){Dygraph.prototype.drawZoomRect_.apply(this,this.currentZoomRectArgs_)}if(this.selPoints_.length>0){var b=this.selPoints_[0].canvasx;n.save();for(h=0;h=0){if(f!=this.lastRow_){e=true}this.lastRow_=f;for(var d=0;d=0){e=true}this.lastRow_=-1}if(this.selPoints_.length){this.lastx_=this.selPoints_[0].xval}else{this.lastx_=-1}if(h!==undefined){if(this.highlightSet_!==h){e=true}this.highlightSet_=h}if(g!==undefined){this.lockedSet_=g}if(e){this.updateSelection_(undefined)}return e};Dygraph.prototype.mouseOut_=function(a){if(this.attr_("unhighlightCallback")){this.attr_("unhighlightCallback")(a)}if(this.attr_("hideOverlayOnMouseOut")&&!this.lockedSet_){this.clearSelection()}};Dygraph.prototype.clearSelection=function(){this.cascadeEvents_("deselect",{});this.lockedSet_=false;if(this.fadeLevel){this.animateSelection_(-1);return}this.canvas_ctx_.clearRect(0,0,this.width_,this.height_);this.fadeLevel=0;this.selPoints_=[];this.lastx_=-1;this.lastRow_=-1;this.highlightSet_=null};Dygraph.prototype.getSelection=function(){if(!this.selPoints_||this.selPoints_.length<1){return -1}for(var c=0;cg){a=g}if(ef){f=e}if(h===null||af){f=g}if(h===null||g=i){return}for(var p=i;po[1]){o[1]=a}if(a=1;u--){if(!this.visibility()[u-1]){continue}if(f){l=w[u];var z=f[0];var j=f[1];var g=null,y=null;for(r=0;r=z&&g===null){g=r}if(l[r][0]<=j){y=r}}if(g===null){g=0}var e=g;var d=true;while(d&&e>0){e--;d=b(l[e])}if(y===null){y=l.length-1}var c=y;d=true;while(d&&c0){this.setIndexByName_[g[0]]=0}var e=0;for(var f=1;f0){var a=this.readyFns_.pop();a(this)}}};Dygraph.prototype.computeYAxes_=function(){var b,d,c,f,a;if(this.axes_!==undefined&&this.user_attrs_.hasOwnProperty("valueRange")===false){b=[];for(c=0;c0){D=0}if(A<0){A=0}}if(D==Infinity){D=0}if(A==-Infinity){A=1}x=A-D;if(x===0){if(A!==0){x=Math.abs(A)}else{A=1;x=1}}var h,H;if(C){if(b){h=A+B*x;H=D}else{var E=Math.exp(Math.log(x)*B);h=A*E;H=D/E}}else{h=A+B*x;H=D-B*x;if(b&&!this.attr_("avoidMinZero")){if(H<0&&D>=0){H=0}if(h>0&&A<=0){h=0}}}c.extremeRange=[H,h]}if(c.valueWindow){c.computedValueRange=[c.valueWindow[0],c.valueWindow[1]]}else{if(c.valueRange){var e=g(c.valueRange[0])?c.extremeRange[0]:c.valueRange[0];var d=g(c.valueRange[1])?c.extremeRange[1]:c.valueRange[1];if(!b){if(c.logscale){var E=Math.exp(Math.log(x)*B);e*=E;d/=E}else{x=d-e;e-=x*B;d+=x*B}}c.computedValueRange=[e,d]}else{c.computedValueRange=c.extremeRange}}if(l){c.independentTicks=l;var r=this.optionsViewForAxis_("y"+(y?"2":""));var F=r("ticker");c.ticks=F(c.computedValueRange[0],c.computedValueRange[1],this.height_,r,this);if(!p){p=c}}}if(p===undefined){throw ('Configuration Error: At least one axis has to have the "independentTicks" option activated.')}for(var y=0;y=0){k-=l[z-d][1][0];h-=l[z-d][1][1]}var C=l[z][0];var w=h?k/h:0;if(this.attr_("errorBars")){if(this.attr_("wilsonInterval")){if(h){var s=w<0?0:w,u=h;var B=t*Math.sqrt(s*(1-s)/u+t*t/(4*u*u));var a=1+t*t/h;G=(s+t*t/(2*h)-B)/a;o=(s+t*t/(2*h)+B)/a;b[z]=[C,[s*e,(s-G)*e,(o-s)*e]]}else{b[z]=[C,[0,0,0]]}}else{A=h?t*Math.sqrt(w*(1-w)/h):1;b[z]=[C,[e*w,e*A,e*A]]}}else{b[z]=[C,e*w]}}}else{if(this.attr_("customBars")){G=0;var D=0;o=0;var g=0;for(z=0;z=0){var r=l[z-d];if(r[1][1]!==null&&!isNaN(r[1][1])){G-=r[1][0];D-=r[1][1];o-=r[1][2];g-=1}}if(g){b[z]=[l[z][0],[1*D/g,1*(D-G)/g,1*(o-D)/g]]}else{b[z]=[l[z][0],[null,null,null]]}}}else{if(!this.attr_("errorBars")){if(d==1){return l}for(z=0;z0&&(b[c-1]!="e"&&b[c-1]!="E"))||b.indexOf("/")>=0||isNaN(parseFloat(b))){a=true}else{if(b.length==8&&b>"19700101"&&b<"20371231"){a=true}}this.setXAxisOptions_(a)};Dygraph.prototype.setXAxisOptions_=function(a){if(a){this.attrs_.xValueParser=Dygraph.dateParser;this.attrs_.axes.x.valueFormatter=Dygraph.dateString_;this.attrs_.axes.x.ticker=Dygraph.dateTicker;this.attrs_.axes.x.axisLabelFormatter=Dygraph.dateAxisFormatter}else{this.attrs_.xValueParser=function(b){return parseFloat(b)};this.attrs_.axes.x.valueFormatter=function(b){return b};this.attrs_.axes.x.ticker=Dygraph.numericLinearTicks;this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}};Dygraph.prototype.parseFloat_=function(a,c,b){var e=parseFloat(a);if(!isNaN(e)){return e}if(/^ *$/.test(a)){return null}if(/^ *nan *$/i.test(a)){return NaN}var d="Unable to parse '"+a+"' as a number";if(b!==null&&c!==null){d+=" on line "+(1+c)+" ('"+b+"') of CSV."}this.error(d);return null};Dygraph.prototype.parseCSV_=function(t){var r=[];var s=Dygraph.detectLineDelimiter(t);var a=t.split(s||"\n");var g,k;var p=this.attr_("delimiter");if(a[0].indexOf(p)==-1&&a[0].indexOf("\t")>=0){p="\t"}var b=0;if(!("labels" in this.user_attrs_)){b=1;this.attrs_.labels=a[0].split(p);this.attributes_.reparseSeries()}var o=0;var m;var q=false;var c=this.attr_("labels").length;var f=false;for(var l=b;l0&&h[0]0){j=String.fromCharCode(65+(i-1)%26)+j.toLowerCase();i=Math.floor((i-1)/26)}return j};var h=w.getNumberOfColumns();var g=w.getNumberOfRows();var f=w.getColumnType(0);if(f=="date"||f=="datetime"){this.attrs_.xValueParser=Dygraph.dateParser;this.attrs_.axes.x.valueFormatter=Dygraph.dateString_;this.attrs_.axes.x.ticker=Dygraph.dateTicker;this.attrs_.axes.x.axisLabelFormatter=Dygraph.dateAxisFormatter}else{if(f=="number"){this.attrs_.xValueParser=function(i){return parseFloat(i)};this.attrs_.axes.x.valueFormatter=function(i){return i};this.attrs_.axes.x.ticker=Dygraph.numericLinearTicks;this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}else{this.error("only 'date', 'datetime' and 'number' types are supported for column 1 of DataTable input (Got '"+f+"')");return null}}var m=[];var t={};var s=false;var q,o;for(q=1;q0&&e[0]0){this.setAnnotations(a,true)}this.attributes_.reparseSeries()};Dygraph.prototype.start_=function(){var d=this.file_;if(typeof d=="function"){d=d()}if(Dygraph.isArrayLike(d)){this.rawData_=this.parseArray_(d);this.predraw_()}else{if(typeof d=="object"&&typeof d.getColumnRange=="function"){this.parseDataTable_(d);this.predraw_()}else{if(typeof d=="string"){var c=Dygraph.detectLineDelimiter(d);if(c){this.loadedEvent_(d)}else{var b;if(window.XMLHttpRequest){b=new XMLHttpRequest()}else{b=new ActiveXObject("Microsoft.XMLHTTP")}var a=this;b.onreadystatechange=function(){if(b.readyState==4){if(b.status===200||b.status===0){a.loadedEvent_(b.responseText)}}};b.open("GET",d,true);b.send(null)}}else{this.error("Unknown data format: "+(typeof d))}}}};Dygraph.prototype.updateOptions=function(e,b){if(typeof(b)=="undefined"){b=false}var d=e.file;var c=Dygraph.mapLegacyOptions_(e);if("rollPeriod" in c){this.rollPeriod_=c.rollPeriod}if("dateWindow" in c){this.dateWindow_=c.dateWindow;if(!("isZoomedIgnoreProgrammaticZoom" in c)){this.zoomed_x_=(c.dateWindow!==null)}}if("valueRange" in c&&!("isZoomedIgnoreProgrammaticZoom" in c)){this.zoomed_y_=(c.valueRange!==null)}var a=Dygraph.isPixelChangingOptionList(this.attr_("labels"),c);Dygraph.updateDeep(this.user_attrs_,c);this.attributes_.reparseSeries();if(d){this.file_=d;if(!b){this.start_()}}else{if(!b){if(a){this.predraw_()}else{this.renderGraph_(false)}}}};Dygraph.mapLegacyOptions_=function(c){var a={};for(var b in c){if(b=="file"){continue}if(c.hasOwnProperty(b)){a[b]=c[b]}}var e=function(g,f,h){if(!a.axes){a.axes={}}if(!a.axes[g]){a.axes[g]={}}a.axes[g][f]=h};var d=function(f,g,h){if(typeof(c[f])!="undefined"){Dygraph.warn("Option "+f+" is deprecated. Use the "+h+" option for the "+g+" axis instead. (e.g. { axes : { "+g+" : { "+h+" : ... } } } (see http://dygraphs.com/per-axis.html for more information.");e(g,h,c[f]);delete a[f]}};d("xValueFormatter","x","valueFormatter");d("pixelsPerXLabel","x","pixelsPerLabel");d("xAxisLabelFormatter","x","axisLabelFormatter");d("xTicker","x","ticker");d("yValueFormatter","y","valueFormatter");d("pixelsPerYLabel","y","pixelsPerLabel");d("yAxisLabelFormatter","y","axisLabelFormatter");d("yTicker","y","ticker");return a};Dygraph.prototype.resize=function(d,b){if(this.resize_lock){return}this.resize_lock=true;if((d===null)!=(b===null)){this.warn("Dygraph.resize() should be called with zero parameters or two non-NULL parameters. Pretending it was zero.");d=b=null}var a=this.width_;var c=this.height_;if(d){this.maindiv_.style.width=d+"px";this.maindiv_.style.height=b+"px";this.width_=d;this.height_=b}else{this.width_=this.maindiv_.clientWidth;this.height_=this.maindiv_.clientHeight}if(a!=this.width_||c!=this.height_){this.resizeElements_();this.predraw_()}this.resize_lock=false};Dygraph.prototype.adjustRoll=function(a){this.rollPeriod_=a;this.predraw_()};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.predraw_()}};Dygraph.prototype.size=function(){return{width:this.width_,height:this.height_}};Dygraph.prototype.setAnnotations=function(b,a){Dygraph.addAnnotationRule();this.annotations_=b;if(!this.layout_){this.warn("Tried to setAnnotations before dygraph was ready. Try setting them in a ready() block. See dygraphs.com/tests/annotation.html");return}this.layout_.setAnnotations(this.annotations_);if(!a){this.predraw_()}};Dygraph.prototype.annotations=function(){return this.annotations_};Dygraph.prototype.getLabels=function(){var a=this.attr_("labels");return a?a.slice():null};Dygraph.prototype.indexFromSetName=function(a){return this.setIndexByName_[a]};Dygraph.prototype.ready=function(a){if(this.is_initial_draw_){this.readyFns_.push(a)}else{a(this)}};Dygraph.addAnnotationRule=function(){if(Dygraph.addedAnnotationCSS){return}var f="border: 1px solid black; background-color: white; text-align: center;";var e=document.createElement("style");e.type="text/css";document.getElementsByTagName("head")[0].appendChild(e);for(var b=0;bb){return -1}if(i===null||i===undefined){i=0}var h=function(j){return j>=0&&ja){if(i>0){f=g-1;if(h(f)&&d[f]a){return g}}return Dygraph.binarySearch(a,d,i,g+1,b)}}}return -1};Dygraph.dateParser=function(a){var b;var c;if(a.search("-")==-1||a.search("T")!=-1||a.search("Z")!=-1){c=Dygraph.dateStrToMillis(a);if(c&&!isNaN(c)){return c}}if(a.search("-")!=-1){b=a.replace("-","/","g");while(b.search("-")!=-1){b=b.replace("-","/")}c=Dygraph.dateStrToMillis(b)}else{if(a.length==8){b=a.substr(0,4)+"/"+a.substr(4,2)+"/"+a.substr(6,2);c=Dygraph.dateStrToMillis(b)}else{c=Dygraph.dateStrToMillis(a)}}if(!c||isNaN(c)){Dygraph.error("Couldn't parse "+a+" as a date")}return c};Dygraph.dateStrToMillis=function(a){return new Date(a).getTime()};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.updateDeep=function(b,d){function c(e){return(typeof Node==="object"?e instanceof Node:typeof e==="object"&&typeof e.nodeType==="number"&&typeof e.nodeName==="string")}if(typeof(d)!="undefined"&&d!==null){for(var a in d){if(d.hasOwnProperty(a)){if(d[a]===null){b[a]=null}else{if(Dygraph.isArrayLike(d[a])){b[a]=d[a].slice()}else{if(c(d[a])){b[a]=d[a]}else{if(typeof(d[a])=="object"){if(typeof(b[a])!="object"||b[a]===null){b[a]={}}Dygraph.updateDeep(b[a],d[a])}else{b[a]=d[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=g){return}Dygraph.requestAnimFrame.call(window,function(){var l=new Date().getTime();var j=l-b;d=i;i=Math.floor(j/f);var k=i-d;var m=(i+k)>e;if(m||(i>=e)){h(e);a()}else{if(k!==0){h(i)}c()}})})()};Dygraph.isPixelChangingOptionList=function(h,e){var d={annotationClickHandler:true,annotationDblClickHandler:true,annotationMouseOutHandler:true,annotationMouseOverHandler:true,axisLabelColor:true,axisLineColor:true,axisLineWidth:true,clickCallback:true,digitsAfterDecimal:true,drawCallback:true,drawHighlightPointCallback:true,drawPoints:true,drawPointCallback:true,drawXGrid:true,drawYGrid:true,fillAlpha:true,gridLineColor:true,gridLineWidth:true,hideOverlayOnMouseOut:true,highlightCallback:true,highlightCircleSize:true,interactionModel:true,isZoomedIgnoreProgrammaticZoom:true,labelsDiv:true,labelsDivStyles:true,labelsDivWidth:true,labelsKMB:true,labelsKMG2:true,labelsSeparateLines:true,labelsShowZeroValues:true,legend:true,maxNumberWidth:true,panEdgeFraction:true,pixelsPerYLabel:true,pointClickCallback:true,pointSize:true,rangeSelectorPlotFillColor:true,rangeSelectorPlotStrokeColor:true,showLabelsOnHighlight:true,showRoller:true,sigFigs:true,strokeWidth:true,underlayCallback:true,unhighlightCallback:true,xAxisLabelFormatter:true,xTicker:true,xValueFormatter:true,yAxisLabelFormatter:true,yValueFormatter:true,zoomCallback:true};var a=false;var b={};if(h){for(var f=1;fc.boundedDates[1]){h=h-(a-c.boundedDates[1]);a=h+c.dateRange}}k.dateWindow_=[h,a];if(c.is2DPan){var d=c.dragEndY-c.dragStartY;for(var j=0;j=10&&e.dragDirection==Dygraph.HORIZONTAL){var f=Math.min(e.dragStartX,e.dragEndX),k=Math.max(e.dragStartX,e.dragEndX);f=Math.max(f,b.x);k=Math.min(k,b.x+b.w);if(f=10&&e.dragDirection==Dygraph.VERTICAL){var j=Math.min(e.dragStartY,e.dragEndY),a=Math.max(e.dragStartY,e.dragEndY);j=Math.max(j,b.y);a=Math.min(a,b.y+b.h);if(j1){d.startTimeForDoubleTapMs=null}var h=[];for(var c=0;c=2){d.initialPinchCenter={pageX:0.5*(h[0].pageX+h[1].pageX),pageY:0.5*(h[0].pageY+h[1].pageY),dataX:0.5*(h[0].dataX+h[1].dataX),dataY:0.5*(h[0].dataY+h[1].dataY)};var a=180/Math.PI*Math.atan2(d.initialPinchCenter.pageY-h[0].pageY,h[0].pageX-d.initialPinchCenter.pageX);a=Math.abs(a);if(a>90){a=90-a}d.touchDirections={x:(a<(90-45/2)),y:(a>45/2)}}}d.initialRange={x:e.xAxisRange(),y:e.yAxisRange()}};Dygraph.Interaction.moveTouch=function(n,q,d){d.startTimeForDoubleTapMs=null;var p,l=[];for(p=0;p=2){var e=(a[1].pageX-j.pageX);w=(l[1].pageX-h.pageX)/e;var v=(a[1].pageY-j.pageY);c=(l[1].pageY-h.pageY)/v}}w=Math.min(8,Math.max(0.125,w));c=Math.min(8,Math.max(0.125,c));var u=false;if(d.touchDirections.x){q.dateWindow_=[j.dataX-m.dataX+(d.initialRange.x[0]-j.dataX)/w,j.dataX-m.dataX+(d.initialRange.x[1]-j.dataX)/w];u=true}if(d.touchDirections.y){for(p=0;p<1;p++){var b=q.axes_[p];var s=q.attributes_.getForAxis("logscale",p);if(s){}else{b.valueWindow=[j.dataY-m.dataY+(d.initialRange.y[0]-j.dataY)/c,j.dataY-m.dataY+(d.initialRange.y[1]-j.dataY)/c];u=true}}}q.drawGraph_(false);if(u&&l.length>1&&q.attr_("zoomCallback")){var r=q.xAxisRange();q.attr_("zoomCallback")(r[0],r[1],q.yAxisRanges())}};Dygraph.Interaction.endTouch=function(e,d,c){if(e.touches.length!==0){Dygraph.Interaction.startTouch(e,d,c)}else{if(e.changedTouches.length==1){var a=new Date().getTime();var b=e.changedTouches[0];if(c.startTimeForDoubleTapMs&&a-c.startTimeForDoubleTapMs<500&&c.doubleTapX&&Math.abs(c.doubleTapX-b.screenX)<50&&c.doubleTapY&&Math.abs(c.doubleTapY-b.screenY)<50){d.resetZoom()}else{c.startTimeForDoubleTapMs=a;c.doubleTapX=b.screenX;c.doubleTapY=b.screenY}}}};Dygraph.Interaction.defaultModel={mousedown:function(c,b,a){if(c.button&&c.button==2){return}a.initializeMouseDown(c,b,a);if(c.altKey||c.shiftKey){Dygraph.startPan(c,b,a)}else{Dygraph.startZoom(c,b,a)}},mousemove:function(c,b,a){if(a.isZooming){Dygraph.moveZoom(c,b,a)}else{if(a.isPanning){Dygraph.movePan(c,b,a)}}},mouseup:function(c,b,a){if(a.isZooming){Dygraph.endZoom(c,b,a)}else{if(a.isPanning){Dygraph.endPan(c,b,a)}}},touchstart:function(c,b,a){Dygraph.Interaction.startTouch(c,b,a)},touchmove:function(c,b,a){Dygraph.Interaction.moveTouch(c,b,a)},touchend:function(c,b,a){Dygraph.Interaction.endTouch(c,b,a)},mouseout:function(c,b,a){if(a.isZooming){a.dragEndX=null;a.dragEndY=null;b.clearZoomRect_()}},dblclick:function(c,b,a){if(a.cancelNextDblclick){a.cancelNextDblclick=false;return}if(c.altKey||c.shiftKey){return}b.resetZoom()}};Dygraph.DEFAULT_ATTRS.interactionModel=Dygraph.Interaction.defaultModel;Dygraph.defaultInteractionModel=Dygraph.Interaction.defaultModel;Dygraph.endZoom=Dygraph.Interaction.endZoom;Dygraph.moveZoom=Dygraph.Interaction.moveZoom;Dygraph.startZoom=Dygraph.Interaction.startZoom;Dygraph.endPan=Dygraph.Interaction.endPan;Dygraph.movePan=Dygraph.Interaction.movePan;Dygraph.startPan=Dygraph.Interaction.startPan;Dygraph.Interaction.nonInteractiveModel_={mousedown:function(c,b,a){a.initializeMouseDown(c,b,a)},mouseup:function(c,b,a){a.dragEndX=b.dragGetX_(c,a);a.dragEndY=b.dragGetY_(c,a);var e=Math.abs(a.dragEndX-a.dragStartX);var d=Math.abs(a.dragEndY-a.dragStartY);if(e<2&&d<2&&b.lastx_!==undefined&&b.lastx_!=-1){Dygraph.Interaction.treatMouseOpAsClick(b,c,a)}}};Dygraph.Interaction.dragIsPanInteractionModel={mousedown:function(c,b,a){a.initializeMouseDown(c,b,a);Dygraph.startPan(c,b,a)},mousemove:function(c,b,a){if(a.isPanning){Dygraph.movePan(c,b,a)}},mouseup:function(c,b,a){if(a.isPanning){Dygraph.endPan(c,b,a)}}};"use strict";Dygraph.TickList=undefined;Dygraph.Ticker=undefined;Dygraph.numericLinearTicks=function(d,c,i,g,f,h){var e=function(a){if(a==="logscale"){return false}return g(a)};return Dygraph.numericTicks(d,c,i,e,f,h)};Dygraph.numericTicks=function(F,E,u,p,d,q){var z=(p("pixelsPerLabel"));var G=[];var C,A,t,y;if(q){for(C=0;C=y/4){for(var r=H;r>=l;r--){var m=Dygraph.PREFERRED_LOG_TICK_VALUES[r];var k=Math.log(m/F)/Math.log(E/F)*u;var D={v:m};if(s===null){s={tickValue:m,pixel_coord:k}}else{if(Math.abs(k-s.pixel_coord)>=z){s={tickValue:m,pixel_coord:k}}else{D.label=""}}G.push(D)}G.reverse()}}if(G.length===0){var g=p("labelsKMG2");var n,h;if(g){n=[1,2,4,8,16,32,64,128,256];h=16}else{n=[1,2,5,10,20,50,100];h=10}var w=Math.ceil(u/z);var o=Math.abs(E-F)/w;var v=Math.floor(Math.log(o)/Math.log(h));var f=Math.pow(h,v);var I,x,c,e;for(A=0;Az){break}}if(x>c){I*=-1}for(C=0;C=0){return Dygraph.getDateAxis(e,c,d,g,f)}else{return[]}};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.CENTENNIAL=20;Dygraph.NUM_GRANULARITIES=21;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.LONG_TICK_PLACEMENTS=[];Dygraph.LONG_TICK_PLACEMENTS[Dygraph.MONTHLY]={months:[0,1,2,3,4,5,6,7,8,9,10,11],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.QUARTERLY]={months:[0,3,6,9],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.BIANNUAL]={months:[0,6],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.ANNUAL]={months:[0],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.DECADAL]={months:[0],year_mod:10};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.CENTENNIAL]={months:[0],year_mod:100};Dygraph.PREFERRED_LOG_TICK_VALUES=function(){var c=[];for(var b=-39;b<=39;b++){var a=Math.pow(10,b);for(var d=1;d<=9;d++){var e=a*d;c.push(e)}}return c}();Dygraph.pickDateTickGranularity=function(d,c,j,h){var g=(h("pixelsPerLabel"));for(var f=0;f=g){return f}}return -1};Dygraph.numDateTicks=function(e,b,f){if(f=Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]);for(m=p;m<=l;m+=c){A=new Date(m);if(e&&A.getTimezoneOffset()!=B){var k=A.getTimezoneOffset()-B;m+=k*60*1000;A=new Date(m);B=A.getTimezoneOffset();if(new Date(m+c).getTimezoneOffset()!=B){m+=c;A=new Date(m);B=A.getTimezoneOffset()}}C.push({v:m,label:w(A,a,n,z)})}}else{var f;var r=1;if(al){continue}C.push({v:m,label:w(new Date(m),a,n,z)})}}}return C};if(Dygraph&&Dygraph.DEFAULT_ATTRS&&Dygraph.DEFAULT_ATTRS.axes&&Dygraph.DEFAULT_ATTRS.axes["x"]&&Dygraph.DEFAULT_ATTRS.axes["y"]&&Dygraph.DEFAULT_ATTRS.axes["y2"]){Dygraph.DEFAULT_ATTRS.axes["x"]["ticker"]=Dygraph.dateTicker;Dygraph.DEFAULT_ATTRS.axes["y"]["ticker"]=Dygraph.numericTicks;Dygraph.DEFAULT_ATTRS.axes["y2"]["ticker"]=Dygraph.numericTicks}Dygraph.Plugins={};Dygraph.Plugins.Annotations=(function(){var a=function(){this.annotations_=[]};a.prototype.toString=function(){return"Annotations Plugin"};a.prototype.activate=function(b){return{clearChart:this.clearChart,didDrawChart:this.didDrawChart}};a.prototype.detachLabels=function(){for(var c=0;cu.x+u.w||l.canvasyu.y+u.h){continue}var w=l.annotation;var n=6;if(w.hasOwnProperty("tickHeight")){n=w.tickHeight}var j=document.createElement("div");for(var A in x){if(x.hasOwnProperty(A)){j.style[A]=x[A]}}if(!w.hasOwnProperty("icon")){j.className="dygraphDefaultAnnotation"}if(w.hasOwnProperty("cssClass")){j.className+=" "+w.cssClass}var m=w.hasOwnProperty("width")?w.width:16;var k=w.hasOwnProperty("height")?w.height:16;if(w.hasOwnProperty("icon")){var z=document.createElement("img");z.src=w.icon;z.width=m;z.height=k;j.appendChild(z)}else{if(l.annotation.hasOwnProperty("shortText")){j.appendChild(document.createTextNode(l.annotation.shortText))}}var c=l.canvasx-m/2;j.style.left=c+"px";var f=0;if(w.attachAtBottom){var d=(u.y+u.h-k-n);if(q[c]){d-=q[c]}else{q[c]=0}q[c]+=(n+k);f=d}else{f=l.canvasy-k-n}j.style.top=f+"px";j.style.width=m+"px";j.style.height=k+"px";j.title=l.annotation.text;j.style.color=t.colorsMap_[l.name];j.style.borderColor=t.colorsMap_[l.name];w.div=j;t.addAndTrackEvent(j,"click",b("clickHandler","annotationClickHandler",l,this));t.addAndTrackEvent(j,"mouseover",b("mouseOverHandler","annotationMouseOverHandler",l,this));t.addAndTrackEvent(j,"mouseout",b("mouseOutHandler","annotationMouseOutHandler",l,this));t.addAndTrackEvent(j,"dblclick",b("dblClickHandler","annotationDblClickHandler",l,this));h.appendChild(j);this.annotations_.push(j);var o=v.drawingContext;o.save();o.strokeStyle=t.colorsMap_[l.name];o.beginPath();if(!w.attachAtBottom){o.moveTo(l.canvasx,l.canvasy);o.lineTo(l.canvasx,l.canvasy-2-n)}else{var d=f+k;o.moveTo(l.canvasx,d);o.lineTo(l.canvasx,d+n)}o.closePath();o.stroke();o.restore()}};a.prototype.destroy=function(){this.detachLabels()};return a})();Dygraph.Plugins.Axes=(function(){var a=function(){this.xlabels_=[];this.ylabels_=[]};a.prototype.toString=function(){return"Axes Plugin"};a.prototype.activate=function(b){return{layout:this.layout,clearChart:this.clearChart,willDrawChart:this.willDrawChart}};a.prototype.layout=function(f){var d=f.dygraph;if(d.getOption("drawYAxis")){var b=d.getOption("yAxisLabelWidth")+2*d.getOption("axisTickSize");f.reserveSpaceLeft(b)}if(d.getOption("drawXAxis")){var c;if(d.getOption("xAxisHeight")){c=d.getOption("xAxisHeight")}else{c=d.getOptionForAxis("axisLabelFontSize","x")+2*d.getOption("axisTickSize")}f.reserveSpaceBottom(c)}if(d.numAxes()==2){if(d.getOption("drawYAxis")){var b=d.getOption("yAxisLabelWidth")+2*d.getOption("axisTickSize");f.reserveSpaceRight(b)}}else{if(d.numAxes()>2){d.error("Only two y-axes are supported at this time. (Trying to use "+d.numAxes()+")")}}};a.prototype.detachLabels=function(){function b(d){for(var c=0;c0){var h=F.numAxes();for(D=0;Dd){s.style.bottom="0px"}else{s.style.top=z+"px"}if(E[0]===0){s.style.left=(G.x-F.getOption("yAxisLabelWidth")-F.getOption("axisTickSize"))+"px";s.style.textAlign="right"}else{if(E[0]==1){s.style.left=(G.x+G.w+F.getOption("axisTickSize"))+"px";s.style.textAlign="left"}}s.style.width=F.getOption("yAxisLabelWidth")+"px";v.appendChild(s);this.ylabels_.push(s)}var n=this.ylabels_[0];var k=F.getOptionForAxis("axisLabelFontSize","y");var q=parseInt(n.style.top,10)+k;if(q>d-k){n.style.top=(parseInt(n.style.top,10)-k/2)+"px"}}var c;if(F.getOption("drawAxesAtZero")){var w=F.toPercentXCoord(0);if(w>1||w<0||isNaN(w)){w=0}c=B(G.x+w*G.w)}else{c=B(G.x)}j.strokeStyle=F.getOptionForAxis("axisLineColor","y");j.lineWidth=F.getOptionForAxis("axisLineWidth","y");j.beginPath();j.moveTo(c,A(G.y));j.lineTo(c,A(G.y+G.h));j.closePath();j.stroke();if(F.numAxes()==2){j.strokeStyle=F.getOptionForAxis("axisLineColor","y2");j.lineWidth=F.getOptionForAxis("axisLineWidth","y2");j.beginPath();j.moveTo(A(G.x+G.w),A(G.y));j.lineTo(A(G.x+G.w),A(G.y+G.h));j.closePath();j.stroke()}}if(F.getOption("drawXAxis")){if(I.xticks){for(D=0;DJ){l=J-F.getOption("xAxisLabelWidth");s.style.textAlign="right"}if(l<0){l=0;s.style.textAlign="left"}s.style.left=l+"px";s.style.width=F.getOption("xAxisLabelWidth")+"px";v.appendChild(s);this.xlabels_.push(s)}}j.strokeStyle=F.getOptionForAxis("axisLineColor","x");j.lineWidth=F.getOptionForAxis("axisLineWidth","x");j.beginPath();var b;if(F.getOption("drawAxesAtZero")){var w=F.toPercentYCoord(0,0);if(w>1||w<0){w=1}b=A(G.y+w*G.h)}else{b=A(G.y+G.h)}j.moveTo(B(G.x),b);j.lineTo(B(G.x+G.w),b);j.closePath();j.stroke()}j.restore()};return a})();Dygraph.Plugins.ChartLabels=(function(){var c=function(){this.title_div_=null;this.xlabel_div_=null;this.ylabel_div_=null;this.y2label_div_=null};c.prototype.toString=function(){return"ChartLabels Plugin"};c.prototype.activate=function(d){return{layout:this.layout,didDrawChart:this.didDrawChart}};var b=function(d){var e=document.createElement("div");e.style.position="absolute";e.style.left=d.x+"px";e.style.top=d.y+"px";e.style.width=d.w+"px";e.style.height=d.h+"px";return e};c.prototype.detachLabels_=function(){var e=[this.title_div_,this.xlabel_div_,this.ylabel_div_,this.y2label_div_];for(var d=0;d=2)}}u=t.yticks;l.save();for(p=0;p=2);if(n){l.installPattern(d)}l.strokeStyle=q.getOptionForAxis("gridLineColor","x");l.lineWidth=q.getOptionForAxis("gridLineWidth","x");for(p=0;p":" ")}m=w.getOption("strokePattern",z[u]);s=d(m,q.color,f);r+=""+s+" "+z[u]+""}return r}var A=w.optionsViewForAxis_("x");var o=A("valueFormatter");r=o(p,A,z[0],w);if(r!==""){r+=":"}var v=[];var j=w.numAxes();for(u=0;u"}var q=w.getPropertiesForSeries(t.name);var n=v[q.axis-1];var y=n("valueFormatter");var e=y(t.yval,n,t.name,w);var h=(t.name==B)?" class='highlight'":"";r+=" "+t.name+": "+e+""}return r};d=function(s,h,r){var e=(/MSIE/.test(navigator.userAgent)&&!window.opera);if(e){return"—"}if(!s||s.length<=1){return'
'}var l,k,f,o;var g=0,q=0;var p=[];var n;for(l=0;l<=s.length;l++){g+=s[l%s.length]}n=Math.floor(r/(g-s[0]));if(n>1){for(l=0;l'}}return m};return c})();Dygraph.Plugins.RangeSelector=(function(){var a=function(){this.isIE_=/MSIE/.test(navigator.userAgent)&&!window.opera;this.hasTouchInterface_=typeof(TouchEvent)!="undefined";this.isMobileDevice_=/mobile|android/gi.test(navigator.appVersion);this.interfaceCreated_=false};a.prototype.toString=function(){return"RangeSelector Plugin"};a.prototype.activate=function(b){this.dygraph_=b;this.isUsingExcanvas_=b.isUsingExcanvas_;if(this.getOption_("showRangeSelector")){this.createInterface_()}return{layout:this.reserveSpace_,predraw:this.renderStaticLayer_,didDrawChart:this.renderInteractiveLayer_}};a.prototype.destroy=function(){this.bgcanvas_=null;this.fgcanvas_=null;this.leftZoomHandle_=null;this.rightZoomHandle_=null;this.iePanOverlay_=null};a.prototype.getOption_=function(b){return this.dygraph_.getOption(b)};a.prototype.setDefaultOption_=function(b,c){return this.dygraph_.attrs_[b]=c};a.prototype.createInterface_=function(){this.createCanvases_();if(this.isUsingExcanvas_){this.createIEPanOverlay_()}this.createZoomHandles_();this.initInteraction_();if(this.getOption_("animatedZooms")){this.dygraph_.warn("Animated zooms and range selector are not compatible; disabling animatedZooms.");this.dygraph_.updateOptions({animatedZooms:false},true)}this.interfaceCreated_=true;this.addToGraph_()};a.prototype.addToGraph_=function(){var b=this.graphDiv_=this.dygraph_.graphDiv;b.appendChild(this.bgcanvas_);b.appendChild(this.fgcanvas_);b.appendChild(this.leftZoomHandle_);b.appendChild(this.rightZoomHandle_)};a.prototype.removeFromGraph_=function(){var b=this.graphDiv_;b.removeChild(this.bgcanvas_);b.removeChild(this.fgcanvas_);b.removeChild(this.leftZoomHandle_);b.removeChild(this.rightZoomHandle_);this.graphDiv_=null};a.prototype.reserveSpace_=function(b){if(this.getOption_("showRangeSelector")){b.reserveSpaceBottom(this.getOption_("rangeSelectorHeight")+4)}};a.prototype.renderStaticLayer_=function(){if(!this.updateVisibility_()){return}this.resize_();this.drawStaticLayer_()};a.prototype.renderInteractiveLayer_=function(){if(!this.updateVisibility_()||this.isChangingRange_){return}this.placeZoomHandles_();this.drawInteractiveLayer_()};a.prototype.updateVisibility_=function(){var b=this.getOption_("showRangeSelector");if(b){if(!this.interfaceCreated_){this.createInterface_()}else{if(!this.graphDiv_||!this.graphDiv_.parentNode){this.addToGraph_()}}}else{if(this.graphDiv_){this.removeFromGraph_();var c=this.dygraph_;setTimeout(function(){c.width_=0;c.resize()},1)}}return b};a.prototype.resize_=function(){function d(e,f){e.style.top=f.y+"px";e.style.left=f.x+"px";e.width=f.w;e.height=f.h;e.style.width=e.width+"px";e.style.height=e.height+"px"}var c=this.dygraph_.layout_.getPlotArea();var b=0;if(this.getOption_("drawXAxis")){b=this.getOption_("xAxisHeight")||(this.getOption_("axisLabelFontSize")+2*this.getOption_("axisTickSize"))}this.canvasRect_={x:c.x,y:c.y+c.h+b+4,w:c.w,h:this.getOption_("rangeSelectorHeight")};d(this.bgcanvas_,this.canvasRect_);d(this.fgcanvas_,this.canvasRect_)};a.prototype.createCanvases_=function(){this.bgcanvas_=Dygraph.createCanvas();this.bgcanvas_.className="dygraph-rangesel-bgcanvas";this.bgcanvas_.style.position="absolute";this.bgcanvas_.style.zIndex=9;this.bgcanvas_ctx_=Dygraph.getContext(this.bgcanvas_);this.fgcanvas_=Dygraph.createCanvas();this.fgcanvas_.className="dygraph-rangesel-fgcanvas";this.fgcanvas_.style.position="absolute";this.fgcanvas_.style.zIndex=9;this.fgcanvas_.style.cursor="default";this.fgcanvas_ctx_=Dygraph.getContext(this.fgcanvas_)};a.prototype.createIEPanOverlay_=function(){this.iePanOverlay_=document.createElement("div");this.iePanOverlay_.style.position="absolute";this.iePanOverlay_.style.backgroundColor="white";this.iePanOverlay_.style.filter="alpha(opacity=0)";this.iePanOverlay_.style.display="none";this.iePanOverlay_.style.cursor="move";this.fgcanvas_.appendChild(this.iePanOverlay_)};a.prototype.createZoomHandles_=function(){var b=new Image();b.className="dygraph-rangesel-zoomhandle";b.style.position="absolute";b.style.zIndex=10;b.style.visibility="hidden";b.style.cursor="col-resize";if(/MSIE 7/.test(navigator.userAgent)){b.width=7;b.height=14;b.style.backgroundColor="white";b.style.border="1px solid #333333"}else{b.width=9;b.height=16;b.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAzwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7sqSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII="}if(this.isMobileDevice_){b.width*=2;b.height*=2}this.leftZoomHandle_=b;this.rightZoomHandle_=b.cloneNode(false)};a.prototype.initInteraction_=function(){var o=this;var i=this.isIE_?document:window;var u=0;var v=null;var s=false;var d=false;var g=!this.isMobileDevice_&&!this.isUsingExcanvas_;var k=new Dygraph.IFrameTarp();var p,f,r,j,w,h,x,t,q,c,l;var e,n,m;p=function(C){var B=o.dygraph_.xAxisExtremes();var z=(B[1]-B[0])/o.canvasRect_.w;var A=B[0]+(C.leftHandlePos-o.canvasRect_.x)*z;var y=B[0]+(C.rightHandlePos-o.canvasRect_.x)*z;return[A,y]};f=function(y){Dygraph.cancelEvent(y);s=true;u=y.clientX;v=y.target?y.target:y.srcElement;if(y.type==="mousedown"||y.type==="dragstart"){Dygraph.addEvent(i,"mousemove",r);Dygraph.addEvent(i,"mouseup",j)}o.fgcanvas_.style.cursor="col-resize";k.cover();return true};r=function(C){if(!s){return false}Dygraph.cancelEvent(C);var z=C.clientX-u;if(Math.abs(z)<4){return true}u=C.clientX;var B=o.getZoomHandleStatus_();var y;if(v==o.leftZoomHandle_){y=B.leftHandlePos+z;y=Math.min(y,B.rightHandlePos-v.width-3);y=Math.max(y,o.canvasRect_.x)}else{y=B.rightHandlePos+z;y=Math.min(y,o.canvasRect_.x+o.canvasRect_.w);y=Math.max(y,B.leftHandlePos+v.width+3)}var A=v.width/2;v.style.left=(y-A)+"px";o.drawInteractiveLayer_();if(g){w()}return true};j=function(y){if(!s){return false}s=false;k.uncover();Dygraph.removeEvent(i,"mousemove",r);Dygraph.removeEvent(i,"mouseup",j);o.fgcanvas_.style.cursor="default";if(!g){w()}return true};w=function(){try{var z=o.getZoomHandleStatus_();o.isChangingRange_=true;if(!z.isZoomed){o.dygraph_.resetZoom()}else{var y=p(z);o.dygraph_.doZoomXDates_(y[0],y[1])}}finally{o.isChangingRange_=false}};h=function(A){if(o.isUsingExcanvas_){return A.srcElement==o.iePanOverlay_}else{var z=o.leftZoomHandle_.getBoundingClientRect();var y=z.left+z.width/2;z=o.rightZoomHandle_.getBoundingClientRect();var B=z.left+z.width/2;return(A.clientX>y&&A.clientX=o.canvasRect_.x+o.canvasRect_.w){y=o.canvasRect_.x+o.canvasRect_.w;E=y-D}else{E+=z;y+=z}}var A=o.leftZoomHandle_.width/2;o.leftZoomHandle_.style.left=(E-A)+"px";o.rightZoomHandle_.style.left=(y-A)+"px";o.drawInteractiveLayer_();if(g){c()}return true};q=function(y){if(!d){return false}d=false;Dygraph.removeEvent(i,"mousemove",t);Dygraph.removeEvent(i,"mouseup",q);if(!g){c()}return true};c=function(){try{o.isChangingRange_=true;o.dygraph_.dateWindow_=p(o.getZoomHandleStatus_());o.dygraph_.drawGraph_(false)}finally{o.isChangingRange_=false}};l=function(y){if(s||d){return}var z=h(y)?"move":"default";if(z!=o.fgcanvas_.style.cursor){o.fgcanvas_.style.cursor=z}};e=function(y){if(y.type=="touchstart"&&y.targetTouches.length==1){if(f(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{if(y.type=="touchmove"&&y.targetTouches.length==1){if(r(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{j(y)}}};n=function(y){if(y.type=="touchstart"&&y.targetTouches.length==1){if(x(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{if(y.type=="touchmove"&&y.targetTouches.length==1){if(t(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{q(y)}}};m=function(B,A){var z=["touchstart","touchend","touchmove","touchcancel"];for(var y=0;y1&&v[t][1]!==null){m=typeof v[t][1]!="number";if(m){d=[];h=[];for(r=0;r0)){b=Math.min(b,g);c=Math.max(c,g)}}var o=0.25;if(u){c=Dygraph.log10(c);c+=c*o;b=Dygraph.log10(b);for(t=0;tthis.canvasRect_.x||b+1 tsung-1.5.1/src/templates/graph_dy.thtml0000644000175000017500000001112612147621622021410 0ustar nniclaussenniclausse[% 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.5.1/src/tsung/0000755000175000017500000000000012321173206015674 5ustar nniclaussenniclaussetsung-1.5.1/src/tsung/gen_ts_transport.erl0000644000175000017500000000266012147621622022005 0ustar nniclaussenniclausse%%% Copyright (C) 2012 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 : 7 Sep 2012 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(gen_ts_transport). -export([behaviour_info/1]). behaviour_info(callbacks) -> [{connect, 3}, {send, 3}, {close, 1}, {set_opts, 2}, {protocol_options, 1}, {normalize_incomming_data, 2}]; behaviour_info(_Other) -> undefined. tsung-1.5.1/src/tsung/ts_server_websocket.erl0000644000175000017500000001500212301350731022456 0ustar nniclaussenniclausse%%% %%% Copyright 2010 © ProcessOne %%% %%% Author : Eric Cestari %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module (ts_server_websocket). -export([ connect/3, send/3, close/1, set_opts/2, protocol_options/1, normalize_incomming_data/2 ]). -behaviour(gen_ts_transport). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("ts_websocket.hrl"). -record(state, {parent, socket = none, accept, host, port, path, opts, version, frame, buffer = <<>>, state = not_connected, subprotos = []}). -record(ws_config, {path, version = "13", frame}). protocol_options(#proto_opts{tcp_rcv_size = Rcv, tcp_snd_size = Snd, websocket_path = Path, websocket_frame = Frame}) -> [#ws_config{path = Path, frame = Frame}, binary, {active, once}, {recbuf, Rcv}, {sndbuf, Snd}, {keepalive, true} %% FIXME: should be an option ]. connect(Host, Port, Opts) -> Parent = self(), [WSConfig | TcpOpts] = Opts, Path = WSConfig#ws_config.path, Version = WSConfig#ws_config.version, Frame = WSConfig#ws_config.frame, case gen_tcp:connect(Host, Port, opts_to_tcp_opts(TcpOpts)) of {ok, Socket} -> Pid = spawn_link( fun() -> loop(#state{parent = Parent, host = Host, port = Port, opts = TcpOpts, path = Path, version = Version, frame = Frame, socket = Socket}) end), gen_tcp:controlling_process(Socket, Pid), inet:setopts(Socket, [{active, once}]), {ok, Pid}; Ret -> Ret end. loop(#state{socket = Socket, host = Host, path = Path, version = Version, subprotos = SubProtocol, state = not_connected} = State)-> {Handshake, Accept} = websocket:get_handshake(Host, Path, SubProtocol, Version), gen_tcp:send(Socket, Handshake), loop(State#state{socket = Socket, accept = Accept, state = waiting_handshake}); loop(#state{parent = Parent, socket = Socket, accept = Accept, state = waiting_handshake} = State)-> receive {tcp, Socket, Data}-> CheckResult = websocket:check_handshake(Data, Accept), case CheckResult of ok -> ?Debug("handshake success: ~n"), inet:setopts(Socket, [{active, once}]), loop(State#state{state = connected}); {error, Reason} -> ?DebugF("handshake fail: ~p~n", [Reason]), Parent ! {gen_ts_transport, self(), error, Reason} end; {tcp_closed, Socket}-> ?LOGF("tcp closed:~p~n", [Socket], ?ERR), Parent ! {gen_ts_transport, self(), closed}; {tcp_error, Socket, Error}-> ?LOGF("tcp error:~p~n", [Socket], ?ERR), Parent ! {gen_ts_transport, self(), error, Error} end; loop(#state{parent = Parent, socket = Socket, state = connected, buffer = Buffer, frame = Frame} = State)-> receive {send, Data, Ref} -> EncodedData = case Frame of "text" -> websocket:encode_text(Data); _ -> websocket:encode_binary(Data) end, gen_tcp:send(Socket, EncodedData), Parent ! {ok, Ref}, loop(State); close -> EncodedData = websocket:encode_close(<<"close">>), gen_tcp:send(Socket, EncodedData), gen_tcp:close(Socket); {set_opts, Opts} -> inet:setopts(Socket, Opts), loop(State); {tcp, Socket, Data}-> case websocket:decode(<>) of more -> ?DebugF("receive incomplete from server: ~p~n", [Data]), loop(State#state{buffer = <>}); {?OP_CLOSE, _Reason, _} -> ?DebugF("receive close from server: ~p~n", [_Reason]), Parent ! {gen_ts_transport, self(), closed}; {_Opcode, Payload, Left} -> ?DebugF("receive from server: ~p ~p~n", [_Opcode, Payload]), Parent ! {gen_ts_transport, self(), Payload}, loop(State#state{buffer = Left}) end; {tcp_closed, Socket}-> Parent ! {gen_ts_transport, self(), closed}; {tcp_error, Socket, Error}-> Parent ! {gen_ts_transport, self(), error, Error}; E -> ?LOGF("Message:~p~n", [E], ?WARN) end. opts_to_tcp_opts(Opts) -> Opts. %% send/3 -> ok | {error, Reason} send(Socket, Data, _Opts) -> ?DebugF("sending to server: ~p~n",[Data]), Ref = make_ref(), Socket ! {send, Data, Ref}, MonitorRef = erlang:monitor(process,Socket), receive {'DOWN', MonitorRef, _Type, _Object, _Info} -> {error, no_ws_connection}; {ok, Ref} -> erlang:demonitor(MonitorRef), ok after 30000 -> erlang:demonitor(MonitorRef), {error, timeout} end. close(Socket) -> Socket ! close. %% set_opts/2 -> socket() set_opts(Socket, Opts) -> Socket ! {set_opts, Opts}, Socket. normalize_incomming_data(_Socket, X) -> %% nothing to do here, ts_websocket uses a special process to handle %%http requests,the incoming data is already delivered to %%ts_client as {gen_ts_transport, ..} instead of gen_tcp | ssl X. tsung-1.5.1/src/tsung/tsung.app.in0000644000175000017500000000412712317474675020172 0ustar nniclaussenniclausse{application, tsung, [{description, "tsung, a load testing tool for TCP/UDP servers"}, {vsn, "@PACKAGE_VERSION@"}, {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 {idle_timeout, 600000}, % 10min timeout {global_ack_timeout, infinity}, % global ack 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"}, %%% -------- WEBSOCKET OPTIONS {websocket_path, "/chat"} ]}, {applications, [ @ERLANG_APPLICATIONS@]}, {mod, {tsung, []}} ]}. tsung-1.5.1/src/tsung/ts_stats.erl0000644000175000017500000001301012147621622020245 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%% 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.5.1/src/tsung/ts_dynvars.erl0000644000175000017500000001131312147621622020601 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%% @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.5.1/src/tsung/ts_raw.erl0000644000175000017500000001043512236145741017712 0ustar nniclaussenniclausse%%% 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([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})-> {list_to_binary(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, {DynVars, _Session}, OldReq, _Host) -> subst(OldReq, DynVars); add_dynparams(_Subst, _DynData, Param, _Host) -> Param. %%---------------------------------------------------------------------- %% Function: subst/1 %%---------------------------------------------------------------------- subst(Req=#raw{datasize=Size,data=Data},DynVars) -> Req#raw{datasize = ts_search:subst(Size, DynVars), data= ts_search:subst(Data, DynVars)}. tsung-1.5.1/src/tsung/ts_client_sup.erl0000644000175000017500000000616012147621622021264 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_client_sup). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -behaviour(supervisor). -include("ts_macros.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.5.1/src/tsung/ts_ssl6.erl0000644000175000017500000000403512147621622020005 0ustar nniclaussenniclausse%%% %%% Copyright 2012 © Nicolas Niclausse %%% %%% Author : Nicolas Niclausse %%% Created: 7 sep 2012 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_ssl6). -export([ connect/3,connect/2, send/3, close/1, set_opts/2, protocol_options/1, normalize_incomming_data/2 ]). -behaviour(gen_ts_transport). -include("ts_profile.hrl"). -include("ts_config.hrl"). protocol_options(Opts) -> [inet6]++ts_ssl:protocol_options(Opts). %% -> {ok, Socket} connect(Host, Port, Opts) -> ssl:connect(Host, Port, Opts). connect(Socket, Opts)-> ssl:connect(Socket, Opts). %% send/3 -> ok | {error, Reason} send(Socket, Data, _Opts) -> ssl:send(Socket, Data). close(none) -> ok; close(Socket) -> ssl:close(Socket). % set_opts/2 -> socket() set_opts(none, _Opts) -> none; set_opts(Socket, Opts) -> ssl:setopts(Socket, Opts), Socket. normalize_incomming_data(Socket, Data) -> ts_ssl:normalize_incomming_data(Socket,Data). tsung-1.5.1/src/tsung/ts_mon_cache.erl0000644000175000017500000002023412147621622021031 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%%------------------------------------------------------------------- %%% 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_macros.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(), Transactions::list()}) -> ok add_match(Data,{UserId,SessionId,RequestId,TimeStamp,Bin,Tr,Name}) -> gen_server:cast(?MODULE, {add_match, Data, {UserId,SessionId,RequestId,TimeStamp,Bin,Tr,Name}}). %%==================================================================== %% 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,Tr,Name}}, State=#state{stats=List, match=MatchList})-> NewMatchList=lists:append([{UserId,SessionId,RequestId,TimeStamp,First, Bin, Tr,Name}], 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.5.1/src/tsung/ts_cport.erl0000644000175000017500000001430512320752470020245 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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_macros.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.5.1/src/tsung/ts_sup.erl0000644000175000017500000000735612104023217017723 0ustar nniclaussenniclausse%%% 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_macros.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.5.1/src/tsung/ts_tcp6.erl0000644000175000017500000000367412147621622020002 0ustar nniclaussenniclausse%%% %%% Copyright 2012 © Nicolas Niclausse %%% %%% Author : Nicolas Niclausse %%% Created: 7 sep 2012 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_tcp6). -export([ connect/3, send/3, close/1, set_opts/2, protocol_options/1, normalize_incomming_data/2 ]). -behaviour(gen_ts_transport). -include("ts_profile.hrl"). -include("ts_config.hrl"). protocol_options(Opts) -> [inet6]++ts_tcp:protocol_options(Opts). connect(Host, Port, Opts) -> gen_tcp:connect(Host, Port, Opts). %% send/3 -> ok | {error, Reason} send(Socket, Data, _Opts) -> gen_tcp:send(Socket, Data). close(Socket) -> ts_tcp:close(Socket). % set_opts/2 -> socket() set_opts(none, _Opts) -> none; set_opts(Socket, Opts) -> inet:setopts(Socket, Opts), Socket. normalize_incomming_data(Socket,Data) -> ts_tcp:normalize_incomming_data(Socket,Data). tsung-1.5.1/src/tsung/ts_launcher_static.erl0000644000175000017500000002165212147621622022272 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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.5.1/src/tsung/ts_pgsql.erl0000644000175000017500000003466412104023217020244 0ustar nniclaussenniclausse%%% %%% 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_macros.hrl"). -include("ts_profile.hrl"). -include("ts_pgsql.hrl"). -export([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_session{}) -> Buffer. % nothing to do for pgsql %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> #pgsql_session{}. %%---------------------------------------------------------------------- %% Function: get_message/21 %% Purpose: Build a message/request , %% Args: record %% Returns: {binary,#pgsql_session} %%---------------------------------------------------------------------- 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_session{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_session.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 = [], session=S}) -> 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}, _ } -> NewS=S#pgsql_session{auth_method=AuthType}, {State#state_rcv{ack_done = true, session=NewS},[],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, {_DynVars,Session}, Param, HostData) -> add_dynparams(Session, Param, HostData); add_dynparams(true, {DynVars,Session}, Param, HostData) -> NewParam = subst(Param, DynVars), add_dynparams(Session,NewParam, HostData). add_dynparams(DynPgsql, Param, _HostData) -> ?DebugF("Dyndata=~p, param=~p~n",[DynPgsql, Param]), Param#pgsql_request{auth_method=DynPgsql#pgsql_session.auth_method, salt=DynPgsql#pgsql_session.salt}. %%---------------------------------------------------------------------- %% Function: subst/2 %% Purpose: Replace on the fly dynamic element of the request. %% Returns: #pgsql_request %%---------------------------------------------------------------------- subst(Req=#pgsql_request{sql=SQL,database=DB,username=User,passwd=Passwd, parameters=Params}, DynVars) -> Req#pgsql_request{sql=ts_search:subst(SQL, DynVars), username=ts_search:subst(User, DynVars), passwd=ts_search:subst(Passwd, DynVars), parameters=case is_list(Params) of true -> lists:map(fun(X)-> ts_search:subst(X, DynVars) end, Params); false -> Params end, database=ts_search:subst(DB, DynVars) }. %%% -- 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.5.1/src/tsung/ts_amqp.erl0000644000175000017500000006254412236145741020067 0ustar nniclaussenniclausse%%% This code was developped by Zhihui Jiao(jzhihui521@gmail.com). %%% %%% Copyright (C) 2013 Zhihui Jiao %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_amqp). -vc('$Id$ '). -author('jzhihui521@gmail.com'). -behavior(ts_plugin). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("ts_amqp.hrl"). -include("rabbit.hrl"). -include("rabbit_framing.hrl"). -export([add_dynparams/4, get_message/2, session_defaults/0, 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 %% Returns: {ok, ack_type = parse|no_ack|local, persistent = true|false} %%---------------------------------------------------------------------- session_defaults() -> {ok, true}. %% @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,#amqp_session{}) -> Buffer. % nothing to do for amqp %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> #amqp_session{map_num_pa = gb_trees:empty(), ack_buf = <<>>}. dump(A,B) -> ts_plugin:dump(A,B). %%---------------------------------------------------------------------- %% Function: get_message/1 %% Purpose: Build a message/request , %% Args: record %% Returns: binary %%---------------------------------------------------------------------- get_message(Request = #amqp_request{channel = ChannelStr}, State) -> ?DebugF("get message on channel: ~p ~p~n", [ChannelStr, Request]), ChannelNum = list_to_integer(ChannelStr), get_message1(Request#amqp_request{channel = ChannelNum}, State). get_message1(#amqp_request{type = connect}, #state_rcv{session = AMQPSession}) -> Waiting = {0, 'connection.start'}, {?PROTOCOL_HEADER, AMQPSession#amqp_session{status = handshake, waiting = Waiting, protocol = ?PROTOCOL}}; get_message1(#amqp_request{type = 'connection.start_ok', username = UserName, password = Password}, #state_rcv{session = AMQPSession}) -> Protocol = AMQPSession#amqp_session.protocol, ?DebugF("start with: user=~p, password=~p~n", [UserName, Password]), Resp = plain(none, list_to_binary(UserName), list_to_binary(Password)), StartOk = #'connection.start_ok'{client_properties = client_properties([]), mechanism = <<"PLAIN">>, response = Resp}, Frame = assemble_frame(0, StartOk, Protocol), Waiting = {0, 'connection.tune'}, {Frame, AMQPSession#amqp_session{waiting = Waiting}}; get_message1(#amqp_request{type = 'connection.tune_ok', heartbeat = HeartBeat}, #state_rcv{session = AMQPSession}) -> Protocol = AMQPSession#amqp_session.protocol, Tune = #'connection.tune_ok'{frame_max = 131072, heartbeat = HeartBeat}, Frame = assemble_frame(0, Tune, Protocol), {Frame, AMQPSession#amqp_session{waiting = none}}; get_message1(#amqp_request{type = 'connection.open', vhost = VHost}, #state_rcv{session = AMQPSession}) -> Protocol = AMQPSession#amqp_session.protocol, Open = #'connection.open'{virtual_host = list_to_binary(VHost)}, Frame = assemble_frame(0, Open, Protocol), Waiting = {0, 'connection.open_ok'}, {Frame, AMQPSession#amqp_session{waiting = Waiting}}; get_message1(#amqp_request{type = 'channel.open', channel = Channel}, #state_rcv{session = AMQPSession}) -> Protocol = AMQPSession#amqp_session.protocol, MapNPA = AMQPSession#amqp_session.map_num_pa, ChannelOpen = #'channel.open'{}, case new_number(Channel, AMQPSession) of {ok, Number} -> MapNPA1 = gb_trees:enter(Number, unused, MapNPA), put({chstate, Number}, #ch{unconfirmed_set = gb_sets:new(), next_pub_seqno = 0}), Frame = assemble_frame(Number, ChannelOpen, Protocol), Waiting = {Number, 'channel.open_ok'}, {Frame, AMQPSession#amqp_session{waiting = Waiting, map_num_pa = MapNPA1}}; {error, _} -> {<<>>, AMQPSession#amqp_session{waiting = none}} end; get_message1(#amqp_request{type = 'channel.close', channel = Channel}, #state_rcv{session = AMQPSession}) -> Protocol = AMQPSession#amqp_session.protocol, ChannelClose = #'channel.close'{reply_text = <<"Goodbye">>, reply_code = 200, class_id = 0, method_id = 0}, Frame = assemble_frame(Channel, ChannelClose, Protocol), Waiting = {Channel, 'channel.close_ok'}, {Frame, AMQPSession#amqp_session{waiting = Waiting}}; get_message1(#amqp_request{type = 'confirm.select', channel = Channel}, #state_rcv{session = AMQPSession}) -> Protocol = AMQPSession#amqp_session.protocol, Confirm = #'confirm.select'{}, Frame = assemble_frame(Channel, Confirm, Protocol), Waiting = {Channel, 'confirm.select_ok'}, {Frame, AMQPSession#amqp_session{waiting = Waiting}}; get_message1(#amqp_request{type = 'basic.qos', prefetch_size = PrefetchSize, channel = Channel, prefetch_count = PrefetchCount}, #state_rcv{session = AMQPSession}) -> Protocol = AMQPSession#amqp_session.protocol, Qos = #'basic.qos'{prefetch_size = PrefetchSize, prefetch_count = PrefetchCount}, Frame = assemble_frame(Channel, Qos, Protocol), Waiting = {Channel, 'basic.qos_ok'}, {Frame, AMQPSession#amqp_session{waiting = Waiting}}; get_message1(#amqp_request{type = 'basic.publish', channel = Channel, exchange = Exchange, routing_key = RoutingKey, payload_size = Size, payload = Payload, persistent = Persistent}, #state_rcv{session = AMQPSession}) -> Protocol = AMQPSession#amqp_session.protocol, MsgPayload = case Payload of "" -> list_to_binary(ts_utils:urandomstr_noflat(Size)); _ -> list_to_binary(Payload) end, Publish = #'basic.publish'{exchange = list_to_binary(Exchange), routing_key = list_to_binary(RoutingKey)}, Msg = case Persistent of true -> Props = #'P_basic'{delivery_mode = 2}, %% persistent message build_content(Props, MsgPayload); false -> Props = #'P_basic'{}, build_content(Props, MsgPayload) end, Frame = assemble_frames(Channel, Publish, Msg, ?FRAME_MIN_SIZE, Protocol), ChState = get({chstate, Channel}), NewChState = case ChState#ch.next_pub_seqno of 0 -> ChState; SeqNo -> USet = ChState#ch.unconfirmed_set, ChState#ch{unconfirmed_set = gb_sets:add(SeqNo, USet), next_pub_seqno = SeqNo + 1} end, put({chstate, Channel}, NewChState), ts_mon:add({count, amqp_published}), {Frame, AMQPSession}; get_message1(#amqp_request{type = 'basic.consume', channel = Channel, queue = Queue, ack = Ack}, #state_rcv{session = AMQPSession}) -> Protocol = AMQPSession#amqp_session.protocol, NoAck = case Ack of true -> false; _ -> true end, ConsumerTag = list_to_binary(["tsung-", ts_utils:randombinstr(10)]), Sub = #'basic.consume'{queue = list_to_binary(Queue), consumer_tag = ConsumerTag, no_ack = NoAck}, ChState = get({chstate, Channel}), put({chstate, Channel}, ChState#ch{ack = Ack}), Frame = assemble_frame(Channel, Sub, Protocol), Waiting = {Channel, 'basic.consume_ok'}, {Frame, AMQPSession#amqp_session{waiting = Waiting}}; get_message1(#amqp_request{type = 'connection.close'}, #state_rcv{session = AMQPSession}) -> Protocol = AMQPSession#amqp_session.protocol, Close = #'connection.close'{reply_text = <<"Goodbye">>, reply_code = 200, class_id = 0, method_id = 0}, Frame = assemble_frame(0, Close, Protocol), Waiting = {0, 'connection.close_ok'}, {Frame, AMQPSession#amqp_session{waiting = Waiting}}. %%---------------------------------------------------------------------- %% 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)}); %% handshake stage, parse response, and validate parse(Data, State=#state_rcv{acc = []}) -> do_parse(Data, State); %% 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}). parse_bidi(<<>>, State=#state_rcv{acc = [], session = AMQPSession}) -> AckBuf = AMQPSession#amqp_session.ack_buf, NewAMQPSession = AMQPSession#amqp_session{ack_buf = <<>>}, ?DebugF("ack buf: ~p~n", [AckBuf]), {confirm_ack_buf(AckBuf), State#state_rcv{session = NewAMQPSession}}; parse_bidi(Data, State=#state_rcv{acc = [], session = AMQPSession}) -> ?DebugF("parse bidi data: ~p ~p~n", [size(Data), Data]), Protocol = AMQPSession#amqp_session.protocol, AckBuf = AMQPSession#amqp_session.ack_buf, case decode_frame(Protocol, Data) of {error, _Reason} -> ?DebugF("decode error: ~p~n", [_Reason]), {nodata, State}; {ok, heartbeat, Left} -> ?DebugF("receive bidi: ~p~n", [heartbeat]), HB = list_to_binary(rabbit_binary_generator:build_heartbeat_frame()), NewAckBuf = <>, NewAMQPSession = AMQPSession#amqp_session{ack_buf = NewAckBuf}, parse_bidi(Left, State#state_rcv{session = NewAMQPSession}); {ok, _, none, Left} -> parse_bidi(Left, State); {ok, Channel, Method, Left} -> ?DebugF("receive bidi: ~p ~p~n", [Channel, Method]), NewAMQPSession = should_ack(Channel, AckBuf, Method, AMQPSession), parse_bidi(Left, State#state_rcv{session = NewAMQPSession}); {incomplete, Left} -> ?DebugF("incomplete frame: ~p~n", [Left]), {confirm_ack_buf(AckBuf), State#state_rcv{acc = Left}} end; parse_bidi(Data, State=#state_rcv{acc = Acc, datasize = DataSize, session = AMQPSession}) -> NewSize = DataSize + size(Data), ?DebugF("parse bidi data: ~p ~p~n", [NewSize, Data, Acc]), parse_bidi(<>, State#state_rcv{acc = [], datasize = NewSize, session = AMQPSession#amqp_session{ack_buf = <<>>}}). %%---------------------------------------------------------------------- %% Function: parse_config/2 %% Purpose: parse tags in the XML config file related to the protocol %% Returns: List %%---------------------------------------------------------------------- parse_config(Element, Conf) -> ts_config_amqp:parse_config(Element, Conf). %%---------------------------------------------------------------------- %% Function: add_dynparams/4 %% Purpose: we dont actually do anything %% Returns: #amqp_request %%---------------------------------------------------------------------- add_dynparams(false, {_DynVars, _Session}, Param, _HostData) -> Param; add_dynparams(true, {DynVars, _Session}, Req = #amqp_request{channel = Channel, payload = Payload}, _HostData) -> SubstChannel = ts_search:subst(Channel, DynVars), SubstPayload = ts_search:subst(Payload, DynVars), Req#amqp_request{channel = SubstChannel, payload = SubstPayload}. %%---------------------------------------------------------------------- plain(none, Username, Password) -> <<0, Username/binary, 0, Password/binary>>. do_parse(Data, State = #state_rcv{session = AMQPSession}) -> ?DebugF("start do_parse: ~p ~n", [Data]), Protocol = AMQPSession#amqp_session.protocol, Waiting = AMQPSession#amqp_session.waiting, case decode_and_check(Data, Waiting, State, Protocol) of {ok, _Method, Result} -> Result; {fail, Result} -> Result end. get_post_fun(_Channel, 'connection.open_ok') -> fun({NewState, Options, Close}) -> AMQPSession = NewState#state_rcv.session, NewAMQPSession = AMQPSession#amqp_session{status = connected}, NewState1 = NewState#state_rcv{session = NewAMQPSession}, ts_mon:add({count, amqp_connected}), {NewState1, Options, Close} end; get_post_fun(_Channel, 'channel.open_ok') -> fun({NewState, Options, Close}) -> ts_mon:add({count, amqp_channel_opened}), {NewState, Options, Close} end; get_post_fun(_Channel, 'channel.close_ok') -> fun({NewState, Options, Close}) -> ts_mon:add({count, amqp_channel_closed}), {NewState, Options, Close} end; get_post_fun(Channel, 'confirm.select_ok') -> fun({NewState, Options, Close}) -> ChState = get({chstate, Channel}), NewChState = ChState#ch{next_pub_seqno = 1}, put({chstate, Channel}, NewChState), NewState1 = NewState#state_rcv{acc = []}, {NewState1, Options, Close} end; get_post_fun(_Channel, 'basic.consume_ok') -> fun({NewState, Options, Close}) -> AMQPSession = NewState#state_rcv.session, Socket = NewState#state_rcv.socket, ts_mon:add({count, amqp_consumer}), LeftData = NewState#state_rcv.acc, NewAMQPSession = AMQPSession#amqp_session{waiting = none}, NewState1 = NewState#state_rcv{acc = [], session = NewAMQPSession}, case LeftData of <<>> -> ok; %% trick, trigger the parse_bidi call _ -> self() ! {gen_ts_transport, Socket, LeftData} end, {NewState1, Options, Close} end; get_post_fun(_Channel, 'connection.close_ok') -> fun({NewState, Options, _Close}) -> ts_mon:add({count, amqp_closed}), {NewState, Options, true} end; get_post_fun(_Channel, _) -> fun({NewState, Options, Close}) -> AMQPSession = NewState#state_rcv.session, NewAMQPSession = AMQPSession#amqp_session{waiting = none}, NewState1 = NewState#state_rcv{session = NewAMQPSession}, {NewState1, Options, Close} end. new_number(0, #amqp_session{channel_max = ChannelMax, map_num_pa = MapNPA}) -> case gb_trees:is_empty(MapNPA) of true -> {ok, 1}; false -> {Smallest, _} = gb_trees:smallest(MapNPA), if Smallest > 1 -> {ok, Smallest - 1}; true -> {Largest, _} = gb_trees:largest(MapNPA), if Largest < ChannelMax -> {ok, Largest + 1}; true -> find_free(MapNPA) end end end; new_number(Proposed, Session = #amqp_session{channel_max = ChannelMax, map_num_pa = MapNPA}) -> IsValid = Proposed > 0 andalso Proposed =< ChannelMax andalso not gb_trees:is_defined(Proposed, MapNPA), case IsValid of true -> {ok, Proposed}; false -> new_number(none, Session) end. find_free(MapNPA) -> find_free(gb_trees:iterator(MapNPA), 1). find_free(It, Candidate) -> case gb_trees:next(It) of {Number, _, It1} -> if Number > Candidate -> {ok, Number - 1}; Number =:= Candidate -> find_free(It1, Candidate + 1) end; none -> {error, out_of_channel_numbers} end. confirm_ack_buf(AckBuf) -> case AckBuf of <<>> -> nodata; _ -> AckBuf end. should_ack(Channel, AckBuf, #'basic.deliver'{delivery_tag = DeliveryTag}, AMQPSession = #amqp_session{protocol = Protocol}) -> ChState = get({chstate, Channel}), case ChState#ch.ack of true -> ?DebugF("delivered: ~p ~n", [ack]), Ack = #'basic.ack'{delivery_tag = DeliveryTag}, Frame = assemble_frame(Channel, Ack, Protocol), ts_mon:add({count, amqp_delivered}), NewAckBuf = case AckBuf of nodata -> Frame; _ -> <> end, AMQPSession#amqp_session{ack_buf = NewAckBuf}; false -> ?DebugF("delivered: ~p ~n", [noack]), ts_mon:add({count, amqp_delivered}), AMQPSession#amqp_session{ack_buf = AckBuf} end; should_ack(Channel, AckBuf, Method = #'basic.ack'{}, AMQPSession) -> ?DebugF("publish confirm: ~p ~n", [ack]), update_confirm_set(Channel, Method), AMQPSession#amqp_session{ack_buf = AckBuf}; should_ack(Channel, AckBuf, Method = #'basic.nack'{}, AMQPSession) -> ?DebugF("publish confirm: ~p ~n", [nack]), update_confirm_set(Channel, Method), AMQPSession#amqp_session{ack_buf = AckBuf}; should_ack(_Channel, AckBuf, _Method, AMQPSession) -> ?DebugF("delivered: ~p ~n", [other]), AMQPSession#amqp_session{ack_buf = AckBuf}. update_confirm_set(Channel, #'basic.ack'{delivery_tag = SeqNo, multiple = Multiple}) -> ChState = get({chstate, Channel}), USet = ChState#ch.unconfirmed_set, USet1 = update_unconfirmed(ack, SeqNo, Multiple, USet), put({chstate, Channel}, ChState#ch{unconfirmed_set = USet1}); update_confirm_set(Channel, #'basic.nack'{delivery_tag = SeqNo, multiple = Multiple}) -> ChState = get({chstate, Channel}), USet = ChState#ch.unconfirmed_set, USet1 = update_unconfirmed(nack, SeqNo, Multiple, USet), put({chstate, Channel}, ChState#ch{unconfirmed_set = USet1}). update_unconfirmed(AckType, SeqNo, false, USet) -> add_ack_stat(AckType), gb_sets:del_element(SeqNo, USet); update_unconfirmed(AckType, SeqNo, true, USet) -> case gb_sets:is_empty(USet) of true -> USet; false -> {S, USet1} = gb_sets:take_smallest(USet), case S > SeqNo of true -> USet; false -> add_ack_stat(AckType), update_unconfirmed(AckType, SeqNo, true, USet1) end end. add_ack_stat(ack) -> ts_mon:add({count, amqp_confirmed}); add_ack_stat(nack) -> ts_mon:add({count, amqp_unconfirmed}). client_properties(UserProperties) -> Default = [{<<"product">>, longstr, <<"Tsung">>}, {<<"version">>, longstr, list_to_binary("0.0.1")}, {<<"platform">>, longstr, <<"Erlang">>}, {<<"capabilities">>, table, ?CLIENT_CAPABILITIES}], lists:foldl(fun({K, _, _} = Tuple, Acc) -> lists:keystore(K, 1, Acc, Tuple) end, Default, UserProperties). assemble_frame(Channel, MethodRecord, Protocol) -> list_to_binary(rabbit_binary_generator:build_simple_method_frame( Channel, MethodRecord, Protocol)). assemble_frames(Channel, MethodRecord, Content, FrameMax, Protocol) -> MethodName = rabbit_misc:method_record_type(MethodRecord), true = Protocol:method_has_content(MethodName), % assertion MethodFrame = rabbit_binary_generator:build_simple_method_frame( Channel, MethodRecord, Protocol), ContentFrames = rabbit_binary_generator:build_simple_content_frames( Channel, Content, FrameMax, Protocol), list_to_binary([MethodFrame | ContentFrames]). build_content(Properties, BodyBin) when is_binary(BodyBin) -> build_content(Properties, [BodyBin]); build_content(Properties, PFR) -> %% basic.publish hasn't changed so we can just hard-code amqp_0_9_1 {ClassId, _MethodId} = rabbit_framing_amqp_0_9_1:method_id('basic.publish'), #content{class_id = ClassId, properties = Properties, properties_bin = none, protocol = none, payload_fragments_rev = PFR}. decode_and_check(Data, Waiting, State, Protocol) -> case decode_frame(Protocol, Data) of {error, _Reason} -> ?DebugF("decode error: ~p~n", [_Reason]), ts_mon:add({count, amqp_error}), {fail, {State#state_rcv{ack_done = true}, [], true}}; {ok, heartbeat, Left} -> {ok, heartbeat, {State#state_rcv{ack_done = false, acc = Left}, [], true}}; {ok, Channel, Method, Left} -> check(Channel, Waiting, Method, State, Left); {incomplete, Left} -> ?DebugF("incomplete frame: ~p~n", [Left]), {fail, {State#state_rcv{ack_done = false, acc = Left}, [], false}} end. check(Channel, {Channel, Expecting}, Method, State, Left) -> ?DebugF("receive from server: ~p~n", [Method]), case {Expecting, element(1, Method)} of {E, M} when E =:= M -> PostFun = get_post_fun(Channel, Expecting), {ok, Method, PostFun({State#state_rcv{ack_done = true, acc = Left}, [], false})}; _ -> ts_mon:add({count, amqp_unexpected}), ?DebugF("unexpected_method: ~p, expecting ~p~n", [Method, Expecting]), {fail, {State#state_rcv{ack_done = true}, [], true}} end; check(Channel, Waiting = {WaitingCh, Expecting}, Method = #'basic.deliver'{}, State = #state_rcv{session = AMQPSession}, Left) -> ?LOGF("waiting on ~p, expecting ~p, but receive deliver on ~p ~p~n", [WaitingCh, Expecting, Channel, Method], ?NOTICE), AckBuf = AMQPSession#amqp_session.ack_buf, NewAMQPSession = should_ack(Channel, AckBuf, Method, AMQPSession), Protocol = AMQPSession#amqp_session.protocol, decode_and_check(Left, Waiting, State#state_rcv{session = NewAMQPSession}, Protocol); check(Channel, Waiting = {WaitingCh, Expecting}, Method, State = #state_rcv{session = AMQPSession}, Left) -> ?LOGF("waiting on ~p, but received on ~p, expecting: ~p, actual: ~p~n", [WaitingCh, Channel, Expecting, Method], ?NOTICE), Protocol = AMQPSession#amqp_session.protocol, decode_and_check(Left, Waiting, State, Protocol). decode_frame(Protocol, <>) when size(Body) > Length -> <> = Body, case rabbit_command_assembler:analyze_frame(Type, PayLoad, Protocol) of heartbeat -> {ok, heartbeat, Left}; AnalyzedFrame -> process_frame(AnalyzedFrame, Channel, Protocol, Left) end; decode_frame(_Protocol, Data) -> {incomplete, Data}. process_frame(Frame, Channel, Protocol, Left) -> AState = case get({channel, Channel}) of undefined -> {ok, InitAState} = rabbit_command_assembler:init(Protocol), InitAState; AState1-> AState1 end, case process_channel_frame(Frame, AState, Left) of {ok, Method, NewAState, Left} -> put({channel, Channel}, NewAState), {ok, Channel, Method, Left}; Other -> Other end. process_channel_frame(Frame, AState, Left) -> case rabbit_command_assembler:process(Frame, AState) of {ok, NewAState} -> {ok, none, NewAState, Left}; {ok, Method, NewAState} -> {ok, Method, NewAState, Left}; {ok, Method, _Content, NewAState} -> {ok, Method, NewAState, Left}; {error, Reason} -> {error, Reason} end. tsung-1.5.1/src/tsung/ts_ldap.erl0000644000175000017500000003005712104023217020026 0ustar nniclaussenniclausse%%% 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([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_macros.hrl"). -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 = [], 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}}, dynvars = DynVars}) -> case ResultVar of none -> State; {ok,VarName} -> State#state_rcv{dynvars=accumulate_dyndata(R,VarName,DynVars)} end. accumulate_dyndata(R,VarName,DynVars) when is_list(DynVars)-> Prev = proplists:get_value(VarName,DynVars,[]), NewDynVars = lists:keystore(VarName,1,DynVars,{VarName,[R|Prev]}), NewDynVars; accumulate_dyndata(R,VarName,_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, {DynVars, _Session}, Param = #ldap_request{type=bind,user=User,password=Password}, _HostData) -> Param#ldap_request{user=ts_search:subst(User,DynVars),password=ts_search:subst(Password,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, {DynVars, _Session}, Param = #ldap_request{type=search, filter = Filter}, _HostData) -> Param#ldap_request{filter = subs_filter(Filter,DynVars)}; %% Add message. Substitution on DN and attrs values. add_dynparams(true,{DynVars, _Session},Param = #ldap_request{type=add,dn=DN,attrs=Attrs},_HostData) -> Param#ldap_request{dn=ts_search:subst(DN,DynVars), attrs=subs_attrs(Attrs,DynVars)}; %% Modification message. Substitution on DN and attrs values. add_dynparams(true,{DynVars, _Session},Param = #ldap_request{type=modify,dn=DN,modifications=Modifications},_HostData) -> SubsModifications = [{Operation,AttrType,[ts_search:subst(Value,DynVars) || Value <- Values]} || {Operation,AttrType,Values}<- Modifications ], Param#ldap_request{dn=ts_search:subst(DN,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 ]. %%---------------------------------------------------------------- %%-----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.5.1/src/tsung/ts_bosh_ssl.erl0000644000175000017500000000401312147621622020726 0ustar nniclaussenniclausse%%% %%% Copyright 2010 © ProcessOne %%% %%% Author : Eric Cestari %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_bosh_ssl). -export([ connect/3, send/3, close/1, set_opts/2, protocol_options/1, normalize_incomming_data/2 ]). -behaviour(gen_ts_transport). %% This is exactly like ts_bosh, but using ssl instead of plain connections. %% It is easier (fewer tsung modifications required) to have two separate modules, %% and delegate from here to the original. connect(Host, Port, Opts) -> ts_bosh:connect(Host, Port, Opts, ssl). send(Pid, Data, _Opts) -> ts_bosh:send(Pid, Data, _Opts). close(Pid) -> ts_bosh:close(Pid). set_opts(Pid, _Opts) -> ts_bosh:set_opts(Pid, _Opts). protocol_options(_P) -> ts_bosh:protocol_options(_P). normalize_incomming_data(_Socket, X) -> X. %% nothing to do here, ts_bosh uses a special process to handle http requests, %% the incoming data is already delivered to ts_client as {gen_ts_transport, ..} instead of gen_tcp | ssl tsung-1.5.1/src/tsung/ts_ip_scan.erl0000644000175000017500000001674212147621622020542 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%%------------------------------------------------------------------- %%% @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_macros.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.5.1/src/tsung/ts_http.erl0000644000175000017500000003314112320752470020074 0ustar nniclaussenniclausse%%% 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_macros.hrl"). -include("ts_profile.hrl"). -include("ts_http.hrl"). -export([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() -> UserAgent = ts_session_cache:get_user_agent(), #http{user_agent=UserAgent}. %% @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,Duration,Transactions})-> 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, Tr=ts_utils:log_transaction(Transactions), Data=ts_utils:join(";",[integer_to_list(UserId), atom_to_list(HttpReq#http_request.method), Server, get(last_url), Status,integer_to_list(Size), Duration,Tr,Match,Error, HttpReq#http_request.tag] ), 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_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); get_message2(Req=#http_request{method=patch}) -> ts_http_common:http_body(?PATCH, 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), {DynVars = #dynvars, #http_session}, Param = #http_request, %% HostData = {Hostname, Port} %% Returns: #http_request or { #http_request, {Host, Port, Scheme}} %%---------------------------------------------------------------------- add_dynparams(false, {_DynVars, Session}, Param, HostData) -> add_dynparams(Session, Param, HostData); add_dynparams(SubstParam, {DynVars, Session}, OldReq=#http_request{url=OldUrl}, HostData={_PrevHost, _PrevPort, PrevProto}) -> Req = subst(SubstParam, OldReq, DynVars), case Req#http_request.url of OldUrl -> add_dynparams(Session,Req, HostData); "http" ++ Rest -> % URL has changed and is absolute URL=ts_config_http:parse_URL(Req#http_request.url), ?LOGF("URL dynamic subst: ~p~n",[URL],?INFO), NewPort = ts_config_http:set_port(URL), NewReq = add_dynparams(Session, Req#http_request{host_header=undefined}, {URL#url.host, NewPort, PrevProto, URL#url.scheme}), % add scheme case OldReq#http_request.use_proxy of 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(Session, Req, HostData) end. %% Function: add_dynparams/3 add_dynparams(Session,Param=#http_request{host_header=undefined}, HostData )-> Header = case HostData of {Host,80, _,http}-> Host; {Host,443,_,https}-> Host; {Host,80, ts_tcp}-> Host; {Host,443, ts_ssl}-> Host; {Host,80, ts_tcp6}-> ts_config_http:encode_ipv6_address(Host); {Host,443, ts_ssl6}-> ts_config_http:encode_ipv6_address(Host); {Host,Port,_,_} -> ts_config_http:encode_ipv6_address(Host)++":"++ integer_to_list(Port); {Host,Port,_Proto} -> ts_config_http:encode_ipv6_address(Host)++":"++ integer_to_list(Port) end, ?DebugF("set host header dynamically: ~s~n",[Header]), add_dynparams(Session, Param#http_request{host_header=Header},HostData); %% no cookies add_dynparams(#http{session_cookies=[],user_agent=UA},Param, _) -> Param#http_request{user_agent=UA}; %% cookies add_dynparams(#http{session_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}. %%---------------------------------------------------------------------- %% @spec subst(SubstParam::true|all_except_body, 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(SubstParam, Req=#http_request{url=URL, body=Body, headers = Headers, oauth_url=OUrl, oauth_access_token=AToken, oauth_access_secret=ASecret,digest_qop = QOP, digest_cnonce=CNonce, digest_nc=Nc,digest_nonce=Nonce, digest_opaque=Opaque, realm=Realm, userid=UserId, passwd=Passwd, cookie = Cookies}, DynVars) -> Req#http_request{url = escape_url(ts_search:subst(URL, DynVars)), body = case SubstParam of true -> ts_search:subst(Body, DynVars); all_except_body -> Body end, headers = lists:foldl(fun ({Name, Value}, Result) -> [{Name, ts_search:subst(Value, DynVars)} | Result] end, [], Headers), oauth_access_token = ts_search:subst(AToken, DynVars), digest_nonce = ts_search:subst(Nonce, DynVars), digest_cnonce = ts_search:subst(CNonce, DynVars), digest_nc = ts_search:subst(Nc, DynVars), digest_opaque = ts_search:subst(Opaque, DynVars), digest_qop = ts_search:subst(QOP, DynVars), realm = ts_search:subst(Realm, DynVars), oauth_access_secret = ts_search:subst(ASecret, DynVars), oauth_url = ts_search:subst(OUrl, DynVars), cookie = lists:foldl( fun (#cookie{ value = Value } = C, Result) -> [C#cookie{ value = ts_search:subst(Value, DynVars) } | Result] end, [], Cookies), userid = ts_search:subst(UserId, DynVars), passwd = ts_search:subst(Passwd, DynVars)}. %% 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.5.1/src/tsung/ts_job.erl0000644000175000017500000002033312147621622017667 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_job). -author('nicolas.niclausse@inria.fr'). -behaviour(ts_plugin). -include("ts_macros.hrl"). -include("ts_profile.hrl"). -include("ts_job.hrl"). -include_lib("kernel/include/file.hrl"). -export([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 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, {_DynVars,Session}, Param, HostData) -> add_dynparams(Session, Param, HostData); add_dynparams(true, {DynVars,Session}, Param, HostData) -> NewParam = subst(Param, DynVars), add_dynparams(Session,NewParam, HostData). add_dynparams(#job_session{}, 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,_,_,_,Transactions})-> {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))), Tr=ts_utils:log_transaction(Transactions), Data=ts_utils:join(";",[JobId,Name,Tr,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.5.1/src/tsung/ts_webdav.erl0000644000175000017500000000661712320752470020375 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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([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). %%% 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.5.1/src/tsung/ts_utils.erl0000644000175000017500000010377612317474675020307 0ustar nniclaussenniclausse%%% 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_macros.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, 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, randombinstr/1,urandombinstr/1,log_transaction/1,conv_entities/1, wildcard/2, ensure_all_started/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"). -define(DUPBINSTR_SIZE,20). -define(DUPBINSTR,<<"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(exclude_tag, 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, case After - Before of Neg when Neg < 0 -> % time duration must not be negative 0; Val -> Val end. %%---------------------------------------------------------------------- %% 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 1000000), 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. %%---------------------------------------------------------------------- %% 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 {ok, 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-z_ ]+\r\n","",ReOpts++[caseless]), RealHeader = re:replace(TmpHeader2,"Host: -","Host: ",ReOpts++[caseless]), 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) -> io_lib:format("~.3f",[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) when is_list(String), is_list(Chr) -> re:split(String,Chr,[{return,list}]); split(String,Chr) when is_binary(String), is_binary(Chr) -> binary:split(String,[Chr],[global]). %% 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 urandombinstr(Size::integer()) ->binary() %% @doc same as urandomstr/1, but returns a binary. %% @end %%---------------------------------------------------------------------- urandombinstr(Size) when is_integer(Size) , Size >= ?DUPBINSTR_SIZE -> Loop = Size div ?DUPBINSTR_SIZE, Rest = Size rem ?DUPBINSTR_SIZE, Res=lists:foldl(fun(_X,Acc)-> <> end, << >>,lists:seq(1,Loop)), << Res/binary, ?DUPBINSTR:Rest/binary>>; urandombinstr(Size) when is_integer(Size), Size >= 0 -> <> . %%---------------------------------------------------------------------- %% @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 randombinstr(Size::integer()) ->binary() %% @doc returns a random binary string. slow if Size is high. %% @end %%---------------------------------------------------------------------- randombinstr(0) -> <<>>; randombinstr(Size) when is_integer(Size), Size > 0 -> randombinstr(Size,<<>>). randombinstr(0,Bin) -> Bin; randombinstr(Size,Bin) -> C=random:uniform(25)+$a, randombinstr(Size-1, << Bin/binary, C >>). %%---------------------------------------------------------------------- %% @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. log_transaction([]) -> "-"; log_transaction([{TransactionName,_}| _Tail]) -> TransactionName. %%-------------------------------------------------------------------- %% Func: conv_entities/1 %% Purpose: Convert html entities to string %%-------------------------------------------------------------------- conv_entities(Binary)-> conv_entities(Binary,[]). conv_entities(<< >>,Acc) -> list_to_binary(Acc); conv_entities(<< "&", T/binary >> ,Acc) -> conv_entities(T,[ Acc, << "&">>]); conv_entities(<< "<", T/binary >>,Acc) -> conv_entities(T,[ Acc, << "<">>]); conv_entities(<< ">", T/binary >>,Acc) -> conv_entities(T,[ Acc, << ">">>]); conv_entities(<<""", T/binary >>,Acc) -> conv_entities(T,[ Acc, << "\"">>]); conv_entities(<<"'", T/binary >>,Acc) -> conv_entities(T,[ Acc, << "'">>]); conv_entities(<>,Acc) -> conv_entities(T,[ Acc, H]). %% start an application and it's dependencies recursively %% does the same as application:ensure_all_started (only in R16B2) ensure_all_started(App, Type) -> start_ok(App, Type, application:start(App, Type)). start_ok(_App, _Type, ok) -> ok; start_ok(_App, _Type, {error, {already_started, _App}}) -> ok; start_ok(App, Type, {error, {not_started, Dep}}) -> ok = ensure_all_started(Dep, Type), ensure_all_started(App, Type); start_ok(App, _Type, {error, Reason}) -> erlang:error({app_start_failed, App, Reason}). wildcard(Wildcard,Names) -> PatternTmp = re:replace("^"++Wildcard,"\\*",".*",[{return,list}]), Pattern = re:replace(PatternTmp,"\\?",".{1}",[{return,list}]) ++ "$" , lists:filter(fun(N) -> re:run(N, Pattern) =/= nomatch end, Names). tsung-1.5.1/src/tsung/ts_digest.erl0000644000175000017500000000667612212102345020377 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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:hash(sha,Clear) of {'EXIT',_} -> crypto:start(), crypto:hash(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.5.1/src/tsung/ts_ssl.erl0000644000175000017500000000254512317102151017711 0ustar nniclaussenniclausse-module(ts_ssl). -export([ connect/3,connect/2, send/3, close/1, set_opts/2, protocol_options/1, normalize_incomming_data/2 ]). -behaviour(gen_ts_transport). -include("ts_profile.hrl"). -include("ts_config.hrl"). protocol_options(#proto_opts{ssl_ciphers=negociate, certificate = Cert}) -> [binary, {active, once} ] ++ Cert; protocol_options(#proto_opts{ssl_ciphers=Ciphers, certificate = Cert}) -> ?DebugF("cipher is ~p~n",[Ciphers]), [binary, {active, once}, {ciphers, Ciphers} ] ++ Cert. %% -> {ok, Socket} connect(Host, Port, Opts) -> ssl:connect(Host, Port, opts_to_tcp_opts(Opts)). connect(Socket, Opts)-> ssl:connect(Socket, opts_to_tcp_opts(Opts)). opts_to_tcp_opts(Opts) -> Opts. %% send/3 -> ok | {error, Reason} send(Socket, Data, _Opts) -> ssl:send(Socket, Data). close(none) -> ok; close(Socket) -> ssl:close(Socket). % set_opts/2 -> socket() set_opts(none, _Opts) -> none; set_opts(Socket, Opts) -> ssl:setopts(Socket, Opts), Socket. normalize_incomming_data(Socket, {ssl, Socket, Data}) -> {gen_ts_transport, Socket, Data}; normalize_incomming_data(Socket, {ssl_closed, Socket}) -> {gen_ts_transport, Socket, closed}; normalize_incomming_data(Socket, {ssl_error, Socket, Error}) -> {gen_ts_transport, Socket, error, Error}; normalize_incomming_data(_Socket, X) -> X. %%Other, non gen_tcp packet. tsung-1.5.1/src/tsung/ts_websocket.erl0000644000175000017500000001702412301350731021076 0ustar nniclaussenniclausse%%% This code was developped by Zhihui Jiao(jzhihui521@gmail.com). %%% %%% Copyright (C) 2013 Zhihui Jiao %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_websocket). -vc('$Id$ '). -author('jzhihui521@gmail.com'). -behavior(ts_plugin). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("ts_websocket.hrl"). -export([add_dynparams/4, get_message/2, session_defaults/0, 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, true}. %% @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,#websocket_session{}) -> case websocket:decode(Buffer) of more -> <<>>; {_Opcode, Payload, _Rest} -> Payload end. %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> #websocket_session{}. dump(A,B) -> ts_plugin:dump(A,B). %%---------------------------------------------------------------------- %% Function: get_message/1 %% Purpose: Build a message/request , %% Args: record %% Returns: binary %%---------------------------------------------------------------------- get_message(#websocket_request{type = connect, path = Path, subprotos = SubProtocol, version = Version}, State=#state_rcv{session = WebsocketSession}) -> {Request, Accept} = websocket:get_handshake(State#state_rcv.host, Path, SubProtocol, Version), {Request, WebsocketSession#websocket_session{status = waiting_handshake, accept = Accept}}; get_message(#websocket_request{type = message, data = Data, frame = Frame}, #state_rcv{session = WebsocketSession}) when WebsocketSession#websocket_session.status == connected -> ResultData = case Frame of "text" -> websocket:encode_text(list_to_binary(Data)); _ -> websocket:encode_binary(list_to_binary(Data)) end, {ResultData, WebsocketSession}; get_message(#websocket_request{type = close}, #state_rcv{session = WebsocketSession}) when WebsocketSession#websocket_session.status == connected -> {websocket:encode_close(<<"close">>), WebsocketSession}. %%---------------------------------------------------------------------- %% 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)}); %% handshake stage, parse response, and validate parse(Data, State=#state_rcv{acc = [], session = WebsocketSession}) when WebsocketSession#websocket_session.status == waiting_handshake -> Acc = list_to_binary(State#state_rcv.acc), Header = <>, Accept = WebsocketSession#websocket_session.accept, case websocket:check_handshake(Header, Accept) of ok -> ?Debug("handshake success:~n"), ts_mon:add({count, websocket_succ}), {State#state_rcv{ack_done = true, session = WebsocketSession#websocket_session{ status = connected}}, [], false}; {error, _Reason} -> ?DebugF("handshake fail: ~p~n", [_Reason]), ts_mon:add({count, websocket_fail}), {State#state_rcv{ack_done = true}, [], true} end; %% normal websocket message parse(Data, State=#state_rcv{acc = [], session = WebsocketSession}) when WebsocketSession#websocket_session.status == connected -> case websocket:decode(Data) of {?OP_CLOSE, _Reason, _} -> ?DebugF("receive close from server: ~p~n", [_Reason]), {State#state_rcv{ack_done = true}, [], true}; {_Opcode, _Payload, Left} -> ?DebugF("receive from server: ~p ~p~n", [_Opcode, _Payload]), {State#state_rcv{ack_done = true, acc = Left}, [], false}; more -> ?DebugF("receive incomplete frame from server: ~p~n", [Data]), {State#state_rcv{ack_done = true, 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}). parse_bidi(Data, State) -> ts_plugin:parse_bidi(Data, State). %%---------------------------------------------------------------------- %% Function: parse_config/2 %% Purpose: parse tags in the XML config file related to the protocol %% Returns: List %%---------------------------------------------------------------------- parse_config(Element, Conf) -> ts_config_websocket:parse_config(Element, Conf). %%---------------------------------------------------------------------- %% Function: add_dynparams/4 %% Purpose: we dont actually do anything %% Returns: #websocket_request %%---------------------------------------------------------------------- add_dynparams(true, {DynVars, _S}, Param = #websocket_request{type = message, data = Data}, _HostData) -> NewData = ts_search:subst(Data, DynVars), Param#websocket_request{data = NewData}; add_dynparams(true, {DynVars, _S}, Param = #websocket_request{type = connect, path = Path}, _HostData) -> NewPath = ts_search:subst(Path, DynVars), Param#websocket_request{path = NewPath}; add_dynparams(_Bool, _DynData, Param, _HostData) -> Param#websocket_request{}. tsung-1.5.1/src/tsung/ts_jabber.erl0000644000175000017500000003433012317102151020332 0ustar nniclaussenniclausse%%% 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_macros.hrl"). -include("ts_profile.hrl"). -include("ts_jabber.hrl"). -export([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, username/2, userid/1]). -export ([starttls_bidi/2, presence_bidi/2]). %%---------------------------------------------------------------------- %% 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_session{}) -> Buffer. % nothing to do for jabber %% @spec userid({Session::record(jabber_session), Dynvars::dynvars()}) -> UID::string() %% @doc return the current userid @end userid({#jabber_session{username=UID},_DynVars})-> UID. %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> #jabber_session{}. %%---------------------------------------------------------------------- %% Function: get_message/1 %% Purpose: Build a message/request %% Args: #jabber %% Returns: binary %%---------------------------------------------------------------------- get_message(Req=#jabber{domain={domain,Domain}}, State=#state_rcv{session=S}) when S#jabber_session.domain == undefined -> NewS = S#jabber_session{domain=Domain, user_server=default}, get_message(Req#jabber{domain=Domain, user_server=default},State#state_rcv{session=NewS}); get_message(Req=#jabber{domain={vhost,FileId}}, State=#state_rcv{session=S}) when S#jabber_session.domain == undefined -> {Domain,UserServer} = choose_domain(FileId), NewS = S#jabber_session{domain=Domain, user_server=UserServer}, get_message(Req#jabber{domain=Domain, user_server=UserServer},State#state_rcv{session=NewS}); get_message(Req=#jabber{id=user_defined, username=User, passwd=Passwd}, State=#state_rcv{session=S}) when S#jabber_session.id == undefined -> NewS = S#jabber_session{id=user_defined,username=User,passwd=Passwd}, %% NewDynVars =ts_dynvars:set(xmpp_userid, User, DynData#dyndata.dynvars), %% ?LOGF("Setting up username ~p for ~p~n",[User,ts_dynvars:lookup(tsung_userid,NewDynVars)],?DEB), get_message(Req, State#state_rcv{session=NewS}); get_message(Req=#jabber{prefix=Prefix, passwd=Passwd}, State=#state_rcv{session=S}) when S#jabber_session.id == undefined -> Id = case ts_user_server:get_idle(S#jabber_session.user_server) of {error, no_free_userid} -> ts_mon:add({ count, error_no_free_userid }), exit(no_free_userid); Val-> Val end, {NewUser,NewPasswd} = {username(Prefix,Id), password(Passwd,Id)}, %% NewDynVars =ts_dynvars:set(xmpp_userid, NewUser, DynData#dyndata.dynvars), %% ?LOGF("Setting up username ~p for ~p~n",[NewUser,ts_dynvars:lookup(tsung_userid,NewDynVars)],?DEB), NewS = S#jabber_session{id=Id,username=NewUser,passwd=NewPasswd}, get_message(Req#jabber{username=NewUser,passwd=NewPasswd},State#state_rcv{session=NewS}); 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), BidiElements = [{"]*subscribe[\"\']", presence_bidi}, {" case re:run(RcvdXml,Regex) of {match,_} -> ?LOGF("RECEIVED : ~p~n",[RcvdXml],?DEB), ?MODULE:Handler(RcvdXml, State); _Else -> Acc end end, {nodata, State}, BidiElements). presence_bidi(RcvdXml, State)-> {match,SubMatches} = re:run(RcvdXml,"]*subscribe[\"\'][^>]*>",[global]), bidi_resp(subscribed,RcvdXml,SubMatches,State). starttls_bidi(_RcvdXml, #state_rcv{socket= Socket}=State)-> ssl:start(), Req = subst(State#state_rcv.request#ts_request.param, State#state_rcv.dynvars), Opt = lists:filter(fun({_,V}) -> V /= undefined end, [{certfile,Req#jabber.certfile}, {keyfile,Req#jabber.keyfile}, {password,Req#jabber.keypass}, {cacertfile,Req#jabber.cacertfile}]), {ok, SSL} = ts_ssl:connect(Socket, Opt), ?LOGF("Upgrading to TLS : ~p",[SSL],?INFO), {nodata, State#state_rcv{socket=SSL,protocol=ts_ssl}}. %%---------------------------------------------------------------------- %% 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. %% first request in a session, do nothing add_dynparams(Subst, {DynVars, S}, Param=#jabber{}, Host) when S#jabber_session.id == undefined -> add_dynparams2(Subst,DynVars, Param, Host); add_dynparams(Subst, {DynVars, S}, Param=#jabber{}, Host) -> add_dynparams2(Subst,DynVars, Param#jabber{id=S#jabber_session.id, username=S#jabber_session.username, passwd=S#jabber_session.passwd, domain=S#jabber_session.domain, user_server=S#jabber_session.user_server},Host). add_dynparams2(false,_, Param, _Host) -> Param; add_dynparams2(true, 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) -> {ok,Domain} = ts_file_server:get_random_line(VHostFileId), UserServer = global:whereis_name(list_to_atom("us_"++Domain)), {Domain,UserServer}. %%---------------------------------------------------------------------- %% Function: subst/2 %% Purpose: Replace on the fly dynamic element %%---------------------------------------------------------------------- subst(Req=#jabber{id=user_defined, username=Name,passwd=Pwd, data=Data, resource=Resource}, Dynvars) -> NewUser = ts_search:subst(Name,Dynvars), NewPwd = ts_search:subst(Pwd,Dynvars), NewData = ts_search:subst(Data,Dynvars), subst2(Req#jabber{username=NewUser,passwd=NewPwd,data=NewData,resource=ts_search:subst(Resource,Dynvars)}, Dynvars); subst(Req=#jabber{data=Data,resource=Resource}, Dynvars) -> subst2(Req#jabber{data=ts_search:subst(Data,Dynvars),resource=ts_search:subst(Resource,Dynvars)},Dynvars). subst2(Req=#jabber{type = Type}, Dynvars) when Type == 'starttls' -> Req#jabber{cacertfile = ts_search:subst(Req#jabber.cacertfile, Dynvars), keyfile = ts_search:subst(Req#jabber.keyfile, Dynvars), keypass = ts_search:subst(Req#jabber.keypass, Dynvars), certfile = ts_search:subst(Req#jabber.certfile, Dynvars)}; subst2(Req=#jabber{type = Type}, Dynvars) when Type == 'muc:chat' ; Type == 'muc:join'; Type == 'muc:nick' ; Type == 'muc:exit' -> Req#jabber{nick = ts_search:subst(Req#jabber.nick, Dynvars), room = ts_search:subst(Req#jabber.room, Dynvars)}; subst2(Req=#jabber{type = Type}, Dynvars) when Type == 'pubsub:create' ; Type == 'pubsub:subscribe'; Type == 'pubsub:publish'; Type == 'pubsub:delete' -> Req#jabber{node = ts_search:subst(Req#jabber.node, Dynvars)}; subst2(Req=#jabber{type = Type}, Dynvars) when Type == 'pubsub:unsubscribe' -> NewNode=ts_search:subst(Req#jabber.node,Dynvars), NewSubId=ts_search:subst(Req#jabber.subid,Dynvars), Req#jabber{node=NewNode,subid=NewSubId}; subst2(Req, _Dynvars) -> Req. %%---------------------------------------------------------------------- %% 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). %%%---------------------------------------------------------------------- %%% 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, DestId) -> Prefix ++ DestId. %%%---------------------------------------------------------------------- %%% Func: password/1 %%% Generate password for a given username %%%---------------------------------------------------------------------- password(Prefix,Id) -> username(Prefix,Id). tsung-1.5.1/src/tsung/ts_mqtt.erl0000644000175000017500000003172012236145741020106 0ustar nniclaussenniclausse%%% This code was developped by Zhihui Jiao(jzhihui521@gmail.com). %%% %%% Copyright (C) 2013 Zhihui Jiao %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_mqtt). -vc('$Id$ '). -author('jzhihui521@gmail.com'). -behavior(ts_plugin). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("ts_mqtt.hrl"). -include("mqtt.hrl"). -export([add_dynparams/4, get_message/2, session_defaults/0, parse/2, dump/2, parse_bidi/2, parse_config/2, decode_buffer/2, new_session/0]). -export([ping_loop/3]). %%---------------------------------------------------------------------- %% Function: session_default/0 %% Purpose: default parameters for session (persistent & bidirectional) %% Returns: {ok, true|false, true|false} %%---------------------------------------------------------------------- session_defaults() -> {ok, true, true}. %% @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, #mqtt_session{}) -> Buffer. %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> #mqtt_session{}. dump(A, B) -> ts_plugin:dump(A,B). %%---------------------------------------------------------------------- %% Function: get_message/1 %% Purpose: Build a message/request , %% Args: record %% Returns: binary %%---------------------------------------------------------------------- get_message(#mqtt_request{type = connect, clean_start = CleanStart, keepalive = KeepAlive, will_topic = WillTopic, will_qos = WillQos, will_msg = WillMsg, will_retain = WillRetain}, #state_rcv{session = MqttSession}) -> ClientId = ["tsung-", ts_utils:randombinstr(10)], PublishOptions = mqtt_frame:set_publish_options([{qos, WillQos}, {retain, WillRetain}]), Will = #will{topic = WillTopic, message = WillMsg, publish_options = PublishOptions}, Options = mqtt_frame:set_connect_options([{client_id, ClientId}, {clean_start, CleanStart}, {keepalive, KeepAlive}, Will]), Message = #mqtt{type = ?CONNECT, arg = Options}, {mqtt_frame:encode(Message), MqttSession#mqtt_session{wait = ?CONNACK, keepalive = KeepAlive}}; get_message(#mqtt_request{type = disconnect}, #state_rcv{session = MqttSession}) -> PingPid = MqttSession#mqtt_session.ping_pid, PingPid ! stop, Message = #mqtt{type = ?DISCONNECT}, ts_mon:add({count, mqtt_disconnected}), {mqtt_frame:encode(Message), MqttSession#mqtt_session{wait = none, status = disconnect}}; get_message(#mqtt_request{type = publish, topic = Topic, qos = Qos, retained = Retained, payload = Payload}, #state_rcv{session = MqttSession = #mqtt_session{curr_id = Id}}) -> NewMqttSession = case Qos of 0 -> MqttSession; _ -> MqttSession#mqtt_session{curr_id = Id + 1} end, MsgId = NewMqttSession#mqtt_session.curr_id, Message = #mqtt{id = MsgId, type = ?PUBLISH, qos = Qos, retain = Retained, arg = {Topic, Payload}}, Wait = case Qos of 1 -> ?PUBACK; _ -> none end, ts_mon:add({count, mqtt_published}), {mqtt_frame:encode(Message), NewMqttSession#mqtt_session{wait = Wait}}; get_message(#mqtt_request{type = subscribe, topic = Topic, qos = Qos}, #state_rcv{session = MqttSession = #mqtt_session{curr_id = Id}}) -> NewMqttSession = MqttSession#mqtt_session{curr_id = Id + 1}, Arg = [#sub{topic = Topic, qos = Qos}], MsgId = NewMqttSession#mqtt_session.curr_id, Message = #mqtt{id = MsgId, type = ?SUBSCRIBE, arg = Arg}, {mqtt_frame:encode(Message), NewMqttSession#mqtt_session{wait = ?SUBACK}}; get_message(#mqtt_request{type = unsubscribe, topic = Topic}, #state_rcv{session = MqttSession = #mqtt_session{curr_id = Id}}) -> NewMqttSession = MqttSession#mqtt_session{curr_id = Id + 1}, Arg = [#sub{topic = Topic}], MsgId = NewMqttSession#mqtt_session.curr_id, Message = #mqtt{id = MsgId, type = ?UNSUBSCRIBE, arg = Arg}, {mqtt_frame:encode(Message),NewMqttSession#mqtt_session{wait = ?UNSUBACK}}. %%---------------------------------------------------------------------- %% 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)}); %% normal mqtt message parse(Data, State=#state_rcv{acc = [], session = MqttSession, socket = Socket}) -> Wait = MqttSession#mqtt_session.wait, AckBuf = MqttSession#mqtt_session.ack_buf, case mqtt_frame:decode(Data) of {_MqttMsg = #mqtt{type = Wait}, Left} -> ?DebugF("receive mqtt_msg: ~p ~p~n", [mqtt_frame:command_for_type(Wait), _MqttMsg]), NewLeft = case Wait of ?SUBACK -> <<>>; _ -> Left end, case Wait of ?CONNACK -> ts_mon:add({count, mqtt_connected}); ?PUBACK -> ts_mon:add({count, mqtt_server_pubacked}); ?SUBACK -> case {AckBuf, Left} of {<<>>, <<>>} -> ok; _ -> self() ! {gen_ts_transport, Socket, Left} end; _ -> ok end, NewMqttSession = case Wait of ?CONNACK -> Proto = State#state_rcv.protocol, KeepAlive = MqttSession#mqtt_session.keepalive, PingPid = create_ping_proc(Proto, Socket, KeepAlive), MqttSession#mqtt_session{ping_pid = PingPid}; _ -> MqttSession end, {State#state_rcv{ack_done = true, acc = NewLeft, session = NewMqttSession}, [], false}; {_MqttMsg = #mqtt{id = MessageId, type = Type, qos = Qos}, Left} -> ?DebugF("receive mqtt_msg, expecting: ~p, actual: ~p ~p~n", [mqtt_frame:command_for_type(Wait), mqtt_frame:command_for_type(Type), _MqttMsg]), NewMqttSession = case {Wait, Type, Qos} of {?SUBACK, ?PUBLISH, 1} -> Message = #mqtt{type = ?PUBACK, arg = MessageId}, EncodedData = mqtt_frame:encode(Message), ts_mon:add({count, mqtt_server_published}), NewAckBuf = <>, MqttSession#mqtt_session{ack_buf = NewAckBuf}; _ -> MqttSession end, {State#state_rcv{ack_done = false, acc = Left, session = NewMqttSession}, [], false}; more -> ?DebugF("incomplete mqtt frame: ~p~n", [Data]), {State#state_rcv{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}). parse_bidi(<<>>, State=#state_rcv{acc = [], session = MqttSession}) -> AckBuf = MqttSession#mqtt_session.ack_buf, Ack = case AckBuf of <<>> -> nodata; _ -> AckBuf end, NewMqttSession = MqttSession#mqtt_session{ack_buf = <<>>}, ?DebugF("ack buf: ~p~n", [AckBuf]), {Ack, State#state_rcv{session = NewMqttSession}}; parse_bidi(Data, State=#state_rcv{acc = [], session = MqttSession}) -> AckBuf = MqttSession#mqtt_session.ack_buf, case mqtt_frame:decode(Data) of {_MqttMsg = #mqtt{type = ?PUBLISH, qos = Qos, id = MessageId}, Left} -> ?DebugF("receive bidi mqtt_msg: ~p ~p~n", [mqtt_frame:command_for_type(?PUBLISH), _MqttMsg]), ts_mon:add({count, mqtt_server_published}), ts_mon:add({count, mqtt_pubacked}), Ack = case Qos of 1 -> Message = #mqtt{type = ?PUBACK, arg = MessageId}, mqtt_frame:encode(Message); _ -> <<>> end, NewAckBuf = <>, NewMqttSession = MqttSession#mqtt_session{ack_buf = NewAckBuf}, parse_bidi(Left, State#state_rcv{session = NewMqttSession}); {_MqttMsg = #mqtt{type = _Type}, Left} -> ?DebugF("receive bidi mqtt_msg: ~p ~p~n", [mqtt_frame:command_for_type(_Type), _MqttMsg]), parse_bidi(Left, State); more -> {nodata, State#state_rcv{acc = Data}} end; parse_bidi(Data, State=#state_rcv{acc = Acc, datasize = DataSize}) -> NewSize = DataSize + size(Data), ?DebugF("parse mqtt bidi data: ~p ~p~n", [Data, Acc]), parse_bidi(<>, 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_mqtt:parse_config(Element, Conf). %%---------------------------------------------------------------------- %% Function: add_dynparams/4 %% Purpose: we dont actually do anything %% Returns: #websocket_request %%---------------------------------------------------------------------- add_dynparams(true, {DynVars, _S}, Param = #mqtt_request{type = publish, topic = Topic, payload = Payload}, _HostData) -> NewTopic = ts_search:subst(Topic, DynVars), NewPayload = ts_search:subst(Payload, DynVars), Param#mqtt_request{topic = NewTopic, payload = NewPayload}; add_dynparams(true, {DynVars, _S}, Param = #mqtt_request{type = subscribe, topic = Topic}, _HostData) -> NewTopic = ts_search:subst(Topic, DynVars), Param#mqtt_request{topic = NewTopic}; add_dynparams(true, {DynVars, _S}, Param = #mqtt_request{type = unsubscribe, topic = Topic}, _HostData) -> NewTopic = ts_search:subst(Topic, DynVars), Param#mqtt_request{topic = NewTopic}; add_dynparams(_Bool, _DynData, Param, _HostData) -> Param#mqtt_request{}. %%%=================================================================== %%% Internal functions %%%=================================================================== create_ping_proc(Proto, Socket, KeepAlive) -> PingPid = proc_lib:spawn_link(?MODULE, ping_loop, [Proto, Socket, KeepAlive]), erlang:send_after(KeepAlive * 1000, PingPid, ping), PingPid. ping_loop(Proto, Socket, KeepAlive) -> receive ping -> try Message = #mqtt{type = ?PINGREQ}, PingFrame = mqtt_frame:encode(Message), Proto:send(Socket, PingFrame, []) catch Error -> ?LOGF("Error sending mqtt pingreq: ~p~n",[Error], ?ERR) end, erlang:send_after(KeepAlive * 1000, self(), ping), ping_loop(Proto, Socket, KeepAlive); stop -> ok end. tsung-1.5.1/src/tsung/ts_tcp.erl0000644000175000017500000000452512147621622017710 0ustar nniclaussenniclausse%%% %%% Copyright 2010 © ProcessOne %%% %%% Author : Eric Cestari %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_tcp). -export([ connect/3, send/3, close/1, set_opts/2, protocol_options/1, normalize_incomming_data/2 ]). -behaviour(gen_ts_transport). -include("ts_profile.hrl"). -include("ts_config.hrl"). protocol_options(#proto_opts{tcp_rcv_size=Rcv, tcp_snd_size=Snd}) -> [binary, {active, once}, {recbuf, Rcv}, {sndbuf, Snd}, {keepalive, true} %% FIXME: should be an option ]. %% -> {ok, Socket} connect(Host, Port, Opts) -> gen_tcp:connect(Host, Port, opts_to_tcp_opts(Opts)). opts_to_tcp_opts(Opts) -> Opts. %% send/3 -> ok | {error, Reason} send(Socket, Data, _Opts) -> gen_tcp:send(Socket, Data). close(none) -> ok; close(Socket) -> gen_tcp:close(Socket). % set_opts/2 -> socket() set_opts(none, _Opts) -> none; set_opts(Socket, Opts) -> inet:setopts(Socket, Opts), Socket. normalize_incomming_data(Socket, {tcp, Socket, Data}) -> {gen_ts_transport, Socket, Data}; normalize_incomming_data(Socket, {tcp_closed, Socket}) -> {gen_ts_transport, Socket, closed}; normalize_incomming_data(Socket, {tcp_error, Socket, Error}) -> {gen_ts_transport, Socket, error, Error}; normalize_incomming_data(_Socket, X) -> X. %%Other, non gen_tcp packet. tsung-1.5.1/src/tsung/ts_udp6.erl0000644000175000017500000000371412147621622017777 0ustar nniclaussenniclausse%%% %%% Copyright 2012 © Nicolas Niclausse %%% %%% Author : Nicolas Niclausse %%% Created: 7 sep 2012 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_udp6). -export([ connect/3, send/3, close/1, set_opts/2, protocol_options/1, normalize_incomming_data/2 ]). -behaviour(gen_ts_transport). -include("ts_profile.hrl"). -include("ts_config.hrl"). protocol_options(Opts) -> [inet6] ++ ts_udp:protocol_options(Opts). %% -> {ok, Socket} connect(_Host, _Port, Opts) -> gen_udp:open(0, Opts). %% send/3 -> ok | {error, Reason} send(Socket, Data, Opts) -> ts_udp:send(Socket, Data, Opts). close(Socket) -> ts_udp:close(Socket). % set_opts/2 -> socket() set_opts(none, _Opts) -> none; set_opts(Socket, Opts) -> inet:setopts(Socket, Opts), Socket. normalize_incomming_data(Socket, Data) -> ts_udp:normalize_incomming_data(Socket,Data). tsung-1.5.1/src/tsung/ts_session_cache.erl0000644000175000017500000002033512147621622021725 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%%------------------------------------------------------------------- %%% 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_macros.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.5.1/src/tsung/ts_plugin.erl0000644000175000017500000000416112147621622020414 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_plugin). -export([dump/2, parse_bidi/2]). -export([behaviour_info/1]). behaviour_info(callbacks) -> [{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.5.1/src/tsung/ts_http_common.erl0000644000175000017500000007434712301350731021452 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%% 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, get_line/1 ]). %%---------------------------------------------------------------------- %% 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}=Req)-> ?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(Req), oauth_sign(Method,Req), 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}=Req) -> ?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(Req), oauth_sign(Method,Req), 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}=Req) -> 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(Req), soap_action(SOAPAction), oauth_sign(Method, Req), 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(#http_request{userid=undefined})-> []; authenticate(#http_request{passwd=undefined})-> []; authenticate(#http_request{passwd=Passwd, auth_type="basic",userid=UserId})-> AuthStr = ts_utils:encode_base64(lists:append([UserId,":",Passwd])), ["Authorization: Basic ",AuthStr,?CRLF]; authenticate(#http_request{method=Method, passwd=Passwd,userid=UserId, auth_type="digest", realm=Realm, digest_cnonce=CNonce, digest_nc=NC, digest_qop=QOP, digest_nonce=Nonce, digest_opaque=Opaque, url=URL }) -> HA1 = md5_hex(string:join([UserId, Realm, Passwd], ":")), HA2 = md5_hex(string:join([string:to_upper(atom_to_list(Method)), URL], ":")), Response = digest_response({HA1, Nonce,NC, CNonce,QOP,HA2}), digest_header(UserId,Realm,Nonce,URL,QOP,NC,CNonce,Response,Opaque). digest_header(User,Realm,Nonce,URI, QOP,NC,CNonce, Response,Opaque) -> Acc= ["Authorization: Digest " "username=\"",User,"\", ", "realm=\"", Realm, "\", ", "nonce=\"", Nonce, "\", ", "uri=\"", URI, "\", ", "response=\"", Response, "\""], digest_header_opt(Acc, QOP, NC, CNonce, Opaque). %% qop and opaque are undefined digest_header_opt(Acc, undefined, _NC, _CNonce, undefined) -> [Acc, ?CRLF]; digest_header_opt(Acc, QOP, NC, CNonce, Opaque) when is_list(Opaque)-> NewAcc=[Acc,", opaque=\"",Opaque,"\""], digest_header_opt(NewAcc,QOP,NC,CNonce,undefined); digest_header_opt(Acc, QOP, NC, CNonce,undefined) -> NewAcc=[Acc,", qop=\"",QOP,"\"", ", nc=", NC, ", cnonce=\"", CNonce, "\"" ], digest_header_opt(NewAcc,undefined,"","",undefined). digest_response({HA1,Nonce, _NC, _CNonce, undefined, HA2})-> %qop undefined md5_hex(string:join([HA1, Nonce, HA2], ":")); digest_response({HA1,Nonce, NC, CNonce, QOP, HA2})-> md5_hex(string:join([HA1,Nonce,NC,CNonce,QOP,HA2], ":")). md5_hex(String)-> lists:flatten([io_lib:format("~2.16.0b",[N])||N<-binary_to_list(erlang:md5(String))]). oauth_sign(_, #http_request{oauth_consumer = undefined})->[]; oauth_sign(Method, #http_request{url=URL, oauth_consumer=Consumer, oauth_access_token=AccessToken, oauth_access_secret=AccessSecret, oauth_url=ServerURL})-> %%UrlParams = oauth_uri:params_from_string(URL), [_He|Ta] = string:tokens(URL,"?"), UrlParams = oauth_uri:params_from_string(lists:flatten(Ta)), Params = oauth:signed_params(Method, ServerURL, UrlParams, Consumer, AccessToken, AccessSecret), ["Authorization: OAuth ", oauth_uri:params_to_header_string(Params),?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} -> NewCookies = concat_cookies(Http#http.cookie, Http#http.session_cookies), case parse_chunked(Tail, State#state_rcv{session=Http, acc=[]}) of {NewState=#state_rcv{ack_done=false, session=NewHttp}, Opts} -> {NewState#state_rcv{session=NewHttp#http{session_cookies=NewCookies}}, Opts, false}; {NewState=#state_rcv{session=NewHttp}, Opts} -> {NewState#state_rcv{acc=[],session=NewHttp#http{session_cookies=NewCookies}}, Opts, Http#http.close} end; {ok, Http=#http{content_length=0, close=true}, _} -> %% no content length, close=true: the server will close the connection NewCookies = concat_cookies(Http#http.cookie, Http#http.session_cookies), {State#state_rcv{ack_done = false, datasize = TotalSize, session=Http#http{session_cookies=NewCookies}}, [], 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} -> NewCookies = concat_cookies(Http#http.cookie, Http#http.session_cookies), check_resp_size(Http#http{session_cookies=NewCookies}, length(Tail), 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=#state_rcv{session=Http, datasize=PreviousSize}) -> DataSize = size(Data), ?DebugF("HTTP Body size=~p ~n",[DataSize]), 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 -> {State#state_rcv{session = Http#http{body_size = Size}, 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, State, DataSize, _Dump) -> %% end of response {State#state_rcv{session= reset_session(Http), ack_done = true, datasize = DataSize }, [], Close}; check_resp_size(Http=#http{content_length=CLength, close=Close}, BodySize, 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 }, [], Close}; check_resp_size(Http=#http{}, BodySize, State, DataSize,_Dump) -> %% need to read more data {State#state_rcv{session = Http#http{body_size = BodySize}, ack_done = false, datasize = DataSize },[],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=[],session=Http}, Int, Acc) -> % not enough data in buffer BodySize = size(Data), ?DebugF("Partial chunk received (~p/~p)~n", [BodySize,Int]), NewHttp = Http#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([], Cookies) -> Cookies; concat_cookies(Cookie, []) -> Cookie; 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("patch")-> 'PATCH'; 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) when hd(Tail) /= $\s -> %% tuning: handle common case (single LWS) to avoid a call to string:strip CL = list_to_integer(Tail), ?DebugF("HTTP Content-Length ~p~n",[CL]), Http#http{content_length=CL}; parse_line("content-length: "++Tail, Http, _Host)-> % multiple white spaces CL = list_to_integer(string:strip(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:"++Tail, Http, _Host)-> ?DebugF("~p transfer encoding~n",[Tail]), case string:strip(Tail) of [C|"hunked"++_] when C == $C; C == $c -> Http#http{chunk_toread=0}; _ -> ?LOGF("Unknown transfer encoding ~p~n",[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{user_agent=UA,session_cookies=Cookies, compressed={Compressed,_}, status= {Status,_}, chunk_toread=Val}) when Val > -1 -> #http{session_cookies=Cookies,user_agent=UA,compressed={false,Compressed}, chunk_toread=-2, status={none,Status}} ; reset_session(#http{user_agent=UA,session_cookies=Cookies, compressed={Compressed,_}, status= {Status,_}}) -> #http{session_cookies=Cookies,user_agent=UA,compressed={false,Compressed}, 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.5.1/src/tsung/ts_jabber_common.erl0000644000175000017500000010076212147621622021717 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_jabber_common). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -export([ get_message/1, starttls/0 ]). -include("ts_macros.hrl"). -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 = 'starttls'}) -> starttls(); 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,prefix=Prefix, 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); {ok, Dest} -> presence(directed, ts_jabber:username(Prefix,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, prefix=Prefix, domain=Domain,user_server=UserServer})-> case ts_user_server:get_online(UserServer,set_id(Id,User,Pwd)) of {ok, {Dest,_}} -> message(Dest, Jabber, Domain); {ok, Dest} -> message(ts_jabber:username(Prefix,Dest), Jabber, Domain); {error, no_online} -> ts_mon:add({ count, error_no_online }), << >> end; get_message(Jabber=#jabber{type = 'chat',domain=Domain,prefix=Prefix,dest=offline,user_server=UserServer})-> case ts_user_server:get_offline(UserServer) of {ok, {Dest,_}} -> message(Dest, Jabber, Domain); {ok, Dest} -> message(ts_jabber:username(Prefix,Dest), Jabber, Domain); {error, no_offline} -> ts_mon:add({ count, error_no_offline }), << >> end; get_message(Jabber=#jabber{type = 'chat', dest=random, prefix=Prefix, domain=Domain,user_server=UserServer}) -> case ts_user_server:get_id(UserServer) of {error, Msg} -> ?LOGF("Can't find a random user (~p)~n", [Msg],?ERR), << >>; {Dest,_} -> message(Dest, Jabber, Domain); DestId -> message(ts_jabber:username(Prefix,DestId), Jabber, Domain) end; get_message(Jabber=#jabber{type = 'chat', dest=unique, prefix=Prefix, domain=Domain,user_server=UserServer})-> case ts_user_server:get_first(UserServer) of {Dest, _} -> message(Dest, Jabber, Domain); IdDest -> message(ts_jabber:username(Prefix,IdDest), Jabber, Domain) end; 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, prefix=Prefix}) -> case ts_user_server:get_online(UserServer,set_id(Id,User,Pwd)) of {ok, {Dest,_}} -> request(roster_add, Domain, Dest, Group); {ok, DestId} -> request(roster_add, Domain, ts_jabber:username(Prefix,DestId), Group); {error, no_online} -> ts_mon:add({ count, error_no_online }), << >> end; get_message(#jabber{type = 'iq:roster:add',dest = offline, prefix=Prefix, domain=Domain, group=Group, user_server=UserServer})-> case ts_user_server:get_offline(UserServer) of {ok, {Dest,_}} -> request(roster_add, Domain, Dest, Group); {ok, Dest} -> request(roster_add, Domain, ts_jabber:username(Prefix,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', username=Username, node=Node, node_type=NodeType, data = Data, pubsub_service = PubSubComponent, domain = Domain}) -> create_pubsub_node(Domain, PubSubComponent, Username, Node, NodeType, Data); %% For node subscription, data contain the pubsub nodename (relative to user %% hierarchy or absolute) get_message(#jabber{type = 'pubsub:subscribe', id=Id, username=UserFrom, user_server=UserServer, passwd=Pwd, prefix=Prefix, dest=online, node=Node, pubsub_service = PubSubComponent, domain = Domain}) -> case ts_user_server:get_online(UserServer,set_id(Id,UserFrom,Pwd)) of {ok, {UserTo,_}} -> subscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node); {ok, Dest} -> UserTo = ts_jabber:username(Prefix, Dest), %%FIXME: we need the username prefix here 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', username=UserFrom, user_server=UserServer, prefix=Prefix, dest=offline, node=Node, domain = Domain, pubsub_service = PubSubComponent}) -> case ts_user_server:get_offline(UserServer) of {ok, {UserTo,_}} -> subscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node); {ok, DestId} -> UserTo = ts_jabber:username(Prefix,DestId), 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', username=UserFrom, user_server=UserServer, prefix=Prefix, dest=random, node=Node, domain = Domain, pubsub_service = PubSubComponent}) -> case ts_user_server:get_id(UserServer) of {UserTo,_} -> subscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node); DestId -> UserTo = ts_jabber:username(Prefix,DestId), subscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node) end; get_message(#jabber{type = 'pubsub:subscribe', username=UserFrom, dest=UserTo, node=Node, domain = Domain, pubsub_service = PubSubComponent}) -> subscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node); %% FIXME is it ok ?! %% For node unsubscribe, data contain the pubsub nodename (relative to user %% hierarchy or absolute) get_message(#jabber{type = 'pubsub:unsubscribe', username=UserFrom, user_server=UserServer, prefix=Prefix, dest=random, node=Node, domain=Domain, pubsub_service=PubSubComponent, subid=SubId}) -> case ts_user_server:get_id(UserServer) of {UserTo,_} -> unsubscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node, SubId); DestId -> UserTo = ts_jabber:username(Prefix,DestId), unsubscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node, SubId) end; get_message(#jabber{type = 'pubsub:unsubscribe', username=UserFrom, dest=UserTo, node=Node, domain=Domain, pubsub_service=PubSubComponent, subid=SubId}) -> unsubscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node, SubId); %% For node publication, data contain the pubsub nodename (relative to user %% hierarchy or absolute) get_message(#jabber{type = 'pubsub:publish', size=Size, username=Username, node=Node, pubsub_service=PubSubComponent, domain=Domain}) -> 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, domain = Domain}) -> privacy_get_names(Name, Domain); get_message(#jabber{type = 'privacy:set_active', username = Name, domain = Domain}) -> privacy_set_active(Name, Domain); get_message(Jabber) -> get_message2(Jabber). %%---------------------------------------------------------------------- %% 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, version = Version}) -> VersionStr = case Version of "legacy" -> ""; V when is_list(V) -> "version='" ++ Version ++"' " end, list_to_binary([ ""]). %%---------------------------------------------------------------------- %% Func: close/0 %% Purpose: close jabber session %%---------------------------------------------------------------------- close () -> list_to_binary(""). %%---------------------------------------------------------------------- %% Func: starttls/0 %% Purpose: send the starttls element %%---------------------------------------------------------------------- starttls()-> <<"">>. %%---------------------------------------------------------------------- %% 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,resource=Resource})-> auth_set_plain(Name, Passwd, "auth", Resource). %%---------------------------------------------------------------------- %% Func: auth_set_plain/3 %%---------------------------------------------------------------------- auth_set_plain(Username, Passwd, Type, Resource) -> list_to_binary([ "", "", "", Username, "", "", Resource,"", "", Passwd, ""]). %%---------------------------------------------------------------------- %% Func: auth_set_digest/2 %%---------------------------------------------------------------------- auth_set_digest(#jabber{username=Name,passwd=Passwd, resource=Resource}, Sid)-> auth_set_digest(Name, Passwd, "auth", Sid, Resource). %%---------------------------------------------------------------------- %% Func: auth_set_digest/4 %%---------------------------------------------------------------------- auth_set_digest(Username, Passwd, Type, Sid, Resource) -> {Digest} = ts_digest:digest(Sid, Passwd), list_to_binary([ "", "", "", Username, "", "",Resource,"", "", Digest, ""]). %%---------------------------------------------------------------------- %% Func: auth_set_sip/3 %%---------------------------------------------------------------------- auth_set_sip(#jabber{username=Name,passwd=Passwd,domain=Domain,resource=Resource}, Nonce, Realm)-> auth_set_sip(Name, Passwd, Domain, "auth", Nonce, Realm,Resource). %%---------------------------------------------------------------------- %% Func: auth_set_sip/6 %%---------------------------------------------------------------------- auth_set_sip(Username, Passwd, Domain, Type, Nonce, Realm,Resource) -> Jid = Username ++ "@" ++ Realm, {SipDigest,Integrity} = ts_digest:sip_digest(Nonce, Jid, Realm, Passwd), list_to_binary([ "", "", "", Jid, "", "",Resource,"", "", "", 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, resource=Resource})-> auth_sasl_bind(Name, Passwd, Domain, Resource). %%---------------------------------------------------------------------- %% Func: auth_sasl_bind/3 %%---------------------------------------------------------------------- auth_sasl_bind(_Username, _Passwd, _Domain, Resource) -> list_to_binary(["", "",Resource,"", ""]). %%---------------------------------------------------------------------- %% 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,resource=Resource})-> auth_set_plain(Name, Passwd, "register",Resource). %%---------------------------------------------------------------------- %% Func: message/3 %% Purpose: send message to defined user at the Service (aim, ...) %%---------------------------------------------------------------------- message(Dest, #jabber{size=Size,data=undefined}, Service) when is_integer(Size) -> put(previous, Dest), list_to_binary([ "",ts_utils:urandomstr_noflat(Size), ""]); message(Dest, #jabber{data=Data}, Service) when is_list(Data) -> put(previous, Dest), list_to_binary([ "",Data, ""]). %%---------------------------------------------------------------------- %% Func: presence/0 %%---------------------------------------------------------------------- presence() -> list_to_binary([ ""]). %%---------------------------------------------------------------------- %% Func: presence/1 %%---------------------------------------------------------------------- presence(unavailable)-> list_to_binary([ ""]). %%---------------------------------------------------------------------- %% Func: presence/2 %%---------------------------------------------------------------------- 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=DestName, domain=Domain})-> list_to_binary([ ""]). %%---------------------------------------------------------------------- %% Func: presence/3 %%---------------------------------------------------------------------- presence(broadcast, Show, Status) -> list_to_binary([ "", "", Show, "", Status, ""]). %%---------------------------------------------------------------------- %% Func: presence/4 %%---------------------------------------------------------------------- presence(directed, DestName, #jabber{domain=Domain}, Show, Status) -> 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/4 %%---------------------------------------------------------------------- request(roster_add, Domain, Dest, Group)-> RosterJid = Dest ++ "@" ++ 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, Data) -> list_to_binary(["" "" "", " ", create_pubsub_node_options(Data), ""]). create_pubsub_node_options(undefined) -> ""; create_pubsub_node_options(Data) when is_list(Data) -> case erl_scan:string(Data) of {ok, Ts, _} -> field_elements(erl_parse:parse_term(Ts)); _ -> ?LOG("Warn: Invalid erlang term scanned from data in pubsub create node", ?WARN), "" end. field_value(Value) when is_list(Value) -> F = fun(Item, Acc) -> Acc ++ "" ++ atom_to_list(Item) ++ "" end, lists:foldl(F, "", Value); field_value(Value) -> "" ++ atom_to_list(Value) ++ "". field_elements({ok, Fields}) -> F = fun({Field, Value}, Acc) -> Acc ++ "" ++ field_value(Value) ++ "" end, lists:foldl(F, "", Fields); field_elements(_) -> ?LOG("Warn: Invalid erlang term parsed from data in pubsub create node", ?WARN), "". %% 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: unsubscribe_pubsub_node/4 %%% Unsubscribe from a pubsub node: Generate XML packet %%% If node name is undefined (data attribute), we unsubscribe from target user %%% root node %%% Nodenames are relative to the User pubsub hierarchy (ejabberd); they are %%% absolute with leading slash. %%%---------------------------------------------------------------------- unsubscribe_pubsub_node(Domain, PubSubComponent, UserFrom, UserTo, Node, SubId) -> 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). %% 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.5.1/src/tsung/ts_erlang.erl0000644000175000017500000000446512320752470020374 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_erlang). -vc('$Id: ts_erlang.erl,v 0.0 2009/08/20 16:31:58 nniclaus Exp $ '). -author('nniclaus@sophia.inria.fr'). -behaviour(gen_ts_transport). -define(TIMEOUT,36000000). % 1 hour -include("ts_profile.hrl"). -export([ connect/3, send/3, close/1, set_opts/2, protocol_options/1, normalize_incomming_data/2, 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. protocol_options(_Opts) -> []. %% -> {ok, Socket} connect(Host, Port, Opts) -> Pid=spawn_link(ts_erlang,client,[self(),Host,Port,Opts]), {ok, Pid}. %% send/3 -> ok | {error, Reason} send(Pid, Data, _Opts) -> Pid ! Data, ok. close(_Socket) -> ok. set_opts(Socket, _Opts) -> Socket. normalize_incomming_data(_Socket, Data={timeout,_,_}) -> Data; normalize_incomming_data(Socket, Data) -> {gen_ts_transport, Socket, Data}. tsung-1.5.1/src/tsung/ts_mysql.erl0000644000175000017500000002603612320752470020267 0ustar nniclaussenniclausse%%% Created : July 2008 by Grégoire 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_macros.hrl"). -include("ts_profile.hrl"). -include("ts_mysql.hrl"). -export([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_session{}) -> Buffer. % FIXME ? %%---------------------------------------------------------------------- %% Function: new_session/0 %% Purpose: initialize session information %% Returns: record or [] %%---------------------------------------------------------------------- new_session() -> #mysql_session{}. 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 = [],session=S, datasize= 0}) -> ?LOGF("Parsing greeting ~p ~n",[Data], ?DEB), Salt= get_salt(Data), NewS=S#mysql_session{salt=Salt}, {State#state_rcv{ack_done = true, datasize=size(Data), session=NewS},[],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, {_DynVars, Session}, Param, HostData) -> add_dynparams(Session, Param, HostData); add_dynparams(true, {DynVars, Session}, Param, HostData) -> NewParam = subst(Param, DynVars), add_dynparams(Session,NewParam, HostData). add_dynparams(DynMysql, Param, _HostData) -> Param#mysql_request{salt=DynMysql#mysql_session.salt}. %%---------------------------------------------------------------------- %% Function: subst/2 %% Purpose: Replace on the fly dynamic element of the request. %% Returns: #mysql_request %%---------------------------------------------------------------------- subst(Req=#mysql_request{sql=SQL}, DynVars) -> Req#mysql_request{sql=ts_search:subst(SQL, DynVars)}. %%% -- 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:hash(sha,Password) of {'EXIT',_} -> crypto:start(), crypto:hash(sha,Password); Sha -> Sha end, Stage2 = crypto:hash(sha,Stage1), Res = crypto:hash_final(crypto:hash_update(crypto:hash_update(crypto:hash_init(sha), 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.5.1/src/tsung/ts_fs.erl0000644000175000017500000002706412212102345017522 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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_macros.hrl"). -include("ts_profile.hrl"). -include("ts_fs.hrl"). -include_lib("kernel/include/file.hrl"). -export([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_session{}. %% @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_session{}) -> Buffer. %% @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, {_,Session}, Param, HostData) -> add_dynparams(Session, Param, HostData); add_dynparams(true, {DynVars,Session}, Param, HostData) -> NewParam = subst(Param, DynVars), add_dynparams(Session,NewParam, HostData). add_dynparams(#fs_session{position=Pos,iodev=IODevice}, Req=#fs{}, _HostData) when is_integer(Pos)-> Req#fs{position=Pos,iodev=IODevice}; add_dynparams(#fs_session{}, 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,dest=Dest}, DynVars) -> Req#fs{path=ts_search:subst(Path,DynVars),dest=ts_search:subst(Dest,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{session=S}) -> NewDyn=S#fs_session{iodev=IODevice,position=0}, {State#state_rcv{ack_done=true,datasize=0,session=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{session=S}) -> NewDyn=S#fs_session{iodev=undefined,position=0}, {State#state_rcv{ack_done=true,datasize=0,session=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{session=S,datasize=DataSize}) -> NewDyn=S#fs_session{position=Pos+Size}, {State#state_rcv{ack_done=true,datasize=DataSize+Size,session=NewDyn}, [], false}; parse({file, pread, [_IODev,_Pos,Size], eof},State=#state_rcv{session=S,datasize=DataSize}) -> NewDyn=S#fs_session{position=0}, {State#state_rcv{ack_done=true,datasize=DataSize+Size,session=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{session=S}) -> NewDyn=S#fs_session{position=Pos+length(Data)}, {State#state_rcv{ack_done=true,datasize=0,session=NewDyn}, [], false}; parse({file, pwrite, Args, {error,Reason}},State) -> ?LOGF("error while writing file: ~p (~p)~n",[Args, Reason],?ERR), ts_mon:add({count,error_fs_pwrite}), {State#state_rcv{ack_done=true, datasize=0}, [], 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, eexist} },State) -> ?LOGF("error while creating diretory: ~p already exists~n",[Path],?NOTICE), {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, make_symlink, _Args, ok},State) -> {State#state_rcv{ack_done=true, datasize=0}, [], false}; parse({file, make_symlink, [_Existing, New], {error, eexist} },State) -> ?LOGF("error while creating symlink: ~p already exists~n",[New],?NOTICE), {State#state_rcv{ack_done=true, datasize=0}, [], false}; parse({file, make_symlink, [Existing, New], {error,Reason}},State) -> ?LOGF("error while creating symlink: ~p to ~p (~p)~n",[Existing, New, Reason],?ERR), ts_mon:add({count,error_fs_mksymlink}), {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({file, read_file_info, [_Path], {ok, _FileInfo}},State) -> %% which value should we use for datasize ? {State#state_rcv{ack_done=true,datasize=0}, [], false}; parse({file, read_file_info, [Path], {error,Reason}},State) -> ?LOGF("error while running stat file: ~p (~p)~n",[Path,Reason],?ERR), ts_mon:add({count,error_fs_stat}), {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=make_symlink, path=Existing, dest=New}) -> {file,make_symlink,[Existing, New],0}; get_message2(#fs{command=stat, path=Path}) -> {file,read_file_info,[Path],0}; get_message2(#fs{command=write,path=Path, size=Size}) -> {file,write_file,[Path,ts_utils:urandomstr(Size),[raw]],Size}. tsung-1.5.1/src/tsung/ts_client.erl0000644000175000017500000016710212317102151020367 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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_config.hrl"). -include("ts_profile.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]), DynVars = ts_dynvars:new(tsung_userid,Id), 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, dynvars = DynVars }}. %%-------------------------------------------------------------------- %% Func: StateName/2 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%-------------------------------------------------------------------- think(next_msg,State=#state_rcv{}) -> ?LOG("Global ack received, continue~n", ?DEB), NewSocket = (State#state_rcv.protocol):set_opts(State#state_rcv.socket, [{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 = (State#state_rcv.protocol):set_opts(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) %%---------------------------------------------------------------------- %% received data 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 = case (NewState#state_rcv.request)#ts_request.ack of global -> (NewState#state_rcv.proto_opts)#proto_opts.global_ack_timeout; _ -> (NewState#state_rcv.proto_opts)#proto_opts.idle_timeout end, {next_state, wait_ack, NewState, TimeOut} end; handle_info(Info, StateName, State = #state_rcv{protocol = Transport, socket = Socket}) -> handle_info2(Transport:normalize_incomming_data(Socket, Info), StateName, State). handle_info2({gen_ts_transport, _Socket, Data}, wait_ack, State=#state_rcv{rate_limit=TokenParam}) when is_binary(Data)-> ?DebugF("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 = (NewState#state_rcv.protocol):set_opts(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 = case (NewState#state_rcv.request)#ts_request.ack of global -> (NewState#state_rcv.proto_opts)#proto_opts.global_ack_timeout; _ -> (NewState#state_rcv.proto_opts)#proto_opts.idle_timeout end, {next_state, wait_ack, NewState#state_rcv{socket=NewSocket,rate_limit=NewTokenParam}, TimeOut} end; %% inet close messages; persistent session, waiting for ack handle_info2({gen_ts_transport, _Socket, closed}, wait_ack, State = #state_rcv{persistent=true}) -> ?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_info2({gen_ts_transport, _Socket, closed}, think, State = #state_rcv{persistent=true}) -> ?LOG("connection closed, stay alive (persistent)",?INFO), set_connected_status(false), catch (State#state_rcv.protocol):close(State#state_rcv.socket), % mandatory for ssl {next_state, think, State#state_rcv{socket = none}}; %% inet close messages handle_info2({gen_ts_transport, _Socket, closed}, _StateName, State) -> ?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), catch (State#state_rcv.protocol):close(State#state_rcv.socket), % mandatory for ssl {stop, normal, State#state_rcv{socket = none}}; %% inet errors handle_info2({gen_ts_transport, _Socket, error, Reason}, _StateName, State) -> ?LOGF("Net error: ~p~n",[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_info2({timeout, _Ref, end_thinktime}, think, State= #state_rcv{ count=0 }) -> ?LOG("Session ending ~n", ?INFO), {stop, normal, State}; %% the timer expires handle_info2({timeout, _Ref, end_thinktime}, think, State ) -> handle_next_action(State); handle_info2(timeout, StateName, State ) -> ?LOGF("Error: timeout receive in state ~p~n",[StateName], ?ERR), ts_mon:add({ count, timeout }), {stop, normal, State}; % bidirectional protocol handle_info2({gen_ts_transport, Socket, Data}, think,State=#state_rcv{ clienttype=Type, bidi=true,host=Host,port=Port}) -> 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 = (NewState#state_rcv.protocol):set_opts(NewState#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_info2({gen_ts_transport, Socket, Data}, think, State = #state_rcv{request=Req} ) when (Req#ts_request.ack /= parse) -> 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 = (State#state_rcv.protocol):set_opts(Socket, [{active, once}]), {next_state, think, State#state_rcv{socket=NewSocket}}; handle_info2({gen_ts_transport, _Socket, Data}, think, State) -> 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}; %% pablo TODO: when this could happen?? handle_info2({inet_reply, _Socket,ok}, StateName, State ) -> ?LOGF("inet_reply ok received in state ~p~n",[StateName],?NOTICE), {next_state, StateName, State}; %% TODO this would happen in mixed session when previous session was saved and %% there are data send from the server, and handle_info will NOT normalize %% these data as {gen_ts_transport, Socket, Data}. Ignore it currently. handle_info2({tcp, Socket, _Data}, StateName, State ) -> ?LOGF("tcp data received in state ~p~n",[StateName],?NOTICE), %% we need a set_opts call and update the old socket to the new one, %% or if we switch back to the saved session, we can't receive data %% from the old socket. NewSocket = (State#state_rcv.protocol):set_opts(Socket, [{active, once}]), DictList = get(), lists:foldl(fun({Key, Value}, Acc) -> case {Key, Value} of {{state, _}, {Socket, Session}} -> put(Key, {NewSocket, Session}); _ -> ok end, Acc end, unused, DictList), {next_state, StateName, State}; handle_info2({tcp_closed, Socket, _Data}, StateName, State ) -> ?LOGF("tcp_closed received in state ~p~n",[StateName],?NOTICE), %% socket closed for the saved session, update the old socket to none. %% it's ok if we don't do that: when the old closed socket is used, %% tsung will aware the closed state, and do a reconnection. DictList = get(), lists:foldl(fun({Key, Value}, Acc) -> case {Key, Value} of {{state, _}, {Socket, Session}} -> put(Key, {none, Session}); _ -> ok end, Acc end, unused, DictList), {next_state, StateName, State}; handle_info2(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=#state_rcv{dynvars = DynVars}) -> 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,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} -> DynVars=State#state_rcv.dynvars, Result = set_dynvars(SourceType,Args,VarNames,DynVars,{State#state_rcv.host,State#state_rcv.port},State#state_rcv.session), NewDynVars = ts_dynvars:set(VarNames,Result,DynVars), ?DebugF("set dynvars: ~p ~n",[NewDynVars]), handle_next_action(State#state_rcv{dynvars = NewDynVars, 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, Bidi} -> ?DebugF("Change client type, use: ~p ~p~n",[NewCType, [Server , Port, PType, Store, Restore]]), NewPort = case ts_search:subst(Port,DynVars) of I when is_integer(I) -> I; S when is_list(S) -> list_to_integer(S) end, NewServer = ts_search:subst(Server,DynVars), case Store of true -> % keep state put({state, State#state_rcv.clienttype} , {State#state_rcv.socket,State#state_rcv.session}); false -> % don't keep state of old type, close connection (State#state_rcv.protocol):close(State#state_rcv.socket), set_connected_status(false) end, {Socket,Session} = case {Restore, get({state,NewCType})} of {true,{OldSocket, OldSession}} -> % restore is true and we have something stored {OldSocket, OldSession}; {_,_} -> % nothing to restore, or no restore asked, set new session {none,NewCType:new_session()} end, NewState=State#state_rcv{session=Session,socket=Socket,count=Count,clienttype=NewCType,protocol=PType, port=NewPort,host=NewServer,bidi=Bidi}, 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, undefined, certificate, {Cacert, KeyFile, KeyPass, CertFile}} -> ?LOGF("Set client certificate: ~p ~p ~p ~p~n",[Cacert, KeyFile, KeyPass, CertFile],?DEB), Opts = lists:filtermap(fun({N,V}) -> case V of undefined -> false; B when is_binary(B)-> {true, {N,ts_search:subst(binary_to_list(B), DynVars)}}; Val -> {true, {N,ts_search:subst(Val, DynVars)}} end end , [{certfile, CertFile}, {keyfile,KeyFile}, {password,KeyPass}, {cacertfile,Cacert}]), ?LOGF("SSL options for certificate: ~p~n",[Opts],?DEB), OldOpts = State#state_rcv.proto_opts, NewOpts = OldOpts#proto_opts{certificate = Opts}, handle_next_action(State#state_rcv{proto_opts=NewOpts,count=Count}); {set_option, Type, Name, Args} -> NewState=Type:set_option(Name,Args,State), handle_next_action(NewState); {interaction, send, Id} -> ts_interaction_server:send({ts_search:subst(Id, DynVars),?NOW}), handle_next_action(State#state_rcv{count=Count}); {interaction, 'receive', Id} -> ts_interaction_server:rcv({ts_search:subst(Id, DynVars),?NOW}), handle_next_action(State#state_rcv{count=Count}); 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|value|file, Args::tuple(), %% Variables::list(), DynVars::#dynvars{}, %% {Server::string(),Port::integer()}, Session::record()) -> integer()|binary()|list() %% @doc setting the value of several dynamic variables at once. %% @end %%---------------------------------------------------------------------- set_dynvars(erlang,{Module,Callback},_Vars,DynVars,_,Session) -> Module:Callback({Session,DynVars}); set_dynvars(code,Fun,_Vars,DynVars,_,Session) -> Fun({Session,DynVars}); set_dynvars(random,{number,Start,End},Vars,_DynVars,_,_) -> lists:map(fun(_) -> ts_stats:uniform(Start,End) end,Vars); set_dynvars(random,{string,Length},Vars,_DynVars,_,_) -> R = fun(_) -> ts_utils:randombinstr(Length) end, lists:map(R,Vars); set_dynvars(urandom,{string,Length},Vars,_DynVars,_,_) -> %% not random, but much faster R = fun(_) -> ts_utils:urandombinstr(Length) end, lists:map(R,Vars); set_dynvars(value,{string,Value},_Vars,_DynVars,_,_) -> [Value]; set_dynvars(file,{random,FileId,Delimiter},_Vars,_DynVars,_,_) -> {ok,Line} = ts_file_server:get_random_line(FileId), ts_utils:split(Line,Delimiter); set_dynvars(file,{iter,FileId,Delimiter},_Vars,_DynVars,_,_) -> {ok,Line} = ts_file_server:get_next_line(FileId), ts_utils:split(Line,Delimiter); set_dynvars(jsonpath,{JSONPath, From},_Vars,DynVars,_,_) -> {ok, Val} = ts_dynvars:lookup(From,DynVars), JSON=mochijson2:decode(Val), case ts_utils:jsonpath(JSONPath, JSON) of undefined -> << >>; {struct,S}-> iolist_to_binary(mochijson2:encode({struct,S})); V -> V end; set_dynvars(server,_,_,_,{Host,Port},_) -> [Host,Port]. %% @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.dynvars) of {next,NewDynVars} -> handle_next_action(State#state_rcv{dynvars=NewDynVars,count=Count}); {jump,Target,NewDynVars} -> %%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{dynvars=NewDynVars,count=Next}) end. %%---------------------------------------------------------------------- %% @spec ctrl_struct_impl(ControlStruct::term(),DynVars::#dynvars{}) -> %% {next,NewDynVars::#dynvars{}} | %% {jump, Target::integer(), NewDynVars::#dynvars{}} %% @doc return {next,NewDynVars} to continue with the sequential flow, %% {jump,Target,NewDynVars} to jump to action number 'Target' %% @end %%---------------------------------------------------------------------- ctrl_struct_impl({for_start,Init="%%_"++_,VarName},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},DynVars); ctrl_struct_impl({for_start,InitialValue,VarName},DynVars) -> NewDynVars = ts_dynvars:set(VarName,InitialValue,DynVars), {next,NewDynVars}; ctrl_struct_impl({for_end,VarName,End="%%_"++_,Increment,Target},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},DynVars); ctrl_struct_impl({for_end,VarName,EndValue,Increment,Target},DynVars) -> case ts_dynvars:lookup(VarName,DynVars) of {ok,Value} when Value >= EndValue -> % Reach final value, end loop {next,DynVars}; {ok,Value} -> % New iteration NewValue = Value + Increment, NewDynVars = ts_dynvars:set(VarName,NewValue,DynVars), {jump,Target,NewDynVars} end; ctrl_struct_impl({if_start,Rel, VarName, Value, Target},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,DynVars); false -> ts_mon:add({ count, 'error_if_undef'}), {next,DynVars} end; ctrl_struct_impl({repeat,RepeatName, _,_,_,_,_,_},[]) -> Msg= list_to_atom("error_repeat_"++atom_to_list(RepeatName)++"_undef"), ts_mon:add({ count, Msg}), {next,[]}; ctrl_struct_impl({repeat,RepeatName, While,Rel,VarName,Value,Target,Max},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,DynVars}; 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,NewDynVars); false -> Msg= list_to_atom("error_repeat_"++atom_to_list(RepeatName)++"undef"), ts_mon:add({ count, Msg}), {next,DynVars} end end; ctrl_struct_impl({foreach_start,ForEachName,VarName,Filter}, 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,DynVars}; [First|_Tail] -> TmpDynVars = ts_dynvars:set(ForEachName,First,DynVars), NewDynVars = ts_dynvars:set(ts_utils:concat_atoms([ForEachName,'_iter']),2,TmpDynVars), {next,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,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,NewDynVars} end; ctrl_struct_impl({foreach_end,ForEachName,VarName,Filter,Target}, 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,DynVars}; 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,DynVars}; Val -> TmpDynVars = ts_dynvars:set(ForEachName,Val,DynVars), NewDynVars = ts_dynvars:set(IterName,Iteration+1,TmpDynVars), {jump, Target ,NewDynVars} end; _ ->% not a list, don't loop {next,DynVars} end. %% rel(R,A,B) when is_integer(B) and not is_integer(A)-> rel(R,A,list_to_binary(integer_to_list(B))); rel(R,A,B) when is_integer(A) and not is_integer(B)-> rel(R,list_to_binary(integer_to_list(A)),B); rel(R,A,B) when is_list(B) -> rel(R,A,list_to_binary(B)); rel(R,A,B) when is_list(A) -> rel(R,list_to_binary(A),B); rel(R,A,B) when is_atom(A) -> rel(R,atom_to_binary(A,utf8),B); rel(R,A,B) when is_atom(B) -> rel(R,A,atom_to_binary(B,utf8)); rel('eq',A,B) -> A == B; rel('neq',A,B) -> A /= B; rel('gt',A,B) -> binary_to_num(B) > binary_to_num(A); rel('lt',A,B) -> binary_to_num(B) < binary_to_num(A); rel('gte',A,B) -> binary_to_num(B) >= binary_to_num(A); rel('lte',A,B) -> binary_to_num(B) =< binary_to_num(A). need_jump('while',F) -> F; need_jump('until',F) -> not F; need_jump('if',F) -> not F. jump_if(true,Target,DynVars) -> {jump,Target,DynVars}; jump_if(false,_Target,DynVars) -> {next,DynVars}. binary_to_num([H|_T]) -> binary_to_num(H); binary_to_num(Value) -> case (catch list_to_float(binary_to_list(Value))) of {'EXIT', _} -> list_to_integer(binary_to_list(Value)); Float -> Float end. %%---------------------------------------------------------------------- %% 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.dynvars, State#state_rcv.session}, 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, State#state_rcv.socket} of {Host, Port, Protocol, _} -> % server setup unchanged State#state_rcv.socket; {_,_,_, none} -> ?Debug("Change server configuration inside a session. Socket not opened~n"), set_connected_status(false), none; _ -> ?Debug("Change server configuration inside a session ~n"), (State#state_rcv.protocol):close(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} when State#state_rcv.retries < ?MAX_RETRIES -> ?LOG("connection close while sending message !~n", ?WARN), Retries = State#state_rcv.retries +1, handle_close_while_sending(State#state_rcv{socket=NewSocket, protocol=Protocol, host=Host, session=NewSession, retries=Retries, port=Port}); {error, Reason} when State#state_rcv.retries < ?MAX_RETRIES -> %% 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) }), Retries = State#state_rcv.retries +1, handle_timeout_while_sending(State#state_rcv{session=NewSession,retries=Retries}); {'EXIT', {noproc, _Rest}} when State#state_rcv.retries < ?MAX_RETRIES -> ?LOG("EXIT from ssl app while sending message !~n", ?WARN), Retries = State#state_rcv.retries +1, handle_close_while_sending(State#state_rcv{socket=NewSocket, protocol=Protocol, session=NewSession, retries=Retries, host=Host, port=Port}); Exit when State#state_rcv.retries < ?MAX_RETRIES -> ?LOGF("EXIT Error: Unable to send data, reason: ~p~n", [Exit], ?ERR), ts_mon:add({ count, error_send }), {stop, normal, State}; _ -> ts_mon:add({ count, error_abort_max_send_retries }), {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})-> Proto:close(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==ts_tcp6 orelse Proto==ts_ssl6 orelse Proto==ts_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(Proto, Socket, Message, Host, Port) -> Proto:send(Socket, Message, [{host, Host}, {port, Port}]). connect(Proto, Server, Port, Opts) -> ?LOGF("connect to port ~p",[Port],?DEB), Proto:connect(Server, Port, Opts). %%---------------------------------------------------------------------- %% Func: protocol_options/1 %% Purpose: set connection's options for the given protocol %%---------------------------------------------------------------------- protocol_options(Proto, #proto_opts{} = ProtoOpts) -> Proto:protocol_options(ProtoOpts). %%---------------------------------------------------------------------- %% 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,transactions=Transactions}) 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("Session and dynvars are now ~p ~p~n",[NewState#state_rcv.session, NewState#state_rcv.dynvars]), case NewState#state_rcv.ack_done of true -> ?DebugF("Response done:~p~n", [NewState#state_rcv.datasize]), {PageTimeStamp, DynVars,Elapsed} = 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.dynvars), NewCount =ts_search:match(Req#ts_request.match,NewBuffer,MatchArgs,NewDynVars,Transactions), Type:dump(Dump,{Req,NewState#state_rcv.session,Id, NewState#state_rcv.host,NewState#state_rcv.datasize,Elapsed,Transactions}), case Close of true -> ?Debug("Close connection required by protocol~n"), (State#state_rcv.protocol):close(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, dynvars = NewDynVars, buffer = <<>>}, Opts}; false -> {NewState#state_rcv{ page_timestamp = PageTimeStamp, count = NewCount, size_mon = State#state_rcv.size_mon_thresh, datasize = 0, dynvars = NewDynVars, 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,transactions=Transactions}) -> 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.dynvars), NewCount = ts_search:match(Req#ts_request.match,NewBuffer,MatchArgs,NewDynVars,Transactions), {State#state_rcv{ack_done = true, buffer= NewBuffer, dynvars = NewDynVars, 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(S=#state_rcv{size_mon_thresh=T,page_timestamp=PageTime, send_timestamp=SendTime,datasize=Datasize})-> Now = ?NOW, Elapsed = ts_utils:elapsed(SendTime, Now), Stats = case S#state_rcv.size_mon > T of true -> LastSize=Datasize-S#state_rcv.size_mon+T, [{ sample, request, Elapsed}, { sum, size_rcv, LastSize}]; false-> [{ sample, request, Elapsed}, { sum, size_rcv, Datasize}] end, Request = S#state_rcv.request, DynVars = ts_search:parse_dynvar(Request#ts_request.dynvar_specs, S#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, Elapsed}; _ -> ts_mon:add(Stats), {PageTime, DynVars, Elapsed} 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.5.1/src/tsung/ts_shell.erl0000644000175000017500000001337512147621622020234 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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([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_sess{}. %% @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 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, {_DynVars, Session}, Param, HostData) -> add_dynparams(Session, Param, HostData); add_dynparams(true, {DynVars, Session}, Param, HostData) -> NewParam = subst(Param, DynVars), add_dynparams(Session,NewParam, HostData). add_dynparams(#shell_sess{}, 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.5.1/src/tsung/ts_launcher.erl0000644000175000017500000005170612301350731020716 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%% 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"). -include("ts_config.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), check_max_users(Max), 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, PhaseId}, 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, phase_id = PhaseId, 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, phase_id = Id, started_users = Started, intensity = Intensity}) -> BeforeLaunch = ?NOW, case do_launch({Intensity,State#launcher.myhostname,Id}) 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, phase_id = Id+1, 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 -> trunc(Wait - LaunchDuration); false -> 0 end, ?DebugF("Real Wait = ~p (was ~p)~n", [NewWait,Wait]), {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 trying the next one @end skip_empty_phase(State=#launcher{phases=Phases,phase_id=Id,phase_duration=Duration})-> ?LOGF("No user, skip phase (~p ~p)~n",[Phases,Duration],?INFO), case change_phase(0, Phases, 0, {Duration, 0}) of {stop} -> {stop, normal, State}; {change, 0, _, PhaseLength, Rest} -> %% next phase is also empty, loop skip_empty_phase(State#launcher{phases=Rest,phase_duration=PhaseLength,phase_id=Id+1}); {change, NewUsers, NewIntensity, PhaseLength,Rest} -> {next_state,launcher,State#launcher{phases = Rest, nusers = NewUsers, phase_nusers = NewUsers, phase_duration=PhaseLength, phase_start = ?NOW, phase_id = Id+1, 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, phase_id=Id, 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, Id}), 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, PhaseId})-> %%Get one client %%set the profile of the client case catch ts_config_server:get_next_session({MyHostName, PhaseId} ) 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. check_max_users(Max) -> try Data = os:cmd("grep \"open files\" /proc/self/limits"), {match,[Val]} = re:run(Data,"Max open files\\s+(\\d+)",[{capture,all_but_first,list}]), Limit = list_to_integer(Val), case (Max > Limit ) of true -> ?LOGF("WARNING !!! too few file descriptors available (~w), you should decrease maxusers (currently ~w)",[Limit,Max], ?CRIT); false -> ?LOGF("maxusers is below file descriptors limit (~p)",[Limit], ?DEB) end catch _Error:_Reason -> ?LOG("Can't get file descriptors limit from system, you should verify that 'maxusers' has a good value ",?NOTICE) end. tsung-1.5.1/src/tsung/ts_udp.erl0000644000175000017500000000434712147621622017714 0ustar nniclaussenniclausse%%% %%% Copyright 2012 © Nicolas Niclausse %%% %%% Author : Nicolas Niclausse %%% Created: 7 sep 2012 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_udp). -export([ connect/3, send/3, close/1, set_opts/2, protocol_options/1, normalize_incomming_data/2 ]). -behaviour(gen_ts_transport). -include("ts_profile.hrl"). -include("ts_config.hrl"). protocol_options(#proto_opts{udp_rcv_size=Rcv, udp_snd_size=Snd}) -> [binary, {active, once}, {recbuf, Rcv}, {sndbuf, Snd} ]. %% -> {ok, Socket} connect(_Host, _Port, Opts) -> gen_udp:open(0, Opts). %% send/3 -> ok | {error, Reason} send(Socket, Data, [{host, Host}, {port, Port}]) -> gen_udp:send(Socket, Host,Port, Data). close(none) -> ok; close(Socket) -> gen_udp:close(Socket). % set_opts/2 -> socket() set_opts(none, _Opts) -> none; set_opts(Socket, Opts) -> inet:setopts(Socket, Opts), Socket. normalize_incomming_data(Socket, {udp, Socket,_IP,_InPortNo, Data}) -> ?DebugF("UDP packet received: size=~p ~n",[size(Data)]), {gen_ts_transport, Socket, Data}; normalize_incomming_data(_Socket, X) -> X. %%Other, non gen_udp packet. tsung-1.5.1/src/tsung/ts_ldap_common.erl0000644000175000017500000001306012104023217021371 0ustar nniclaussenniclausse%%% 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.5.1/src/tsung/ts_launcher_mgr.erl0000644000175000017500000002014112321167730021557 0ustar nniclaussenniclausse%%% %%% Copyright 2009 © Nicolas Niclausse %%% %%% Author : Nicolas Niclausse %%% Created: 09 déc. 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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_macros.hrl"). -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, check_timeout}). %%==================================================================== %% 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{check_timeout=?check_noclient_timeout}}. %%-------------------------------------------------------------------- %% 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=#state{check_timeout=CheckTimeout}) -> case ts_client_sup:active_clients() of 0 -> % no users left, and no more launchers, stop 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), ts_mon:stop(), %% we must warn ts_mon that our clients have finished {noreply, State}; false -> ?LOGF("No more active users ~p ~p~n",[node(), os:getpid()], ?NOTICE), {stop, normal, State} end; ActiveClients when ActiveClients > 1000 -> %% the call to active_clients can be cpu hungry if lot's of clients are running %% use a long timer in this case. ?LOGF("Still ~p active client(s)~n", [ActiveClients],?NOTICE), erlang:start_timer(CheckTimeout, self(), check_noclient ), {noreply, State}; ActiveClients -> ?LOGF("Still ~p active client(s)~n", [ActiveClients],?DEB), erlang:start_timer(?fast_check_noclient_timeout, self(), check_noclient ), {noreply, State} end. tsung-1.5.1/src/tsung/ts_bosh.erl0000644000175000017500000005612312241427606020057 0ustar nniclaussenniclausse%%% %%% Copyright 2010 © ProcessOne %%% %%% Author : Eric Cestari %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_bosh). -export([ connect/3, send/3, close/1, set_opts/2, protocol_options/1, normalize_incomming_data/2 ]). -export([connect/4]). %% used for ts_bosh_ssl sessions. -behaviour(gen_ts_transport). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include_lib("xmerl/include/xmerl.hrl"). -define(CONTENT_TYPE, "text/xml; charset=utf-8"). -define(VERSION, "1.8"). -define(WAIT, 60). %1 minute -define(HOLD, 1). %only 1 request pending -define(CONNECT_TIMEOUT, 20 * 1000). -define(MAX_QUEUE_SIZE, 5). %% at most 5 messages queued, after that close the connection. %% In practice we never had more than 1 pending packet, as we are blocking %% the client process until we sent the packet. But I keep this functionality in place, %% in case we decide to do the sending() of data asynchronous. -record(state, { host, path, port, % {Host::string(), Port:integer(), Path::string(), Ssl::bool()} domain = undefined, sid, rid, parent_pid, max_requests, %TODO: use this, now fixed on 2 queue = [], %% stanzas that have been queued because we reach the limit of requets open = [], free = [], local_ip, local_port, session_state = fresh, %% fresh | normal | closing pending_ref, type %% 'tcp' | 'ssl' }). normalize_incomming_data(_Socket, X) -> X. %% nothing to do here, ts_bosh uses a special process to handle http requests, %% the incoming data is already delivered to ts_client as {gen_ts_transport, ..} instead of gen_tcp | ssl connect(Host, Port, Opts) -> connect(Host, Port, Opts, tcp). connect(Host, Port, Opts, Type) when Type =:= 'tcp' ; Type =:= 'ssl' -> Parent = self(), [BoshPath | OtherOpts] = Opts, Pid = spawn(fun() -> loop(Host, Port, BoshPath, OtherOpts, Type, Parent) end), ?DebugF("connect ~p ~p ~p ~p ~p",[Host, Port, BoshPath, self(), Pid]), {ok, Pid}. extract_domain("to='" ++ Rest) -> lists:takewhile(fun(C) -> C =/= $' end, Rest); extract_domain([_|Rest]) -> extract_domain(Rest). send(Pid, Data, _Opts) -> Ref = make_ref(), Msg = case Data of <<"> -> %%HACK: use this to detect stream start (or restarts) Domain = extract_domain(binary_to_list(Rest)), {stream, Domain, Ref}; <<"", _/binary>> -> %%Use this to detect stream end {stream, terminate, Ref}; _ -> {send, Data, Ref} end, Pid ! Msg, MonitorRef = erlang:monitor(process,Pid), receive {'DOWN', MonitorRef, _Type, _Object, _Info} -> {error, no_bosh_connection}; {ok, Ref} -> erlang:demonitor(MonitorRef, [flush]), ok after 30000 -> erlang:demonitor(MonitorRef, [flush]), {error, timeout} end. close(Pid) -> Pid ! close. set_opts(Pid, _Opts) -> Pid. protocol_options(#proto_opts{bosh_path = BoshPath}) -> [BoshPath]. loop(Host, Port, Path, Opts, Type, Parent) -> {A,B,C} = now(), random:seed(A,B,C), _MonitorRef = erlang:monitor(process,Parent), loop(#state{session_state = fresh, port = Port, path = Path, parent_pid = Parent, host = Host, local_ip = proplists:get_value(ip, Opts, undefined), local_port = proplists:get_value(port, Opts, undefined), type = Type }). loop(#state{parent_pid = ParentPid} = State) -> ?DebugF("loop: wait for message free:~p open:~p",[State#state.free, State#state.open]), receive {'DOWN', _MonitorRef, _Type, _Object, _Info} -> %%parent terminates ok; {'EXIT', ParentPid, _Reason} -> %%even 'normal' terminates this ok; close -> ok; {send, Data, Ref} -> case do_send(State, Data) of {sent, NewState} -> ParentPid ! {ok, Ref}, loop(NewState); {queued, #state{queue =Q} = NewState} when length(Q) < ?MAX_QUEUE_SIZE -> %%do not return yet.. loop(NewState#state{pending_ref = Ref}); {queued, NewState} -> %% we reach the max allowed queued messages.. close the connection. ?LOGF("Client reached max bosh requests queue size: ~p. Closing session", [length(NewState#state.queue)], ?ERR), ts_mon:add({count, error_bosh_maxqueued}), ParentPid ! {ok, Ref}, ParentPid ! {gen_ts_transport, self(), closed} end; {stream, terminate, Ref} -> #state{host = Host, path = Path, sid = Sid, rid = Rid, type = Type} = State, {NewState, Socket} = new_socket(State, once), ok = make_raw_request(Type, Socket, Host, Path, close_stream_msg(Sid, Rid)), ParentPid ! {ok, Ref}, loop(NewState#state{session_state = closing, open = [{Socket, Rid+1}|NewState#state.open]}); {stream, Domain, Ref} when State#state.domain == undefined -> NewState = do_connect(State, Domain), ParentPid ! {ok, Ref}, loop(NewState); {stream, _Domain, Ref} -> %%here we must do a reset NewState = do_reset(State), ParentPid ! {ok, Ref}, loop(NewState); {Tag, Socket, {http_response, Vsn, 200, "OK"}} when Tag == 'http' ; Tag == 'ssl'-> ?Debug("loop: http response received"), case do_receive_http_response(State, Socket, Vsn) of {ok, NewState} -> loop(NewState); terminate -> if State#state.session_state /= 'closing' -> ts_mon:add({count, error_bosh_terminated}), ?LOG("Session terminated by server", ?INFO); true -> ok end, State#state.parent_pid ! {gen_ts_transport, self(), closed} end; {Close, Socket} when Close == tcp_closed ; Close == 'ssl_closed' -> ?LOG("loop: close",?DEB), case lists:keymember(Socket, 1, State#state.open) of true -> %%ERROR, a current request is closed ?LOG("Open request closed by server", ?ERR), ts_mon:add({count, error_bosh_socket_closed}), State#state.parent_pid ! {gen_ts_transport, self(), closed}; false -> %% A HTTP persistent connection, currently not in use, is closed by the server. %% We can continue without trouble, just remove it, it will be reopened when needed. loop(State#state{free = lists:delete(Socket, State#state.free)}) end; {Tag, _Socket, {http_response, _Vsn, ResponseCode, _StatusLine}} when Tag == 'http' ; Tag == 'ssl' -> State#state.parent_pid ! {gen_ts_transport, self(), error, list_to_atom(integer_to_list(ResponseCode))}; Unexpected -> ?LOGF("Bosh process received unexpected message: ~p", [Unexpected], ?ERR), State#state.parent_pid ! {gen_ts_transport, self(), error, unexpected_data} end. do_receive_http_response(State, Socket, Vsn) -> #state{open = Open, sid = Sid, rid = Rid, queue = Queue, host = Host, path = Path, type = Type, parent_pid = ParentPid} = State, {ok, {{200, "OK"}, Hdrs, Resp}} = read_response(Type, Socket, Vsn, {200, "OK"}, [], <<>>, httph), ts_mon:add({ sum, size_rcv, iolist_size([ [if is_atom(H) -> atom_to_list(H); true -> H end, V] || {H,V} <- Hdrs])}), %% count header size {_El = #xmlElement{name = body, attributes = Attrs, content = Content}, []}= xmerl_scan:string(binary_to_list(Resp)), case get_attr(Attrs, type) of "terminate" -> terminate; _R -> NewOpen = lists:keydelete(Socket, 1, Open), NewState2 = if NewOpen == [] andalso State#state.session_state =:= 'normal' -> socket_setopts(Type, Socket, [{packet, http}, {active, once}]), ?DebugF("make empty request for normal session state ~p ~p ~p queue:~p", [Type,Socket,Rid, Queue]), ok = make_empty_request(Type, Socket,Sid, Rid, Queue, Host, Path), case length(Queue) of 0 -> ok; _ -> ParentPid ! {ok, State#state.pending_ref} %% we just sent the pending packet, wakeup the client end, State#state{open = [{Socket, Rid}], rid = Rid +1, queue = []}; length(NewOpen) == 1 andalso length(State#state.queue) > 0 -> %%there are pending packet, sent it if the RID is ok, otherwise wait case NewOpen of [{_, R}] when (Rid - R) =< 1 -> socket_setopts(Type, Socket, [{packet, http}, {active, once}]), ok = make_empty_request(Type, Socket,Sid, Rid, Queue, Host, Path), ParentPid ! {ok, State#state.pending_ref}, %% we just sent the pending packet, wakeup the client State#state{open = [{Socket, Rid}], rid = Rid +1, queue = []}; _ -> NewState = return_socket(State, Socket), NewState#state{open = NewOpen} end; true -> NewState = return_socket(State, Socket), NewState#state{open = NewOpen} end, case Content of [] -> %%empty response, do not bother the ts_client process with this %% (so Noack/Bidi won't count this bosh specific thing, only async stanzas) %% since ts_client don't see this, we need to count the size received ts_mon:add({ sum, size_rcv, iolist_size(Resp)}); _ -> ParentPid ! {gen_ts_transport, self(), Resp} end, {ok, NewState2} end. do_connect(#state{type = Type, host = Host, path = Path, parent_pid = ParentPid} = State, Domain) -> ?DebugF("do_connect ~p",[State]), Rid = 1000 + random:uniform(100000), %%Port= proplists:get_value(local_port, Options, undefined), NewState = State#state{ domain = Domain, rid = Rid, open = [], queue = [], free = [] }, {NewState2, Socket} = new_socket(NewState, false), ok = make_raw_request(Type, Socket, Host, Path, create_session_msg(Rid, Domain, ?WAIT, ?HOLD)), {ok, {{200, "OK"}, Hdrs, Resp}} = read_response(Type, Socket, nil, nil, [], <<>>, http), ts_mon:add({ sum, size_rcv, iolist_size([ [if is_atom(H) -> atom_to_list(H); true -> H end, V] || {H,V} <- Hdrs])}), %% count header size NewState3 = return_socket(NewState2, Socket), {_El = #xmlElement{name = body, attributes = Attrs, content = _Content}, []} = xmerl_scan:string(binary_to_list(Resp)), ParentPid ! {gen_ts_transport, self(), Resp}, NewState3#state{rid = Rid +1, open = [], sid = get_attr(Attrs, sid), max_requests = 2 }. do_reset(State) -> ?DebugF("do_reset free: ~p open:~p",[State#state.free,State#state.open]), #state{sid = Sid, rid = Rid, host = Host, path = Path, domain = Domain, type = Type} = State, {NewState, Socket} = new_socket(State, once), ok = make_raw_request(Type, Socket, Host, Path, restart_stream_msg(Sid, Rid, Domain)), NewState#state{session_state = normal, rid = Rid +1, open = [{Socket, Rid}|State#state.open]}. get_attr([], _Name) -> undefined; get_attr([#xmlAttribute{name = Name, value = Value}|_], Name) -> Value; get_attr([_|Rest], Name) -> get_attr(Rest, Name). do_send(State, Data) -> #state{open = Open, rid = Rid, sid = Sid, host = Host, type = Type, path = Path, queue = Queue} = State, ?LOGF("do_send, rid:~p open:~p free:~p", [Rid,Open, State#state.free],?DEB), Result = if Open == [] -> send; true -> Min = lists:min(lists:map(fun({_S,R}) -> R end, Open)), if (Rid -Min) =< 1 -> send; true -> queue end end, case Result of send -> {NewState, Socket} = new_socket(State, once), ok = make_request(Type, Socket, Sid, Rid, Queue, Host, Path, Data), {sent, NewState#state{rid = Rid +1, open = [{Socket, Rid}|Open], queue = Queue}}; queue -> Queue = State#state.queue, NewQueue = [Data|Queue], {queued, State#state{queue = NewQueue}} end. make_empty_request(Type, Socket, Sid, Rid, Queue, Host, Path) -> StanzasText = lists:reverse(Queue), ?LOGF("make empty request ~p ~p ~p", [Type,Socket,Rid],?DEB), Body = stanzas_msg(Sid, Rid, StanzasText), make_request(Type, Socket, Host, Path, Body, iolist_size(StanzasText)). make_raw_request(Type, Socket, Host, Path, Body) -> make_request(Type, Socket, Host, Path, Body, 0). make_request(Type, Socket, Sid, Rid, Queue, Host, Path, Packet) -> StanzasText = lists:reverse([Packet|Queue]), ?LOGF("make request ~p ~p ~p ~p", [Type,Socket,Rid, StanzasText],?DEB), Body = stanzas_msg(Sid, Rid, StanzasText), make_request(Type, Socket, Host, Path, Body, iolist_size(StanzasText)). make_request(Type, Socket,Host, Path, Body, OriginalSize) -> ts_mon:add({count, bosh_http_req}), Hdrs = [{"Content-Type", ?CONTENT_TYPE}, {"keep-alive", "true"}], Request = format_request(Path, "POST", Hdrs, Host, Body), ok = socket_send(Type, Socket, Request), ts_mon:add({ sum, size_sent, iolist_size(Request) - OriginalSize}). %% add the http overhead. The size of the stanzas are already counted by ts_client code. new_socket(State = #state{free = [Socket | Rest], type = Type}, Active) -> socket_setopts(Type, Socket, [{active, Active}, {packet, http}]), {State#state{free = Rest}, Socket}; new_socket(State = #state{type = Type, host = Host, port = Port, local_ip = LocalIp, local_port = LocalPort}, Active) -> Options = case LocalIp of undefined -> [{active, Active}, {packet, http}]; _ -> case LocalPort of undefined -> [{active, Active}, {packet, http},{ip, LocalIp}]; _ -> {ok, LPort} = ts_config_server:get_user_port(LocalIp), [{active, Active}, {packet, http},{ip, LocalIp}, {port, LPort}] end end, {ok, Socket} = socket_connect(Type, Host, Port, Options, ?CONNECT_TIMEOUT), ts_mon:add({count, bosh_http_conn}), {State, Socket}. return_socket(State, Socket) -> socket_setopts(State#state.type, Socket, [{active, once}]), %%receive data from it, we want to know if something happens State#state{free = [Socket | State#state.free]}. create_session_msg(Rid, To, Wait, Hold) -> [ ""]. stanzas_msg(Sid, Rid, Text) -> [ "", Text, ""]. restart_stream_msg(Sid, Rid, Domain) -> [ ""]. close_stream_msg(Sid, Rid) -> [ ""]. read_response(Type, Socket, Vsn, Status, Hdrs, Body, PacketType) when PacketType == http ; PacketType == httph-> socket_setopts(Type, Socket, [{packet, PacketType}, {active, false}]), case socket_recv(Type, Socket, 0) of {ok, {http_response, NewVsn, StatusCode, Reason}} -> NewStatus = {StatusCode, Reason}, read_response(Type, Socket, NewVsn, NewStatus, Hdrs, Body, httph); {ok, {http_header, _, Name, _, Value}} -> Header = {Name, Value}, read_response(Type, Socket, Vsn, Status, [Header | Hdrs], Body, httph); {ok, http_eoh} -> socket_setopts(Type, Socket, [{packet, raw}, binary]), {NewBody, NewHdrs} = read_body(Type, Vsn, Hdrs, Socket), Response = {Status, NewHdrs, NewBody}, {ok, Response}; {error, closed} -> erlang:error(closed); {error, Reason} -> erlang:error(Reason) end. read_body(Type, _Vsn, Hdrs, Socket) -> % Find out how to read the entity body from the request. % * If we have a Content-Length, just use that and read the complete % entity. % * If Transfer-Encoding is set to chunked, we should read one chunk at % the time % * If neither of this is true, we need to read until the socket is % closed (AFAIK, this was common in versions before 1.1). case proplists:get_value('Content-Length', Hdrs, undefined) of undefined -> throw({no_content_length, Hdrs}); ContentLength -> read_length(Type, Hdrs, Socket, list_to_integer(ContentLength)) end. read_length(Type, Hdrs, Socket, Length) -> case socket_recv(Type, Socket, Length) of {ok, Data} -> {Data, Hdrs}; {error, Reason} -> erlang:error(Reason) end. %% @spec (Path, Method, Headers, Host, Body) -> Request %% Path = iolist() %% Method = atom() | string() %% Headers = [{atom() | string(), string()}] %% Host = string() %% Body = iolist() format_request(Path, Method, Hdrs, Host, Body) -> [ Method, " ", Path, " HTTP/1.1\r\n", format_hdrs(add_mandatory_hdrs(Method, Hdrs, Host, Body), []), Body ]. %% spec normalize_method(AtomOrString) -> Method %% AtomOrString = atom() | string() %% Method = string() %% doc %% Turns the method in to a string suitable for inclusion in a HTTP request %% line. %% end %-spec normalize_method(atom() | string()) -> string(). %normalize_method(Method) when is_atom(Method) -> % string:to_upper(atom_to_list(Method)); %normalize_method(Method) -> % Method. format_hdrs([{Hdr, Value} | T], Acc) -> NewAcc = [ Hdr, ":", Value, "\r\n" | Acc ], format_hdrs(T, NewAcc); format_hdrs([], Acc) -> [Acc, "\r\n"]. add_mandatory_hdrs(Method, Hdrs, Host, Body) -> add_host(add_content_length(Method, Hdrs, Body), Host). add_content_length("POST", Hdrs, Body) -> add_content_length(Hdrs, Body); add_content_length("PUT", Hdrs, Body) -> add_content_length(Hdrs, Body); add_content_length(_, Hdrs, _) -> Hdrs. add_content_length(Hdrs, Body) -> case proplists:get_value("content-length", Hdrs, undefined) of undefined -> ContentLength = integer_to_list(iolist_size(Body)), [{"Content-Length", ContentLength} | Hdrs]; _ -> % We have a content length Hdrs end. add_host(Hdrs, Host) -> case proplists:get_value("host", Hdrs, undefined) of undefined -> [{"Host", Host } | Hdrs]; _ -> % We have a host Hdrs end. socket_connect(tcp, Host, Port, Options, Timeout) -> gen_tcp:connect(Host, Port, Options, Timeout); socket_connect(ssl, Host, Port, Options, Timeout) -> %% First connect using tcp, and then upgrades. The local ip and port directives seems to not work if %% the socket is opened directly as ssl. % {ForConnection, ForSSL} = lists:partition(fun({ip, _}) -> true; ({port, _}) -> true; (_) -> false end, Options), % {ok, S} = gen_tcp:connect(Host, Port, [{active, false}|ForConnection], Timeout), % ssl:connect(S, ForSSL, Timeout). % ?LOGF("Connect ~p", [ForSSL], ?ERR), ssl:connect(Host, Port, [{ssl_imp, new}|Options], Timeout). socket_send(tcp, Socket, Data) -> gen_tcp:send(Socket, Data); socket_send(ssl, Socket, Data) -> ssl:send(Socket, Data). socket_recv(tcp, Socket, Len) -> gen_tcp:recv(Socket, Len); socket_recv(ssl, Socket, Len) -> ssl:recv(Socket, Len). % Not used %socket_close(tcp, Socket) -> % gen_tcp:close(Socket); %socket_close(ssl, Socket) -> % ssl:close(Socket). socket_setopts(tcp, Socket, Opts) -> inet:setopts(Socket, Opts); socket_setopts(ssl, Socket, Opts) -> ssl:setopts(Socket, Opts). tsung-1.5.1/src/tsung/tsung.erl0000644000175000017500000000457512317474675017576 0ustar nniclaussenniclausse%%% 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/0, start/2, stop/1]). -behaviour(application). -include("ts_macros.hrl"). %% start the application with it's dependencies start() -> ts_utils:ensure_all_started(tsung, permanent). %%---------------------------------------------------------------------- %% 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.5.1/src/tsung/ts_search.erl0000644000175000017500000005155612212102345020362 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%% 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/5, parse_dynvar/2]). -include("ts_macros.hrl"). -include("ts_profile.hrl"). %% @type dynvar() = {Key::atom(), Value::string()} | []. %% @type dynvars() = [dynvar()] %% ---------------------------------------------------------------------- %% @spec subst(Data::term(), DynVar::dynvars() ) -> term() %% @doc search into a given string and replace %%Mod:Fun%% (resp %% %%__Variable%%) strings by the result of the call to %% Mod:Fun({Pid, DynVars }) (resp the value of the variable) where Pid %% is the Pid of the client. The substitution tag are %% intended to be used in tsung.xml scenarii files. %% @end %% ---------------------------------------------------------------------- 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(), Transactions::list() ) -> Count::integer() %% @doc search for regexp in Data; send result to ts_mon %% @end %%---------------------------------------------------------------------- match([], _Data, {Count, _MaxC, _SessionId, _UserId}, _DynVars, _Tr) -> Count; match([Match=#match{'skip_headers'=http}|Tail], Data, Counts, DynVars, Tr) 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, Tr); _ -> ?LOGF("Skip http headers failure, data was: ~p ~n",[Data], ?ERR), match([Match#match{'skip_headers'=no}|Tail], Data, Counts, DynVars, Tr) end; match([Match=#match{'apply_to_content'=undefined}|Tail], Data, Counts,DynVars,Tr) -> ?DebugF("Matching Data size ~p; apply undefined~n",[size(Data)]), match([Match|Tail], Data, Counts, [],DynVars, Tr); match([Match=#match{'apply_to_content'={Module,Fun}}|Tail], Data, Counts,DynVars,Tr) -> ?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, Tr). %% @spec match(Match::#match{}, Data::binary() | list(), Count::tuple(), %% Stats::list(), DynVars::term(), Transaction::atom()) -> 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, Tr)-> 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), case Action of Act when Act =:= 'continue'; Act =:= 'log'; Act =:= 'dump' -> setcount(Match, Counts, [{count, match}| Stats], Data, Tr), match(Tail, Data, Counts, Stats,DynVars, Tr); _ -> setcount(Match, Counts, [{count, match}| Stats], Data, Tr) end; When -> % nomatch ?LOGF("Bad Match (regexp=~p) do=~p~n",[RegExp, Action], ?INFO), case Action of Act when Act =:= 'continue'; Act =:= 'log'; Act =:= 'dump' -> setcount(Match, Counts, [{count, nomatch}| Stats], Data, Tr), match(Tail, Data, Counts, Stats,DynVars, Tr); _ -> setcount(Match, Counts, [{count, nomatch}| Stats], Data, Tr) end; {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, Tr); 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,Tr) 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, name=Name}, {Count, MaxC, SessionId, UserId}, Stats,_,Tr)-> ts_mon:add_match(Stats,{UserId,SessionId,MaxC-Count,Tr, Name}), Count; setcount(#match{do=dump, name=Name}, {Count, MaxC, SessionId, UserId}, Stats, Data, Tr)-> ts_mon:add_match(Stats,{UserId,SessionId,MaxC-Count, Data, Tr, Name}), Count; setcount(#match{do=restart, max_restart=MaxRestart, name=Name}, {Count, MaxC,SessionId,UserId}, Stats,_, Tr)-> CurRestart = get(restart_count), Ids={UserId,SessionId,MaxC-Count,Tr,Name}, ?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,name=Name}, {Count,MaxC,SessionId,UserId}, Stats,_, Tr) -> ts_mon:add_match([{count, match_stop} | Stats],{UserId,SessionId,MaxC-Count,Tr, Name}), 0. %%---------------------------------------------------------------------- %% @spec parse_dynvar(Dynvarspecs::list(), Data::binary | list) -> dynvars() %% @doc Look for dynamic variables in Data %% @end %%---------------------------------------------------------------------- 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,Name,RE}| Tail],Binary,Data,Tree,DynVars) -> parse_dynvar([{re,Name, RE, undefined}| Tail],Binary,Data,Tree,DynVars); parse_dynvar([{re,VarName, RegExp, Apply}| DynVarsSpecs],Binary,Data,Tree,DynVars) -> case re:run(Data, RegExp,[{capture,[1],binary}]) of {match,[Value]} -> ConvValue = apply_fun(Apply,Value), ?LOGF("DynVar (RE): Match (~p=~p) Converted: ~p~n",[VarName, Value, ConvValue], ?INFO), parse_dynvar(DynVarsSpecs,Binary,Data,Tree, ts_dynvars:set(VarName,ConvValue,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([{header,VarName, HeaderName}| DynVarsSpecs], Binary,String,Tree, DynVars) -> BinHeaders = extract_headers(Binary), Headers = mochiweb_headers:from_binary(BinHeaders), case string:tokens(HeaderName, "/") of [H1] -> V1 = mochiweb_headers:get_value(H1, Headers), ?LOGF("DynVar: Header (~p=~p) ~n",[VarName, V1], ?NOTICE), parse_dynvar(DynVarsSpecs, Binary,String,Tree, ts_dynvars:set(VarName,V1,DynVars)); [H1,SubH] -> Value = case mochiweb_headers:get_value(H1, Headers) of [] -> {ok, Old} = ts_dynvars:lookup(VarName, DynVars, ""), ?LOGF("DynVar: Header ~p not found ; using ~p ~n",[H1, Old], ?WARN), Old; undefined -> {ok, Old} = ts_dynvars:lookup(VarName, DynVars, ""), ?LOGF("DynVar: Header ~p not found ; using ~p ~n",[H1, Old], ?WARN), Old; SubV when H1 == "www-authenticate" orelse H1 == "authentication-info"-> ?LOGF("DynVar: Found header ~p ~n",[SubV], ?WARN), {_, Params} = parse_header(SubV, ","), ?LOGF("DynVar: Parsed subheader ~p ~n",[Params], ?WARN), case lists:keyfind(SubH, 1, Params) of false -> {ok, Old} = ts_dynvars:lookup(VarName, DynVars, ""), ?LOGF("DynVar: SubHeader ~p not found ; using ~p ~n",[VarName, Old], ?WARN), Old; {_, V} -> ?LOGF("DynVar: SubHeader (~p=~p) ~n",[VarName, V], ?DEB), V end; SubV -> {_, Params}= parse_header(SubV, ";"), case lists:keyfind(SubH, 1, Params) of false -> ?LOGF("DynVar: SubHeader ~p not found ~n",[VarName], ?WARN), {ok, Old} = ts_dynvars:lookup(VarName, DynVars, ""), Old; {_, V} -> ?LOGF("DynVar: SubHeader (~p=~p) ~n",[VarName, V], ?NOTICE), V end end, parse_dynvar(DynVarsSpecs, Binary,String,Tree, ts_dynvars:set(VarName,Value,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 = case mochiweb_xpath:execute(Expr,Tree) of [] -> ?LOGF("Dyn Var: no Match (varname=~p), ~n",[VarName],?WARN), << >>; Val -> ?LOGF("Dyn Var: Match (~p=~p), ~n",[VarName,Val],?INFO), Val end, parse_dynvar(DynVarsSpecs, Binary,String,Tree,ts_dynvars:set(VarName,Value,DynVars)); parse_dynvar([{jsonpath,VarName, Expr}| DynVarsSpecs],Binary,String,JSON,DynVars)-> Values = case ts_utils:jsonpath(Expr,JSON) of undefined -> ?LOGF("Dyn Var: no Match (varname=~p), ~n",[VarName],?WARN), << >>; {struct, Struct} -> ?LOGF("Dyn Var: Match (~p=~p), ~n",[VarName,Struct],?INFO), iolist_to_binary(mochijson2:encode({struct, Struct})); Val -> ?LOGF("Dyn Var: Match (~p=~p), ~n",[VarName,Val],?INFO), Val 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 = case ts_pgsql:find_pair(Expr,PGSQL) of undefined -> ?LOGF("Dyn Var: no Match (varname=~p), ~n",[VarName],?WARN), << >>; Val -> ?LOGF("Dyn Var: Match (~p=~p), ~n",[VarName,Val],?INFO), Val 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), << >>. apply_fun(undefined, Value) -> Value; apply_fun(Fun,Value) -> Fun(Value). 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. extract_headers(<<"\r\n",Rest/binary>>) -> Rest; extract_headers(<<_:1/binary,Rest/binary>>) -> extract_headers(Rest); extract_headers(<<>>) -> <<>>. %% Comes from mochiweb_utils.erl ; very slightly adapted. parse_header(String, ";")-> % for Content-Type and friends [Type | Parts] = [string:strip(S) || S <- string:tokens(String, ";")], {string:to_lower(Type), lists:foldr(fun prepare_headers/2, [], Parts)}; parse_header(String, ",")-> % for Auth [Type | Rest] = [string:strip(S) || S <- string:tokens(String, " ")], Parts = [string:strip(S) || S <- string:tokens(string:join(Rest, " "), ",")], {string:to_lower(Type), lists:foldr(fun prepare_headers/2, [], Parts)}. unquote_header("\"" ++ Rest) -> unquote_header(Rest, []); unquote_header(S) -> S. prepare_headers(S, Acc)-> case lists:splitwith(fun (C) -> C =/= $= end, S) of {"", _} -> %% Skip anything with no name Acc; {_, ""} -> %% Skip anything with no value Acc; {Name, [$\= | Value]} -> [{string:to_lower(string:strip(Name)), unquote_header(string:strip(Value))} | Acc] end. unquote_header("", Acc) -> lists:reverse(Acc); unquote_header("\"", Acc) -> lists:reverse(Acc); unquote_header([$\\, C | Rest], Acc) -> unquote_header(Rest, [C | Acc]); unquote_header([C | Rest], Acc) -> unquote_header(Rest, [C | Acc]). tsung-1.5.1/src/tsung-plotter/0000755000175000017500000000000012321173206017363 5ustar nniclaussenniclaussetsung-1.5.1/src/tsung-plotter/pgsql.plots.fr.conf0000644000175000017500000000501012104023217023115 0ustar nniclaussenniclausse# 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 écoulées xfactor = 1 yfactor = 1 styles = b- r- g- [users] title = Utilisateurs Simultanés ylabel = Utilisateurs simultanés 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 = Requêtes SQL par seconde ylabel = Nombres de requêtes sql par seconde stats = request.count legend = Requêtes [request_mean] title = Durée moyenne des requêtes SQL ylabel = Durée en secondes stats = request.mean legend = Requêtes yfactor = 1000 [request_count] title = Requêtes SQL par seconde ylabel = Nombres de requêtes SQL par seconde stats = request.count legend = Requêtes [request_mean] title = Durée moyenne des requêtes SQL ylabel = Durée en secondes stats = request.mean legend = Requêtes yfactor = 1000 [request_count] title = Requêtes SQL par seconde ylabel = Nombres de requêtes SQL par seconde stats = request.count legend = Requêtes [request_mean] title = Durée moyenne des requêtes SQL ylabel = Durée en secondes stats = request.mean legend = Requêtes yfactor = 1000 [connect_count] title = Connexions PostgreSQL par seconde ylabel = Nombres de connexions pgsql par seconde stats = connect.count legend = Connexion [connect_mean] title = Durée moyenne des connexions PostgreSQL ylabel = Durée 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 = Durée moyenne des sessions PostgreSQL ylabel = Durée 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 réseau 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 = Mémoire libre moyenne ylabel = Mémoire libre moyenne stats = {freemem,"os_mon@localhost"}.mean legend = RAM tsung-1.5.1/src/tsung-plotter/pgsql.plots.en.conf0000644000175000017500000000362612104023217023123 0ustar nniclaussenniclausse# 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.5.1/src/tsung-plotter/http.plots.en.conf0000644000175000017500000000277112104023217022754 0ustar nniclaussenniclausse# 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.5.1/src/tsung-plotter/tsplot.py.in0000644000175000017500000002614312104023217021670 0ustar nniclaussenniclausse#! /usr/bin/env 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.5.1/src/tsung-plotter/tsung/0000755000175000017500000000000012321173206020523 5ustar nniclaussenniclaussetsung-1.5.1/src/tsung-plotter/tsung/stats.conf0000644000175000017500000000267012104023217022530 0ustar nniclaussenniclausse# tsung plotter configuration # # tsung provides three types of statistics: # # sample: 'name';'count(during the last 10sec)';mean;stddev;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.5.1/src/tsung-plotter/tsung/__init__.py0000644000175000017500000000003712104023217022627 0ustar nniclaussenniclausse""" Tsung log data parser. """ tsung-1.5.1/src/tsung-plotter/tsung/tsung.py0000644000175000017500000001637212320752470022253 0ustar nniclaussenniclausse# -*- 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.stddev = float(values[2]) self.max = float(values[3]) self.min = 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.stddev, self.max, self.min, 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.5.1/src/tsung-plotter/http.plots.fr.conf0000644000175000017500000000317512104023217022760 0ustar nniclaussenniclausse# 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 écoulées xfactor = 60 yfactor = 1 [users] title = Utilisateurs Simultanés ylabel = Utilisateurs stats = users.count legend = Utilisateurs [connected] title = Utilisateurs connectés ylabel = Connexions simultanées stats = connected.totalcount legend = Utilisateurs connectés position = best [http] title = Réponses http par seconde ylabel = Réponses http par seconde stats = 200.count 400.count styles = b- g+ r- cx legend = http ok, http 400 [size_sent] title = Débit réseau en émission ylabel = Débit en Mbps stats = size_sent.count legend = Sent yfactor = 1310720 position = 2 [size_rcv] title = Débit réseau en réception ylabel = Débit 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 = Requêtes http par seconde ylabel = Nombres de requêtes http par seconde stats = request.count legend = Requêtes [request_mean] title = Durée moyenne des requêtes http ylabel = Durée en secondes stats = request.mean legend = Requêtes 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 = Durée moyenne d'obtention de pages ylabel = Durée en secondes stats = page.mean legend = Pages yfactor = 1000 tsung-1.5.1/src/tsung-plotter/fs.plots.en.conf0000644000175000017500000000416712104023217022406 0ustar nniclaussenniclausse# 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.5.1/src/tsung_recorder/0000755000175000017500000000000012321173206017561 5ustar nniclaussenniclaussetsung-1.5.1/src/tsung_recorder/tsung_recorder.erl0000644000175000017500000000570112320752470023322 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%%------------------------------------------------------------------- %%% 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/0, start/2, stop/1, stop_all/1]). -behaviour(application). -include("ts_macros.hrl"). %% start the application with it's dependencies start() -> ts_utils:ensure_all_started(tsung_recorder, permanent). %%---------------------------------------------------------------------- %% 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.5.1/src/tsung_recorder/ts_recorder_sup.erl0000644000175000017500000000631712320752470023503 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%%------------------------------------------------------------------- %%% 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_macros.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.5.1/src/tsung_recorder/ts_client_proxy_sup.erl0000644000175000017500000000604312147621622024412 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_client_proxy_sup). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -behaviour(supervisor). -include("ts_macros.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.5.1/src/tsung_recorder/ts_proxy_pgsql.erl0000644000175000017500000005023312147621622023373 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_proxy_pgsql). -vc('$Id$ '). -author('Nicolas.Niclausse@niclux.org'). -include("ts_macros.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.5.1/src/tsung_recorder/ts_proxy_http.erl0000644000175000017500000004205212147621622023224 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_proxy_http). -vc('$Id$ '). -author('Nicolas.Niclausse@niclux.org'). -include("ts_macros.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("application/x-silverlight-app") -> true; save_binary_post("application/xaml+xml") -> true; save_binary_post("application/x-ms-xbap") -> true; save_binary_post("application/soap+msbin1") -> true; save_binary_post("application/msbin1") -> 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.5.1/src/tsung_recorder/ts_proxy_listener.erl0000644000175000017500000002067212320752470024075 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%%------------------------------------------------------------------- %%% 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_macros.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), ts_client_proxy:set_active(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, false}]), 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.5.1/src/tsung_recorder/ts_proxy_webdav.erl0000644000175000017500000001401612147621622023514 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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.5.1/src/tsung_recorder/tsung_recorder.app.in0000644000175000017500000000145712317474675023747 0ustar nniclaussenniclausse{application, tsung_recorder, [{description, "tsung recorder"}, {vsn, "@PACKAGE_VERSION@"}, {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.5.1/src/tsung_recorder/ts_client_proxy.erl0000644000175000017500000002262112320752470023522 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%%------------------------------------------------------------------- %%% 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_macros.hrl"). -include("ts_recorder.hrl"). %%-------------------------------------------------------------------- %% External exports -export([start/1, set_active/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], []). %% tells the client to activate socket set_active(Pid) -> gen_server:cast(Pid, {set_active}). %%==================================================================== %% 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({set_active}, State=#proxy{clientsock=Socket}) -> ts_utils:inet_setopts(tcp, Socket,[{active, once}]), {noreply, State}; 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.5.1/src/tsung_recorder/ts_proxy_recorder.erl0000644000175000017500000002074512147621622024057 0ustar nniclaussenniclausse%%% %%% @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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_proxy_recorder). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -behaviour(gen_server). %%-------------------------------------------------------------------- %% Include files %%-------------------------------------------------------------------- -include("ts_macros.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.5.1/src/tsung_controller/0000755000175000017500000000000012321173206020137 5ustar nniclaussenniclaussetsung-1.5.1/src/tsung_controller/ts_config_raw.erl0000644000175000017500000000647612320752470023511 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%% 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.5.1/src/tsung_controller/ts_os_mon_snmp.erl0000644000175000017500000002535612147621622023721 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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_macros.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.5.1/src/tsung_controller/ts_config_websocket.erl0000644000175000017500000000670312301350731024670 0ustar nniclaussenniclausse%%% This code was developped by Zhihui Jiao(jzhihui521@gmail.com). %%% %%% Copyright (C) 2013 Zhihui Jiao %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_config_websocket). -vc('$Id$ '). -author('jzhihui521@gmail.com'). -export([parse_config/2]). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("ts_websocket.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 = websocket}, Config = #config{curid = Id, session_tab = Tab, sessions = [CurS | _], dynvar = DynVar, subst = SubstFlag, match = MatchRegExp}) -> Type = ts_config:getAttr(atom, Element#xmlElement.attributes, type), ValRaw = ts_config:getText(Element#xmlElement.content), Path = ts_config:getAttr(string, Element#xmlElement.attributes, path, "/"), Frame = ts_config:getAttr(string, Element#xmlElement.attributes, frame, "binary"), %%is this needed ? CleanStr = ts_utils:clean_str(ValRaw), Request = #websocket_request{data = CleanStr, type = Type, path = Path, frame = Frame}, Ack = case Type of message -> ts_config:getAttr(atom, Element#xmlElement.attributes, ack, parse); _ -> parse 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 other elements parse_config(Element = #xmlElement{}, Conf = #config{}) -> ts_config:parse(Element,Conf); %% Parsing non #xmlElement elements parse_config(_, Conf = #config{}) -> Conf. tsung-1.5.1/src/tsung_controller/ts_match_logger.erl0000644000175000017500000001665212147621622024024 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%---------------------------------------------------------------------- %% @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_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",?INFO), 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",?INFO), 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",?INFO), io:format(Fd,"# timestamp userid sessionid requestid event transaction name~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:foldr(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("stopping match logger (~p)~n",[Reason],?INFO), 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},[], Tr,Name},State=#state{fd=File}) -> TS=ts_utils:time2sec_hires(TimeStamp), io:format(File,"~f ~B ~B ~B ~p ~s ~s~n",[TS,UserId,SessionId,RequestId,Val,ts_utils:log_transaction(Tr),Name]), State; log({UserId,SessionId,RequestId,TimeStamp,{count, Val},Bin, Tr,MatchName}, State=#state{logdir=LogDir, dumpid=Id}) -> log({UserId,SessionId,RequestId,TimeStamp,{count, Val},[],Tr, MatchName}, 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.5.1/src/tsung_controller/ts_config.erl0000644000175000017500000015752712320752470022644 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%%---------------------------------------------------------------------- %%% @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/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), Result = handle_read(catch xmerl_scan:file(Filename, [{fetch_path,["/usr/share/tsung/","./"]}, {validation,true}]),Filename,LogDir), %% In case of error we reparse the file with xmerl_sax_parser:file/2 to obtain %% a more verbose output case Result of {ok, _} -> Result; _ -> xmerl_sax_parser:file(Filename,[]), Result end. 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, total_server_weights=OldTotal}) -> Server = getAttr(Attrs, host), Port = getAttr(integer, Attrs, port), Weight = getAttr(float_or_integer, Attrs, weight,1), Type = set_net_type(getAttr(Attrs, type)), Total = OldTotal + Weight, lists:foldl(fun parse/2, Conf#config{servers = [#server{host = Server, port = Port, weight= Weight, type = Type }|ServerList], total_server_weights = Total}, 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 -> case lists:keysearch(mysqladmin,#xmlElement.name, Element#xmlElement.content) of {value, MysqlEl=#xmlElement{} } -> Port = getAttr(integer,MysqlEl#xmlElement.attributes, port, ?config(mysql_port)), Username = getAttr(string,MysqlEl#xmlElement.attributes, username, ?config(mysql_user)), Password = getAttr(string,MysqlEl#xmlElement.attributes, password, ?config(mysql_password)), {erlang, [{mysqladmin, {Port, Username, Password}}]}; _ -> {erlang, []} end; 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 "" -> []; 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],?INFO), Probability = getAttr(float_or_integer, Attrs, probability, -1), Weight = getAttr(float_or_integer, Attrs, weight, -1), {Popularity, NewUseWeights, NewTotal} = get_popularity(Probability, Weight, Conf#config.use_weights,Conf#config.total_popularity), 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 = Popularity, 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, use_weights=NewUseWeights, total_popularity=NewTotal, curid=0, cur_req_id=0},% re-initialize request id Element#xmlElement.content); %% Parsing the session_setup element parse(Element = #xmlElement{name=session_setup, attributes=Attrs}, Conf = #config{arrivalphases=[Phase|Phases]}) -> Name = getAttr(Attrs, name), {Popularity, NewUseWeights} = case { Conf#config.use_weights, getAttr(float_or_integer, Attrs, weight, -1) } of {Use, -1} when (Use == undefined orelse Use == false) -> { getAttr(float_or_integer, Attrs, probability, -1), false }; {_, Val} -> { Val, true} end, SessionsPopularities = Phase#arrivalphase.popularities, NewPhase = Phase#arrivalphase{popularities= [{Name, Popularity}| SessionsPopularities]}, lists:foldl(fun parse/2, Conf#config{ use_weights = NewUseWeights, arrivalphases = [NewPhase | Phases] }, 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), getAttr(string,Attrs,neq,none), getAttr(string,Attrs,gt,none), getAttr(string,Attrs,lt,none), getAttr(string,Attrs,gte,none), getAttr(string,Attrs,lte,none)} of {none, Neq, none, none, none, none} -> {neq,Neq}; {Eq, none, none, none, none, none} -> {eq,Eq}; {none, none, Gt, none, none, none} -> {gt,Gt}; {none, none, none, Lt, none, none} -> {lt,Lt}; {none, none, none, none, Gte, none} -> {gt,Gte}; {none, none, none, none, none, Lte} -> {lt,Lte} 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), getAttr(string,WhileAttrs,neq,none), getAttr(string,WhileAttrs,gt,none), getAttr(string,WhileAttrs,lt,none), getAttr(string,WhileAttrs,gte,none), getAttr(string,WhileAttrs,lte,none)} of {none, Neq, none, none, none, none} -> {neq,Neq}; {Eq, none, none, none, none, none} -> {eq,Eq}; {none, none, Gt, none, none, none} -> {gt,Gt}; {none, none, none, Lt, none, none} -> {lt,Lt}; {none, none, none, none, Gte, none} -> {gt,Gte}; {none, none, none, none, none, Lte} -> {lt,Lte} end, %either , %either , %either , 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,re,none), getAttr(string,Attrs,pgsql_expr,none), getAttr(string,Attrs,xpath,none), getAttr(string,Attrs,header,none), getAttr(string,Attrs,jsonpath,none)} of {none,none,none,none,none} -> DefaultRegExp = ?DEF_RE_DYNVAR_BEGIN ++ StrName ++?DEF_RE_DYNVAR_END, {re,DefaultRegExp}; {RE,none,none,none, none} -> {re,RE}; {none,PG,none,none, none} -> {pgsql_expr,PG}; {none,none,XPath,none,none} -> {xpath,XPath}; {none,none,none,AuthHeader,none} -> {header, AuthHeader}; {none,none,none,none,JSONPath} -> {jsonpath,JSONPath} end, FlattenExpr =lists:flatten(Expr), %% precompilation of the exp DynVar = case Type of re -> ?LOGF("Add new re: ~s ~n", [Expr],?INFO), {ok, CompiledRegExp} = re:compile(FlattenExpr), %% TSUN-249 case getAttr(string,Attrs,decode,none) of "html_entities" -> ?LOGF("The re will be decoded: ~s ~n", [Expr],?INFO), {re,Name,CompiledRegExp,fun ts_utils:conv_entities/1}; _ -> {re,Name,CompiledRegExp, undefined} end; 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", [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(string, Attrs, port), Store = getAttr(atom, Attrs, store, false), Restore = getAttr(atom, Attrs, restore, false), PType = set_net_type(getAttr(Attrs, server_type)), Bidi = getAttr(atom, Attrs, bidi, false), 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, Bidi}}), ?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( #xmlElement{name=interaction, attributes=Attrs}, Conf = #config{sessions=[CurS|_Other], curid=Id,session_tab = Tab}) -> Action = list_to_atom(getAttr(string, Attrs, action, "send")), RawId = getAttr(Attrs, id), {ok, [{atom,1,IdInteraction}],1} = erl_scan:string("tr_"++RawId), ets:insert(Tab,{{CurS#session.id, Id+1}, {interaction, Action, IdInteraction}}), ?LOGF("Parse interaction ~p:~p ~n",[Action,Id],?NOTICE), Conf#config{curid=Id+1 }; 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}}; "certificate" -> {value, #xmlElement{attributes=AttrCert}} = lists:keysearch(certificate, #xmlElement.name, Element#xmlElement.content), Cacert = getAttr(string, AttrCert, cacertfile, undefined), KeyFile = getAttr(string, AttrCert, keyfile, undefined), KeyPass = getAttr(string, AttrCert, keypass, undefined), CertFile = getAttr(string, AttrCert, certfile, undefined), {undefined, certificate, {Cacert, KeyFile,KeyPass,CertFile}} 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), Tag = getAttr(string, Attrs, tag, ""), Tags = lists:map(fun(X)->{X,ok} end, string:tokens(?config(exclude_tag),",")), %% do not add in Conf excluded requests case proplists:is_defined(Tag, Tags) of true -> ?LOGF("Tag ~p in ~p ~p ~p ~n",[Tag,true,?config(exclude_tag),Tags],?NOTICE), Conf; 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=[], tag=Tag }, Element#xmlElement.content) end; %%% Match parse(Element=#xmlElement{name=match,attributes=Attrs}, Conf=#config{match=Match})-> Do = getAttr(atom, Attrs, do, continue), When = getAttr(atom, Attrs, 'when', match), Name = getAttr(string, Attrs, name, "-"), 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, name=Name, 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); "idle_timeout" -> Timeout = getAttr(integer,Attrs, value, ?config(idle_timeout)), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{idle_timeout=Timeout}, lists:foldl( fun parse/2, Conf#config{proto_opts=NewProto}, Element#xmlElement.content); "global_ack_timeout" -> Timeout = getAttr(integer,Attrs, value, ?config(global_ack_timeout)), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{global_ack_timeout=Timeout}, lists:foldl( fun parse/2, Conf#config{proto_opts=NewProto}, Element#xmlElement.content); "retry_timeout" -> Timeout = getAttr(integer,Attrs, value, ?config(client_retry_timeout)), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{retry_timeout=Timeout}, lists:foldl( fun parse/2, Conf#config{proto_opts=NewProto}, Element#xmlElement.content); "websocket_path" -> Path = getAttr(string,Attrs, value, ?config(websocket_path)), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{websocket_path=Path}, lists:foldl( fun parse/2, Conf#config{proto_opts=NewProto}, Element#xmlElement.content); "websocket_frame" -> Frame = getAttr(string,Attrs, value, ?config(websocket_frame)), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{websocket_frame=Frame}, lists:foldl( fun parse/2, Conf#config{proto_opts=NewProto}, Element#xmlElement.content); "bosh_path" -> Path = getAttr(string,Attrs, value, ?config(bosh_path)), OldProto = Conf#config.proto_opts, NewProto = OldProto#proto_opts{bosh_path=Path}, 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 = list_to_binary(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}; "value" -> Value = getAttr(string,Attrs,value,""), {setdynvars,value,{string,Value},Vars}; "server" -> {setdynvars,server,{},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. get_default(Tab, Key) when is_atom(Key) -> get_default(Tab, Key, Key). %%%---------------------------------------------------------------------- %%% 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") -> ts_tcp; set_net_type("tcp6") -> ts_tcp6; set_net_type("udp") -> ts_udp; set_net_type("udp6") -> ts_udp6; set_net_type("ssl") -> ts_ssl; set_net_type("ssl6") -> ts_ssl6; set_net_type("websocket") -> ts_server_websocket; set_net_type("bosh") -> ts_bosh; set_net_type("bosh_ssl") -> ts_bosh_ssl; set_net_type("erlang") -> ts_erlang. 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. %% @spec get_popularity(Proba::number(), Weight::number(), UseWeight::(true|false|undefined), Total::number()) -> %% {Value::number(), UseWeight::boolean(), Total::number()} %% @doc check if we are using popularity or weights; keep the total up to date. @end get_popularity(-1, -1, _, _)-> erlang:error({"must set weight or probability in session"}); get_popularity(Proba,Weight,_,_) when is_number(Proba), Proba >= 0, is_number(Weight), Weight >= 0 -> erlang:error({"can't mix probabilites and weights", Proba, Weight} ); get_popularity(Proba, _Weight, true,_) when is_number(Proba), Proba >= 0-> erlang:error({"can't use probability when using weight"}); get_popularity(_, Weight, false,_) when is_number(Weight), Weight >= 0-> erlang:error({"can't use weights when using probabilities"}); get_popularity(_, Weight, undefined,_) when is_number(Weight), Weight >= 0 -> {Weight, true, Weight}; get_popularity(Proba, _, undefined,Total) when is_number(Proba) -> {Proba, false, Proba+Total}; get_popularity(Proba, _, false,Total) when is_number(Proba) -> {Proba, false, Proba+Total}; get_popularity(_, Weight, true, Total) when is_number(Weight) -> {Weight, true, Weight+Total}. tsung-1.5.1/src/tsung_controller/ts_config_fs.erl0000644000175000017500000000632512320752470023321 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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.5.1/src/tsung_controller/ts_config_mysql.erl0000644000175000017500000000743512320752470024061 0ustar nniclaussenniclausse%%% Created: July 2008 by Grégoire 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.5.1/src/tsung_controller/ts_config_shell.erl0000644000175000017500000000554412320752470024022 0ustar nniclaussenniclausse%%% %%% Copyright 2010 © INRIA %%% %%% Author : Nicolas Niclausse %%% Created: 18 août 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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.5.1/src/tsung_controller/ts_stats_mon.erl0000644000175000017500000004126512157674022023401 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%---------------------------------------------------------------------- %% @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_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],?INFO), gen_server:start_link({global, Id}, ?MODULE, [Id], []). start() -> ?LOG("starting stats server, global ~n",?INFO), 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],?INFO), 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],?INFO), 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("stopping stats monitor (~p)~n",[Reason],?INFO), 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,\"stddev\": ~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,\"stddev\": ~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({Name,Type, ", {\"name\": \"http_~p\", \"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})-> StdDev = math:sqrt(Var/Count), io:format(Logfile, Format, [Name, Count, Mean, StdDev, 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.5.1/src/tsung_controller/ts_user_server_sup.erl0000644000175000017500000000365612147621622024624 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%% %%% 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.5.1/src/tsung_controller/ts_file_server.erl0000644000175000017500000002061312147621622023666 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%%------------------------------------------------------------------- %%% 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_config.hrl"). -include("xmerl.hrl"). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: parse_config/2 %% Args: Element, Config %% Returns: Binary %% 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), case file:read_file(Path) of {ok, Bin} -> List_items = binary:split( Bin , <<"\n">> , [global,trim]), FileDesc = #file{items = list_to_tuple(List_items), size=length(List_items)}, {reply, Result, State#state{files = ?DICT:store(ID, FileDesc, State#state.files)}}; {error,Reason} -> ?LOGF("Error while opening file ~p :~p~n",[ Path, Reason], ?ERR), {reply, {error, Path}, State} end end. tsung-1.5.1/src/tsung_controller/tsung_controller.app.in0000644000175000017500000000465712317474675024710 0ustar nniclaussenniclausse{application, tsung_controller, [{description, "tsung, a bench tool for TCP/UDP servers"}, {vsn, "@PACKAGE_VERSION@"}, {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"}, {mysql_port, 3306}, {mysql_user, "root"}, {mysql_password, false}, {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"}, {exclude_tag, ""} ]}, {applications, [ @ERLANG_APPLICATIONS@ ]}, {start_phases, [{load_config, []},{start_os_monitoring,[{timeout,30000}]}, {start_clients,[]}]}, {mod, {tsung_controller, []}} ]}. tsung-1.5.1/src/tsung_controller/ts_config_job.erl0000644000175000017500000000776212320752470023471 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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.5.1/src/tsung_controller/ts_config_http.erl0000644000175000017500000005117612320752470023674 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%% 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, encode_ipv6_address/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 = [Server|_] = 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, tag = Config#config.tag}, %% 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 Request4 = case lists:keysearch(www_authenticate, #xmlElement.name, Element#xmlElement.content) of {value, AuthEl=#xmlElement{} } -> UserId= ts_config:getAttr(string,AuthEl#xmlElement.attributes, userid, undefined), Type = ts_config:getAttr(string,AuthEl#xmlElement.attributes, type, "basic"), Nonce = ts_config:getAttr(string,AuthEl#xmlElement.attributes, nonce, undefined), Opaque = ts_config:getAttr(string,AuthEl#xmlElement.attributes, opaque, undefined), Cnonce = ts_config:getAttr(string,AuthEl#xmlElement.attributes, cnonce, "%%ts_user_server:get_really_unique_id%%"), Nc = ts_config:getAttr(string,AuthEl#xmlElement.attributes, nc, "00000001"), Passwd= ts_config:getAttr(string,AuthEl#xmlElement.attributes, passwd, undefined), Realm = ts_config:getAttr(string,AuthEl#xmlElement.attributes, realm, undefined), QOP = ts_config:getAttr(string,AuthEl#xmlElement.attributes, qop, undefined), ?LOGF("DIGEST ? : ~p ~p ~p", [Type, Nonce, Realm], ?WARN), Request3#http_request{userid=UserId, passwd=Passwd, auth_type=Type, digest_nonce=Nonce, digest_cnonce=Cnonce, digest_nc=Nc, digest_opaque=Opaque, realm=Realm, digest_qop=QOP}; _ -> Request3 end, %% OAuth Msg = case lists:keysearch(oauth, #xmlElement.name, Element#xmlElement.content) of {value, AuthEl2=#xmlElement{} } -> ConsumerKey = ts_config:getAttr(string,AuthEl2#xmlElement.attributes, consumer_key, undefined), ConsumerSecret = ts_config:getAttr(string,AuthEl2#xmlElement.attributes, consumer_secret, undefined), AccessToken = ts_config:getAttr(string,AuthEl2#xmlElement.attributes, access_token, []), AccessTokenSecret = ts_config:getAttr(string,AuthEl2#xmlElement.attributes, access_token_secret, []), Encoding = ts_config:getAttr(string,AuthEl2#xmlElement.attributes, encoding, "HMAC-SHA1"), AbsoluteURL = case URL of "http" ++ _Rest -> hd(string:tokens(URL, "?")); _Relative -> server_to_url(Server) ++ hd(string:tokens(URL, "?")) end, SigEncoding = case Encoding of "PLAINTEXT" -> plaintext; "HMAC-SHA1" -> hmac_sha1; "RSA-SHA1" -> rsa_sha1 end, NewReq=Request4#http_request{oauth_access_token=AccessToken, oauth_url=AbsoluteURL, oauth_access_secret=AccessTokenSecret, oauth_consumer={ConsumerKey, ConsumerSecret, SigEncoding}}, set_msg(NewReq, {SubstFlag, MatchRegExp, UseProxy, Servers, PreviousHTTPServer, Tab, CurS#session.id} ); _ -> set_msg(Request4, {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}) -> case {SubstFlag, re:run(URL, "%%.+%%")} of {true, {match,_}} -> %% url is a dynvar, don't preset host header set_msg2(HTTP, #ts_request{ack = parse, subst = true, match = MatchRegExp }); _ -> 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, use_proxy=UseProxy}, #ts_request{ack = parse, subst = SubstFlag, match = MatchRegExp, host = RealServer#server.host, scheme = RealServer#server.type, port = RealServer#server.port}) end; %% 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 ",?INFO), 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==ts_ssl orelse Type==ts_ssl6-> "https://" ++ encode_ipv6_address(Host); server_to_url(#server{port=Port, host= Host, type= Type})when Type==ts_ssl orelse Type==ts_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, ts_tcp6}) -> ts_tcp6; set_scheme({http, ts_ssl6}) -> ts_tcp6; set_scheme({http, _}) -> ts_tcp; set_scheme({https, ts_ssl6}) -> ts_ssl6; set_scheme({https, ts_tcp6}) -> ts_ssl6; set_scheme({https, _}) -> ts_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.5.1/src/tsung_controller/ts_msg_server.erl0000644000175000017500000001145312104023217023524 0ustar nniclaussenniclausse%%% 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_macros.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()); get_id({_,_DynData}) -> % to use this fun in substitutions get_id(list). 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.5.1/src/tsung_controller/ts_controller_sup.erl0000644000175000017500000001117412147621622024435 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_controller_sup). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -include("ts_macros.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]}, Interaction = {ts_interaction_server, {ts_interaction_server, start, []}, transient, 2000, worker, [ts_interaction_server]}, {ok,{{one_for_one,?retries,10}, [Config, Mon, Stats_Mon, Request_Mon, Page_Mon, Connect_Mon, Transaction_Mon, Match_Log, Timer, Msg, Notify, Interaction, UserSup, ErlangSup, MuninSup,SNMPSup]}}. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- tsung-1.5.1/src/tsung_controller/ts_os_mon_munin.erl0000644000175000017500000002707712147621622024074 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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_macros.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.5.1/src/tsung_controller/ts_config_server.erl0000644000175000017500000012412512320752470024216 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%%------------------------------------------------------------------- %%% 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_user_port/1, get_jobs_state/0 ]). %%debug -export([choose_client_ip/1, choose_session/3]). %% 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 }). -define(RPC_TIMEOUT, 30000). %%==================================================================== %% 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, Args)-> gen_server:cast({global, ?MODULE},{newbeam, Host, Args }). %%-------------------------------------------------------------------- %% 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). %%-------------------------------------------------------------------- %% @spec get_monitor_hosts() -> List %% List = [Hosts::string()] %% @doc get list of hosts to monitor @end %%-------------------------------------------------------------------- get_monitor_hosts()-> gen_server:call({global,?MODULE},{get_monitor_hosts}). %%-------------------------------------------------------------------- %% @spec get_next_session({Host::string(), PhaseId::integer()}) -> %% {ok, SessionId::integer(), SessionSize::integer(), IP::tuple(), UserId::integer() } %% @doc Choose randomly a session %% @end %%-------------------------------------------------------------------- get_next_session({Host, PhaseId})-> gen_server:call({global, ?MODULE},{get_next_session, Host, PhaseId}). get_user_param(Host)-> gen_server:call({global, ?MODULE},{get_user_param, Host}). get_user_port(Ip) -> gen_server:call({global, ?MODULE},{get_user_port, Ip}). 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]}} -> 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]), ConfigTmp = loop_load(sort_static(Config#config{sessions=[NewLast]++Sessions})), %% Compute per phase popularities NewConfig = compute_popularities(ConfigTmp), ts_job_notify:listen(NewConfig#config.job_notify_port), case check_config(NewConfig) of ok -> {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 user port. This is needed by bosh, as there are more than one socket per bosh connection. handle_call({get_user_port, IP}, _From, State=#state{ports=Ports}) -> Config = State#state.config, {NewPorts,CPort} = choose_port(IP, Ports,Config#config.ports_range), {reply, {ok, CPort}, State#state{ports = NewPorts}}; %% get a new session id and user parameters for the given node handle_call({get_next_session, HostName, PhaseId}, _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, Config#config.total_popularity, PhaseId) 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 if 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, syncing ~n",?NOTICE), global:sync(), ?LOG("Syncing done, start remote tsung application ~n", ?DEB), {Resl, BadNodes} = rpc:multicall(RemoteNodes,tsung,start,[],?RPC_TIMEOUT), ?LOGF("RPC result: ~p ~p ~n",[Resl,BadNodes],?DEB), case BadNodes of [] -> StartLaunchers = fun(Node) -> ts_launcher_static:launch({Node,[]}), ts_launcher:launch({Node, [], Seed}) end, case Config#config.ports_range of undefined -> ?LOG("Undefined ports_range config ~n",?NOTICE), 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), set_max_duration(Config#config.duration), {noreply, State#state{last_beam_id = LastId}}; Bad -> ?LOGF("Can't start tsung application on all remote clients, abort ~p~n",[Bad],?ERR), ts_mon:abort(), {stop,normal,State} end 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, Args}, 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, Args, 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, Config#config.total_server_weights), 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([Server], _TotalWeight) -> {ok, Server}; choose_server(Servers, Total) -> choose_server(Servers, random:uniform() * Total, 0). choose_server([S=#server{weight=P} | _],Rand,Cur) when Rand =< P+Cur-> {ok, S}; choose_server([#server{weight=P} | SList], Rand, Cur) -> choose_server(SList, Rand, Cur+P). %%---------------------------------------------------------------------- %% 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/2 %% Args: List of #session %% Purpose: choose an session randomly %% Returns: #session %%---------------------------------------------------------------------- choose_session([Session], _Total, _PhaseId) -> %% only one Session {ok, Session}; choose_session(Sessions,Total,PhaseId) when is_number(Total)-> choose_session(Sessions, random:uniform() * Total, 0, PhaseId); choose_session(Sessions,Total,PhaseId) when is_list(Total) -> choose_session(Sessions, random:uniform() * lists:nth(PhaseId, Total), 0, PhaseId). choose_session([S=#session{popularity=P} | _],Rand,Cur,_PhaseId) when is_number(P) andalso Rand =< P+Cur-> {ok, S}; choose_session([#session{popularity=P} | SList], Rand, Cur, PhaseId) when is_number(P)-> choose_session(SList, Rand, Cur+P, PhaseId); choose_session([S=#session{popularity=PopList} | SList],Rand,Cur,PhaseId) -> P = lists:nth(PhaseId,PopList), if Rand =< P+Cur -> {ok, S}; true -> choose_session(SList, Rand, Cur+P, PhaseId) end. %%---------------------------------------------------------------------- %% @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],?WARN), ?LOGF("SYSINFO:Erlang version: ~s~n",[erlang:system_info(system_version)],?WARN), ?LOGF("SYSINFO:System architecture ~s~n",[erlang:system_info(system_architecture)],?WARN), ?LOGF("SYSINFO:Current path: ~s~n",[code:which(tsung)],?WARN). %%---------------------------------------------------------------------- %% 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=#config{use_weights=UseWeights})-> case lists:dropwhile(fun(Pop) -> check_popularity(UseWeights,Pop) == ok end, Config#config.total_popularity) of [] -> ts_config_http:check_user_agent_sum(Config#config.session_tab); [BadPop|_] -> {error, {bad_sum, BadPop, ?SESSION_POP_ERROR_MSG}} end. check_popularity(false, Val) when abs(100-Val) < 0.05 -> ok; check_popularity(false,_Val) -> {error, bad_sum }; check_popularity(true, _Val) -> ok. 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=Loop,arrivalphases=Arrival}) when is_integer(Loop) -> Sorted=lists:keysort(#arrivalphase.phase, Arrival), {SortedWithId,_} = lists:mapfoldl(fun(Phase, Id) -> {Phase#arrivalphase{id=Id}, Id+1} end, 1, Sorted), loop_load(Config#config{arrivalphases=SortedWithId}, ts_utils:keymax(#arrivalphase.phase, Arrival), SortedWithId ). %% 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), ?LOGF("sorted phases: ~p ~n", [Sorted], ?DEB), 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), ES = expand_static(S,Config#config.sessions), SortedL= lists:keysort(1,ES), Config#config{static_users=static_name_to_session(Config#config.sessions,SortedL)}. %% expand static users (if it contains wildcards) expand_static(StaticUsers, Sessions) -> Names = lists:map(fun(#session{name=A}) -> A end ,Sessions), expand_static(StaticUsers, Names, []). expand_static([], _Names, Static) -> Static; expand_static([{Delay, Name} | Static],SessionsNames, Acc) -> Names = ts_utils:wildcard(Name, SessionsNames), NewStatic = lists:map(fun(N) -> {Delay, N} end, Names), expand_static(Static, SessionsNames, Acc ++ NewStatic). %% %% @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("Remote beam started on node ~p ~n", [Node], ?NOTICE), Res = net_adm:ping(Node), ?LOGF("ping ~p ~p~n", [Node,Res], ?INFO), 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(Client,undefined, Range) -> choose_port(Client,dict:new(), Range); choose_port(ClientIp,Ports, {Min, Max}) -> case dict:find(ClientIp,Ports) of {ok, Val} when Val =< Max -> NewPorts=dict:update_counter(ClientIp,1,Ports), {NewPorts,Val}; _ -> % Max Reached or new entry NewPorts=dict:store(ClientIp,Min+1,Ports), {NewPorts,Min} 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+?config(warm_time))*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), case Config#config.ports_range of {Min, Max} -> application:set_env(tsung, cport_min, Min), application:set_env(tsung, cport_max, Max); undefined -> "" end, 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 ~p on host ~p with Args ~p~n", [Name, Host, Args], ?INFO), start_slave(Host, Name, Args). set_remote_args(LogDir,PortsRange)-> {ok, PAList} = init:get_argument(pa), PA = lists:flatmap(fun(A) -> [" -pa "] ++A end,PAList), ?DebugF("PA list ~p ~n", [PA]), 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, 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. %% compute popularities of sessions for all phases compute_popularities(Config=#config{arrivalphases=Phases, sessions=Sessions}) -> %% popularities can contains wildcards, need to expand them Names = lists:map(fun(#session{name=A}) -> A end ,Sessions), Expand = fun( Phase = #arrivalphase{popularities= Pops} ) -> NewPops = lists:foldl(fun({Name,Popularity},Acc) -> Expanded = ts_utils:wildcard(Name, Names), Acc ++ lists:map(fun(X) -> {X, Popularity} end, Expanded) end, [], Pops), Phase#arrivalphase{popularities=NewPops} end, NewPhases = lists:map(Expand,Phases), ?LOGF("Compute popularities per phases ~p",[NewPhases],?DEB), F = fun(Session=#session{popularity=Pop, name=Name}) -> NewPop = set_pop(Name, Pop, NewPhases), Session#session{popularity=NewPop} end, NewSessions = lists:map(F, Sessions), ?LOGF("Old sessions:~p",[Sessions],?DEB), ?LOGF("New sessions:~p",[NewSessions],?DEB), Config#config{sessions=NewSessions, arrivalphases = NewPhases, total_popularity=update_total_pop(Config#config.use_weights, NewPhases, NewSessions)}. update_total_pop(UseWeight,Phases, Sessions) -> update_total_pop(UseWeight, length(Phases), Sessions, []). update_total_pop(_UseWeight,0, _, Total) -> ?LOGF("New Total popularities:~p",[Total],?DEB), Total; update_total_pop(UseWeight,N, Sessions, Total) -> Sum = fun(#session{popularity=P},Acc) when is_number(P) -> Acc+P ; (#session{popularity=L},Acc) -> Acc+lists:nth(N,L) end, PhaseTotal = lists:foldl(Sum, 0, Sessions), update_total_pop(UseWeight, N-1, Sessions, [PhaseTotal |Total]). %% set popularity of session 'Name' per phase (needed when is used) set_pop(Name,Popularity,Phases) -> set_pop(Name,Popularity,Phases,[]). set_pop(_Name,_Popularity,[], Acc) -> %% optimization: if all values are equal, return a single value and not a list Min=lists:min(Acc), case lists:max(Acc) of Min -> Min; % min = max _ -> lists:reverse(Acc) end; set_pop(Name,Popularity,[#arrivalphase{popularities=Pop}|Tail], Acc) -> New = case lists:keysearch(Name,1,Pop) of false -> Popularity; {value, {_, Val}} -> Val end, set_pop(Name,Popularity,Tail, [New|Acc]). tsung-1.5.1/src/tsung_controller/ts_os_mon_sup.erl0000644000175000017500000000531112147621622023540 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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_macros.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.5.1/src/tsung_controller/ts_user_server.erl0000644000175000017500000005032412147621622023727 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_user_server). -author('jflecomte@IDEALX.com'). -vc('$Id$ '). -include("ts_macros.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, set_offline_fileid/1, set_random_fileid/1, set_fileid_delimiter/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) random_server_id, % file_server id for random users offline_server_id, % file_server id for initial offline users delimiter = << ";" >>, % delimiter for file_server id username 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({_, 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}). %% @spec set_random_fileid(Id::atom()) -> ok %% @doc Set file_server id for random users %% This is useful and usernames and password are set from a CSV file. %% @end set_random_fileid(Id) -> gen_server:cast({global, ?MODULE}, {set_random_fileid, Id}). %% @spec set_offline_fileid(Id::atom()) -> ok %% @doc Set file_server id for initial offline users %% This is useful and usernames and password are set from a CSV file. %% @end set_offline_fileid(Id) -> gen_server:cast({global, ?MODULE}, {set_offline_fileid, Id}). %% @spec set_fileid_delimiter(D::string()) -> ok %% @doc Set file_server delimiter for random users @end set_fileid_delimiter(D) -> gen_server:cast({global, ?MODULE}, {set_fileid_delimiter, D}). 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=#state{random_server_id = Id, delimiter=D}) when Id /= undefined-> case ts_file_server:get_random_line(Id) of {ok, Line} -> [Val|_] = ts_utils:split(Line,D), {reply, {binary_to_list(Val), unused}, State}; %% FIXME: use binaries in jabber_common everywhere and remove the binary_to_list call here. _Else -> {reply, {error, userid_max_zero }, State} end; handle_call(get_id, _From, State=#state{userid_max = 0}) -> % no user defined in the pool, probably we are using usernames from external file (CSV) {reply, {error, userid_max_zero }, State}; handle_call(get_id, _From, State) -> Key = random:uniform( State#state.userid_max ), {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 ets_iterator_next(Offline, State#state.last_offline ) of {error, empty_ets} -> ?LOG("No more free users !~n", ?WARN), {reply, {error, no_free_userid}, State}; {ok, Key} -> NextOffline = ets_iterator_del(Offline, Key, State#state.last_offline), 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 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",[Next]), {reply, {ok, {Next,Pwd}}, State#state{last_offline={Next,Pwd}}}; {ok, Next} -> ?DebugF("Choose offline user ~p~n",[Prev]), {reply, {ok, Next}, 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, {State#state.offline_server_id, State#state.delimiter}), State2 = State#state{offline = Offline, first_client = undefined, last_offline = undefined, 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}) -> ?DebugF("get_online from ~p~n",[Id]), 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,Pwd}}, 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,ets:first(Offline)} of {undefined, Id} -> %% if we don't set last_offline, the next get_idle will %% respond with Id again. If possible we prefer to use %% another offline user {noreply, State#state{last_online=LastOnline, last_offline=Id}}; _Else -> {noreply, State#state{last_online=LastOnline}} end; %% user_defined user case handle_cast({add_to_connected, Id}, State=#state{connected=Connected, offline=Offline,first_client=First}) -> ets:insert(Connected, {Id,1}), NextOffline = ets_iterator_del(Offline, Id, State#state.last_offline), case First of undefined -> {noreply, State#state{last_connected=Id, first_client=Id, last_offline=NextOffline}}; _ -> {noreply, State#state{last_connected=Id,last_offline=NextOffline}} end; handle_cast({add_to_online, Id}, State=#state{online=Online, connected=Connected}) -> ?DebugF("add_to_online ~p~n",[Id]), 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}) -> ?DebugF("remove_from_online ~p~n",[Id]), {noreply, LastOnline} = ets_delete_online(Online,Id,State), ets:insert(Connected, {Id,1}), {noreply, State#state{last_online=LastOnline}}; handle_cast({set_random_fileid, Id}, State) -> ?LOGF("Set file_server id for random users to ~p~n",[Id],?INFO), {noreply, State#state{random_server_id=Id}}; handle_cast({set_offline_fileid, Id}, State) -> ?LOGF("Set file_server id for offline users to ~p~n",[Id],?INFO), {noreply, State#state{offline_server_id=Id}}; handle_cast({set_fileid_delimiter, D}, State) -> ?LOGF("Set file_server delimiter ~p~n",[D],?DEB), {noreply, State#state{delimiter=D}}. %%---------------------------------------------------------------------- %% 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, _, {undefined,_})-> ?LOG("no offline user defined",?DEB), ok; fill_offline(0, Offline, {FileId, Delimiter})-> %% fill offline from file id case ts_file_server:get_all_lines(FileId) of {ok, Data} -> ?LOGF("offline user from csv ~p",[Data],?DEB), Fun = fun(Line) -> [User,Pwd| _]= ts_utils:split(Line,Delimiter), ets:insert(Offline,{{binary_to_list(User),binary_to_list(Pwd)},1}) end, lists:foreach(Fun, Data), ok; Error -> ?LOGF("error no offline user from csv~p",[Error],?DEB), ok end; fill_offline(N, Tab, Opts) when is_integer(N) -> ets:insert(Tab,{N, 0}), fill_offline(N-1, Tab, Opts). %%%---------------------------------------------------------------------- %%% Func: ets_iterator_del/3 %%% Args: Ets, Key, Iterator %%% Purpose: delete entry Key from Ets, update iterator if needed %%% Returns: Key:: integer | {string(),string()}|undefined %%%---------------------------------------------------------------------- %% iterator equal key:it will no longer be valid ets_iterator_del(Ets, Key, Key) -> Last = ets:prev(Ets,Key), ets:delete(Ets,Key), case Last of '$end_of_table' -> case ets:first(Ets) of '$end_of_table' -> undefined; Key -> undefined; NewIter -> NewIter end; NewIter -> NewIter end; ets_iterator_del(Ets, Key, Iterator) -> ets:delete(Ets,Key), 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, should be different from 'Key', if possible %%%---------------------------------------------------------------------- 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' -> %% Key is the only entry of offline table {ok, Key}; 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}; [_|_] -> 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.5.1/src/tsung_controller/ts_mon.erl0000644000175000017500000005005312236716142022154 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%---------------------------------------------------------------------- %% @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_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,Tr,Name}) -> add_match(Data,{UserId,SessionId,RequestId,[],Tr,Name}); add_match(Data=[Head|_],{UserId,SessionId,RequestId,Bin,Tr,Name}) -> TimeStamp=?NOW, put(last_match,Head), ts_mon_cache:add_match(Data,{UserId,SessionId,RequestId,TimeStamp, Bin,Tr,Name}). 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],?INFO), 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",?INFO), {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", ?INFO), {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;duration;transaction;match;error;tag~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) -> ?LOGF("Tsung clients setup: ~p~n",[Machines],?DEB), GetHost = fun(A) -> list_to_atom(A#client.host) end, HostList = lists:map(GetHost, Machines), ?LOGF("Starting tsung clients on hosts: ~p~n",[HostList],?NOTICE), %% starts beam on all client hosts ts_config_server:newbeams(HostList). tsung-1.5.1/src/tsung_controller/ts_config_mqtt.erl0000644000175000017500000001145512236145741023701 0ustar nniclaussenniclausse%%% This code was developped by Zhihui Jiao(jzhihui521@gmail.com). %%% %%% Copyright (C) 2013 Zhihui Jiao %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_config_mqtt). -vc('$Id$ '). -author('jzhihui521@gmail.com'). -export([parse_config/2]). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("ts_mqtt.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 = mqtt}, Config = #config{curid = Id, session_tab = Tab, sessions = [CurS | _], dynvar = DynVar, subst = SubstFlag, match = MatchRegExp}) -> Type = ts_config:getAttr(atom, Element#xmlElement.attributes, type), CleanStart = ts_config:getAttr(atom, Element#xmlElement.attributes, clean_start, true), KeepAlive = ts_config:getAttr(float_or_integer, Element#xmlElement.attributes, keepalive, 10), WillTopic = ts_config:getAttr(string, Element#xmlElement.attributes, will_topic, ""), WillQos = ts_config:getAttr(float_or_integer, Element#xmlElement.attributes, will_qos, 0), WillMsg = ts_config:getAttr(string, Element#xmlElement.attributes, will_msg, ""), WillRetain = ts_config:getAttr(atom, Element#xmlElement.attributes, will_retain, false), Topic = ts_config:getAttr(string, Element#xmlElement.attributes, topic, ""), Qos = ts_config:getAttr(float_or_integer, Element#xmlElement.attributes, qos, 0), Retained = ts_config:getAttr(atom, Element#xmlElement.attributes, retained, false), RetainValue = case Retained of true -> 1; false -> 0 end, Timeout = ts_config:getAttr(float_or_integer, Element#xmlElement.attributes, timeout, 1), Payload = ts_config:getText(Element#xmlElement.content), Request = #mqtt_request{type = Type, clean_start = CleanStart, keepalive = KeepAlive, will_topic = WillTopic, will_qos = WillQos, will_msg = WillMsg, will_retain = WillRetain, topic = Topic, qos = Qos, retained = RetainValue, payload = Payload}, Ack = case {Type, Qos} of {publish, 0} -> no_ack; {disconnect, _} -> no_ack; _ -> parse 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), case Type of waitForMessages -> ets:insert(Tab, {{CurS#session.id, Id}, {thinktime, Timeout * 1000}}); _ -> ets:insert(Tab, {{CurS#session.id, Id}, Msg }) end, ?LOGF("request tab: ~p~n", [ets:match(Tab, '$1')], ?INFO), 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.5.1/src/tsung_controller/tsung_controller.erl0000644000175000017500000001445412317474675024301 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(tsung_controller). -vc('$Id$ '). -author('nicolas.niclausse@niclux.org'). -export([start/0, start/2, start_phase/3, stop/1, stop_all/1, status/1]). -behaviour(application). -include("ts_macros.hrl"). -include_lib("kernel/include/file.hrl"). %% start the application with it's dependencies start() -> ts_utils:ensure_all_started(tsung_controller, permanent). %%---------------------------------------------------------------------- %% Func: start/2 %% Returns: {ok, Pid} | %% {ok, Pid, State} | %% {error, Reason} %%---------------------------------------------------------------------- start(_Type, _StartArgs) -> error_logger:tty(false), case ts_utils:setsubdir(?config(log_dir)) of {error, {error, eacces} } -> Msg = "Error while creating log directory in ~s: permission denied~n" , io:format(standard_error,Msg,[?config(log_dir)]), halt(77); {error, Err} -> Msg = "Error while creating log directory : ~s~n" , io:format(standard_error,Msg,[Err]), halt(1); {ok, {LogDir, _Name}} -> 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]), halt(1) end; {error, Reason} -> Msg = "Error while opening log file: " , io:format(standard_error,Msg ++ " ~p ~n",[Reason]), halt(1) end 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 file of size ~p~n ",[Size]), 300000; % 5mn {ok, #file_info{size=Size}} when Size > 1000000 -> % > 1MB io:format(standard_error,"Can take up to 3mn to read config file of size ~p~n ",[Size]), 180000; % 3mn {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.5.1/src/tsung_controller/ts_os_mon_erlang.erl0000644000175000017500000003063412301350731024177 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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_macros.hrl"). -include("ts_os_mon.hrl"). -export([start/1, updatestats/3, 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 options % updatestats options }). start(Args) -> gen_server:start_link(?MODULE, Args, []). %%-------------------------------------------------------------------- %% Function: init/1 %% Description: Initiates the server %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%-------------------------------------------------------------------- init( {Host, Options, 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, [Options, Interval, MonServer]), {ok, #state{node=node(),mon=MonServer, host=Host, interval=Interval, pid=Pid, options=Options}}; _ -> erlang:start_timer(?INIT_WAIT, self(), start_beam), {ok, #state{host=Host, mon=MonServer, interval=Interval, options=Options}} 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.options, 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.options, 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/3 %% Purpose: update stats for erlang monitoring. Executed on the remote host %%-------------------------------------------------------------------- updatestats(Options, Interval,Mon_Server) -> Node = atom_to_list(node()), {Cpu, FreeMem, RecvPackets, SentPackets, Load} = node_data(), Stats = [{sample, {cpu, Node}, Cpu}, {sample, {freemem, Node}, FreeMem}, {sample, {load, Node}, Load}, {sample_counter, {recvpackets, Node}, RecvPackets}, {sample_counter, {sentpackets, Node}, SentPackets}], Fun = fun(Option, AccumStats) -> case Option of {mysqladmin, MysqlOptions} -> {Threads, Questions} = mysqladmin_data(MysqlOptions), lists:append(AccumStats, [{sample, {mysql_threads, Node}, Threads}, {sample_counter, {mysql_questions, Node}, Questions}]); _ -> AccumStats end end, ts_os_mon:send(Mon_Server, lists:append(Stats, lists:foldl(Fun, [], Options))), timer:sleep(Interval), updatestats(Options, 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: mysqladmin_data/1 %%-------------------------------------------------------------------- mysqladmin_data({Port, Username, Password}) -> PasswdArg = case Password of false -> ""; _ -> io_lib:format("-p\"~s\"", [Password]) end, Cmd = io_lib:format("mysqladmin -u\"~s\" ~s -P~B status", [Username, PasswdArg, Port]), Result = os:cmd(Cmd), % Uptime: 1146892 Threads: 2 Questions: 15242050 Slow queries: 0 Opens: 176 Flush tables: 1 Open tables: 101 Queries per second avg: 13.290 [_, _Uptime, _, Threads, _, Questions, _, _, _SlowQueries, _, _Opens, _, _, _FlushTables, _, _, _OpenTables, _, _, _, _, _QPS] = string:tokens(Result, " \n"), {list_to_integer(Threads), list_to_integer(Questions)}. %%-------------------------------------------------------------------- %% 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, Val}) -> Data=os:cmd("netstat -in"), get_os_data(packets, {unix, Val},string:tokens(Data, "\n")); get_os_data(packets, _OS) -> {0, 0 }. % FIXME: not implemented for other arch. %% %% packets , special case with File as a variable for easy testing get_os_data(packets, {unix, _}, Data) -> %% lists:zf is called lists:filtermap in erlang R16B1 and newer Eth=[io_lib:fread("~d~d~d~d~d~d~d~d~d", X) || {E,X}<-lists:zf(fun(Y)-> case string:chr(Y, $:) of 0 -> {true, ts_utils:split2(Y,32,strip)}; _ -> false end end , Data), string:str(E,"eth") /= 0], Fun = fun (A, {Rcv, Sent}) -> {ok,[_,_,RcvBytes,_,_,_,SentBytes,_,_],_}=A, {Rcv+RcvBytes,Sent+SentBytes} 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 on host ~p ~n", [Host], ?NOTICE), ?LOGF("~p Args: ~p~n", [Host, Args], ?DEB), slave:start(list_to_atom(Host), ?NODE, Args). tsung-1.5.1/src/tsung_controller/ts_timer.erl0000644000175000017500000001573612147621622022513 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_timer). -vc('$Id$ '). -author('jflecomte@IDEALX.com'). -modifiedby('nicolas@niclux.org'). -include("ts_macros.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) -> ?LOGF("Configure fsm timer with ~p",[NClients],?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.5.1/src/tsung_controller/ts_os_mon.erl0000644000175000017500000000527612147621622022663 0ustar nniclaussenniclausse%%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%% 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_macros.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.5.1/src/tsung_controller/ts_config_amqp.erl0000644000175000017500000002152112236145741023645 0ustar nniclaussenniclausse%%% This code was developped by Zhihui Jiao(jzhihui521@gmail.com). %%% %%% Copyright (C) 2013 Zhihui Jiao %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -module(ts_config_amqp). -vc('$Id$ '). -author('jzhihui521@gmail.com'). -export([parse_config/2]). -include("ts_profile.hrl"). -include("ts_config.hrl"). -include("ts_amqp.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 = amqp}, Config = #config{curid = Id, session_tab = Tab, sessions = [CurS | _], dynvar = DynVar, subst = SubstFlag, match = MatchRegExp}) -> initialize_options(Tab), TypeStr = ts_config:getAttr(string, Element#xmlElement.attributes, type), Type = list_to_atom(TypeStr), ReqList = case Type of %% connection.open request, we add all the requests to be done 'connection.open' -> ['connect', 'connection.start_ok', 'connection.tune_ok', 'connection.open']; 'waitForConfirms' -> Timeout = ts_config:getAttr(float_or_integer, Element#xmlElement.attributes, timeout, 1), ets:insert(Tab, {{CurS#session.id, Id}, {thinktime, Timeout * 1000}}), []; 'waitForMessages' -> Timeout = ts_config:getAttr(float_or_integer, Element#xmlElement.attributes, timeout, 60), ets:insert(Tab, {{CurS#session.id, Id}, {thinktime, Timeout * 1000}}), []; _ -> [Type] end, Result = lists:foldl(fun(RequestType, CurrId) -> {Ack, Request} = parse_request(Element, RequestType, Tab), Msg = #ts_request{ack = Ack, endpage = true, dynvar_specs = DynVar, subst = SubstFlag, match = MatchRegExp, param = Request}, ets:insert(Tab, {{CurS#session.id, CurrId}, Msg}), CurrId + 1 end, Id, ReqList), ResultId = case ReqList of [] -> Id; _ -> Result - 1 end, ts_config:mark_prev_req(Id - 1, Tab, CurS), lists:foldl(fun(A, B) -> ts_config:parse(A, B) end, Config#config{dynvar = [], curid = ResultId}, 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,?AMQP_USER), ets:insert(Tab,{{amqp_username,value}, Val}), Conf; "password" -> Val = ts_config:getAttr(string,Element#xmlElement.attributes, value,?AMQP_PASSWORD), ets:insert(Tab,{{amqp_password,value}, Val}), Conf; "heartbeat" -> Val = ts_config:getAttr(float_or_integer, Element#xmlElement.attributes, value, 600), ets:insert(Tab,{{amqp_heartbeat,value}, Val}), 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. parse_request(Element, Type = 'connection.open', _Tab) -> Vhost = ts_config:getAttr(string, Element#xmlElement.attributes, vhost, "/"), Request = #amqp_request{type = Type, vhost = Vhost}, {parse, Request}; parse_request(_Element, Type = 'connection.start_ok', Tab) -> UserName = ts_config:get_default(Tab, amqp_username), Password = ts_config:get_default(Tab, amqp_password), Request = #amqp_request{type = Type, username = UserName, password = Password}, {parse, Request}; parse_request(_Element, Type = 'connection.tune_ok', Tab) -> HeartBeat = ts_config:get_default(Tab, amqp_heartbeat), Request = #amqp_request{type = Type, heartbeat = HeartBeat}, {no_ack, Request}; parse_request(Element, Type = 'channel.open', _Tab) -> Channel = ts_config:getAttr(string, Element#xmlElement.attributes, channel, "0"), Request = #amqp_request{type = Type, channel = Channel}, {parse, Request}; parse_request(Element, Type = 'basic.publish', _Tab) -> Channel = ts_config:getAttr(string, Element#xmlElement.attributes, channel, "1"), Exchange = ts_config:getAttr(string, Element#xmlElement.attributes, exchange, ""), RoutingKey = ts_config:getAttr(string, Element#xmlElement.attributes, routing_key, "/"), Size = ts_config:getAttr(float_or_integer, Element#xmlElement.attributes, payload_size, 100), PersistentStr = ts_config:getAttr(string, Element#xmlElement.attributes, persistent, "false"), Payload = ts_config:getAttr(string, Element#xmlElement.attributes, payload, ""), Persistent = list_to_atom(PersistentStr), Request = #amqp_request{type = Type, channel = Channel, exchange = Exchange, routing_key = RoutingKey, payload_size = Size, payload = Payload, persistent = Persistent}, {no_ack, Request}; parse_request(Element, Type = 'basic.consume', _Tab) -> Channel = ts_config:getAttr(string, Element#xmlElement.attributes, channel, "1"), Queue = ts_config:getAttr(string, Element#xmlElement.attributes, queue, ""), AckStr = ts_config:getAttr(string, Element#xmlElement.attributes, ack, "false"), Ack = list_to_atom(AckStr), Request = #amqp_request{type = Type, channel = Channel, queue = Queue, ack = Ack}, {parse, Request}; parse_request(Element, Type = 'basic.qos', _Tab) -> Channel = ts_config:getAttr(string, Element#xmlElement.attributes, channel, "1"), PrefetchSize = ts_config:getAttr(float_or_integer, Element#xmlElement.attributes, prefetch_size, 0), PrefetchCount = ts_config:getAttr(float_or_integer, Element#xmlElement.attributes, prefetch_count, 0), Request = #amqp_request{type = Type, channel = Channel, prefetch_size = PrefetchSize, prefetch_count = PrefetchCount}, {parse, Request}; parse_request(Element, Type, _Tab) -> Channel = ts_config:getAttr(string, Element#xmlElement.attributes, channel, "1"), Request = #amqp_request{type = Type, channel = Channel}, {parse, Request}. initialize_options(Tab) -> case ts_config:get_default(Tab, amqp_initialized) of {undef_var, _} -> ets:insert_new(Tab,{{amqp_username,value}, ?AMQP_USER}), ets:insert_new(Tab,{{amqp_password,value}, ?AMQP_PASSWORD}), ets:insert_new(Tab,{{amqp_heartbeat,value}, 600}); _Else -> ok end. tsung-1.5.1/src/tsung_controller/ts_config_ldap.erl0000644000175000017500000002061512104023217023615 0ustar nniclaussenniclausse%%% 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.5.1/src/tsung_controller/ts_config_jabber.erl0000644000175000017500000002726312320752470024142 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. -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 |_]}) -> initialize_options(Tab), 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"), Resource= ts_config:getAttr(string,Element#xmlElement.attributes, resource, "tsung"), Type= list_to_atom(TypeStr), Version = ts_config:getAttr(string,Element#xmlElement.attributes, version, "1.0"), Cacert = ts_config:getAttr(string,Element#xmlElement.attributes, cacertfile, undefined), KeyFile = ts_config:getAttr(string,Element#xmlElement.attributes, keyfile, undefined), KeyPass = ts_config:getAttr(string,Element#xmlElement.attributes, keypass, undefined), CertFile = ts_config:getAttr(string,Element#xmlElement.attributes, certfile, undefined), 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 SubId = ts_config:getAttr(string, Element#xmlElement.attributes, 'subid', undefined), Domain =ts_config:get_default(Tab, jabber_domain_name, jabber_domain), ?LOGF("XMPP domain is ~p~n",[Domain],?DEB), MUC_service = ts_config:get_default(Tab, muc_service), PubSub_service =ts_config:get_default(Tab, pubsub_service), UserPrefix=ts_config:get_default(Tab, jabber_username), UserIdMax = ts_config:get_default(Tab, jabber_userid_max), %% 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}; _ -> GPasswd =ts_config:get_default(Tab, jabber_passwd), {0,UserPrefix,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, resource = Resource, room = Room, nick = Nick, group = Group, muc_service = MUC_service, pubsub_service = PubSub_service, node = Node, node_type = NodeType, subid = SubId, version = Version, cacertfile = Cacert, keyfile = KeyFile, keypass = KeyPass, certfile = CertFile, prefix = UserPrefix } }, 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=[], user_server_maxuid = UserIdMax}, 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,?xmpp_username), ets:insert(Tab,{{jabber_username,value}, Val}), Conf; "passwd" -> Val = ts_config:getAttr(string,Element#xmlElement.attributes, value,?xmpp_passwd), ets:insert(Tab,{{jabber_passwd,value}, Val}), Conf; "domain" -> Val = ts_config:getAttr(string,Element#xmlElement.attributes, value, ?xmpp_domain), 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, ?xmpp_global_number), ets:insert(Tab,{{jabber_global_number, value}, N}), Conf; "userid_max" -> N = ts_config:getAttr(integer,Element#xmlElement.attributes, value, ?xmpp_userid_max), 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; "random_from_fileid" -> FileId = ts_config:getAttr(atom,Element#xmlElement.attributes, value, none), ?LOGF("set random fileid to ~p~n",[FileId],?WARN), ts_user_server:set_random_fileid(FileId), Conf; "offline_from_fileid" -> FileId = ts_config:getAttr(atom,Element#xmlElement.attributes, value, none), ?LOGF("set offline fileid to ~p~n",[FileId],?WARN), ts_user_server:set_offline_fileid(FileId), Conf; "fileid_delimiter" -> D = ts_config:getAttr(string,Element#xmlElement.attributes, value, ";"), ts_user_server:set_fileid_delimiter(D), 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. initialize_options(Tab) -> case ts_config:get_default(Tab, jabber_initialized) of {undef_var,_} -> ets:insert_new(Tab,{{jabber_userid_max,value}, ?xmpp_userid_max}), ets:insert_new(Tab,{{jabber_global_number,value}, ?xmpp_global_number}), ets:insert_new(Tab,{{jabber_username,value}, ?xmpp_username}), ets:insert_new(Tab,{{jabber_passwd,value}, ?xmpp_passwd}), ets:insert_new(Tab,{{jabber_domain_name,value}, {domain,?xmpp_domain}}), ets:insert_new(Tab,{{jabber_initialized,value}, true}), ts_user_server:reset(ts_config:get_default(Tab, jabber_userid_max)), ts_timer:config(ts_config:get_default(Tab, jabber_global_number)); _Else -> ok end. tsung-1.5.1/src/tsung_controller/ts_interaction_server.erl0000644000175000017500000002054612320752470025272 0ustar nniclaussenniclausse%%%------------------------------------------------------------------- %%% @author Nicolas Niclausse %%% @copyright (C) 2012, Nicolas Niclausse %%% @doc %%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%% %%% @end %%% Created: 20 août 2009 by Nicolas Niclausse %%%------------------------------------------------------------------- -module(ts_interaction_server). -behaviour(gen_server). -include("ts_macros.hrl"). %% API -export([start/0, send/1, rcv/1, notify/1, delete/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, {to, notify}). %%%=================================================================== %%% API %%%=================================================================== %%-------------------------------------------------------------------- %% @doc %% Starts the server %% %% @spec start() -> {ok, Pid} | ignore | {error, Error} %% @end %%-------------------------------------------------------------------- start() -> gen_server:start_link({global, ?SERVER}, ?MODULE, [], []). %%-------------------------------------------------------------------- %% @spec send({StatsName::atom(), Date::term()}) -> ok %% @doc %% %% %% @end %%-------------------------------------------------------------------- send({StatsName, Date}) when is_atom(StatsName) -> gen_server:cast({global, ?SERVER}, {send, StatsName, Date}). %%-------------------------------------------------------------------- %% @spec rcv({StatsName, Date}) -> ok %% @doc %% %% @end %%-------------------------------------------------------------------- rcv({StatsName, Date}) when is_atom(StatsName) -> gen_server:cast({global, ?SERVER}, {'receive', StatsName, Date}). %%-------------------------------------------------------------------- %% @spec delete({StatsName}) -> ok %% @doc %% %% %% @end %%-------------------------------------------------------------------- delete({StatsName}) when is_atom(StatsName) -> gen_server:cast({global, ?SERVER}, {delete, StatsName}). %%-------------------------------------------------------------------- %% @spec notify({Action::atom(), StatsName::atom(), Pid::pid()}) -> ok %% @doc %% %% %% @end %%-------------------------------------------------------------------- notify({Action, StatsName, Pid}) when is_atom(Action), is_atom(StatsName), is_pid(Pid) -> gen_server:cast({global, ?SERVER}, {notify,Action,StatsName,Pid}). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== %%-------------------------------------------------------------------- %% @private %% @doc %% Initializes the server %% %% @spec init(Args) -> {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %% @end %%-------------------------------------------------------------------- init([]) -> {ok, #state{to=ets:new(to, []), notify=ets:new(notify, [bag])}}. %%-------------------------------------------------------------------- %% @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(_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({send, StatsName, Date}, State=#state{to=To, notify=Notify}) -> case ets:lookup(To,StatsName) of [] -> ?LOGF("interaction: setting send timestamp for ~p~n",[StatsName],?DEB); _Val -> ?LOGF("interaction: resetting send timestamp for ~p~n",[StatsName],?WARN) end, ets:insert(To,{StatsName, Date}), handle_notification(Notify,{send, StatsName}), {noreply, State}; handle_cast({'receive',StatsName, EndDate}, State=#state{to=To, notify=Notify}) -> ?LOGF("~p ~n",[StatsName],?DEB), case ets:lookup(To,StatsName) of [] -> handle_notification(Notify,{'receive',StatsName}), {noreply, State}; [{_Key, StartDate}] -> ?LOGF("to/from ended, logging ~p ~n",[StatsName],?DEB), handle_notification(Notify,{'receive',StatsName}), ts_mon:add({sample,StatsName, ts_utils:elapsed(StartDate,EndDate)}), {noreply, State} end; handle_cast({delete, StatsName}, State=#state{to=To}) -> ets:delete(To,StatsName), {noreply, State}; handle_cast({notify, Action, StatsName, Pid}, State=#state{notify=Notify}) -> %% TODO: check if event already exists ? ets:insert(Notify,{{StatsName, Action}, {Pid}}), {noreply, State}; 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 %%%=================================================================== handle_notification(Notify, {Action, StatsName}) -> case ets:lookup(Notify, {StatsName, Action}) of [] -> ok; List -> ?LOGF("gotlist ~p~n",List,?DEB), Fun = fun({{Stats, Action2},{Pid}}) -> ?LOGF("sending msg to pid ~p~n",[Pid],?DEB), Pid ! {notify, Action2, Stats} end, lists:foreach(Fun,List), ets:delete(Notify,StatsName) end. tsung-1.5.1/src/tsung_controller/ts_config_pgsql.erl0000644000175000017500000001700012320752470024027 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%% 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.5.1/src/tsung_controller/ts_job_notify.erl0000644000175000017500000003075312320752470023530 0ustar nniclaussenniclausse%%% %%% 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; the MPL (Mozilla Public License), which EPL (Erlang %%% Public License) is based on, is included in this exception. %%% %%% @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_macros.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",?INFO), 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",?INFO), 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",?INFO), {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.5.1/src/tsung_percentile.pl.in0000644000175000017500000000750512147621622021065 0ustar nniclaussenniclausse#!/usr/bin/perl -w # -*- Mode: CPerl -*- # # Copyright (C) 2013 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # # Read the log file generated with backend="fullstats" attribute # print percentile 95,98 by default on stdout # # Author: Rodolphe Quiédeville # use strict; use Getopt::Long; use vars qw ($help @files $version $stats @percentiles); use JSON; my $tagvsn = '1.5.0a'; GetOptions( "help",\$help, "stats=s",\$stats, "percentiles=i@",\@percentiles, "version",\$version, ); &usage if $help or $Getopt::Long::error; &version if $version; @percentiles = (95,98) unless @percentiles; for my $cper (@percentiles) { die "Wrong percentile value $cper" if $cper > 100 or $cper < 1; } $stats = "tsung-fullstats.log" unless $stats; die "The stats file ($stats) does not exist, abort !" unless -e $stats; print "Read : $stats\n"; foreach my $val ('connect','request','page') { &parse_stats_file("sample,$val", $stats); } sub parse_stats_file { my ($marker, $file) = @_; my $data; my $flag = 0; my @allvalues; open (FILE,"<$file") or die "Can't open $file $!"; while () { if (/^{$marker/) { $flag = 1; $data = ""; } if ($flag) { s/^\s+|\s+$//g; s/$marker,/"datas": /; $data .= $_; if (/}$/) { $flag = 0; my $json = decode_json $data; foreach my $val (@{$json->{"datas"}}) { push @allvalues, $val; } #print "-".@allvalues."\n"; } } } close FILE; @allvalues = sort { $a <=> $b } @allvalues; print "Read : ".@allvalues ." values for $marker\n"; for my $prcntl (sort { $a <=> $b } @percentiles) { print "Percentile $prcntl : ".percentile($prcntl, \@allvalues)."\n"; } } sub percentile { my ($p,$aref) = @_; my $percentile = int($p * $#{$aref}/100); return (@$aref)[$percentile]; } sub usage { print "this script is part of tsung version $tagvsn, Copyright (C) 2013 Rodolphe Quiédeville \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_percentile.pl --version` for details.\n\n"; print "Usage: $0 []\n","Available options:\n\t", "[--help] (this help text)\n\t", "[--percentiles] (percentile to compute, multiple values accepted)\n\t", "[--stats ] (stats file to analyse, default=tsung-fullstats.log)\n"; exit; } sub version { print " 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.\n"; exit; } tsung-1.5.1/COPYING0000644000175000017500000004325412301350731015006 0ustar nniclaussenniclausse GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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) year 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 Lesser General Public License instead of this License. tsung-1.5.1/aclocal.m40000644000175000017500000000114412104023217015600 0ustar nniclaussenniclausse# 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.5.1/tsung-recorder.sh.in0000644000175000017500000001140212317474675017672 0ustar nniclaussenniclausse#!/usr/bin/env bash UNAME=`uname` case $UNAME in "Linux") HOST=`hostname -s`;; "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_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" DEBUG_LEVEL=5 RECORDER_PLUGIN="http" ERL_RSH=" -rsh ssh " ERL_OPTS=" -smp auto +P 250000 +A 16 +K true @ERL_OPTS@ " COOKIE='tsung' 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 } start() { echo "Starting Tsung recorder on port $LISTEN_PORT" $ERL $ERL_OPTS $ERL_RSH -noshell $NAMETYPE $RECORDER -setcookie $COOKIE \ -s tsung_recorder \ -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" maindir logdir checkrunning start ;; record_tag) record_tag $* ;; stop) PIDFILE="/tmp/tsung_recorder.pid" stop ;; status) status ;; restart) stop start ;; *) usage $0 ;; esac tsung-1.5.1/TODO0000644000175000017500000000006112104023217014425 0ustar nniclaussenniclausseSee https://support.process-one.net/browse/TSUN tsung-1.5.1/CHANGES0000644000175000017500000006541012321170241014742 0ustar nniclaussenniclausse1.5.0 -> 1.5.1 Major enhancements and bugfixes (7 Apr 2014) Bugfix: * [TSUN-250] - BOSH Crash * [TSUN-252] - Too many requests when using max_restart * [TSUN-253] - Code blocks in html version of user manual is unreadable * [TSUN-256] - Unexpected ack="global" behaviour on BOSH * [TSUN-265] - Wrong header line in tsung.dump * [TSUN-270] - Substitution not working in path attribute * [TSUN-271] - SSL does not work with erlang >= R16 * [TSUN-272] - Support literal IPv6 addresses when defining servers * [TSUN-278] - Tsung 1.5.0 is notable to do https out of the box when it is compiled from tarballs * [TSUN-279] - Tsung 1.5.0 is not able to do substitution of hostname or port in a URL. It only can do substitution of path * [TSUN-281] - Fix debian build for Tsung 1.5, replaces DocBook with Sphinx * [TSUN-285] - In some rare conditions in a distributed setup, Tsung fails to start the load test. * [TSUN-287] - request.max statistic lower than request.mean * [PR #71] - oAuth bug fix, PUT method * [PR #41] - Fix websocket path subst * [PR #44] - Add bidi attribute to change_type * [PR #49] - Fix websocket close issue: we should wait a close response Improvements: * [TSUN-255] - Fix unused vars in tq_amqp * [TSUN-259] - Tsung in Fedora * [TSUN-268] - Use xmerl_sax_parser:file/2 in case of xml parsing error * [TSUN-276] - Add text frame support for websocket * [TSUN-284] - Do not use boot files to start tsung and tsung_controller applications * [PR #51] - Updated dygraph charting library to the latest release * [PR #65] - AMQP: add multiple channel, add waitForConfirms and waitForMessages * [PR #70] - Add bosh_path config option * [PR #74] - Add text frame support for Websocket, and update doc New features: * [TSUN-260] - Add option to change popularities of sessions for each phase * [TSUN-264] - New comparison operators * [TSUN-269] - Logging of request tags to dumpfile * [TSUN-275] - Add MQTT support * [TSUN-280] - Tsung to support pkcs#12 certificates or at least cacerts, clientcerts and keys * [PR #42] - Adding all_except_body' option to ts_http request subst. Adding mysqladmin monitoring options to erlang monitors. Adding mean rate calculation to tsung_stats reports. Adding --title option to set header of report * [PR #75] - Support SSL/TLS client certificate file attributes for jabber starttls 1.4.2 -> 1.5.0 Major enhancements and bugfixes (24 May 2013) Bugfix: * [TSUN-208] - in the jabber plugin, substitutions for raw request doesn't work in some cases. * [TSUN-209] - If tag doesn't work with Tsung 1.4.2 * [TSUN-212] - Incorrect ERTS version being set on build. * [TSUN-215] - normal ack timeout shouldn't used for global ack * [TSUN-217] - If statement breaks on empty string * [TSUN-218] - Race condition in tsung-recorder * [TSUN-219] - Site fails to load via proxy recorder * [TSUN-220] - Large configuration files trigger error * [TSUN-229] - compatibility with erlang R15B * [TSUN-230] - can't connect with TLS + ejabberd * [TSUN-232] - Tsung for bosh protocol doesn't send a empty request to keep the user session alive. * [TSUN-234] - Error encoding json string with escape_uri * [TSUN-238] - Content-Length parsing broken * [TSUN-241] - Invalid link Other in the graph.html * [TSUN-245] - Message when dtd is not found not trivial Improvements: * [TSUN-174] - add an option to set resource in XMPP * [TSUN-222] - Support unsubscribe operation for Jabber pubsub module * [TSUN-228] - allow substitutions on cookies * [TSUN-236] - Add probability support for servers * [TSUN-242] - add timestamp and request duration in dump=protocol for http * [TSUN-246] - http PATCH support New Features: * [TSUN-214] - Ability to pass attributes for node creation for XMPP pubsub protocol. * [TSUN-227] - add new dynamic variable to get server hostname and port * [TSUN-231] - add option to use weights instead of probabilities for sessions * [TSUN-239] - add BOSH support * [TSUN-240] - add websocket support * [TSUN-244] - Percentile computation * [TSUN-248] - add AMQP support 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 * [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.5.1/install-sh0000755000175000017500000001272012104023217015746 0ustar nniclaussenniclausse#!/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.5.1/tsung.spec0000644000175000017500000000464312321173154015772 0ustar nniclaussenniclausse%define name tsung %define version 1.5.1 %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 doxygen-latex python-sphinx texlive-titlesec texlive-framed texlive-threeparttable texlive-wrapfig 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.md 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

[% title %]

version [% version %]

[% stats_subtitle %]

[% graph_subtitle %]

[% IF conf %]

XML Config file

[% END %]