From 223ae465653456835fc65295a227dcb3507f8679 Mon Sep 17 00:00:00 2001 From: firewave Date: Fri, 27 Mar 2026 10:39:21 +0100 Subject: [PATCH 1/2] refs #14599 - added `OneShotTimer` / moved some `ShowTime` logic out of `Timer` test/cli/test_other.py: added showtime tests with `FileSettings` --- cli/cppcheckexecutor.cpp | 8 +-- lib/cppcheck.cpp | 8 +-- lib/timer.cpp | 42 ++++++++------ lib/timer.h | 23 ++++---- test/cli/other_test.py | 116 ++++++++++++++++++++++++++++++++------- test/cli/testutils.py | 17 ++++++ 6 files changed, 157 insertions(+), 57 deletions(-) diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index 69f3e842785..680f7387c70 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -267,8 +267,9 @@ int CppCheckExecutor::check(int argc, const char* const argv[]) return EXIT_SUCCESS; } - TimerResults overallTimerResults; - Timer realTimeClock("Overall time", settings.showtime, &overallTimerResults, Timer::Type::OVERALL); + std::unique_ptr overallTimer; + if (settings.showtime == ShowTime::SUMMARY || settings.showtime == ShowTime::TOP5_SUMMARY) + overallTimer.reset(new OneShotTimer("Overall time", settings.showtime)); settings.loadSummaries(); @@ -277,9 +278,6 @@ int CppCheckExecutor::check(int argc, const char* const argv[]) const int ret = check_wrapper(settings, supprs); - realTimeClock.stop(); - overallTimerResults.showResults(settings.showtime, false, true); - return ret; } diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 0a8e721936b..db2b2e5091a 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -923,8 +923,9 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str if (Settings::terminated()) return mLogger->exitcode(); - TimerResults checkTimeResults; - Timer fileTotalTimer{"Check time: " + file.spath(), mSettings.showtime, &checkTimeResults, Timer::Type::FILE}; + std::unique_ptr checkTimeTimer; + if (mSettings.showtime == ShowTime::FILE || mSettings.showtime == ShowTime::FILE_TOTAL || mSettings.showtime == ShowTime::TOP5_FILE) + checkTimeTimer.reset(new OneShotTimer("Check time: " + file.spath(), mSettings.showtime)); if (!mSettings.quiet) { std::string fixedpath = Path::toNativeSeparators(file.spath()); @@ -1296,9 +1297,6 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str if (mTimerResults && (mSettings.showtime == ShowTime::FILE || mSettings.showtime == ShowTime::TOP5_FILE)) mTimerResults->showResults(mSettings.showtime); - fileTotalTimer.stop(); - checkTimeResults.showResults(mSettings.showtime, false, true); - return mLogger->exitcode(); } diff --git a/lib/timer.cpp b/lib/timer.cpp index 5a18953767a..495b114fd98 100644 --- a/lib/timer.cpp +++ b/lib/timer.cpp @@ -38,7 +38,7 @@ namespace { // TODO: this does not include any file context when SHOWTIME_FILE thus rendering it useless - should we include the logging with the progress logging? // that could also get rid of the broader locking -void TimerResults::showResults(ShowTime mode, bool metrics, bool format) const +void TimerResults::showResults(ShowTime mode, bool metrics) const { if (mode == ShowTime::NONE) return; @@ -60,11 +60,7 @@ void TimerResults::showResults(ShowTime mode, bool metrics, bool format) const const double sec = iter->second.getSeconds().count(); const double secAverage = sec / static_cast(iter->second.mNumberOfResults); if ((mode != ShowTime::TOP5_FILE && mode != ShowTime::TOP5_SUMMARY) || (ordinal<=5)) { - std::cout << iter->first << ": "; - if (format) - std::cout << TimerResultsData::durationToString(iter->second.mDuration); - else - std::cout << sec << "s"; + std::cout << iter->first << ": " << sec << "s"; if (metrics) std::cout << " (avg. " << secAverage << "s - " << iter->second.mNumberOfResults << " result(s))"; std::cout << std::endl; @@ -87,10 +83,9 @@ void TimerResults::reset() mResults.clear(); } -Timer::Timer(std::string str, ShowTime showtimeMode, TimerResultsIntf* timerResults, Type type) +Timer::Timer(std::string str, ShowTime showtimeMode, TimerResultsIntf* timerResults) : mName(std::move(str)) , mMode(showtimeMode) - , mType(type) , mStart(Clock::now()) , mResults(timerResults) {} @@ -104,14 +99,6 @@ void Timer::stop() { if (mMode == ShowTime::NONE) return; - if (mType == Type::OVERALL && mMode != ShowTime::TOP5_SUMMARY && mMode != ShowTime::SUMMARY) { - mMode = ShowTime::NONE; - return; - } - if (mType == Type::FILE && mMode != ShowTime::TOP5_FILE && mMode != ShowTime::FILE && mMode != ShowTime::FILE_TOTAL) { - mMode = ShowTime::NONE; - return; - } if (mStart != TimePoint{}) { if (!mResults) { assert(false); @@ -124,7 +111,7 @@ void Timer::stop() mMode = ShowTime::NONE; // prevent multiple stops } -std::string TimerResultsData::durationToString(std::chrono::milliseconds duration) +static std::string durationToString(std::chrono::milliseconds duration) { // Extract hours auto hours = std::chrono::duration_cast(duration); @@ -148,3 +135,24 @@ std::string TimerResultsData::durationToString(std::chrono::milliseconds duratio secondsStr.resize(pos + 4); // keep three decimal return (ellapsedTime + secondsStr + "s"); } + +OneShotTimer::OneShotTimer(std::string name, ShowTime showtime) +{ + if (showtime == ShowTime::NONE) + return; + + class MyResults : public TimerResultsIntf + { + private: + void addResults(const std::string &name, std::chrono::milliseconds duration) override + { + std::lock_guard l(stdCoutLock); + + // TODO: do not use std::cout directly + std::cout << name << ": " << durationToString(duration) << std::endl; + } + }; + + mResults.reset(new MyResults); + mTimer.reset(new Timer(std::move(name), showtime, mResults.get())); +} diff --git a/lib/timer.h b/lib/timer.h index e0c4ee2d45f..e6c6833ad54 100644 --- a/lib/timer.h +++ b/lib/timer.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -52,15 +53,13 @@ struct TimerResultsData { std::chrono::duration getSeconds() const { return std::chrono::duration_cast>(mDuration); } - - static std::string durationToString(std::chrono::milliseconds duration); }; class CPPCHECKLIB WARN_UNUSED TimerResults : public TimerResultsIntf { public: TimerResults() = default; - void showResults(ShowTime mode, bool metrics = true, bool format = false) const; + void showResults(ShowTime mode, bool metrics = true) const; void addResults(const std::string& str, std::chrono::milliseconds duration) override; void reset(); @@ -75,13 +74,7 @@ class CPPCHECKLIB Timer { using Clock = std::chrono::high_resolution_clock; using TimePoint = std::chrono::time_point; - enum class Type : std::uint8_t { - FILE, - OVERALL, - OTHER - }; - - Timer(std::string str, ShowTime showtimeMode, TimerResultsIntf* timerResults = nullptr, Type type = Type::OTHER); + Timer(std::string str, ShowTime showtimeMode, TimerResultsIntf* timerResults = nullptr); ~Timer(); Timer(const Timer&) = delete; @@ -98,10 +91,18 @@ class CPPCHECKLIB Timer { private: const std::string mName; ShowTime mMode{}; - Type mType{}; TimePoint mStart; TimerResultsIntf* mResults{}; }; +class CPPCHECKLIB OneShotTimer +{ +public: + OneShotTimer(std::string name, ShowTime showtime); +private: + std::unique_ptr mResults; + std::unique_ptr mTimer; +}; + //--------------------------------------------------------------------------- #endif // timerH diff --git a/test/cli/other_test.py b/test/cli/other_test.py index 5c06bd07a94..58ff68c9da4 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -9,7 +9,7 @@ import subprocess import shutil -from testutils import cppcheck, assert_cppcheck, cppcheck_ex, __lookup_cppcheck_exe +from testutils import cppcheck, assert_cppcheck, cppcheck_ex, __lookup_cppcheck_exe, create_compile_commands from xml.etree import ElementTree @@ -956,10 +956,9 @@ def test_unused_function_include(tmpdir): # TODO: test with clang-tidy # TODO: test with --addon -# TODO: test with FileSettings # TODO: test with multiple files -def __test_showtime(tmp_path, showtime, exp_res, exp_last, extra_args=None): - test_file = tmp_path / 'test.cpp' +def __test_showtime(tmp_path, showtime, exp_res, exp_last, use_compdb, extra_args=None): + test_file = tmp_path / 'test.cpp' # the use of C++ is intentional with open(test_file, 'wt') as f: f.write( """ @@ -972,10 +971,17 @@ def __test_showtime(tmp_path, showtime, exp_res, exp_last, extra_args=None): args = [ f'--showtime={showtime}', '--quiet', - '--inline-suppr', - str(test_file) + '--inline-suppr' ] + if use_compdb: + compdb_file = tmp_path / 'compile_commands.json' + create_compile_commands(compdb_file, [test_file]) + + args.append(f'--project={compdb_file}') + else: + args.append(str(test_file)) + if extra_args: args += extra_args @@ -994,50 +1000,122 @@ def __test_showtime(tmp_path, showtime, exp_res, exp_last, extra_args=None): assert stderr == '' +def __test_showtime_top5_file(tmp_path, use_compdb): + __test_showtime(tmp_path, 'top5_file', 5, 'Check time: ', use_compdb) + + def test_showtime_top5_file(tmp_path): - __test_showtime(tmp_path, 'top5_file', 5, 'Check time: ') + __test_showtime_top5_file(tmp_path, False) + + +def test_showtime_top5_file_compdb(tmp_path): + __test_showtime_top5_file(tmp_path, True) + + +# TODO: remove extra args when --executor=process works +def __test_showtime_top5_summary(tmp_path, use_compdb): + __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', use_compdb, ['-j1']) -# TODO: remove extra args when --executor=process works works def test_showtime_top5_summary(tmp_path): - __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', ['-j1']) + __test_showtime_top5_summary(tmp_path, False) + +def test_showtime_top5_summary_compdb(tmp_path): + __test_showtime_top5_summary(tmp_path, True) -# TODO: remove when --executor=process works works + +# TODO: remove when --executor=process works def test_showtime_top5_summary_j_thread(tmp_path): - __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', ['-j2', '--executor=thread']) + __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', False, ['-j2', '--executor=thread']) + + +# TODO: remove when --executor=process works +def test_showtime_top5_summary_compdb_j_thread(tmp_path): + __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', True, ['-j2', '--executor=thread']) # TODO: remove override when fixed @pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") @pytest.mark.xfail(strict=True) # TODO: need to transfer the timer results to parent process - see #4452 def test_showtime_top5_summary_j_process(tmp_path): - __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', ['-j2', '--executor=process']) + __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', False, ['-j2', '--executor=process']) + + +# TODO: remove override when fixed +@pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") +@pytest.mark.xfail(strict=True) # TODO: need to transfer the timer results to parent process - see #4452 +def test_showtime_top5_summary_compdb_j_process(tmp_path): + __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', True, ['-j2', '--executor=process']) + + +def __test_showtime_file(tmp_path, use_compdb): + exp_res = 79 + # project analysis does not call Preprocessor::getConfig() + if use_compdb: + exp_res -= 1 + __test_showtime(tmp_path, 'file', exp_res, 'Check time: ', use_compdb) def test_showtime_file(tmp_path): - __test_showtime(tmp_path, 'file', 79, 'Check time: ') + __test_showtime_file(tmp_path, False) + + +def test_showtime_file_compdb(tmp_path): + __test_showtime_file(tmp_path, True) + + +# TODO: remove extra args when --executor=process works +def __test_showtime_summary(tmp_path, use_compdb): + exp_res = 79 + # project analysis does not call Preprocessor::getConfig() + if use_compdb: + exp_res -= 1 + __test_showtime(tmp_path, 'summary', exp_res, 'Overall time: ', use_compdb, ['-j1']) -# TODO: remove extra args when --executor=process works works def test_showtime_summary(tmp_path): - __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', ['-j1']) + __test_showtime_summary(tmp_path, False,) + +def test_showtime_summary_compdb(tmp_path): + __test_showtime_summary(tmp_path, True) -# TODO: remove when --executor=process works works + +# TODO: remove when --executor=process works def test_showtime_summary_j_thread(tmp_path): - __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', ['-j2', '--executor=thread']) + __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', False, ['-j2', '--executor=thread']) + + +# TODO: remove when --executor=process works +def test_showtime_summary_compdb_j_thread(tmp_path): + __test_showtime(tmp_path, 'summary', 78, 'Overall time: ', True, ['-j2', '--executor=thread']) # TODO: remove override when fixed @pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") @pytest.mark.xfail(strict=True) # TODO: need to transfer the timer results to parent process - see #4452 def test_showtime_summary_j_process(tmp_path): - __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', ['-j2', '--executor=process']) + __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', False, ['-j2', '--executor=process']) + + +# TODO: remove override when fixed +@pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") +@pytest.mark.xfail(strict=True) # TODO: need to transfer the timer results to parent process - see #4452 +def test_showtime_summary_compdb_j_process(tmp_path): + __test_showtime(tmp_path, 'summary', 78, 'Overall time: ', True, ['-j2', '--executor=process']) + + +def __test_showtime_file_total(tmp_path, use_compdb): + __test_showtime(tmp_path, 'file-total', 0, 'Check time: ', use_compdb) def test_showtime_file_total(tmp_path): - __test_showtime(tmp_path, 'file-total', 0, 'Check time: ') + __test_showtime_file_total(tmp_path, False) + + +def test_showtime_file_total_compdb(tmp_path): + __test_showtime_file_total(tmp_path, True) def test_showtime_unique(tmp_path): diff --git a/test/cli/testutils.py b/test/cli/testutils.py index f352af49f15..2140c42205b 100644 --- a/test/cli/testutils.py +++ b/test/cli/testutils.py @@ -5,6 +5,7 @@ import subprocess import time import tempfile +import json # Create Cppcheck project file import sys @@ -45,6 +46,22 @@ def create_gui_project_file(project_file, root_path=None, import_project=None, p f.write(cppcheck_xml) +def create_compile_commands(compdb_file, files): + compdb = [] + # TODO: bail out on empty list + for f in files: + compdb.append( + { + "file": str(f), + "directory": os.path.dirname(f), + "command": "gcc -c {}".format(os.path.basename(f)) + } + ) + compdb_j = json.dumps(compdb) + with open(compdb_file, 'wt') as f: + f.write(compdb_j) + + def __lookup_cppcheck_exe(): # path the script is located in script_path = os.path.dirname(os.path.realpath(__file__)) From d7d9850bbb962c80efe8fc9ef1a17adc366d25e6 Mon Sep 17 00:00:00 2001 From: firewave Date: Sat, 28 Mar 2026 08:48:53 +0100 Subject: [PATCH 2/2] fixed #14618 - added more metrics to timer summary / small `TimerResultsData` refactoring [skip ci] --- lib/timer.cpp | 16 +++++++++------- lib/timer.h | 17 ++++++++++++----- test/options.cpp | 4 +--- test/testtimer.cpp | 2 +- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/timer.cpp b/lib/timer.cpp index 495b114fd98..f4025fb3427 100644 --- a/lib/timer.cpp +++ b/lib/timer.cpp @@ -57,24 +57,26 @@ void TimerResults::showResults(ShowTime mode, bool metrics) const size_t ordinal = 1; // maybe it would be nice to have an ordinal in output later! for (auto iter=data.cbegin(); iter!=data.cend(); ++iter) { - const double sec = iter->second.getSeconds().count(); - const double secAverage = sec / static_cast(iter->second.mNumberOfResults); if ((mode != ShowTime::TOP5_FILE && mode != ShowTime::TOP5_SUMMARY) || (ordinal<=5)) { + const double sec = iter->second.getSeconds().count(); std::cout << iter->first << ": " << sec << "s"; - if (metrics) - std::cout << " (avg. " << secAverage << "s - " << iter->second.mNumberOfResults << " result(s))"; + if (metrics) { + const double secAverage = sec / static_cast(iter->second.mResults.size()); + const double secMin = asSeconds(*std::min_element(iter->second.mResults.cbegin(), iter->second.mResults.cend())).count(); + const double secMax = asSeconds(*std::max_element(iter->second.mResults.cbegin(), iter->second.mResults.cend())).count(); + std::cout << " (avg. " << secAverage << "s / min " << secMin << "s / max " << secMax << "s - " << iter->second.mResults.size() << " result(s))"; + } std::cout << std::endl; } ++ordinal; } } -void TimerResults::addResults(const std::string& str, std::chrono::milliseconds duration) +void TimerResults::addResults(const std::string& name, std::chrono::milliseconds duration) { std::lock_guard l(mResultsSync); - mResults[str].mDuration += duration; - mResults[str].mNumberOfResults++; + mResults[name].mResults.push_back(duration); } void TimerResults::reset() diff --git a/lib/timer.h b/lib/timer.h index e6c6833ad54..5611bc5304b 100644 --- a/lib/timer.h +++ b/lib/timer.h @@ -27,8 +27,10 @@ #include #include #include +#include #include #include +#include enum class ShowTime : std::uint8_t { NONE, @@ -43,15 +45,20 @@ class CPPCHECKLIB TimerResultsIntf { public: virtual ~TimerResultsIntf() = default; - virtual void addResults(const std::string& timerName, std::chrono::milliseconds duation) = 0; + virtual void addResults(const std::string& name, std::chrono::milliseconds duration) = 0; + + static std::chrono::duration asSeconds(std::chrono::milliseconds ms) { + return std::chrono::duration_cast>(ms); + } }; struct TimerResultsData { - std::chrono::milliseconds mDuration; - long mNumberOfResults{}; + std::vector mResults; std::chrono::duration getSeconds() const { - return std::chrono::duration_cast>(mDuration); + return std::accumulate(mResults.cbegin(), mResults.cend(), std::chrono::duration{}, [](std::chrono::duration secs, std::chrono::milliseconds duration) { + return secs + TimerResultsIntf::asSeconds(duration); + }); } }; @@ -60,7 +67,7 @@ class CPPCHECKLIB WARN_UNUSED TimerResults : public TimerResultsIntf { TimerResults() = default; void showResults(ShowTime mode, bool metrics = true) const; - void addResults(const std::string& str, std::chrono::milliseconds duration) override; + void addResults(const std::string& name, std::chrono::milliseconds duration) override; void reset(); diff --git a/test/options.cpp b/test/options.cpp index bdb4f553ac8..9db2bce8563 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -59,10 +59,8 @@ options::options(int argc, const char* const argv[]) options::~options() { // TODO: allow more than 5 results to be shown - // TODO: provide higher resolution in output - // TODO: disable the metrics if (mTimerResults) - mTimerResults->showResults(ShowTime::TOP5_FILE); + mTimerResults->showResults(ShowTime::TOP5_FILE, false); } bool options::quiet() const diff --git a/test/testtimer.cpp b/test/testtimer.cpp index c8d468b1afb..3f160c0c2b7 100644 --- a/test/testtimer.cpp +++ b/test/testtimer.cpp @@ -33,7 +33,7 @@ class TestTimer : public TestFixture { void result() const { TimerResultsData t1; - t1.mDuration = std::chrono::milliseconds{1234}; + t1.mResults.emplace_back(1234); ASSERT(t1.getSeconds().count() > 1.233 && t1.getSeconds().count() < 1.235); // TODO : more tests