Improve help message
[ftsbench.git] / ftsbench.c
1 /*
2  * Copyright (c) 2006 Teodor Sigaev <teodor@sigaev.ru>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *        notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *        notice, this list of conditions and the following disclaimer in the
12  *        documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the author nor the names of any co-contributors
14  *        may be used to endorse or promote products derived from this software
15  *        without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <sys/time.h>
36
37 #include "ftsbench.h"
38
39 typedef enum RDBMS {
40         PostgreSQL = 0,
41         MySQL = 1,
42         NULLSQL
43 } RDBMS;
44
45 typedef struct RDBMSDesc {
46         RDBMS   rdbms;
47         char    *shortname;
48         char    *longname;
49         ftsDB*  (*init)(char *);
50 } RDBMSDesc;
51
52 static RDBMSDesc DBDesc[] = {
53         { PostgreSQL, "pgsql", "PostgreSQL", PGInit }, 
54         { MySQL,          "mysql", "MySQL",      MYInit },
55         { NULLSQL,        NULL,    NULL,         NULL   }
56 };
57
58 static void
59 usage() {
60         char buf[1024];
61         int i, first=0;
62
63         *buf = '\0';
64         for(i=0; DBDesc[i].rdbms != NULLSQL; i++) {
65                 if ( DBDesc[i].init == NULL )
66                         continue;
67                 if ( first != 0 )
68                         strcat(buf, ", ");
69                 strcat(buf, DBDesc[i].shortname);
70                 if ( first == 0 ) 
71                         strcat(buf, "(default)");
72                 first++;
73         }
74
75         fputs(
76                 "ftsbench - full text search benchmark for RDBMS\n"
77                 "Initialization of DB:\n"
78                 "ftsbench -i [-b RDBMS] [-n NUMROW] [-l LEXFILE] [-g GAMMAFILE] [-f FLAGS] [-q] -d DBNAME\n"
79                 "  -b RDBMS\t- type of DB: ",
80                 stdout
81         );
82         fputs( buf, stdout );
83         fputs(
84                 "\n"
85                 "  -n NUMROW - number of row in table\n"
86                 "  -l LEXFILE - file with words and its frequents (default gendata/lex)\n"
87                 "  -g GAMMAFILE - file with doc's length distribution (default gendata/gamma-lens)\n"
88                 "  -l FLGAS - options for db's schema (see below)\n"
89                 "  -q - do not print progress message\n",
90                 stdout
91         );
92         fputs(
93                 "Run tests:\n"
94                 "ftsbench [-b RDBMS] [-c NCLIENTS] [-n NUMQUERY] [-l LEXFILE] [-g GAMMAFILE] [-f FLAGS] [-q] -d DBNAME\n"
95                 "  -b RDBMS\t- type of DB: ",
96                 stdout
97         );
98         fputs( buf, stdout );
99         fputs(
100                 "\n"
101                 "  -c NCLIENTS - number of clients in parallel\n"
102                 "  -n NUMQUERY - number of queries per client\n"
103                 "  -l LEXFILE - file with words and its frequents (default gendata/query-lex)\n"
104                 "  -g GAMMAFILE - file with doc's length distribution (default gendata/query-lens)\n"
105                 "  -l FLGAS - options for db's schema (see below)\n"
106                 "  -q - do not print progress message\n",
107                 stdout
108         );
109         fputs(
110                 "FLAGS are comma-separate list of:\n"
111                 "  gin  - use GIN index\n"
112                 "  gist - use GiST index\n"
113                 "  func - use functional index\n"
114                 "  and  - AND'ing lexemes in query (default)\n"
115                 "  or   - OR'ing lexemes in query\n",
116                 stdout
117         );
118         exit(1);
119 }
120
121 static RDBMS
122 getRDBMS(char *name) {
123         int     i;
124
125         for(i=0; DBDesc[i].rdbms != NULLSQL; i++) {
126                 if ( name == NULL ) {
127                         if ( DBDesc[i].init )
128                                 return DBDesc[i].rdbms; 
129                 } else if ( strcasecmp(name,DBDesc[i].shortname) == 0 ) {
130                         if ( DBDesc[i].init == NULL ) {
131                                 fprintf(stderr,"Support of '%s' isn't compiled-in\n", DBDesc[i].longname);
132                                 exit(1);
133                         }
134                         return DBDesc[i].rdbms;
135                 }
136         }
137
138         fprintf(stderr,"Can't find a RDBMS\n");
139         exit(1);
140         
141         return NULLSQL;
142 }
143
144 static int
145 getFLAGS(char *flg) {
146         int flags = 0;
147
148         if ( strcasestr(flg,"gist") )
149                 flags |= FLG_GIST;
150         if ( strcasestr(flg,"gin") )
151                 flags |= FLG_GIN;
152         if ( strcasestr(flg,"func") )
153                 flags |= FLG_FUNC;
154         if ( strcasestr(flg,"and") )
155                 flags |= FLG_AND;
156         if ( strcasestr(flg,"or") )
157                 flags |= FLG_OR;
158
159         if ( (flags & FLG_GIST) && (flags & FLG_GIN) ) {
160                 fprintf(stderr,"GIN and GiST flags are mutually exclusive\n");
161                 exit(1);
162         }
163         if ( (flags & FLG_AND) && (flags & FLG_OR) ) {
164                 fprintf(stderr,"AND and OR flags are mutually exclusive\n");
165                 exit(1);
166         }
167
168         return flags;
169 }
170
171 static ftsDB **
172 initConnections(RDBMS rdbms, int n, char *connstr) {
173         ftsDB   **dbs = (ftsDB**)malloc(sizeof(ftsDB*) * n);
174         int i;
175
176         if (!dbs) {
177                 fprintf(stderr,"Not enough mwmory\n");
178                 exit(1);
179         }
180
181         for(i=0;i<n;i++) { 
182                 dbs[i] = DBDesc[rdbms].init(connstr);
183                 pthread_mutex_init(&dbs[i]->nqueryMutex, NULL);
184         }
185
186         return dbs;
187 }
188
189 static double
190 timediff(struct timeval *begin, struct timeval *end) {
191     return ((double)( end->tv_sec - begin->tv_sec )) + ( (double)( end->tv_usec-begin->tv_usec ) ) / 1.0e+6;
192 }
193
194 static double
195 elapsedtime(struct timeval *begin) {
196     struct timeval end;
197         gettimeofday(&end,NULL);
198         return timediff(begin,&end);
199 }
200
201 static int benchFlags  = 0;
202 static int benchCount  = 0;
203 static pthread_cond_t condFinish = PTHREAD_COND_INITIALIZER;
204 static pthread_mutex_t mutexFinish = PTHREAD_MUTEX_INITIALIZER;
205 static pthread_mutex_t mutexWordGen = PTHREAD_MUTEX_INITIALIZER;
206
207 static void*
208 execBench(void *in) {
209         ftsDB *db = (ftsDB*)in;
210         int i;
211         char **words;
212
213         for(i=0;i<benchCount;i++) {
214                 /*
215                  * generate_querywords() isn't a thread safe
216                  */
217                 pthread_mutex_lock( &mutexWordGen );
218                 words = generate_querywords();
219                 pthread_mutex_unlock( &mutexWordGen );
220
221                 db->execQuery(db, words, benchFlags);
222                 free(words);
223         }
224
225         /*
226          * send message about exitting
227          */
228     pthread_mutex_lock( &mutexFinish );
229         pthread_cond_broadcast( &condFinish );
230         pthread_mutex_unlock( &mutexFinish );
231
232         return NULL;    
233 }
234
235 extern char *optarg;
236
237 int
238 main(int argn, char *argv[]) {
239         int             initMode = 0;
240         int             n = 0, nclients = 1;
241         char    *lex = NULL;
242         char    *doc = NULL;
243         char    *dbname = NULL;
244         RDBMS   rdbms = NULLSQL;
245         int             flags = 0;
246         int i;
247         int             quiet = 0;
248         StringBuf       b = {NULL,0,0};
249
250         while((i=getopt(argn,argv,"ib:n:l:g:d:c:hf:q")) != EOF) {
251                 switch(i) {
252                         case 'i': initMode = 1; break;
253                         case 'b': rdbms = getRDBMS(optarg); break;
254                         case 'n': n=atoi(optarg); break;
255                         case 'c': nclients=atoi(optarg); break;
256                         case 'l': lex = strdup(optarg); break;
257                         case 'g': doc = strdup(optarg); break;
258                         case 'd': dbname = strdup(optarg); break;
259                         case 'f': flags = getFLAGS(optarg); break;
260                         case 'q': quiet = 1; break;
261                         case 'h':
262                         default:
263                                 usage();
264                 }
265         }
266
267         if (rdbms == NULLSQL)
268                 rdbms = getRDBMS(NULL);
269
270         if ( dbname == NULL || n<0 || nclients<1 )
271                 usage();
272
273         printf("Running with '%s' RDBMS\n", DBDesc[ rdbms ].longname); 
274
275         if ( initMode ) {
276                 ftsDB   *db = *initConnections(rdbms, 1, dbname);
277                 time_t  prev;
278
279                 if (!lex)  lex = "gendata/lex";
280                 if (!doc)  doc = "gendata/gamma-lens";
281                 finnegan_init(lex, doc);
282
283                 db->startCreateScheme(db, flags);
284                 prev = time(NULL);
285                 for(i=0;i<n;i++) {
286                         generate_doc(&b);
287                         db->InsertRow(db, i+1, b.str);
288                         if ( !quiet && prev!=time(NULL) ) {
289                                 printf("\r%d(%.02f%%) rows inserted", i, (100.0*i)/n);
290                                 fflush(stdout);
291                                 prev = time(NULL);
292                         }
293                 }
294                 printf("%s%d(100.00%%) rows inserted. Finalyze insertion... ", 
295                         (quiet) ? "" : "\r", i);
296                 fflush(stdout);
297                 db->finishCreateScheme(db);
298                 printf("done\n");
299                 db->Close(db);
300         } else {
301                 ftsDB   **dbs = initConnections(rdbms, nclients, dbname);
302                 pthread_t       *tid = (pthread_t*)malloc( sizeof(pthread_t) * nclients);
303                 struct  timeval begin;
304                 double  elapsed;
305                 int     total=0, nres=0;
306                 struct      timespec  sleepTo = { 0, 0 };
307
308                 /*
309                  * startup generator
310                  */
311                 if (!lex)  lex = "gendata/query-lex";
312                 if (!doc)  doc = "gendata/query-lens";
313                 finnegan_init(lex, doc);
314
315                 /*
316                  * Initial query
317                  */
318                 if ( !quiet ) {
319                         printf("\r0(0.00%%) queries proceed");
320                         fflush(stdout);
321                 }
322                 benchFlags = flags;
323                 benchCount = n;
324
325                 gettimeofday(&begin,NULL);
326
327         pthread_mutex_lock( &mutexFinish );
328                 for(i=0;i<nclients;i++) {
329                         if ( pthread_create(tid+i, NULL, execBench, (void*)dbs[i]) != 0 ) {
330                                 fprintf(stderr,"pthread_create failed: %s\n", strerror(errno));
331                                 exit(1);
332                         }
333                 }
334
335                 for(;;) {
336                         int res, ntogo = 0;
337
338                         total = 0;
339                         for(i=0;i<nclients;i++) {
340                                 pthread_mutex_lock(&dbs[i]->nqueryMutex);
341                                 total +=dbs[i]->nquery;
342                                 if ( dbs[i]->nquery < n )
343                                         ntogo++;
344                                 pthread_mutex_unlock(&dbs[i]->nqueryMutex);
345                         }
346
347                         if ( ntogo == 0 ) 
348                                 break;
349
350                         if ( !quiet ) {
351                                 printf("\r%d(%.02f%%) queries proceed", total, (100.0*(float)total)/(nclients * n));
352                                 fflush(stdout);
353                         }
354                         
355                         sleepTo.tv_sec = time(NULL) + 1;
356                         res = pthread_cond_timedwait( &condFinish, &mutexFinish, &sleepTo );
357
358                         if ( !(res == ETIMEDOUT || res == 0) ) {
359                                 fprintf(stderr,"pthread_cond_timedwait failed: %s\n", strerror(errno));
360                                 exit(1);
361                         }
362                 }
363                 elapsed = elapsedtime(&begin);
364                 pthread_mutex_unlock( &mutexFinish );
365
366                 for(i=0;i<nclients;i++) {
367                         pthread_join(tid[i], NULL);
368                         nres += dbs[i]->nres;
369                         dbs[i]->Close(dbs[i]);
370                 }
371
372                 printf("%s%d(%.02f%%) queries proceed\n", 
373                         (quiet) ? "" : "\r", total, (100.0*(float)total)/(nclients * n));
374                 printf("Total number of result: %d\n", nres);
375                 printf("Total time: %.02f sec, Queries per second: %.02f\n", elapsed, total/elapsed);
376                 fflush(stdout);
377         }
378
379         return 0;
380 }