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),
85 diversifying_started(false)
88 PerfTestLogger::~PerfTestLogger()
99 WORD WSAVerReq
= MAKEWORD(1, 1);
102 if (WSAStartup(WSAVerReq
, &WSAData
) != 0) {
103 // wrong winsock dlls?
106 if (gethostname(buf
, sizeof(buf
)) != 0) {
111 #elif defined HAVE_SYS_UTSNAME_H
112 struct utsname uname_buf
;
113 if (uname(&uname_buf
) != 0) {
114 uname_buf
.nodename
[0] = '\0';
116 return uname_buf
.nodename
;
117 #elif defined HAVE_GETHOSTNAME
119 if (gethostname(buf
, sizeof(buf
)) != 0) {
128 /// Get the load average.
137 loadavg
= stdout_to_string("uptime 2>/dev/null | sed 's/.*: \\([0-9][0-9]*\\)/\\1/;s/, .*//'");
138 } catch (const NoSuchProgram
&) {
139 } catch (const ReadError
&) {
145 /// Get the number of processors.
151 SYSTEM_INFO siSysInfo
;
152 GetSystemInfo(&siSysInfo
);
153 ncpus
= str(siSysInfo
.dwNumberOfProcessors
);
156 // Works on Linux, at least back to kernel 2.2.26.
157 ncpus
= stdout_to_string("getconf _NPROCESSORS_ONLN 2>/dev/null | grep -v '[^0-9]'");
158 } catch (const NoSuchProgram
&) {
159 } catch (const ReadError
&) {
163 // Works on OpenBSD (and apparently FreeBSD and Darwin).
164 ncpus
= stdout_to_string("sysctl hw.ncpu 2>/dev/null | sed 's/.*=//'");
165 } catch (const NoSuchProgram
&) {
166 } catch (const ReadError
&) {
170 // Works on Solaris and OSF/1.
171 ncpus
= stdout_to_string("PATH=/usr/sbin:$PATH psrinfo 2>/dev/null | grep -c on-line");
172 } catch (const NoSuchProgram
&) {
173 } catch (const ReadError
&) {
177 // Works on Linux, just in case the getconf version doesn't.
178 // Different architectures have different formats for /proc/cpuinfo
179 // so this won't work as widely as getconf _NPROCESSORS_ONLN will.
180 ncpus
= stdout_to_string("grep -c processor /proc/cpuinfo 2>/dev/null");
181 } catch (const NoSuchProgram
&) {
182 } catch (const ReadError
&) {
188 /// Get details of the OS and distribution.
195 ZeroMemory(&osvi
, sizeof(OSVERSIONINFO
));
196 osvi
.dwOSVersionInfoSize
= sizeof(OSVERSIONINFO
);
198 // GetVersionEx() is deprecated, but none of the suggested replacements seems
199 // to actually provide the functionality we want here...
200 # pragma warning(push)
201 # pragma warning(disable:4996)
205 # pragma warning(pop)
207 distro
= "Microsoft Windows v";
208 distro
+= str(osvi
.dwMajorVersion
);
210 distro
+= str(osvi
.dwMinorVersion
);
212 distro
+= str(osvi
.dwBuildNumber
);
215 distro
= stdout_to_string("perftest/get_machine_info 2>/dev/null");
216 } catch (const NoSuchProgram
&) {
217 } catch (const ReadError
&) {
223 /// Get the git commit for HEAD.
229 commit_ref
= stdout_to_string("cd \"$srcdir\" && git log -n1 --abbrev-commit --format=%h");
230 } catch (const NoSuchProgram
&) {
231 } catch (const ReadError
&) {
238 PerfTestLogger::open(const string
& logpath
)
240 out
.open(logpath
.c_str(), ios::out
| ios::binary
| ios::trunc
);
241 if (!out
.is_open()) {
242 cerr
<< "Couldn't open output logfile '" << logpath
<< "'\n";
246 string loadavg
= get_loadavg();
247 string hostname
= get_hostname();
248 string ncpus
= get_ncpus();
249 string distro
= get_distro();
251 // Write header, and details of the machine.
252 write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testrun>\n"
254 if (!hostname
.empty())
255 write(" <hostname>" + hostname
+ "</hostname>\n");
256 if (!loadavg
.empty())
257 write(" <loadavg>" + loadavg
+ "</loadavg>\n");
259 write(" <ncpus>" + ncpus
+ "</ncpus>\n");
261 write(" <distro>" + distro
+ "</distro>\n");
262 write(" <physmem>" + str(get_total_physical_memory()) + "</physmem>\n");
263 write(" </machineinfo>\n");
265 const string
& commit_ref
= get_commit_ref();
267 write(" <sourceinfo>\n");
268 if (!commit_ref
.empty())
269 write(" <commitref>" + commit_ref
+ "</commitref>\n");
270 write(" <version>" + string(Xapian::version_string()) + "</version>\n");
271 write(" </sourceinfo>\n");
277 PerfTestLogger::write(const string
& text
)
279 out
.write(text
.data(), text
.size());
284 PerfTestLogger::close()
288 write("</testrun>\n");
294 PerfTestLogger::indexing_begin(const string
& dbname
,
295 const std::map
<std::string
, std::string
> & params
)
299 write(" <indexrun dbname=\"" + dbname
+ "\">\n <params>\n");
300 std::map
<std::string
, std::string
>::const_iterator i
;
301 for (i
= params
.begin(); i
!= params
.end(); ++i
) {
302 write(" <param name=\"" + i
->first
+ "\">" + escape_xml(i
->second
) + "</param>\n");
304 i
= params
.find("flush_threshold");
305 if (i
== params
.end()) {
306 Xapian::doccount flush_threshold
= 0;
307 const char *p
= getenv("XAPIAN_FLUSH_THRESHOLD");
309 flush_threshold
= atoi(p
);
310 if (flush_threshold
== 0)
311 flush_threshold
= 10000;
312 write(" <param name=\"flush_threshold\">" +
313 escape_xml(str(flush_threshold
)) + "</param>\n");
315 write(" </params>\n");
316 indexing_addcount
= 0;
317 indexing_unlogged_changes
= false;
318 indexing_timer
= RealTime::now();
319 last_indexlog_timer
= indexing_timer
;
320 indexing_started
= true;
326 PerfTestLogger::indexing_log()
328 Assert(indexing_started
);
329 last_indexlog_timer
= RealTime::now();
330 double elapsed(last_indexlog_timer
- indexing_timer
);
332 "<time>" + str(elapsed
) + "</time>"
333 "<adds>" + str(indexing_addcount
) + "</adds>"
335 indexing_unlogged_changes
= false;
339 PerfTestLogger::indexing_add()
342 indexing_unlogged_changes
= true;
343 // Log every 1000 documents
344 if (indexing_addcount
% 1000 == 0) {
347 // Or after 5 seconds
348 double now
= RealTime::now();
349 if (now
> last_indexlog_timer
+ 5)
355 PerfTestLogger::indexing_end()
357 if (indexing_started
) {
359 write(" </indexrun>\n");
360 indexing_started
= false;
365 PerfTestLogger::searching_start(const string
& description
)
369 write(" <searchrun>\n"
370 " <description>" + escape_xml(description
) + "</description>\n");
371 searching_started
= true;
376 PerfTestLogger::search_start()
378 searching_timer
= RealTime::now();
382 PerfTestLogger::search_end(const Xapian::Query
& query
,
383 const Xapian::MSet
& mset
)
385 Assert(searching_started
);
386 double elapsed(RealTime::now() - searching_timer
);
388 "<time>" + str(elapsed
) + "</time>"
389 "<query>" + escape_xml(query
.get_description()) + "</query>"
391 "<size>" + str(mset
.size()) + "</size>"
392 "<lb>" + str(mset
.get_matches_lower_bound()) + "</lb>"
393 "<est>" + str(mset
.get_matches_estimated()) + "</est>"
394 "<ub>" + str(mset
.get_matches_upper_bound()) + "</ub>"
401 PerfTestLogger::searching_end()
403 if (searching_started
) {
404 write(" </searchrun>\n");
405 searching_started
= false;
410 PerfTestLogger::diversifying_start(const string
& description
)
414 write(" <diversifyrun>\n"
415 " <description>" + escape_xml(description
) + "</description>\n");
416 diversifying_started
= true;
421 PerfTestLogger::diversify_start()
423 diversifying_timer
= RealTime::now();
427 PerfTestLogger::diversify_end(Xapian::doccount k
,
429 const Xapian::DocumentSet
& dset
)
431 Assert(diversifying_started
);
432 double elapsed(RealTime::now() - diversifying_timer
);
434 "<time>" + str(elapsed
) + "</time>"
436 "<k>" + str(k
) + "</k>"
437 "<r>" + str(r
) + "</r>"
438 "<size>" + str(dset
.size()) + "</size>"
445 PerfTestLogger::diversifying_end()
447 if (diversifying_started
) {
448 write(" </diversifyrun>\n");
449 diversifying_started
= false;
454 PerfTestLogger::testcase_begin(const string
& testcase
)
457 write(" <testcase name=\"" + testcase
+ "\" backend=\"" +
458 backendmanager
->get_dbtype() + "\" repnum=\"" +
459 str(repetition_number
) + "\">\n");
460 testcase_started
= true;
464 PerfTestLogger::testcase_end()
467 if (testcase_started
) {
468 write(" </testcase>\n");
469 testcase_started
= false;
474 PerfTestLogger::repetition_begin(int num
)
477 repetition_number
= num
;
481 PerfTestLogger::repetition_end()
487 class PerfTestRunner
: public TestRunner
489 string repetitions_string
;
490 mutable bool repetitions_parsed
;
491 mutable int repetitions
;
494 : repetitions_parsed(false), repetitions(5)
496 test_driver::add_command_line_option("repetitions", 'r',
497 &repetitions_string
);
502 if (!repetitions_parsed
) {
503 if (!repetitions_string
.empty()) {
504 repetitions
= atoi(repetitions_string
.c_str());
506 repetitions_parsed
= true;
508 for (int i
= 0; i
!= repetitions
; ++i
) {
509 logger
.repetition_begin(i
+ 1);
510 #include "perftest/perftest_collated.h"
511 logger
.repetition_end();
517 int main(int argc
, char **argv
)
519 if (!logger
.open("perflog.xml"))
522 PerfTestRunner runner
;
524 return runner
.run_tests(argc
, argv
);