Add scheme creation for statistic
[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                 "Copyright (c) 2006 Teodor Sigaev <teodor@sigaev.ru>. All rights reserved.\n"
77                 "ftsbench - full text search benchmark for RDBMS\n"
78                 "Initialization of DB:\n"
79                 "ftsbench -i [-b RDBMS] [-n NUMROW] [-l LEXFILE] [-g GAMMAFILE] [-f FLAGS] [-q] -d DBNAME\n"
80                 "  -b RDBMS\t- type of DB: ",
81                 stdout
82         );
83         fputs( buf, stdout );
84         fputs(
85                 "\n"
86                 "  -n NUMROW - number of row in table\n"
87                 "  -l LEXFILE - file with words and its frequents (default gendata/lex)\n"
88                 "  -g GAMMAFILE - file with doc's length distribution (default gendata/gamma-lens)\n"
89                 "  -l FLGAS - options for db's schema (see below)\n"
90                 "  -q - do not print progress message\n",
91                 stdout
92         );
93         fputs(
94                 "Run tests:\n"
95                 "ftsbench [-b RDBMS] [-c NCLIENTS] [-n NUMQUERY] [-l LEXFILE] [-g GAMMAFILE] [-f FLAGS] [-q] -d DBNAME\n"
96                 "  -b RDBMS\t- type of DB: ",
97                 stdout
98         );
99         fputs( buf, stdout );
100         fputs(
101                 "\n"
102                 "  -c NCLIENTS - number of clients in parallel\n"
103                 "  -n NUMQUERY - number of queries per client\n"
104                 "  -l LEXFILE - file with words and its frequents (default gendata/query-lex)\n"
105                 "  -g GAMMAFILE - file with doc's length distribution (default gendata/query-lens)\n"
106                 "  -l FLGAS - options for db's schema (see below)\n"
107                 "  -q - do not print progress message\n",
108                 stdout
109         );
110         fputs(
111                 "FLAGS are comma-separate list of:\n"
112                 "  gin  - use GIN index\n"
113                 "  gist - use GiST index\n"
114                 "  func - use functional index\n"
115                 "  and  - AND'ing lexemes in query (default)\n"
116                 "  or   - OR'ing lexemes in query\n",
117                 stdout
118         );
119         fputs(
120                 "Print SQL-scheme for statistics:\n"
121                 "ftsbench -S\n",
122                 stdout
123         );
124         exit(1);
125 }
126
127 static RDBMS
128 getRDBMS(char *name) {
129         int     i;
130
131         for(i=0; DBDesc[i].rdbms != NULLSQL; i++) {
132                 if ( name == NULL ) {
133                         if ( DBDesc[i].init )
134                                 return DBDesc[i].rdbms; 
135                 } else if ( strcasecmp(name,DBDesc[i].shortname) == 0 ) {
136                         if ( DBDesc[i].init == NULL ) {
137                                 fprintf(stderr,"Support of '%s' isn't compiled-in\n", DBDesc[i].longname);
138                                 exit(1);
139                         }
140                         return DBDesc[i].rdbms;
141                 }
142         }
143
144         fprintf(stderr,"Can't find a RDBMS\n");
145         exit(1);
146         
147         return NULLSQL;
148 }
149
150 static int
151 getFLAGS(char *flg) {
152         int flags = 0;
153
154         if ( strcasestr(flg,"gist") )
155                 flags |= FLG_GIST;
156         if ( strcasestr(flg,"gin") )
157                 flags |= FLG_GIN;
158         if ( strcasestr(flg,"func") )
159                 flags |= FLG_FUNC;
160         if ( strcasestr(flg,"and") )
161                 flags |= FLG_AND;
162         if ( strcasestr(flg,"or") )
163                 flags |= FLG_OR;
164
165         if ( (flags & FLG_GIST) && (flags & FLG_GIN) ) {
166                 fprintf(stderr,"GIN and GiST flags are mutually exclusive\n");
167                 exit(1);
168         }
169         if ( (flags & FLG_AND) && (flags & FLG_OR) ) {
170                 fprintf(stderr,"AND and OR flags are mutually exclusive\n");
171                 exit(1);
172         }
173
174         return flags;
175 }
176
177 static ftsDB **
178 initConnections(RDBMS rdbms, int n, char *connstr) {
179         ftsDB   **dbs = (ftsDB**)malloc(sizeof(ftsDB*) * n);
180         int i;
181
182         if (!dbs) {
183                 fprintf(stderr,"Not enough mwmory\n");
184                 exit(1);
185         }
186
187         for(i=0;i<n;i++) { 
188                 dbs[i] = DBDesc[rdbms].init(connstr);
189                 pthread_mutex_init(&dbs[i]->nqueryMutex, NULL);
190         }
191
192         return dbs;
193 }
194
195 static double
196 timediff(struct timeval *begin, struct timeval *end) {
197     return ((double)( end->tv_sec - begin->tv_sec )) + ( (double)( end->tv_usec-begin->tv_usec ) ) / 1.0e+6;
198 }
199
200 static double
201 elapsedtime(struct timeval *begin) {
202     struct timeval end;
203         gettimeofday(&end,NULL);
204         return timediff(begin,&end);
205 }
206
207 static int benchFlags  = 0;
208 static int benchCount  = 0;
209 static pthread_cond_t condFinish = PTHREAD_COND_INITIALIZER;
210 static pthread_mutex_t mutexFinish = PTHREAD_MUTEX_INITIALIZER;
211 static pthread_mutex_t mutexWordGen = PTHREAD_MUTEX_INITIALIZER;
212
213 static void*
214 execBench(void *in) {
215         ftsDB *db = (ftsDB*)in;
216         int i;
217         char **words;
218
219         for(i=0;i<benchCount;i++) {
220                 /*
221                  * generate_querywords() isn't a thread safe
222                  */
223                 pthread_mutex_lock( &mutexWordGen );
224                 words = generate_querywords();
225                 pthread_mutex_unlock( &mutexWordGen );
226
227                 db->execQuery(db, words, benchFlags);
228                 free(words);
229         }
230
231         /*
232          * send message about exitting
233          */
234     pthread_mutex_lock( &mutexFinish );
235         pthread_cond_broadcast( &condFinish );
236         pthread_mutex_unlock( &mutexFinish );
237
238         return NULL;    
239 }
240
241 extern char *optarg;
242
243 int
244 main(int argn, char *argv[]) {
245         int             initMode = 0;
246         int             n = 0, nclients = 1;
247         char    *lex = NULL;
248         char    *doc = NULL;
249         char    *dbname = NULL;
250         RDBMS   rdbms = NULLSQL;
251         int             flags = 0;
252         int i;
253         int             quiet = 0, scheme=0;
254         StringBuf       b = {NULL,0,0};
255
256         while((i=getopt(argn,argv,"ib:n:l:g:d:c:hf:qS")) != EOF) {
257                 switch(i) {
258                         case 'i': initMode = 1; break;
259                         case 'b': rdbms = getRDBMS(optarg); break;
260                         case 'n': n=atoi(optarg); break;
261                         case 'c': nclients=atoi(optarg); break;
262                         case 'l': lex = strdup(optarg); break;
263                         case 'g': doc = strdup(optarg); break;
264                         case 'd': dbname = strdup(optarg); break;
265                         case 'f': flags = getFLAGS(optarg); break;
266                         case 'q': quiet = 1; break;
267                         case 'S': scheme = 1; break;
268                         case 'h':
269                         default:
270                                 usage();
271                 }
272         }
273
274         if ( scheme ) {
275                 printScheme();
276                 return 0;
277         }
278
279         if (rdbms == NULLSQL)
280                 rdbms = getRDBMS(NULL);
281
282         if ( dbname == NULL || n<0 || nclients<1 )
283                 usage();
284
285         printf("Running with '%s' RDBMS\n", DBDesc[ rdbms ].longname); 
286
287         if ( initMode ) {
288                 ftsDB   *db = *initConnections(rdbms, 1, dbname);
289                 time_t  prev;
290
291                 if (!lex)  lex = "gendata/lex";
292                 if (!doc)  doc = "gendata/gamma-lens";
293                 finnegan_init(lex, doc);
294
295                 db->startCreateScheme(db, flags);
296                 prev = time(NULL);
297                 for(i=0;i<n;i++) {
298                         generate_doc(&b);
299                         db->InsertRow(db, i+1, b.str);
300                         if ( !quiet && prev!=time(NULL) ) {
301                                 printf("\r%d(%.02f%%) rows inserted", i, (100.0*i)/n);
302                                 fflush(stdout);
303                                 prev = time(NULL);
304                         }
305                 }
306                 printf("%s%d(100.00%%) rows inserted. Finalyze insertion... ", 
307                         (quiet) ? "" : "\r", i);
308                 fflush(stdout);
309                 db->finishCreateScheme(db);
310                 printf("done\n");
311                 db->Close(db);
312         } else {
313                 ftsDB   **dbs = initConnections(rdbms, nclients, dbname);
314                 pthread_t       *tid = (pthread_t*)malloc( sizeof(pthread_t) * nclients);
315                 struct  timeval begin;
316                 double  elapsed;
317                 int     total=0, nres=0;
318                 struct      timespec  sleepTo = { 0, 0 };
319
320                 /*
321                  * startup generator
322                  */
323                 if (!lex)  lex = "gendata/query-lex";
324                 if (!doc)  doc = "gendata/query-lens";
325                 finnegan_init(lex, doc);
326
327                 /*
328                  * Initial query
329                  */
330                 if ( !quiet ) {
331                         printf("\r0(0.00%%) queries proceed");
332                         fflush(stdout);
333                 }
334                 benchFlags = flags;
335                 benchCount = n;
336
337                 gettimeofday(&begin,NULL);
338
339         pthread_mutex_lock( &mutexFinish );
340                 for(i=0;i<nclients;i++) {
341                         if ( pthread_create(tid+i, NULL, execBench, (void*)dbs[i]) != 0 ) {
342                                 fprintf(stderr,"pthread_create failed: %s\n", strerror(errno));
343                                 exit(1);
344                         }
345                 }
346
347                 for(;;) {
348                         int res, ntogo = 0;
349
350                         total = 0;
351                         for(i=0;i<nclients;i++) {
352                                 pthread_mutex_lock(&dbs[i]->nqueryMutex);
353                                 total +=dbs[i]->nquery;
354                                 if ( dbs[i]->nquery < n )
355                                         ntogo++;
356                                 pthread_mutex_unlock(&dbs[i]->nqueryMutex);
357                         }
358
359                         if ( ntogo == 0 ) 
360                                 break;
361
362                         if ( !quiet ) {
363                                 printf("\r%d(%.02f%%) queries proceed", total, (100.0*(float)total)/(nclients * n));
364                                 fflush(stdout);
365                         }
366                         
367                         sleepTo.tv_sec = time(NULL) + 1;
368                         res = pthread_cond_timedwait( &condFinish, &mutexFinish, &sleepTo );
369
370                         if ( !(res == ETIMEDOUT || res == 0) ) {
371                                 fprintf(stderr,"pthread_cond_timedwait failed: %s\n", strerror(errno));
372                                 exit(1);
373                         }
374                 }
375                 elapsed = elapsedtime(&begin);
376                 pthread_mutex_unlock( &mutexFinish );
377
378                 for(i=0;i<nclients;i++) {
379                         pthread_join(tid[i], NULL);
380                         nres += dbs[i]->nres;
381                         dbs[i]->Close(dbs[i]);
382                 }
383
384                 printf("%s%d(%.02f%%) queries proceed\n", 
385                         (quiet) ? "" : "\r", total, (100.0*(float)total)/(nclients * n));
386                 printf("Total number of result: %d\n", nres);
387                 printf("Total time: %.02f sec, Queries per second: %.02f\n", elapsed, total/elapsed);
388                 fflush(stdout);
389         }
390
391         return 0;
392 }