2 * @brief performance tests for Xapian.
4 /* Copyright 2008 Lemur Consulting Ltd
5 * Copyright 2008,2009,2010,2012,2013,2015 Olly Betts
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
26 #include "backendmanager.h"
29 #include "perftest/perftest_all.h"
31 #include "runprocess.h"
33 #include "stringutils.h"
34 #include "testrunner.h"
35 #include "testsuite.h"
40 #include "safeunistd.h"
41 #ifdef HAVE_SYS_UTSNAME_H
42 # include <sys/utsname.h>
46 # include "safewindows.h"
47 # include "safewinsock2.h"
52 PerfTestLogger logger
;
55 escape_xml(const string
& str
)
58 string::size_type p
= 0;
59 while (p
< str
.size()) {
81 PerfTestLogger::PerfTestLogger()
82 : testcase_started(false),
83 indexing_started(false),
84 searching_started(false)
87 PerfTestLogger::~PerfTestLogger()
98 WORD WSAVerReq
= MAKEWORD(1, 1);
101 if (WSAStartup(WSAVerReq
, &WSAData
) != 0) {
102 // wrong winsock dlls?
105 if (gethostname(buf
, sizeof(buf
)) != 0) {
110 #elif defined HAVE_SYS_UTSNAME_H
111 struct utsname uname_buf
;
112 if (uname(&uname_buf
) != 0) {
113 uname_buf
.nodename
[0] = '\0';
115 return uname_buf
.nodename
;
116 #elif defined HAVE_GETHOSTNAME
118 if (gethostname(buf
, sizeof(buf
)) != 0) {
127 /// Get the load average.
136 loadavg
= stdout_to_string("uptime 2>/dev/null | sed 's/.*: \\([0-9][0-9]*\\)/\\1/;s/, .*//'");
137 } catch (NoSuchProgram
) {} catch (ReadError
) {}
142 /// Get the number of processors.
148 SYSTEM_INFO siSysInfo
;
149 GetSystemInfo(&siSysInfo
);
150 ncpus
= str(siSysInfo
.dwNumberOfProcessors
);
153 // Works on Linux, at least back to kernel 2.2.26.
154 ncpus
= stdout_to_string("getconf _NPROCESSORS_ONLN 2>/dev/null | grep -v '[^0-9]'");
155 } catch (NoSuchProgram
) {} catch (ReadError
) {}
158 // Works on OpenBSD (and apparently FreeBSD and Darwin).
159 ncpus
= stdout_to_string("sysctl hw.ncpu 2>/dev/null | sed 's/.*=//'");
160 } catch (NoSuchProgram
) {} catch (ReadError
) {}
163 // Works on Solaris and OSF/1.
164 ncpus
= stdout_to_string("PATH=/usr/sbin:$PATH psrinfo 2>/dev/null | grep -c on-line");
165 } catch (NoSuchProgram
) {} catch (ReadError
) {}
168 // Works on Linux, just in case the getconf version doesn't.
169 // Different architectures have different formats for /proc/cpuinfo
170 // so this won't work as widely as getconf _NPROCESSORS_ONLN will.
171 ncpus
= stdout_to_string("grep -c processor /proc/cpuinfo 2>/dev/null");
172 } catch (NoSuchProgram
) {} catch (ReadError
) {}
177 /// Get details of the OS and distribution.
184 ZeroMemory(&osvi
, sizeof(OSVERSIONINFO
));
185 osvi
.dwOSVersionInfoSize
= sizeof(OSVERSIONINFO
);
187 distro
= "Microsoft Windows v";
188 distro
+= str(osvi
.dwMajorVersion
);
190 distro
+= str(osvi
.dwMinorVersion
);
192 distro
+= str(osvi
.dwBuildNumber
);
195 distro
= stdout_to_string("perftest/get_machine_info 2>/dev/null");
196 } catch (NoSuchProgram
) {} catch (ReadError
) {}
201 /// Get the git commit for HEAD.
207 commit_ref
= stdout_to_string("cd \"$srcdir\" && git log -n1 --abbrev-commit --format=%h");
208 } catch (NoSuchProgram
) {} catch (ReadError
) {}
214 PerfTestLogger::open(const string
& logpath
)
216 out
.open(logpath
.c_str(), ios::out
| ios::binary
| ios::trunc
);
217 if (!out
.is_open()) {
218 cerr
<< "Couldn't open output logfile '" << logpath
<< "'" << endl
;
222 string loadavg
= get_loadavg();
223 string hostname
= get_hostname();
224 string ncpus
= get_ncpus();
225 string distro
= get_distro();
227 // Write header, and details of the machine.
228 write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testrun>\n"
230 if (!hostname
.empty())
231 write(" <hostname>" + hostname
+ "</hostname>\n");
232 if (!loadavg
.empty())
233 write(" <loadavg>" + loadavg
+ "</loadavg>\n");
235 write(" <ncpus>" + ncpus
+ "</ncpus>\n");
237 write(" <distro>" + distro
+ "</distro>\n");
238 write(" <physmem>" + str(get_total_physical_memory()) + "</physmem>\n");
239 write(" </machineinfo>\n");
241 const string
& commit_ref
= get_commit_ref();
243 write(" <sourceinfo>\n");
244 if (!commit_ref
.empty())
245 write(" <commitref>" + commit_ref
+ "</commitref>\n");
246 write(" <version>" + string(Xapian::version_string()) + "</version>\n");
247 write(" </sourceinfo>\n");
254 PerfTestLogger::write(const string
& text
)
256 out
.write(text
.data(), text
.size());
261 PerfTestLogger::close()
265 write("</testrun>\n");
271 PerfTestLogger::indexing_begin(const string
& dbname
,
272 const std::map
<std::string
, std::string
> & params
)
276 write(" <indexrun dbname=\"" + dbname
+ "\">\n <params>\n");
277 std::map
<std::string
, std::string
>::const_iterator i
;
278 for (i
= params
.begin(); i
!= params
.end(); ++i
) {
279 write(" <param name=\"" + i
->first
+ "\">" + escape_xml(i
->second
) + "</param>\n");
281 i
= params
.find("flush_threshold");
282 if (i
== params
.end()) {
283 Xapian::doccount flush_threshold
= 0;
284 const char *p
= getenv("XAPIAN_FLUSH_THRESHOLD");
286 flush_threshold
= atoi(p
);
287 if (flush_threshold
== 0)
288 flush_threshold
= 10000;
289 write(" <param name=\"flush_threshold\">" +
290 escape_xml(str(flush_threshold
)) + "</param>\n");
292 write(" </params>\n");
293 indexing_addcount
= 0;
294 indexing_unlogged_changes
= false;
295 indexing_timer
= RealTime::now();
296 last_indexlog_timer
= indexing_timer
;
297 indexing_started
= true;
303 PerfTestLogger::indexing_log()
305 Assert(indexing_started
);
306 last_indexlog_timer
= RealTime::now();
307 double elapsed(last_indexlog_timer
- indexing_timer
);
309 "<time>" + str(elapsed
) + "</time>"
310 "<adds>" + str(indexing_addcount
) + "</adds>"
312 indexing_unlogged_changes
= false;
316 PerfTestLogger::indexing_add()
319 indexing_unlogged_changes
= true;
320 // Log every 1000 documents
321 if (indexing_addcount
% 1000 == 0) {
324 // Or after 5 seconds
325 double now
= RealTime::now();
326 if (now
> last_indexlog_timer
+ 5)
332 PerfTestLogger::indexing_end()
334 if (indexing_started
) {
336 write(" </indexrun>\n");
337 indexing_started
= false;
342 PerfTestLogger::searching_start(const string
& description
)
346 write(" <searchrun>\n"
347 " <description>" + escape_xml(description
) + "</description>\n");
348 searching_started
= true;
353 PerfTestLogger::search_start()
355 searching_timer
= RealTime::now();
359 PerfTestLogger::search_end(const Xapian::Query
& query
,
360 const Xapian::MSet
& mset
)
362 Assert(searching_started
);
363 double elapsed(RealTime::now() - searching_timer
);
365 "<time>" + str(elapsed
) + "</time>"
366 "<query>" + escape_xml(query
.get_description()) + "</query>"
368 "<size>" + str(mset
.size()) + "</size>"
369 "<lb>" + str(mset
.get_matches_lower_bound()) + "</lb>"
370 "<est>" + str(mset
.get_matches_estimated()) + "</est>"
371 "<ub>" + str(mset
.get_matches_upper_bound()) + "</ub>"
378 PerfTestLogger::searching_end()
380 if (searching_started
) {
381 write(" </searchrun>\n");
382 searching_started
= false;
387 PerfTestLogger::testcase_begin(const string
& testcase
)
390 write(" <testcase name=\"" + testcase
+ "\" backend=\"" +
391 backendmanager
->get_dbtype() + "\" repnum=\"" +
392 str(repetition_number
) + "\">\n");
393 testcase_started
= true;
397 PerfTestLogger::testcase_end()
400 if (testcase_started
) {
401 write(" </testcase>\n");
402 testcase_started
= false;
407 PerfTestLogger::repetition_begin(int num
)
410 repetition_number
= num
;
414 PerfTestLogger::repetition_end()
420 class PerfTestRunner
: public TestRunner
422 string repetitions_string
;
423 mutable bool repetitions_parsed
;
424 mutable int repetitions
;
427 : repetitions_parsed(false), repetitions(5)
429 test_driver::add_command_line_option("repetitions", 'r',
430 &repetitions_string
);
435 if (!repetitions_parsed
) {
436 if (!repetitions_string
.empty()) {
437 repetitions
= atoi(repetitions_string
.c_str());
439 repetitions_parsed
= true;
441 for (int i
= 0; i
!= repetitions
; ++i
) {
442 logger
.repetition_begin(i
+ 1);
443 #include "perftest/perftest_collated.h"
444 logger
.repetition_end();
450 int main(int argc
, char **argv
)
452 if (!logger
.open("perflog.xml"))
455 PerfTestRunner runner
;
457 return runner
.run_tests(argc
, argv
);