diff --git a/map/framework.cpp b/map/framework.cpp index aa192e8ee7..d04704af9b 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -850,7 +850,7 @@ void Framework::StartInteractiveSearch(search::SearchParams const & params) m_lastInteractiveSearchParams = params; m_lastInteractiveSearchParams.SetForceSearch(false); m_lastInteractiveSearchParams.SetMode(Mode::Viewport); - m_lastInteractiveSearchParams.m_callback = [this](Results const & results) + m_lastInteractiveSearchParams.m_onResults = [this](Results const & results) { if (!results.IsEndMarker()) { @@ -999,9 +999,12 @@ void Framework::InitSearchEngine() ASSERT(m_infoGetter.get(), ()); try { - m_searchEngine.reset(new search::Engine( - const_cast(m_model.GetIndex()), GetDefaultCategories(), - *m_infoGetter, languages::GetCurrentOrig(), make_unique())); + search::Engine::Params params; + params.m_locale = languages::GetCurrentOrig(); + params.m_numThreads = 1; + m_searchEngine.reset(new search::Engine(const_cast(m_model.GetIndex()), + GetDefaultCategories(), *m_infoGetter, + make_unique(), params)); } catch (RootException const & e) { @@ -2257,8 +2260,8 @@ bool Framework::ParseEditorDebugCommand(search::SearchParams const & params) results.AddResultNoChecks(search::Result(fid, feature::GetCenter(*feature), name, edit.second, DebugPrint(types), types.GetBestType(), smd)); } - params.m_callback(results); - params.m_callback(search::Results::GetEndMarker(false)); + params.m_onResults(results); + params.m_onResults(search::Results::GetEndMarker(false)); return true; } else if (params.m_query == "?eclear") diff --git a/qt/search_panel.cpp b/qt/search_panel.cpp index 77bca9d535..92a42054fc 100644 --- a/qt/search_panel.cpp +++ b/qt/search_panel.cpp @@ -74,7 +74,7 @@ SearchPanel::SearchPanel(DrawWidget * drawWidget, QWidget * parent) CHECK(connect(this, SIGNAL(SearchResultSignal(ResultsT *)), this, SLOT(OnSearchResult(ResultsT *)), Qt::QueuedConnection), ()); - m_params.m_callback = bind(&SearchPanel::SearchResultThreadFunc, this, _1); + m_params.m_onResults = bind(&SearchPanel::SearchResultThreadFunc, this, _1); } void SearchPanel::SearchResultThreadFunc(ResultsT const & result) diff --git a/search/params.hpp b/search/params.hpp index 829404d0ae..e4a4627c8b 100644 --- a/search/params.hpp +++ b/search/params.hpp @@ -12,7 +12,9 @@ namespace search { class Results; - typedef function SearchCallbackT; + + using TOnStarted = function; + using TOnResults = function; class SearchParams { @@ -46,7 +48,8 @@ namespace search void Clear() { m_query.clear(); } public: - SearchCallbackT m_callback; + TOnStarted m_onStarted; + TOnResults m_onResults; string m_query; string m_inputLocale; diff --git a/search/search_engine.cpp b/search/search_engine.cpp index f441e7a8da..827c8ff53f 100644 --- a/search/search_engine.cpp +++ b/search/search_engine.cpp @@ -88,6 +88,7 @@ void SendStatistics(SearchParams const & params, m2::RectD const & viewport, Res } } // namespace +// QueryHandle ------------------------------------------------------------------------------------- QueryHandle::QueryHandle() : m_query(nullptr), m_cancelled(false) {} void QueryHandle::Cancel() @@ -112,18 +113,39 @@ void QueryHandle::Detach() m_query = nullptr; } -Engine::Engine(Index & index, CategoriesHolder const & categories, storage::CountryInfoGetter const & infoGetter, - string const & locale, unique_ptr && factory) - : m_categories(categories), m_factory(move(factory)), m_shutdown(false) +// Engine::Params ---------------------------------------------------------------------------------- +Engine::Params::Params() : m_locale("en"), m_numThreads(1) {} + +Engine::Params::Params(string const & locale, size_t numThreads) + : m_locale(locale), m_numThreads(numThreads) +{ +} + +// Engine ------------------------------------------------------------------------------------------ +Engine::Engine(Index & index, CategoriesHolder const & categories, + storage::CountryInfoGetter const & infoGetter, + unique_ptr factory, Params const & params) + : m_categories(categories), m_shutdown(false) { InitSuggestions doInit; m_categories.ForEachName(bind(ref(doInit), _1)); doInit.GetSuggests(m_suggests); - m_query = m_factory->BuildSearchQuery(index, m_categories, m_suggests, infoGetter); - m_query->SetPreferredLocale(locale); + m_queries.reserve(params.m_numThreads); + for (size_t i = 0; i < params.m_numThreads; ++i) + { + auto query = factory->BuildSearchQuery(index, m_categories, m_suggests, infoGetter); + query->SetPreferredLocale(params.m_locale); + m_queries.push_back(move(query)); + } - m_thread = threads::SimpleThread(&Engine::MainLoop, this); + m_broadcast.resize(params.m_numThreads); + m_threads.reserve(params.m_numThreads); + for (size_t i = 0; i < params.m_numThreads; ++i) + { + m_threads.emplace_back(&Engine::MainLoop, this, ref(*m_queries[i]), ref(m_tasks), + ref(m_broadcast[i])); + } } Engine::~Engine() @@ -131,124 +153,147 @@ Engine::~Engine() { lock_guard lock(m_mu); m_shutdown = true; - m_cv.notify_one(); + m_cv.notify_all(); } - m_thread.join(); + + for (auto & thread : m_threads) + thread.join(); } weak_ptr Engine::Search(SearchParams const & params, m2::RectD const & viewport) { shared_ptr handle(new QueryHandle()); - PostTask(bind(&Engine::DoSearch, this, params, viewport, handle)); + PostTask(bind(&Engine::DoSearch, this, params, viewport, handle, _1)); return handle; } void Engine::SetSupportOldFormat(bool support) { - PostTask(bind(&Engine::DoSupportOldFormat, this, support)); + PostBroadcast(bind(&Engine::DoSupportOldFormat, this, support, _1)); } -void Engine::ClearCaches() { PostTask(bind(&Engine::DoClearCaches, this)); } +void Engine::ClearCaches() { PostBroadcast(bind(&Engine::DoClearCaches, this, _1)); } -void Engine::SetRankPivot(SearchParams const & params, - m2::RectD const & viewport, bool viewportSearch) +void Engine::SetRankPivot(SearchParams const & params, m2::RectD const & viewport, + bool viewportSearch, Query & query) { if (!viewportSearch && params.IsValidPosition()) { m2::PointD const pos = MercatorBounds::FromLatLon(params.m_lat, params.m_lon); if (m2::Inflate(viewport, viewport.SizeX() / 4.0, viewport.SizeY() / 4.0).IsPointInside(pos)) { - m_query->SetRankPivot(pos); + query.SetRankPivot(pos); return; } } - m_query->SetRankPivot(viewport.Center()); + query.SetRankPivot(viewport.Center()); } void Engine::EmitResults(SearchParams const & params, Results const & res) { - params.m_callback(res); + params.m_onResults(res); } -void Engine::MainLoop() +void Engine::MainLoop(Query & query, queue & tasks, queue & broadcast) { while (true) { unique_lock lock(m_mu); - m_cv.wait(lock, [this]() + m_cv.wait(lock, [&]() { - return m_shutdown || !m_tasks.empty(); + return m_shutdown || !tasks.empty() || !broadcast.empty(); }); if (m_shutdown) break; - function task(move(m_tasks.front())); - m_tasks.pop(); + TTask task; + if (!broadcast.empty()) + { + task = move(broadcast.front()); + broadcast.pop(); + } + else + { + task = move(tasks.front()); + tasks.pop(); + } + lock.unlock(); - task(); + task(query); } } -void Engine::PostTask(function && task) +void Engine::PostTask(TTask && task) { lock_guard lock(m_mu); m_tasks.push(move(task)); m_cv.notify_one(); } +void Engine::PostBroadcast(TTask const & task) +{ + lock_guard lock(m_mu); + for (auto & pool : m_broadcast) + pool.push(task); + m_cv.notify_all(); +} + void Engine::DoSearch(SearchParams const & params, m2::RectD const & viewport, - shared_ptr handle) + shared_ptr handle, Query & query) { bool const viewportSearch = params.GetMode() == Mode::Viewport; // Initialize query. - m_query->Init(viewportSearch); - handle->Attach(*m_query); + query.Init(viewportSearch); + handle->Attach(query); MY_SCOPE_GUARD(detach, [&handle] { handle->Detach(); }); // Early exit when query is cancelled. - if (m_query->IsCancelled()) + if (query.IsCancelled()) { - params.m_callback(Results::GetEndMarker(true /* isCancelled */)); + params.m_onResults(Results::GetEndMarker(true /* isCancelled */)); return; } - SetRankPivot(params, viewport, viewportSearch); + SetRankPivot(params, viewport, viewportSearch, query); if (params.IsValidPosition()) - m_query->SetPosition(MercatorBounds::FromLatLon(params.m_lat, params.m_lon)); + query.SetPosition(MercatorBounds::FromLatLon(params.m_lat, params.m_lon)); else - m_query->SetPosition(viewport.Center()); + query.SetPosition(viewport.Center()); - m_query->SetMode(params.GetMode()); + query.SetMode(params.GetMode()); // This flag is needed for consistency with old search algorithm // only. It will be gone when we remove old search code. - m_query->SetSearchInWorld(true); + query.SetSearchInWorld(true); - m_query->SetInputLocale(params.m_inputLocale); + query.SetInputLocale(params.m_inputLocale); ASSERT(!params.m_query.empty(), ()); - m_query->SetQuery(params.m_query); + query.SetQuery(params.m_query); Results res; - m_query->SearchCoordinates(res); + query.SearchCoordinates(res); try { + if (params.m_onStarted) + params.m_onStarted(); + if (viewportSearch) { - m_query->SetViewport(viewport, true /* forceUpdate */); - m_query->SearchViewportPoints(res); + query.SetViewport(viewport, true /* forceUpdate */); + query.SearchViewportPoints(res); } else { - m_query->SetViewport(viewport, params.IsSearchAroundPosition() /* forceUpdate */); - m_query->Search(res, kResultsCount); + query.SetViewport(viewport, params.IsSearchAroundPosition() /* forceUpdate */); + query.Search(res, kResultsCount); } EmitResults(params, res); @@ -258,14 +303,14 @@ void Engine::DoSearch(SearchParams const & params, m2::RectD const & viewport, LOG(LDEBUG, ("Search has been cancelled.")); } - if (!viewportSearch && !m_query->IsCancelled()) + if (!viewportSearch && !query.IsCancelled()) SendStatistics(params, viewport, res); // Emit finish marker to client. - params.m_callback(Results::GetEndMarker(m_query->IsCancelled())); + params.m_onResults(Results::GetEndMarker(query.IsCancelled())); } -void Engine::DoSupportOldFormat(bool support) { m_query->SupportOldFormat(support); } +void Engine::DoSupportOldFormat(bool support, Query & query) { query.SupportOldFormat(support); } -void Engine::DoClearCaches() { m_query->ClearCaches(); } +void Engine::DoClearCaches(Query & query) { query.ClearCaches(); } } // namespace search diff --git a/search/search_engine.hpp b/search/search_engine.hpp index cd464d09df..29f9a7b74e 100644 --- a/search/search_engine.hpp +++ b/search/search_engine.hpp @@ -22,6 +22,7 @@ #include "std/queue.hpp" #include "std/string.hpp" #include "std/unique_ptr.hpp" +#include "std/vector.hpp" #include "std/weak_ptr.hpp" class Index; @@ -77,9 +78,23 @@ private: class Engine { public: + struct Params + { + Params(); + Params(string const & locale, size_t numThreads); + + string m_locale; + + // This field controls number of threads SearchEngine will create + // to process queries. Use this field wisely as large values may + // negatively affect performance due to false sharing. + size_t m_numThreads; + }; + // Doesn't take ownership of index. Takes ownership of categoriesR. - Engine(Index & index, CategoriesHolder const & categories, storage::CountryInfoGetter const & infoGetter, - string const & locale, unique_ptr && factory); + Engine(Index & index, CategoriesHolder const & categories, + storage::CountryInfoGetter const & infoGetter, unique_ptr factory, + Params const & params); ~Engine(); // Posts search request to the queue and returns its handle. @@ -92,34 +107,46 @@ public: void ClearCaches(); private: - // *ALL* following methods are executed on the m_loop thread. + using TTask = function; - void SetRankPivot(SearchParams const & params, m2::RectD const & viewport, bool viewportSearch); + // *ALL* following methods are executed on the m_threads threads. + void SetRankPivot(SearchParams const & params, m2::RectD const & viewport, bool viewportSearch, + Query & query); void EmitResults(SearchParams const & params, Results const & res); - // This method executes tasks from |m_tasks| in a FIFO manner. - void MainLoop(); + // This method executes tasks from a common pool (|tasks|) in a FIFO + // manner. |broadcast| contains per-thread tasks, but nevertheless + // all necessary synchronization primitives must be used to access + // |tasks| and |broadcast|. + void MainLoop(Query & query, queue & tasks, queue & broadcast); - void PostTask(function && task); + void PostTask(TTask && task); + + void PostBroadcast(TTask const & task); void DoSearch(SearchParams const & params, m2::RectD const & viewport, - shared_ptr handle); + shared_ptr handle, Query & query); - void DoSupportOldFormat(bool support); + void DoSupportOldFormat(bool support, Query & query); - void DoClearCaches(); + void DoClearCaches(Query & query); CategoriesHolder const & m_categories; vector m_suggests; - unique_ptr m_query; - unique_ptr m_factory; - bool m_shutdown; mutex m_mu; condition_variable m_cv; - queue> m_tasks; - threads::SimpleThread m_thread; + + // List of per-thread pools, used to deliver broadcast messages to + // search threads. + vector> m_broadcast; + + // Common pool of queries, used to store search tasks. + queue m_tasks; + + vector> m_queries; + vector m_threads; }; } // namespace search diff --git a/search/search_integration_tests/retrieval_test.cpp b/search/search_integration_tests/retrieval_test.cpp index 917578d830..45423771cd 100644 --- a/search/search_integration_tests/retrieval_test.cpp +++ b/search/search_integration_tests/retrieval_test.cpp @@ -376,7 +376,8 @@ UNIT_TEST(Retrieval_CafeMTV) countries.emplace_back(msk.GetCountryName(), mskViewport); countries.emplace_back(mtv.GetCountryName(), mtvViewport); - TestSearchEngine engine("en", make_unique(countries)); + TestSearchEngine engine(make_unique(countries), + search::Engine::Params()); TEST_EQUAL(MwmSet::RegResult::Success, engine.RegisterMap(msk).second, ()); TEST_EQUAL(MwmSet::RegResult::Success, engine.RegisterMap(mtv).second, ()); TEST_EQUAL(MwmSet::RegResult::Success, engine.RegisterMap(testWorld).second, ()); diff --git a/search/search_integration_tests/search_query_v2_test.cpp b/search/search_integration_tests/search_query_v2_test.cpp index 7a62272db5..25a9f8a1f1 100644 --- a/search/search_integration_tests/search_query_v2_test.cpp +++ b/search/search_integration_tests/search_query_v2_test.cpp @@ -56,8 +56,8 @@ public: SearchQueryV2Test() : m_platform(GetPlatform()) , m_scopedLog(LDEBUG) - , m_engine("en", make_unique(), - make_unique()) + , m_engine(make_unique(), + make_unique(), search::Engine::Params()) { } diff --git a/search/search_integration_tests/smoke_test.cpp b/search/search_integration_tests/smoke_test.cpp index 9e18f0aca1..b112da0ab9 100644 --- a/search/search_integration_tests/smoke_test.cpp +++ b/search/search_integration_tests/smoke_test.cpp @@ -71,7 +71,7 @@ UNIT_TEST(GenerateTestMwm_Smoke) } TEST_EQUAL(MapOptions::MapWithCarRouting, file.GetFiles(), ()); - TestSearchEngine engine("en" /* locale */); + TestSearchEngine engine{Engine::Params{}}; auto ret = engine.RegisterMap(file); TEST_EQUAL(MwmSet::RegResult::Success, ret.second, ("Can't register generated map.")); TEST(ret.first.IsAlive(), ("Can't get lock on a generated map.")); @@ -111,7 +111,7 @@ UNIT_TEST(GenerateTestMwm_NotPrefixFreeNames) } TEST_EQUAL(MapOptions::MapWithCarRouting, file.GetFiles(), ()); - TestSearchEngine engine("en" /* locale */); + TestSearchEngine engine{Engine::Params{}}; auto ret = engine.RegisterMap(file); TEST_EQUAL(MwmSet::RegResult::Success, ret.second, ("Can't register generated map.")); TEST(ret.first.IsAlive(), ("Can't get lock on a generated map.")); diff --git a/search/search_quality_tests/search_quality_tests.cpp b/search/search_quality_tests/search_quality_tests.cpp index 112a4d0441..6d08bb2313 100644 --- a/search/search_quality_tests/search_quality_tests.cpp +++ b/search/search_quality_tests/search_quality_tests.cpp @@ -44,6 +44,7 @@ using namespace search::tests_support; DEFINE_string(data_path, "", "Path to data directory (resources dir)"); DEFINE_string(locale, "en", "Locale of all the search queries"); +DEFINE_int32(num_threads, 1, "Number of search engine threads"); DEFINE_string(mwm_list_path, "", "Path to a file containing the names of available mwms, one per line"); DEFINE_string(mwm_path, "", "Path to mwm files (writable dir)"); DEFINE_string(queries_path, "", "Path to the file with queries"); @@ -70,6 +71,8 @@ class SearchQueryV2Factory : public search::SearchQueryFactory } }; +string MakePrefixFree(string const & query) { return query + " "; } + void ReadStringsFromFile(string const & path, vector & result) { ifstream stream(path.c_str()); @@ -176,7 +179,10 @@ int main(int argc, char * argv[]) classificator::Load(); - TestSearchEngine engine(FLAGS_locale, make_unique()); + search::Engine::Params params; + params.m_locale = FLAGS_locale; + params.m_numThreads = FLAGS_num_threads; + TestSearchEngine engine(make_unique(), params); vector mwms; if (!FLAGS_mwm_list_path.empty()) @@ -224,18 +230,21 @@ int main(int argc, char * argv[]) queriesPath = my::JoinFoldersToPath(platform.WritableDir(), kDefaultQueriesPathSuffix); ReadStringsFromFile(queriesPath, queries); - vector responseTimes(queries.size()); + vector> requests; for (size_t i = 0; i < queries.size(); ++i) { // todo(@m) Add a bool flag to search with prefixes? - string const & query = queries[i] + " "; - my::Timer timer; - // todo(@m) Viewport and position should belong to the query info. - TestSearchRequest request(engine, query, FLAGS_locale, search::Mode::Everywhere, viewport); - request.Wait(); + requests.emplace_back(make_unique( + engine, MakePrefixFree(queries[i]), FLAGS_locale, search::Mode::Everywhere, viewport)); + } - responseTimes[i] = timer.ElapsedSeconds(); - PrintTopResults(query, request.Results(), FLAGS_top, responseTimes[i]); + vector responseTimes(queries.size()); + for (size_t i = 0; i < queries.size(); ++i) + { + requests[i]->Wait(); + auto rt = duration_cast(requests[i]->ResponseTime()).count(); + responseTimes[i] = static_cast(rt) / 1000; + PrintTopResults(MakePrefixFree(queries[i]), requests[i]->Results(), FLAGS_top, responseTimes[i]); } double averageTime; diff --git a/search/search_tests_support/test_search_engine.cpp b/search/search_tests_support/test_search_engine.cpp index a08df838bf..e3615152f1 100644 --- a/search/search_tests_support/test_search_engine.cpp +++ b/search/search_tests_support/test_search_engine.cpp @@ -50,38 +50,37 @@ namespace search { namespace tests_support { -TestSearchEngine::TestSearchEngine(string const & locale) +TestSearchEngine::TestSearchEngine(Engine::Params const & params) : m_platform(GetPlatform()) , m_infoGetter(storage::CountryInfoReader::CreateCountryInfoReader(m_platform)) - , m_engine(*this, GetDefaultCategories(), *m_infoGetter, locale, - make_unique()) + , m_engine(*this, GetDefaultCategories(), *m_infoGetter, make_unique(), + params) { } -TestSearchEngine::TestSearchEngine(string const & locale, - unique_ptr infoGetter) +TestSearchEngine::TestSearchEngine(unique_ptr infoGetter, + Engine::Params const & params) : m_platform(GetPlatform()) , m_infoGetter(move(infoGetter)) - , m_engine(*this, GetDefaultCategories(), *m_infoGetter, locale, - make_unique()) + , m_engine(*this, GetDefaultCategories(), *m_infoGetter, make_unique(), + params) { } -TestSearchEngine::TestSearchEngine(string const & locale, - unique_ptr infoGetter, - unique_ptr<::search::SearchQueryFactory> factory) +TestSearchEngine::TestSearchEngine(unique_ptr infoGetter, + unique_ptr<::search::SearchQueryFactory> factory, + Engine::Params const & params) : m_platform(GetPlatform()) , m_infoGetter(move(infoGetter)) - , m_engine(*this, GetDefaultCategories(), *m_infoGetter, locale, - move(factory)) + , m_engine(*this, GetDefaultCategories(), *m_infoGetter, move(factory), params) { } -TestSearchEngine::TestSearchEngine(string const & locale, - unique_ptr<::search::SearchQueryFactory> factory) +TestSearchEngine::TestSearchEngine(unique_ptr<::search::SearchQueryFactory> factory, + Engine::Params const & params) : m_platform(GetPlatform()) , m_infoGetter(storage::CountryInfoReader::CreateCountryInfoReader(m_platform)) - , m_engine(*this, GetDefaultCategories(), *m_infoGetter, locale, move(factory)) + , m_engine(*this, GetDefaultCategories(), *m_infoGetter, move(factory), params) { } diff --git a/search/search_tests_support/test_search_engine.hpp b/search/search_tests_support/test_search_engine.hpp index 8c6b5f9e1e..e1ce01a6ac 100644 --- a/search/search_tests_support/test_search_engine.hpp +++ b/search/search_tests_support/test_search_engine.hpp @@ -25,13 +25,13 @@ namespace tests_support class TestSearchEngine : public Index { public: - TestSearchEngine(string const & locale); - TestSearchEngine(string const & locale, unique_ptr infoGetter); - TestSearchEngine(string const & locale, unique_ptr infoGetter, - unique_ptr factory); - TestSearchEngine(string const & locale, unique_ptr<::search::SearchQueryFactory> factory); - - ~TestSearchEngine(); + TestSearchEngine(Engine::Params const & params); + TestSearchEngine(unique_ptr infoGetter, + Engine::Params const & params); + TestSearchEngine(unique_ptr infoGetter, + unique_ptr factory, Engine::Params const & params); + TestSearchEngine(unique_ptr<::search::SearchQueryFactory> factory, Engine::Params const & params); + ~TestSearchEngine() override; weak_ptr Search(search::SearchParams const & params, m2::RectD const & viewport); diff --git a/search/search_tests_support/test_search_request.cpp b/search/search_tests_support/test_search_request.cpp index d218596155..512c7ae478 100644 --- a/search/search_tests_support/test_search_request.cpp +++ b/search/search_tests_support/test_search_request.cpp @@ -16,10 +16,8 @@ TestSearchRequest::TestSearchRequest(TestSearchEngine & engine, string const & q search::SearchParams params; params.m_query = query; params.m_inputLocale = locale; - params.m_callback = [this](search::Results const & results) - { - Done(results); - }; + params.m_onStarted = bind(&TestSearchRequest::OnStarted, this); + params.m_onResults = bind(&TestSearchRequest::OnResults, this, _1); params.SetMode(mode); engine.Search(params, viewport); } @@ -33,19 +31,19 @@ void TestSearchRequest::Wait() }); } -vector const & TestSearchRequest::Results() const +void TestSearchRequest::OnStarted() { lock_guard lock(m_mu); - CHECK(m_done, ("Results can be get only when request will be completed.")); - return m_results; + m_startTime = m_timer.TimeElapsed(); } -void TestSearchRequest::Done(search::Results const & results) +void TestSearchRequest::OnResults(search::Results const & results) { lock_guard lock(m_mu); if (results.IsEndMarker()) { m_done = true; + m_endTime = m_timer.TimeElapsed(); m_cv.notify_one(); } else diff --git a/search/search_tests_support/test_search_request.hpp b/search/search_tests_support/test_search_request.hpp index ecad10c83e..49dcd0cbe6 100644 --- a/search/search_tests_support/test_search_request.hpp +++ b/search/search_tests_support/test_search_request.hpp @@ -10,6 +10,8 @@ #include "std/string.hpp" #include "std/vector.hpp" +#include "base/timer.hpp" + namespace search { namespace tests_support @@ -27,16 +29,23 @@ public: void Wait(); - vector const & Results() const; + // Call these functions only after call to Wait(). + inline steady_clock::duration ResponseTime() const { return m_endTime - m_startTime; } + inline vector const & Results() const { return m_results; } private: - void Done(search::Results const & results); + void OnStarted(); + void OnResults(search::Results const & results); condition_variable m_cv; mutable mutex m_mu; vector m_results; bool m_done; + + my::Timer m_timer; + steady_clock::duration m_startTime; + steady_clock::duration m_endTime; }; } // namespace tests_support } // namespace search