/*
This file is part of Bolixo.
Bolixo 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 3 of the License, or
(at your option) any later version.
Bolixo 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 Bolixo. If not, see .
*/
/*
Web performance test for bolixo.
It performs a login and then request the main screen in a loop
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "instrument.h"
using namespace std;
#define webapi_test_NOTNEED
#define webapi_addfile_NOTNEED
#define webapi_addfile_bob_NOTNEED
#define webapi_appendfile_NOTNEED
#define webapi_delfile_NOTNEED
#define webapi_undelete_NOTNEED
#define webapi_modifyfile_NOTNEED
#define webapi_modifyfile_bob_NOTNEED
#define webapi_rename_NOTNEED
#define webapi_copy_NOTNEED
#define webapi_readfile_NOTNEED
#define webapi_readfile_bob_NOTNEED
#define webapi_readmore_NOTNEED
#define webapi_mkdir_NOTNEED
#define webapi_rmdir_NOTNEED
#define webapi_listdir_NOTNEED
#define webapi_stat_NOTNEED
#define webapi_set_access_NOTNEED
#define webapi_markview_NOTNEED
#define webapi_list_inboxes_NOTNEED
#define webapi_list_msgs_NOTNEED
#define webapi_sendmsg_NOTNEED
#define webapi_sendmsg_project_NOTNEED
#define webapi_replymsg_NOTNEED
#define webapi_replymsg_project_NOTNEED
#define webapi_sendattach_NOTNEED
#define webapi_sendtalk_NOTNEED
#define webapi_sendtalk_file_NOTNEED
#define webapi_list_talk_NOTNEED
#define webapi_public_listdir_NOTNEED
#define webapi_public_readfile_NOTNEED
#define webapi_public_list_talk_NOTNEED
#define webapi_systempubkey_NOTNEED
#define webapi_verifysign_NOTNEED
#define webapi_getpubkey_NOTNEED
#define webapi_registernode_NOTNEED
#define webapi_remotelogin_NOTNEED
#define webapi_remotepass_NOTNEED
#define webapi_remote_interest_set_NOTNEED
#define webapi_remote_interest_unset_NOTNEED
#define webapi_nodelogin_NOTNEED
#define webapi_nodepass_NOTNEED
#define webapi_config_read_NOTNEED
#define webapi_config_write_NOTNEED
#define webapi_contact_request_NOTNEED
#define webapi_contact_manage_NOTNEED
#define webapi_contact_list_NOTNEED
#define webapi_list_contacts_NOTNEED
#define webapi_list_lists_NOTNEED
#define webapi_list_groups_NOTNEED
#define webapi_create_group_NOTNEED
#define webapi_delete_group_NOTNEED
#define webapi_delete_list_NOTNEED
#define webapi_set_member_NOTNEED
#define webapi_contact_remove_NOTNEED
#define webapi_list_members_NOTNEED
#define webapi_create_group_list_NOTNEED
#define webapi_set_group_NOTNEED
#define webapi_playstep_NOTNEED
#define webapi_playstep_more_NOTNEED
#include "proto/webapi.protoch"
static DEBUG_KEY D_HEADER("header","Print request header");
static DEBUG_KEY D_PROTO("proto","HTTP protocol");
static void sendheader (CONNECT_HTTP_INFO &con, bool reuseconnection, const string &session, const char *file, const char *hostname)
{
string buf;
if (strncmp(hostname,"http://",7)==0){
hostname += 7;
}else if (strncmp(hostname,"https://",8)==0){
hostname += 8;
}
if (reuseconnection){
buf = string_f ("GET %s HTTP/1.1\r\nhost: %s\r\nUser-Agent: bo-webtest\r\n",file,hostname);
}else{
buf = string_f ("GET %s HTTP/1.0\r\nhost: %s\r\nUser-Agent: bo-webtest\r\n",file,hostname);
}
if (session.size() > 0){
buf += string_f ("cookie: session=%s;\r\n",session.c_str());
}
buf += string_f ("Content-Type: text/html; charset=UTF-8\r\n\r\n");
con.send (buf.c_str());
debug_printf (D_HEADER,"%s-----\n",buf.c_str());
}
struct RESULT{
unsigned long nb; // Number of requests done
unsigned long nbok; // Number of successful request
RESULT(unsigned long _nb, unsigned long _nbok){
nb = _nb;
nbok = _nbok;
}
RESULT(){
nb = 0;
nbok = 0;
}
};
static void printrate (long long start, const vector &tbnb)
{
unsigned long total =0;
unsigned long total_ok = 0;
for (auto n:tbnb){
total += n.nb;
total_ok += n.nbok;
}
long long duration = fdpass_getnow()-start;
double rate = total/(duration/1000000.0);
printf ("connections=%lu rate=%lf/s",total,rate);
if (total_ok != total){
printf (" *** total_ok = %lu",total_ok);
}
printf ("\n");
}
int main (int argc, char *argv[])
{
glocal int ret = -1;
glocal const char *bindaddr = "0.0.0.0";
glocal const char *host = "http://test1.bolixo.org";
glocal const char *file = "/index.hc";
glocal int nbproc = 1;
glocal int nbrep=1;
glocal bool reuseconnection = false;
glocal bool verbose = false;
glocal bool stats = false;
glocal bool endless = false;
glocal const char *email = NULL;
glocal const char *passwd = NULL;
glocal bool nonstrict = false;
glocal.ret = (argc,argv,"bolixo");
setproginfo ("bo-webtest",VERSION,"Web performance test");
setgrouparg ("Connection");
setarg ('h',"host","Web server url )(https://...)",glocal.host,false);
setarg ('E',"email","Email for login",glocal.email,false);
setarg ('P',"password","Password for login",glocal.passwd,false);
setarg (' ',"nonstrict","Relaxed rules for SSL connections (certificate and hostname validation)",glocal.nonstrict,false);
setgrouparg ("Tuning");
setarg ('n',"nbrep","Number of iteration",glocal.nbrep,false);
setarg ('N',"nbproc","Process number",glocal.nbproc,false);
setarg ('r',"reusecon","Reuse HTTP connection",glocal.reuseconnection, false);
setarg ('e',"endless","Runs forever and print stats",glocal.endless,false);
setgrouparg ("Misc.");
setarg ('f',"file","File to request from web server",glocal.file,false);
setarg ('v',"verbose","Print the result",glocal.verbose,false);
setarg ('s',"stats","Print some stats at the end",glocal.stats,false);
glocal long long start = fdpass_getnow();
glocal vector tbnb;
glocal unsigned chunk=0;
glocal CONNECT_HTTP_INFO con;
if (glocal.nonstrict) glocal.con.setnonstrictmode();
signal(SIGPIPE,SIG_IGN);
signal(SIGCHLD,SIG_IGN);
if (glocal.email == NULL) glocal.email = getenv("WEBTEST_EMAIL");
if (glocal.passwd == NULL) glocal.passwd = getenv("WEBTEST_PASSWORD");
if (glocal.email == NULL || glocal.passwd == NULL){
tlmp_error ("missing email and/or password, can't continue\n");
exit (-1);
}
// Used to receive the stats of the child processes
();
if (getnbclients() <= 1){
endserver = true;
printrate (glocal.start,glocal.tbnb);
}
//printf ("rec: no=%d %s\n",no,line);
int nb = atoi(line);
const char *pt = str_skip(str_skipdig(line));
int nbok = atoi(pt);
if (nbok != nb) printf ("no=%d %d <> %d\n",no,nb,nbok);
while (glocal.tbnb.size() <= (unsigned)no) glocal.tbnb.push_back(RESULT());
glocal.tbnb[no] = RESULT(nb,nbok);
if (glocal.endless){
unsigned long total = 0;
for (auto n:glocal.tbnb) total += n.nb;
unsigned chunk = total/1000;
if (chunk > glocal.chunk){
glocal.chunk=chunk;
printrate(glocal.start,glocal.tbnb);
}
}
fprintf (stderr,"Connecting to %s, getting file %s, doing %d process%s X %d request%s reusecon=%d email=%s\n"
,glocal.host,glocal.file
,glocal.nbproc,glocal.nbproc > 1 ? "es" : ""
,glocal.nbrep,glocal.nbrep > 1 ? "s" : ""
,glocal.reuseconnection,glocal.email);
if (glocal.con.init (glocal.host)==-1){
tlmp_error ("Invalid host url: %s\n",glocal.host);
exit(-1);
}
bool anonymous = glocal.email[0] == '\0' || is_eq(glocal.email,"anon");
for (int p=0; p(glocal.con,glocal.email,glocal.passwd);
if (!success){
tlmp_error ("login failed\n");
exit (-1);
}else{
glocal.session = sessionid;
}
}
for (glocal.i=0; glocal.endless || glocal.i();
int ret = glocal.con.receive (buf,size);
if (ret <= 0){
debug_printf (D_PROTO,"fill i=%d received=%u ret=%d size=%u %d(%s)\n",glocal.i,glocal.received,ret,size,errno,strerror(errno));
ret = 0;
}else{
glocal.received += ret;
}
return ret;
int ret = 0;
const char *line = (const char *)buf;
const char *endbuf = line+len;
const char *pt = line;
while (pt < endbuf && *pt != '\n') pt++;
//debug_printf (D_PROTO,"process len=%u %lu %d\n",len,pt-line,pt")==0){
glocal.nbok++;
if (glocal.i > glocal.starti+50){
// We are in reuseconnection mode, probably endless mode
// Every 50 connection we quit. The httpd seems to disconnect
// anyway after some amount of request
end = true;
}else if (glocal.reuseconnection){
glocal.i++;
if (glocal.i%30 == 1){
string tmp = string_f("%d %d\n",glocal.i,glocal.nbok);
write (glocal.fdout,tmp.c_str(),tmp.size());
}
if (glocal.endless || glocal.i < glocal.nbrep){
sendheader (glocal.con,glocal.reuseconnection,glocal.session,glocal.file,glocal.host);
}else{
glocal.i--; // So the count is good, as the for loop will increment it
end = true;
}
}else{
end = true;
}
}
}
return ret;
//printf ("apres call %d %d\n",glocal.i,glocal.nbok);
if (glocal.i%30 == 1){
string tmp = string_f("%d %d\n",glocal.i+1,glocal.nbok);
write (glocal.fdout,tmp.c_str(),tmp.size());
}
}
{
string tmp = string_f("%d %d\n",glocal.i,glocal.nbok);
write (glocal.fdout,tmp.c_str(),tmp.size());
}
if (glocal.stats) printf ("nblines=%d\n",glocal.nblines);
if (glocal.nbok != glocal.nbrep) tlmp_error ("nbrep=%d nbok=%d\n",glocal.nbrep,glocal.nbok);
if (glocal.session.size() > 0){
if (!glocal.reuseconnection) glocal.con.reconnect();
(glocal.con,glocal.session);
}
fflush (stdout);
_exit (0);
}else if (pid == (pid_t)-1){
tlmp_error ("Can't fork (%s)\n",strerror(errno));
exit (-1);
}else{
close (tbfd[1]);
o.inject(tbfd[0]);
}
}
}
o.loop();
return 0;
return glocal.ret;
}