49#include <wx/cmdline.h>
50#include <wx/filename.h>
89 void MacOpenFile(
const wxString& aFileName )
override {}
97enum class RULES_VARIANT
115 RULES_VARIANT rulesVariant = RULES_VARIANT::DEFAULT;
116 CACHE_MODE cache = CACHE_MODE::COLD;
125 double compileMs = 0.0;
126 double checkMs = 0.0;
127 double cacheGenMs = 0.0;
129 std::map<wxString, double> providerMs;
130 bool timedOut =
false;
131 double fraction = 1.0;
143STAT computeStat( std::vector<double> aValues )
147 if( aValues.empty() )
150 std::sort( aValues.begin(), aValues.end() );
152 size_t n = aValues.size();
153 stat.median = ( n % 2 ) ? aValues[n / 2] : 0.5 * ( aValues[n / 2 - 1] + aValues[n / 2] );
155 std::vector<double> devs;
158 for(
double v : aValues )
159 devs.push_back( std::fabs( v - stat.median ) );
161 std::sort( devs.begin(), devs.end() );
162 stat.mad = ( n % 2 ) ? devs[n / 2] : 0.5 * ( devs[n / 2 - 1] + devs[n / 2] );
173double readOneMinuteLoad()
175 std::ifstream in(
"/proc/loadavg" );
194void applyThreadConfig(
int aThreads )
196 size_t n = aThreads > 0 ?
static_cast<size_t>( aThreads ) : 0;
208wxFileName resolveRules(
const wxFileName& aBoardName,
const std::optional<wxString>& aDefaultRules,
209 const std::optional<wxString>& aHeavyRules, RULES_VARIANT aVariant )
211 if( aVariant == RULES_VARIANT::NONE )
214 if( aVariant == RULES_VARIANT::HEAVY )
217 return wxFileName( *aHeavyRules );
223 return wxFileName( *aDefaultRules );
225 wxFileName sidecar( aBoardName );
228 if( sidecar.Exists() )
240std::unique_ptr<BOARD> loadBoard(
const wxFileName& aBoardName,
SETTINGS_MANAGER& aManager,
241 const wxFileName& aProjectName )
243 std::unique_ptr<BOARD> board;
248 std::string( aBoardName.GetFullPath().ToUTF8() ) );
252 std::printf(
"error loading board: %s\n",
TO_UTF8( ioe.
What() ) );
258 std::printf(
"error: board failed to load\n" );
262 if( aProjectName.Exists() )
263 board->SetProject( &aManager.
Prj() );
265 board->BuildListOfNets();
266 board->BuildConnectivity();
267 board->GetLengthCalculation()->SynchronizeTuningProfileProperties();
269 if( board->GetProject() )
271 std::unordered_set<wxString>
dummy;
272 board->SynchronizeComponentClasses(
dummy );
286const std::map<wxString, std::vector<int>>& providerErrorCodes()
288 static const std::map<wxString, std::vector<int>> codes = {
335bool applyIsolate(
BOARD* aBoard,
const wxString& aProvider )
337 auto it = providerErrorCodes().find( aProvider );
339 if( it == providerErrorCodes().
end() )
342 std::set<int> keep( it->second.begin(), it->second.end() );
348 if( !keep.count( code ) )
369 explicit BENCH_PROGRESS(
double aTimeoutSec ) :
370 PROGRESS_REPORTER_BASE( 1 ),
371 m_enabled( aTimeoutSec > 0.0 ),
372 m_deadline( std::chrono::steady_clock::now()
373 + std::chrono::duration_cast<std::chrono::steady_clock::duration>(
374 std::chrono::duration<double>( aTimeoutSec ) ) )
378 bool TimedOut()
const {
return m_timedOut.load(); }
381 bool updateUI()
override
383 if( m_enabled && std::chrono::steady_clock::now() >= m_deadline )
385 m_timedOut.store(
true );
386 m_cancelled.store(
true );
396 std::chrono::steady_clock::time_point m_deadline;
397 std::atomic_bool m_timedOut{
false };
401double timeCompile(
BOARD* aBoard,
const wxFileName& aRulesFile )
403 std::shared_ptr<DRC_ENGINE> engine =
409 engine->InitEngine( aRulesFile );
412 return timer.
msecs();
420RUN_SAMPLE timeRun(
BOARD* aBoard,
const wxFileName& aRulesFile,
double aTimeoutSec )
424 std::shared_ptr<DRC_ENGINE> engine =
429 std::atomic<int> violationCount{ 0 };
431 engine->SetViolationHandler(
432 [&](
const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I& aPos,
int aLayer,
433 const std::function<
void(
PCB_MARKER* )>& aCreateMarker )
435 violationCount.fetch_add( 1, std::memory_order_relaxed );
439 engine->InitEngine( aRulesFile );
441 sample.compileMs = compileTimer.
msecs();
445 size_t totalProviders = engine->GetTestProviders().size();
450 wxLog::AddTraceMask( wxT(
"KICAD_DRC_PROFILE" ) );
455 BENCH_PROGRESS progress( aTimeoutSec );
456 engine->SetProgressReporter( &progress );
459 std::chrono::milliseconds runDuration{ 0 };
468 catch(
const std::exception& e )
470 std::printf(
"error during RunTests: %s\n", e.what() );
474 engine->SetProgressReporter(
nullptr );
478 sample.timedOut = progress.TimedOut();
480 if( sample.timedOut )
481 sample.fraction = totalProviders > 0
482 ?
static_cast<double>( sample.providerMs.size() )
483 /
static_cast<double>( totalProviders )
489 double engineTotal = profileLog.
TotalMs();
491 sample.checkMs = engineTotal > 0.0 ? engineTotal :
static_cast<double>( runDuration.count() );
493 double providerSum = 0.0;
495 for(
const auto& [
name, ms] : sample.providerMs )
500 sample.cacheGenMs = std::max( 0.0, sample.checkMs - providerSum );
502 sample.violations = violationCount.load( std::memory_order_relaxed );
513 bool underLoad =
false;
518 bool timedOut =
false;
519 double fraction = 1.0;
520 std::map<wxString, STAT> providerStats;
532SWEEP_RESULT runConfig(
const wxFileName& aBoardName,
SETTINGS_MANAGER& aManager,
533 const wxFileName& aProjectName,
const wxFileName& aRulesFile,
534 const BENCH_CONFIG& aConfig,
const std::optional<wxString>& aIsolate,
535 double aMaxLoad,
double aTimeoutSec )
540 applyThreadConfig( aConfig.threads );
542 std::vector<double> compileSamples;
543 std::vector<double> checkSamples;
544 std::vector<double> cacheSamples;
545 std::map<wxString, std::vector<double>> providerSamples;
547 std::unique_ptr<BOARD> warmBoard;
549 if( aConfig.cache == CACHE_MODE::WARM )
551 warmBoard = loadBoard( aBoardName, aManager, aProjectName );
557 applyIsolate( warmBoard.get(), *aIsolate );
560 for(
int i = 0; i <= aConfig.repeat; ++i )
562 BOARD* board = warmBoard.get();
564 std::unique_ptr<BOARD> coldBoard;
566 if( aConfig.cache == CACHE_MODE::COLD )
568 coldBoard = loadBoard( aBoardName, aManager, aProjectName );
574 applyIsolate( coldBoard.get(), *aIsolate );
576 board = coldBoard.get();
581 if( aMaxLoad > 0.0 && i > 0 )
583 double load = readOneMinuteLoad();
585 if( load > aMaxLoad )
589 RUN_SAMPLE sample = timeRun( board, aRulesFile, aTimeoutSec );
594 if( sample.timedOut )
597 result.fraction = sample.fraction;
599 if( compileSamples.empty() )
600 compileSamples.push_back( sample.compileMs );
608 compileSamples.push_back( sample.compileMs );
609 checkSamples.push_back( sample.checkMs );
610 cacheSamples.push_back( sample.cacheGenMs );
611 result.violations = sample.violations;
613 for(
const auto& [
name, ms] : sample.providerMs )
614 providerSamples[
name].push_back( ms );
619 warmBoard->SetProject(
nullptr );
622 result.compile = computeStat( compileSamples );
623 result.check = computeStat( checkSamples );
624 result.cacheGen = computeStat( cacheSamples );
626 for(
auto& [
name, samples] : providerSamples )
627 result.providerStats[
name] = computeStat( samples );
640STAT runCompileOnly(
const wxFileName& aBoardName,
SETTINGS_MANAGER& aManager,
641 const wxFileName& aProjectName,
const wxFileName& aRulesFile,
int aRepeat )
643 std::unique_ptr<BOARD> board = loadBoard( aBoardName, aManager, aProjectName );
648 std::vector<double> samples;
650 for(
int i = 0; i <= aRepeat; ++i )
652 double ms = timeCompile( board.get(), aRulesFile );
655 samples.push_back( ms );
658 board->SetProject(
nullptr );
660 return computeStat( samples );
664const char* variantName( RULES_VARIANT aVariant )
668 case RULES_VARIANT::NONE:
return "none";
669 case RULES_VARIANT::DEFAULT:
return "default";
670 case RULES_VARIANT::HEAVY:
return "heavy";
677bool parseVariant(
const wxString& aArg, RULES_VARIANT& aVariant )
679 if( aArg == wxT(
"none" ) )
680 aVariant = RULES_VARIANT::NONE;
681 else if( aArg == wxT(
"default" ) )
682 aVariant = RULES_VARIANT::DEFAULT;
683 else if( aArg == wxT(
"heavy" ) )
684 aVariant = RULES_VARIANT::HEAVY;
693wxString slurp(
const wxFileName& aFile )
695 std::ifstream in( aFile.GetFullPath().fn_str() );
698 return wxEmptyString;
700 std::stringstream buffer;
701 buffer << in.rdbuf();
703 return wxString::FromUTF8( buffer.str().c_str() );
708std::string jsonEscape(
const wxString& aStr )
710 std::string utf8( aStr.utf8_str() );
712 out.reserve( utf8.size() + 8 );
718 case '"': out +=
"\\\"";
break;
719 case '\\': out +=
"\\\\";
break;
720 case '\n': out +=
"\\n";
break;
721 case '\r': out +=
"\\r";
break;
722 case '\t': out +=
"\\t";
break;
723 default: out += c;
break;
735 std::set<DRC_CONSTRAINT_T> constraints;
736 std::set<wxString> predicates;
749COVERAGE_ROW collectCoverage(
const wxFileName& aBoardName,
SETTINGS_MANAGER& aManager,
750 const wxFileName& aProjectName,
const wxFileName& aRulesFile )
753 row.board = aBoardName.GetFullName();
755 std::unique_ptr<BOARD> board = loadBoard( aBoardName, aManager, aProjectName );
760 std::shared_ptr<DRC_ENGINE> engine =
761 std::make_shared<DRC_ENGINE>( board.get(), &board->GetDesignSettings() );
763 board->GetDesignSettings().m_DRCEngine = engine;
764 engine->InitEngine( aRulesFile );
768 if( engine->HasRulesForConstraintType( type ) )
769 row.constraints.insert( type );
772 if( aRulesFile.IsOk() && aRulesFile.Exists() )
775 row.predicates.insert( pred );
778 board->SetProject(
nullptr );
788void emitCoverage(
const std::vector<COVERAGE_ROW>& aRows,
const wxString& aOutDir )
790 std::set<DRC_CONSTRAINT_T> coveredConstraints;
791 std::set<wxString> coveredPredicates;
793 for(
const COVERAGE_ROW& row : aRows )
795 coveredConstraints.insert( row.constraints.begin(), row.constraints.end() );
796 coveredPredicates.insert( row.predicates.begin(), row.predicates.end() );
799 std::printf(
"=== coverage matrix ===\n" );
800 std::printf(
"%-28s %s\n",
"board",
"constraints / predicates" );
801 std::printf(
"%-28s %s\n",
"----------------------------",
802 "-------------------------------------" );
804 for(
const COVERAGE_ROW& row : aRows )
818 for(
const wxString& pred : row.predicates )
823 preds += std::string( pred.utf8_str() );
826 std::printf(
"%-28s C[%s] P[%s]\n",
827 static_cast<const char*
>( row.board.utf8_str() ), cons.c_str(),
831 std::vector<const char*> uncoveredConstraints;
835 if( !coveredConstraints.count( type ) )
839 std::vector<wxString> uncoveredPredicates;
843 if( !coveredPredicates.count( pred ) )
844 uncoveredPredicates.push_back( pred );
847 std::printf(
"\nUNCOVERED constraints:" );
849 for(
const char*
name : uncoveredConstraints )
850 std::printf(
" %s",
name );
852 std::printf(
"%s\n", uncoveredConstraints.empty() ?
" (none)" :
"" );
854 std::printf(
"UNCOVERED predicates:" );
856 for(
const wxString& pred : uncoveredPredicates )
857 std::printf(
" %s",
static_cast<const char*
>( pred.utf8_str() ) );
859 std::printf(
"%s\n\n", uncoveredPredicates.empty() ?
" (none)" :
"" );
861 wxFileName outFile( aOutDir, wxT(
"coverage.json" ) );
862 std::ofstream out( outFile.GetFullPath().fn_str() );
867 out <<
"{\n \"boards\": [\n";
869 for(
size_t i = 0; i < aRows.size(); ++i )
871 const COVERAGE_ROW& row = aRows[i];
873 out <<
" {\n \"board\": \"" << jsonEscape( row.board ) <<
"\",\n";
874 out <<
" \"constraints\": [";
884 out <<
"],\n \"predicates\": [";
888 for(
const wxString& pred : row.predicates )
890 out << ( first ?
"" :
", " ) <<
"\"" << jsonEscape( pred ) <<
"\"";
894 out <<
"]\n }" << ( i + 1 < aRows.size() ?
"," :
"" ) <<
"\n";
897 out <<
" ],\n \"uncovered_constraints\": [";
899 for(
size_t i = 0; i < uncoveredConstraints.size(); ++i )
900 out << ( i ?
", " :
"" ) <<
"\"" << uncoveredConstraints[i] <<
"\"";
902 out <<
"],\n \"uncovered_predicates\": [";
904 for(
size_t i = 0; i < uncoveredPredicates.size(); ++i )
905 out << ( i ?
", " :
"" ) <<
"\"" << jsonEscape( uncoveredPredicates[i] ) <<
"\"";
919 double evalOverheadMs = 0.0;
920 bool evalOverheadValid =
false;
922 bool underLoad =
false;
923 bool timedOut =
false;
924 double fraction = 1.0;
925 std::map<wxString, STAT> perProvider;
929void writeResultsJson(
const std::vector<RESULT_ROW>& aRows,
const wxString& aOutDir )
931 wxFileName outFile( aOutDir, wxT(
"results.json" ) );
932 std::ofstream out( outFile.GetFullPath().fn_str() );
939 for(
size_t i = 0; i < aRows.size(); ++i )
941 const RESULT_ROW& row = aRows[i];
944 out <<
" \"board\": \"" << jsonEscape( row.board ) <<
"\",\n";
945 out <<
" \"config\": \"" << jsonEscape( row.config ) <<
"\",\n";
946 out <<
" \"compile_ms\": " << row.compile.median <<
",\n";
947 out <<
" \"compile_mad\": " << row.compile.mad <<
",\n";
948 out <<
" \"cache_gen_ms\": " << row.cacheGen.median <<
",\n";
949 out <<
" \"cache_gen_mad\": " << row.cacheGen.mad <<
",\n";
950 out <<
" \"check_ms\": " << row.check.median <<
",\n";
951 out <<
" \"check_mad\": " << row.check.mad <<
",\n";
953 if( row.evalOverheadValid )
954 out <<
" \"eval_overhead_ms\": " << row.evalOverheadMs <<
",\n";
956 out <<
" \"eval_overhead_ms\": null,\n";
958 out <<
" \"n_violations\": " << row.violations <<
",\n";
959 out <<
" \"under_load\": " << ( row.underLoad ?
"true" :
"false" ) <<
",\n";
960 out <<
" \"timed_out\": " << ( row.timedOut ?
"true" :
"false" ) <<
",\n";
961 out <<
" \"percent_complete\": " << ( row.timedOut ? row.fraction * 100.0 : 100.0 )
963 out <<
" \"per_provider\": {";
967 for(
const auto& [
name, stat] : row.perProvider )
969 out << ( first ?
"\n" :
",\n" );
970 out <<
" \"" << jsonEscape(
name ) <<
"\": { \"median_ms\": " << stat.median
971 <<
", \"mad_ms\": " << stat.mad <<
" }";
975 out << ( first ?
"}" :
"\n }" ) <<
"\n";
976 out <<
" }" << ( i + 1 < aRows.size() ?
"," :
"" ) <<
"\n";
988void writeWorstOffenders(
const std::vector<RESULT_ROW>& aRows,
const wxString& aOutDir,
int aTopN )
990 std::vector<const RESULT_ROW*> byCompile;
991 std::vector<const RESULT_ROW*> byEval;
992 std::vector<const RESULT_ROW*> timedOut;
994 for(
const RESULT_ROW& row : aRows )
996 byCompile.push_back( &row );
998 if( row.evalOverheadValid )
999 byEval.push_back( &row );
1002 timedOut.push_back( &row );
1005 std::sort( byCompile.begin(), byCompile.end(),
1006 [](
const RESULT_ROW* a,
const RESULT_ROW* b )
1008 return a->compile.median > b->compile.median;
1011 std::sort( byEval.begin(), byEval.end(),
1012 [](
const RESULT_ROW* a,
const RESULT_ROW* b )
1014 return a->evalOverheadMs > b->evalOverheadMs;
1019 std::sort( timedOut.begin(), timedOut.end(),
1020 [](
const RESULT_ROW* a,
const RESULT_ROW* b )
1022 return a->fraction < b->fraction;
1025 auto emitList = [&](
const char* aLabel )
1027 std::printf(
"=== worst offenders by %s ===\n", aLabel );
1028 std::printf(
"%-28s %-22s %12s\n",
"board",
"config", aLabel );
1029 std::printf(
"%-28s %-22s %12s\n",
"----------------------------",
1030 "----------------------",
"------------" );
1033 emitList(
"compile_ms" );
1035 for(
int i = 0; i < aTopN && i < static_cast<int>( byCompile.size() ); ++i )
1037 const RESULT_ROW* row = byCompile[i];
1039 std::printf(
"%-28s %-22s %12.3f\n",
static_cast<const char*
>( row->board.utf8_str() ),
1040 static_cast<const char*
>( row->config.utf8_str() ), row->compile.median );
1043 std::printf(
"\n" );
1044 emitList(
"eval_overhead_ms" );
1046 for(
int i = 0; i < aTopN && i < static_cast<int>( byEval.size() ); ++i )
1048 const RESULT_ROW* row = byEval[i];
1050 std::printf(
"%-28s %-22s %12.3f\n",
static_cast<const char*
>( row->board.utf8_str() ),
1051 static_cast<const char*
>( row->config.utf8_str() ), row->evalOverheadMs );
1054 std::printf(
"\n" );
1056 if( !timedOut.empty() )
1058 std::printf(
"=== timed out (eval unbounded; ranked least-complete first) ===\n" );
1059 std::printf(
"%-28s %-22s %12s\n",
"board",
"config",
"percent" );
1060 std::printf(
"%-28s %-22s %12s\n",
"----------------------------",
1061 "----------------------",
"------------" );
1063 for(
const RESULT_ROW* row : timedOut )
1065 std::printf(
"%-28s %-22s %11.1f%%\n",
1066 static_cast<const char*
>( row->board.utf8_str() ),
1067 static_cast<const char*
>( row->config.utf8_str() ), row->fraction * 100.0 );
1070 std::printf(
"\n" );
1073 wxFileName outFile( aOutDir, wxT(
"worst_offenders.json" ) );
1074 std::ofstream out( outFile.GetFullPath().fn_str() );
1076 if( !out.is_open() )
1079 auto writeRanked = [&](
const char* aKey,
const std::vector<const RESULT_ROW*>& aList,
1082 out <<
" \"" << aKey <<
"\": [\n";
1084 int count = std::min<int>( aTopN,
static_cast<int>( aList.size() ) );
1086 for(
int i = 0; i < count; ++i )
1088 const RESULT_ROW* row = aList[i];
1089 double value = aUseEval ? row->evalOverheadMs : row->compile.median;
1091 out <<
" { \"board\": \"" << jsonEscape( row->board ) <<
"\", \"config\": \""
1092 << jsonEscape( row->config ) <<
"\", \"" << ( aUseEval ?
"eval_overhead_ms"
1094 <<
"\": " << value <<
" }" << ( i + 1 < count ?
"," :
"" ) <<
"\n";
1101 writeRanked(
"by_compile_ms", byCompile,
false );
1103 writeRanked(
"by_eval_overhead_ms", byEval,
true );
1106 out <<
" \"timed_out\": [\n";
1108 for(
size_t i = 0; i < timedOut.size(); ++i )
1110 const RESULT_ROW* row = timedOut[i];
1112 out <<
" { \"board\": \"" << jsonEscape( row->board ) <<
"\", \"config\": \""
1113 << jsonEscape( row->config ) <<
"\", \"percent_complete\": " << row->fraction * 100.0
1114 <<
" }" << ( i + 1 < timedOut.size() ?
"," :
"" ) <<
"\n";
1122wxString configTag( CACHE_MODE aCache, RULES_VARIANT aVariant,
int aThreads )
1124 return wxString::Format( wxT(
"%s/%s/t%d" ), aCache == CACHE_MODE::COLD ?
"cold" :
"warm",
1125 variantName( aVariant ), aThreads );
1133 wxInitialize( argc, argv );
1137 std::setlocale( LC_ALL,
"C" );
1141 std::setvbuf( stdout,
nullptr, _IOLBF, 0 );
1145 for(
int i = 1; i < argc; ++i )
1147 if( std::string( argv[i] ) ==
"--selftest" )
1162 static const wxCmdLineEntryDesc cmdLineDesc[] = {
1163 { wxCMD_LINE_SWITCH,
nullptr,
"selftest",
"run the trace-parser self-check and exit",
1164 wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
1165 { wxCMD_LINE_OPTION,
nullptr,
"board",
"ad-hoc board override (skips the corpus manifest)",
1166 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1167 { wxCMD_LINE_OPTION,
"r",
"rules",
"default-variant design rules file (.kicad_dru)",
1168 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1169 { wxCMD_LINE_OPTION,
nullptr,
"heavy-rules",
"heavy-variant design rules file (.kicad_dru)",
1170 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1171 { wxCMD_LINE_OPTION,
nullptr,
"rules-variant",
1172 "none|default|heavy (default: sweep all three)", wxCMD_LINE_VAL_STRING,
1173 wxCMD_LINE_PARAM_OPTIONAL },
1174 { wxCMD_LINE_SWITCH,
nullptr,
"rules-only",
1175 "time only InitEngine() compile in a loop, no checks", wxCMD_LINE_VAL_NONE,
1176 wxCMD_LINE_PARAM_OPTIONAL },
1177 { wxCMD_LINE_OPTION,
nullptr,
"threads",
"worker threads, 0=all (default 0)",
1178 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
1179 { wxCMD_LINE_OPTION,
nullptr,
"repeat",
"timed repeats after warm-up (default 5)",
1180 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
1181 { wxCMD_LINE_OPTION,
nullptr,
"cache",
"cold|warm|both (default both)",
1182 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1183 { wxCMD_LINE_OPTION,
nullptr,
"isolate",
1184 "ignore every provider but this one to attribute its eval cost",
1185 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1186 { wxCMD_LINE_OPTION,
nullptr,
"top-n",
"worst-offender list length (default 10)",
1187 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
1188 { wxCMD_LINE_OPTION,
nullptr,
"out",
"output directory for JSON artifacts (default cwd)",
1189 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1190 { wxCMD_LINE_OPTION,
nullptr,
"max-load",
1191 "abort when 1-min loadavg exceeds this (default ncores*0.5)", wxCMD_LINE_VAL_STRING,
1192 wxCMD_LINE_PARAM_OPTIONAL },
1193 { wxCMD_LINE_OPTION,
nullptr,
"timeout",
1194 "per-test deadline in seconds; 0 disables (default 60)", wxCMD_LINE_VAL_NUMBER,
1195 wxCMD_LINE_PARAM_OPTIONAL },
1196 { wxCMD_LINE_SWITCH,
nullptr,
"quick",
1197 "fast iteration set: small/synthetic boards, warm none+heavy, repeat 3",
1198 wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
1199 { wxCMD_LINE_OPTION,
nullptr,
"quick-max-mb",
1200 "in --quick, also keep real boards smaller than this many MB (default 0 = synthetic only)",
1201 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
1202 { wxCMD_LINE_PARAM,
nullptr,
nullptr,
"board.kicad_pcb", wxCMD_LINE_VAL_STRING,
1203 wxCMD_LINE_PARAM_OPTIONAL },
1207 wxCmdLineParser parser( argc, argv );
1208 parser.SetDesc( cmdLineDesc );
1209 parser.SetLogo(
"qa_drc_benchmark: time DRC rule compile + expression evaluation" );
1211 if( parser.Parse() != 0 )
1220 auto cleanupExit = [&](
int aCode )
1230 std::vector<CORPUS_ENTRY> entries;
1232 wxString adHocBoard;
1233 bool haveAdHocBoard = parser.Found(
"board", &adHocBoard );
1235 if( !haveAdHocBoard && parser.GetParamCount() > 0 )
1237 adHocBoard = parser.GetParam( 0 );
1238 haveAdHocBoard =
true;
1241 std::optional<wxString> cliDefaultRules;
1244 if( parser.Found(
"r", &rulesArg ) )
1245 cliDefaultRules = rulesArg;
1247 std::optional<wxString> cliHeavyRules;
1250 if( parser.Found(
"heavy-rules", &heavyArg ) )
1251 cliHeavyRules = heavyArg;
1253 if( haveAdHocBoard )
1256 wxFileName board( adHocBoard );
1257 board.MakeAbsolute();
1258 entry.
board = board.GetFullPath();
1260 if( cliDefaultRules )
1262 wxFileName rules( *cliDefaultRules );
1263 rules.MakeAbsolute();
1264 entry.
rules = rules.GetFullPath();
1267 entry.
tier = wxT(
"adhoc" );
1268 entries.push_back( entry );
1274 std::printf(
"KICAD_DRC_BENCH_CORPUS is unset or does not name a directory; "
1275 "nothing to benchmark.\n"
1276 "Set it to a corpus root containing corpus.json, or pass a board "
1277 "with --board <file.kicad_pcb>. Skipping.\n" );
1278 return cleanupExit( 0 );
1285 std::printf(
"error loading corpus: %s\n",
1286 static_cast<const char*
>( loadError.utf8_str() ) );
1287 return cleanupExit( 1 );
1290 if( entries.empty() )
1292 std::printf(
"corpus at %s has no entries; nothing to benchmark.\n",
1293 static_cast<const char*
>(
CORPUS::Root().utf8_str() ) );
1294 return cleanupExit( 0 );
1298 long threadsArg = 0;
1301 if( parser.Found(
"threads", &threadsArg ) )
1302 threads =
static_cast<int>( threadsArg );
1304 bool quick = parser.Found(
"quick" );
1306 double timeoutSec = 60.0;
1307 long timeoutArg = 60;
1309 if( parser.Found(
"timeout", &timeoutArg ) )
1310 timeoutSec =
static_cast<double>( std::max( 0
L, timeoutArg ) );
1314 double quickMaxMb = 0.0;
1315 long quickMaxArg = 0;
1317 if( parser.Found(
"quick-max-mb", &quickMaxArg ) )
1318 quickMaxMb =
static_cast<double>( std::max( 0
L, quickMaxArg ) );
1321 int repeat = quick ? 3 : 5;
1323 if( parser.Found(
"repeat", &repeatArg ) )
1324 repeat = std::max( 1,
static_cast<int>( repeatArg ) );
1329 if( parser.Found(
"top-n", &topNArg ) )
1330 topN = std::max( 1,
static_cast<int>( topNArg ) );
1332 std::optional<wxString> isolate;
1333 wxString isolateArg;
1335 if( parser.Found(
"isolate", &isolateArg ) )
1337 isolate = isolateArg;
1339 if( !providerErrorCodes().count( isolateArg ) )
1340 std::printf(
"warning: --isolate '%s' has no known error codes; the full provider "
1342 static_cast<const char*
>( isolateArg.utf8_str() ) );
1345 wxString outDir = wxFileName::GetCwd();
1348 if( parser.Found(
"out", &outArg ) )
1351 unsigned ncores = std::max( 1u, std::thread::hardware_concurrency() );
1352 double maxLoad = ncores * 0.5;
1353 wxString maxLoadArg;
1355 if( parser.Found(
"max-load", &maxLoadArg ) )
1356 maxLoadArg.ToCDouble( &maxLoad );
1360 double startLoad = readOneMinuteLoad();
1362 if( maxLoad > 0.0 && startLoad > maxLoad )
1364 std::printf(
"aborting: 1-min loadavg %.2f exceeds limit %.2f (cores=%u). "
1365 "Run on an idle machine or raise --max-load.\n",
1366 startLoad, maxLoad, ncores );
1367 return cleanupExit( 2 );
1370 std::vector<CACHE_MODE> cacheModes = { CACHE_MODE::COLD, CACHE_MODE::WARM };
1373 if( parser.Found(
"cache", &cacheArg ) )
1375 if( cacheArg == wxT(
"cold" ) )
1376 cacheModes = { CACHE_MODE::COLD };
1377 else if( cacheArg == wxT(
"warm" ) )
1378 cacheModes = { CACHE_MODE::WARM };
1379 else if( cacheArg != wxT(
"both" ) )
1381 std::printf(
"error: --cache must be cold|warm|both\n" );
1382 return cleanupExit( 1 );
1388 cacheModes = { CACHE_MODE::WARM };
1391 std::vector<RULES_VARIANT> variants = { RULES_VARIANT::NONE, RULES_VARIANT::DEFAULT,
1392 RULES_VARIANT::HEAVY };
1393 wxString variantArg;
1395 if( parser.Found(
"rules-variant", &variantArg ) )
1397 RULES_VARIANT v = RULES_VARIANT::DEFAULT;
1399 if( !parseVariant( variantArg, v ) )
1401 std::printf(
"error: --rules-variant must be none|default|heavy\n" );
1402 return cleanupExit( 1 );
1411 variants = { RULES_VARIANT::NONE, RULES_VARIANT::HEAVY };
1418 if( quick && !haveAdHocBoard )
1420 bool anyFlagged = std::any_of( entries.begin(), entries.end(),
1423 std::vector<CORPUS_ENTRY> kept;
1427 wxULongLong size = wxFileName::GetSize( entry.board );
1428 bool small = quickMaxMb > 0.0 && size != wxInvalidSize
1429 && size.ToDouble() < quickMaxMb * 1.0e6;
1430 bool flagged = anyFlagged ? entry.quick : ( entry.tier == wxT(
"C" ) );
1432 if( flagged || small )
1433 kept.push_back( entry );
1436 entries.swap( kept );
1439 std::printf(
"corpus: %s\n", haveAdHocBoard
1441 :
static_cast<const char*
>(
CORPUS::Root().utf8_str() ) );
1442 std::printf(
"boards: %zu%s\n", entries.size(), quick ?
" (quick set)" :
"" );
1443 std::printf(
"threads: %d (0=all)\n", threads );
1444 std::printf(
"repeat: %d\n", repeat );
1445 std::printf(
"timeout: %.0f s/test%s\n", timeoutSec, timeoutSec > 0.0 ?
"" :
" (disabled)" );
1446 std::printf(
"max-load: %.2f (start %.2f, cores %u)\n", maxLoad, startLoad, ncores );
1449 std::printf(
"isolate: %s\n",
static_cast<const char*
>( isolate->utf8_str() ) );
1451 std::printf(
"out: %s\n\n",
static_cast<const char*
>( outDir.utf8_str() ) );
1454 std::vector<COVERAGE_ROW> coverageRows;
1455 std::vector<RESULT_ROW> resultRows;
1457 bool rulesOnly = parser.Found(
"rules-only" );
1461 wxFileName boardName( entry.board );
1462 wxFileName projectName( boardName );
1470 if( projectName.Exists() )
1475 std::optional<wxString> entryDefaultRules;
1477 if( !entry.rules.IsEmpty() )
1478 entryDefaultRules = entry.rules;
1479 else if( cliDefaultRules )
1480 entryDefaultRules = cliDefaultRules;
1482 std::optional<wxString> entryHeavyRules = cliHeavyRules ? cliHeavyRules : entryDefaultRules;
1484 std::printf(
"### board: %s (tier %s)\n",
1485 static_cast<const char*
>( boardName.GetFullName().utf8_str() ),
1486 static_cast<const char*
>( entry.tier.utf8_str() ) );
1490 wxFileName coverageRules =
1491 resolveRules( boardName, entryDefaultRules, entryHeavyRules, RULES_VARIANT::DEFAULT );
1493 coverageRows.push_back(
1494 collectCoverage( boardName, manager, projectName, coverageRules ) );
1498 std::printf(
"%-10s %14s %12s\n",
"variant",
"compile_med",
"compile_mad" );
1499 std::printf(
"%-10s %14s %12s\n",
"----------",
"--------------",
"------------" );
1501 for( RULES_VARIANT variant : variants )
1503 wxFileName rulesFile =
1504 resolveRules( boardName, entryDefaultRules, entryHeavyRules, variant );
1507 runCompileOnly( boardName, manager, projectName, rulesFile, repeat );
1509 std::printf(
"%-10s %14.3f %12.3f\n", variantName( variant ), compile.median,
1512 RESULT_ROW resultRow;
1513 resultRow.board = boardName.GetFullName();
1514 resultRow.config = configTag( CACHE_MODE::COLD, variant, threads ) + wxT(
"/compile" );
1515 resultRow.compile = compile;
1516 resultRows.push_back( resultRow );
1519 std::printf(
"\n" );
1523 for( CACHE_MODE cache : cacheModes )
1525 const char* cacheLabel = cache == CACHE_MODE::COLD ?
"cold" :
"warm";
1527 std::printf(
"--- cache: %s ---\n", cacheLabel );
1528 std::printf(
"%-10s %12s %10s %12s %12s %12s %10s\n",
"variant",
"compile_ms",
"(mad)",
1529 "cache_gen",
"check_ms",
"eval_ovhd",
"violations" );
1530 std::printf(
"%-10s %12s %10s %12s %12s %12s %10s\n",
"----------",
"------------",
1531 "----------",
"------------",
"------------",
"------------",
1534 std::optional<double> noneCheck;
1536 for( RULES_VARIANT variant : variants )
1539 config.rulesVariant = variant;
1541 config.threads = threads;
1544 wxFileName rulesFile =
1545 resolveRules( boardName, entryDefaultRules, entryHeavyRules, variant );
1547 SWEEP_RESULT
result = runConfig( boardName, manager, projectName, rulesFile,
config,
1548 isolate, maxLoad, timeoutSec );
1558 if( variant == RULES_VARIANT::NONE && !
result.timedOut )
1559 noneCheck =
result.check.median;
1561 RESULT_ROW resultRow;
1562 resultRow.board = boardName.GetFullName();
1563 resultRow.config = configTag( cache, variant, threads );
1564 resultRow.compile =
result.compile;
1565 resultRow.cacheGen =
result.cacheGen;
1566 resultRow.check =
result.check;
1567 resultRow.violations =
result.violations;
1568 resultRow.underLoad =
result.underLoad;
1569 resultRow.timedOut =
result.timedOut;
1570 resultRow.fraction =
result.fraction;
1571 resultRow.perProvider =
result.providerStats;
1577 wxString ovhd = wxT(
"n/a" );
1581 ovhd = wxT(
"timeout" );
1583 else if( variant == RULES_VARIANT::NONE )
1585 resultRow.evalOverheadMs = 0.0;
1586 resultRow.evalOverheadValid =
true;
1587 ovhd = wxT(
"0.000" );
1589 else if( noneCheck )
1591 resultRow.evalOverheadMs =
result.check.median - *noneCheck;
1592 resultRow.evalOverheadValid =
true;
1593 ovhd = wxString::Format( wxT(
"%.3f" ), resultRow.evalOverheadMs );
1596 resultRows.push_back( resultRow );
1600 std::printf(
"%-10s %12.3f %10.3f %12s %12s %12s [TIMEOUT %.0f%%]\n",
1601 variantName( variant ),
result.compile.median,
result.compile.mad,
1602 "-",
"-",
static_cast<const char*
>( ovhd.utf8_str() ),
1603 result.fraction * 100.0 );
1607 std::printf(
"%-10s %12.3f %10.3f %12.3f %12.3f %12s %10d%s\n",
1608 variantName( variant ),
result.compile.median,
result.compile.mad,
1610 static_cast<const char*
>( ovhd.utf8_str() ),
result.violations,
1611 result.underLoad ?
" [UNDER_LOAD]" :
"" );
1615 std::printf(
"\n" );
1619 catch(
const std::exception& e )
1621 std::printf(
"error: board '%s' failed and was skipped: %s\n\n",
1622 static_cast<const char*
>( boardName.GetFullName().utf8_str() ), e.what() );
1627 emitCoverage( coverageRows, outDir );
1628 writeWorstOffenders( resultRows, outDir, topN );
1629 writeResultsJson( resultRows, outDir );
1631 std::printf(
"wrote results.json, worst_offenders.json, coverage.json to %s\n",
1632 static_cast<const char*
>( outDir.utf8_str() ) );
1634 return cleanupExit( rv );
General utilities for PCB file IO for QA programs.
Container for design settings for a BOARD object.
std::map< int, SEVERITY > m_DRCSeverities
std::shared_ptr< DRC_ENGINE > m_DRCEngine
Information pertinent to a Pcbnew printed circuit board.
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
static bool Load(std::vector< CORPUS_ENTRY > &aEntries, wxString &aError)
Parse <root>/corpus.json into resolved entries.
static bool IsConfigured()
True when KICAD_DRC_BENCH_CORPUS is set and names an existing directory.
static wxString Root()
The resolved corpus root, or an empty string when unconfigured.
wxLog chain target that scrapes the engine's "KICAD_DRC_PROFILE" trace channel.
const std::map< wxString, double > & ProviderMs() const
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
Container for data for KiCad programs.
BS::priority_thread_pool & GetThreadPool()
bool InitPgm(bool aHeadless=false, bool aIsUnitTest=false)
Initialize this program.
virtual SETTINGS_MANAGER & GetSettingsManager() const
A small class to help profiling.
void Stop()
Save the time when this function was called, and set the counter stane to stop.
double msecs(bool aSinceLast=false)
This implements all the tricky bits for thread safety, but the GUI is left to derived classes.
Provide class metadata.Helper macro to map type hashes to names.
static PROPERTY_MANAGER & Instance()
void Rebuild()
Rebuild the list of all registered properties.
A simple RAII class to measure the time of an operation.
A scoped application of a wxLog target.
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
const char * ConstraintTypeName(DRC_CONSTRAINT_T aType)
Human-readable token for a DRC_CONSTRAINT_T, matching the .kicad_dru keyword where one exists.
std::vector< wxString > ScanPredicatesInRules(const wxString &aRulesText)
Scan raw .kicad_dru text for occurrences of each registered predicate name.
const std::vector< wxString > & AllPredicateNames()
Every pcbexpr predicate registered in pcbexpr_functions.cpp, used for textual coverage scans.
const std::vector< DRC_CONSTRAINT_T > & AllConstraintTypes()
Every DRC_CONSTRAINT_T the engine can carry rules for, in enum order, excluding NULL_CONSTRAINT.
@ DRCE_DP_UNCOUPLED_LENGTH_TOO_LONG
@ DRCE_SILK_EDGE_CLEARANCE
@ DRCE_SILK_MASK_CLEARANCE
@ DRCE_DP_GAP_OUT_OF_RANGE
@ DRCE_MIRRORED_TEXT_ON_FRONT_LAYER
@ DRCE_OVERLAPPING_FOOTPRINTS
@ DRCE_TRACK_ON_POST_MACHINED_LAYER
@ DRCE_NET_CHAIN_STUB_TOO_LONG
@ DRCE_DRILL_OUT_OF_RANGE
@ DRCE_NET_CHAIN_RETURN_PATH_BREAK
@ DRCE_TRACK_SEGMENT_LENGTH
@ DRCE_TRACK_NOT_CENTERED_ON_VIA
@ DRCE_DRILLED_HOLES_TOO_CLOSE
@ DRCE_MICROVIA_DRILL_OUT_OF_RANGE
@ DRCE_MALFORMED_COURTYARD
@ DRCE_FOOTPRINT_TYPE_MISMATCH
@ DRCE_DRILLED_HOLES_COLOCATED
@ DRCE_LENGTH_OUT_OF_RANGE
@ DRCE_PAD_TH_WITH_NO_HOLE
@ DRCE_UNMIRRORED_TEXT_ON_BACK_LAYER
@ DRCE_VIA_COUNT_OUT_OF_RANGE
static const std::string ProjectFileExtension
static const std::string DesignRulesFileExtension
std::unique_ptr< BOARD > ReadBoardFromFileOrStream(const std::string &aFilename, std::istream &aFallback)
Read a board from a file, or another stream, as appropriate.
std::chrono::duration< double, std::milli > ms
void SetPgm(PGM_BASE *pgm)
PGM_BASE & Pgm()
The global program "get" accessor.
std::vector< FAB_LAYER_COLOR > dummy
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
One board+rules pairing from the out-of-tree corpus manifest.
wxString tier
Free-form tier tag from the manifest (A/B/C).
wxString board
Absolute path to the .kicad_pcb.
wxString rules
Absolute path to the .kicad_dru, or empty for none.
wxString result
Test unit parsing edge cases and error handling.
void InvalidateKiCadThreadPool()
Invalidate the cached thread pool pointer.
int RunTraceCaptureSelftest()
Run the three fixed self-test lines through a fresh parser and confirm the parsed provider map and to...
VECTOR2< int32_t > VECTOR2I
Definition of file extensions used in Kicad.