49#include <wx/cmdline.h>
50#include <wx/filename.h>
88 void MacOpenFile(
const wxString& aFileName )
override {}
96enum class RULES_VARIANT
114 RULES_VARIANT rulesVariant = RULES_VARIANT::DEFAULT;
115 CACHE_MODE cache = CACHE_MODE::COLD;
124 double compileMs = 0.0;
125 double checkMs = 0.0;
126 double cacheGenMs = 0.0;
128 std::map<wxString, double> providerMs;
129 bool timedOut =
false;
130 double fraction = 1.0;
142STAT computeStat( std::vector<double> aValues )
146 if( aValues.empty() )
149 std::sort( aValues.begin(), aValues.end() );
151 size_t n = aValues.size();
152 stat.median = ( n % 2 ) ? aValues[n / 2] : 0.5 * ( aValues[n / 2 - 1] + aValues[n / 2] );
154 std::vector<double> devs;
157 for(
double v : aValues )
158 devs.push_back( std::fabs( v - stat.median ) );
160 std::sort( devs.begin(), devs.end() );
161 stat.mad = ( n % 2 ) ? devs[n / 2] : 0.5 * ( devs[n / 2 - 1] + devs[n / 2] );
172double readOneMinuteLoad()
174 std::ifstream in(
"/proc/loadavg" );
193void applyThreadConfig(
int aThreads )
195 size_t n = aThreads > 0 ?
static_cast<size_t>( aThreads ) : 0;
207wxFileName resolveRules(
const wxFileName& aBoardName,
const std::optional<wxString>& aDefaultRules,
208 const std::optional<wxString>& aHeavyRules, RULES_VARIANT aVariant )
210 if( aVariant == RULES_VARIANT::NONE )
213 if( aVariant == RULES_VARIANT::HEAVY )
216 return wxFileName( *aHeavyRules );
222 return wxFileName( *aDefaultRules );
224 wxFileName sidecar( aBoardName );
227 if( sidecar.Exists() )
239std::unique_ptr<BOARD> loadBoard(
const wxFileName& aBoardName,
SETTINGS_MANAGER& aManager,
240 const wxFileName& aProjectName )
242 std::unique_ptr<BOARD> board;
247 std::string( aBoardName.GetFullPath().ToUTF8() ) );
251 std::printf(
"error loading board: %s\n",
TO_UTF8( ioe.
What() ) );
257 std::printf(
"error: board failed to load\n" );
261 if( aProjectName.Exists() )
262 board->SetProject( &aManager.
Prj() );
264 board->BuildListOfNets();
265 board->BuildConnectivity();
266 board->GetLengthCalculation()->SynchronizeTuningProfileProperties();
268 if( board->GetProject() )
270 std::unordered_set<wxString>
dummy;
271 board->SynchronizeComponentClasses(
dummy );
285const std::map<wxString, std::vector<int>>& providerErrorCodes()
287 static const std::map<wxString, std::vector<int>> codes = {
334bool applyIsolate(
BOARD* aBoard,
const wxString& aProvider )
336 auto it = providerErrorCodes().find( aProvider );
338 if( it == providerErrorCodes().
end() )
341 std::set<int> keep( it->second.begin(), it->second.end() );
347 if( !keep.count( code ) )
368 explicit BENCH_PROGRESS(
double aTimeoutSec ) :
369 PROGRESS_REPORTER_BASE( 1 ),
370 m_enabled( aTimeoutSec > 0.0 ),
371 m_deadline( std::chrono::steady_clock::now()
372 + std::chrono::duration_cast<std::chrono::steady_clock::duration>(
373 std::chrono::duration<double>( aTimeoutSec ) ) )
377 bool TimedOut()
const {
return m_timedOut.load(); }
380 bool updateUI()
override
382 if( m_enabled && std::chrono::steady_clock::now() >= m_deadline )
384 m_timedOut.store(
true );
385 m_cancelled.store(
true );
395 std::chrono::steady_clock::time_point m_deadline;
396 std::atomic_bool m_timedOut{
false };
400double timeCompile(
BOARD* aBoard,
const wxFileName& aRulesFile )
402 std::shared_ptr<DRC_ENGINE> engine =
408 engine->InitEngine( aRulesFile );
411 return timer.
msecs();
419RUN_SAMPLE timeRun(
BOARD* aBoard,
const wxFileName& aRulesFile,
double aTimeoutSec )
423 std::shared_ptr<DRC_ENGINE> engine =
428 std::atomic<int> violationCount{ 0 };
430 engine->SetViolationHandler(
431 [&](
const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I& aPos,
int aLayer,
432 const std::function<
void(
PCB_MARKER* )>& aCreateMarker )
434 violationCount.fetch_add( 1, std::memory_order_relaxed );
438 engine->InitEngine( aRulesFile );
440 sample.compileMs = compileTimer.
msecs();
444 size_t totalProviders = engine->GetTestProviders().size();
449 wxLog::AddTraceMask( wxT(
"KICAD_DRC_PROFILE" ) );
452 wxLog* prevTarget = wxLog::SetActiveTarget( profileLog );
457 BENCH_PROGRESS progress( aTimeoutSec );
458 engine->SetProgressReporter( &progress );
466 catch(
const std::exception& e )
468 std::printf(
"error during RunTests: %s\n", e.what() );
473 engine->SetProgressReporter(
nullptr );
474 wxLog::SetActiveTarget( prevTarget );
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 : checkTimer.
msecs();
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 );
515 bool underLoad =
false;
520 bool timedOut =
false;
521 double fraction = 1.0;
522 std::map<wxString, STAT> providerStats;
534SWEEP_RESULT runConfig(
const wxFileName& aBoardName,
SETTINGS_MANAGER& aManager,
535 const wxFileName& aProjectName,
const wxFileName& aRulesFile,
536 const BENCH_CONFIG& aConfig,
const std::optional<wxString>& aIsolate,
537 double aMaxLoad,
double aTimeoutSec )
542 applyThreadConfig( aConfig.threads );
544 std::vector<double> compileSamples;
545 std::vector<double> checkSamples;
546 std::vector<double> cacheSamples;
547 std::map<wxString, std::vector<double>> providerSamples;
549 std::unique_ptr<BOARD> warmBoard;
551 if( aConfig.cache == CACHE_MODE::WARM )
553 warmBoard = loadBoard( aBoardName, aManager, aProjectName );
559 applyIsolate( warmBoard.get(), *aIsolate );
562 for(
int i = 0; i <= aConfig.repeat; ++i )
564 BOARD* board = warmBoard.get();
566 std::unique_ptr<BOARD> coldBoard;
568 if( aConfig.cache == CACHE_MODE::COLD )
570 coldBoard = loadBoard( aBoardName, aManager, aProjectName );
576 applyIsolate( coldBoard.get(), *aIsolate );
578 board = coldBoard.get();
583 if( aMaxLoad > 0.0 && i > 0 )
585 double load = readOneMinuteLoad();
587 if( load > aMaxLoad )
591 RUN_SAMPLE sample = timeRun( board, aRulesFile, aTimeoutSec );
596 if( sample.timedOut )
599 result.fraction = sample.fraction;
601 if( compileSamples.empty() )
602 compileSamples.push_back( sample.compileMs );
610 compileSamples.push_back( sample.compileMs );
611 checkSamples.push_back( sample.checkMs );
612 cacheSamples.push_back( sample.cacheGenMs );
613 result.violations = sample.violations;
615 for(
const auto& [
name, ms] : sample.providerMs )
616 providerSamples[
name].push_back( ms );
621 warmBoard->SetProject(
nullptr );
624 result.compile = computeStat( compileSamples );
625 result.check = computeStat( checkSamples );
626 result.cacheGen = computeStat( cacheSamples );
628 for(
auto& [
name, samples] : providerSamples )
629 result.providerStats[
name] = computeStat( samples );
642STAT runCompileOnly(
const wxFileName& aBoardName,
SETTINGS_MANAGER& aManager,
643 const wxFileName& aProjectName,
const wxFileName& aRulesFile,
int aRepeat )
645 std::unique_ptr<BOARD> board = loadBoard( aBoardName, aManager, aProjectName );
650 std::vector<double> samples;
652 for(
int i = 0; i <= aRepeat; ++i )
654 double ms = timeCompile( board.get(), aRulesFile );
657 samples.push_back( ms );
660 board->SetProject(
nullptr );
662 return computeStat( samples );
666const char* variantName( RULES_VARIANT aVariant )
670 case RULES_VARIANT::NONE:
return "none";
671 case RULES_VARIANT::DEFAULT:
return "default";
672 case RULES_VARIANT::HEAVY:
return "heavy";
679bool parseVariant(
const wxString& aArg, RULES_VARIANT& aVariant )
681 if( aArg == wxT(
"none" ) )
682 aVariant = RULES_VARIANT::NONE;
683 else if( aArg == wxT(
"default" ) )
684 aVariant = RULES_VARIANT::DEFAULT;
685 else if( aArg == wxT(
"heavy" ) )
686 aVariant = RULES_VARIANT::HEAVY;
695wxString slurp(
const wxFileName& aFile )
697 std::ifstream in( aFile.GetFullPath().fn_str() );
700 return wxEmptyString;
702 std::stringstream buffer;
703 buffer << in.rdbuf();
705 return wxString::FromUTF8( buffer.str().c_str() );
710std::string jsonEscape(
const wxString& aStr )
712 std::string utf8( aStr.utf8_str() );
714 out.reserve( utf8.size() + 8 );
720 case '"': out +=
"\\\"";
break;
721 case '\\': out +=
"\\\\";
break;
722 case '\n': out +=
"\\n";
break;
723 case '\r': out +=
"\\r";
break;
724 case '\t': out +=
"\\t";
break;
725 default: out += c;
break;
737 std::set<DRC_CONSTRAINT_T> constraints;
738 std::set<wxString> predicates;
751COVERAGE_ROW collectCoverage(
const wxFileName& aBoardName,
SETTINGS_MANAGER& aManager,
752 const wxFileName& aProjectName,
const wxFileName& aRulesFile )
755 row.board = aBoardName.GetFullName();
757 std::unique_ptr<BOARD> board = loadBoard( aBoardName, aManager, aProjectName );
762 std::shared_ptr<DRC_ENGINE> engine =
763 std::make_shared<DRC_ENGINE>( board.get(), &board->GetDesignSettings() );
765 board->GetDesignSettings().m_DRCEngine = engine;
766 engine->InitEngine( aRulesFile );
770 if( engine->HasRulesForConstraintType( type ) )
771 row.constraints.insert( type );
774 if( aRulesFile.IsOk() && aRulesFile.Exists() )
777 row.predicates.insert( pred );
780 board->SetProject(
nullptr );
790void emitCoverage(
const std::vector<COVERAGE_ROW>& aRows,
const wxString& aOutDir )
792 std::set<DRC_CONSTRAINT_T> coveredConstraints;
793 std::set<wxString> coveredPredicates;
795 for(
const COVERAGE_ROW& row : aRows )
797 coveredConstraints.insert( row.constraints.begin(), row.constraints.end() );
798 coveredPredicates.insert( row.predicates.begin(), row.predicates.end() );
801 std::printf(
"=== coverage matrix ===\n" );
802 std::printf(
"%-28s %s\n",
"board",
"constraints / predicates" );
803 std::printf(
"%-28s %s\n",
"----------------------------",
804 "-------------------------------------" );
806 for(
const COVERAGE_ROW& row : aRows )
820 for(
const wxString& pred : row.predicates )
825 preds += std::string( pred.utf8_str() );
828 std::printf(
"%-28s C[%s] P[%s]\n",
829 static_cast<const char*
>( row.board.utf8_str() ), cons.c_str(),
833 std::vector<const char*> uncoveredConstraints;
837 if( !coveredConstraints.count( type ) )
841 std::vector<wxString> uncoveredPredicates;
845 if( !coveredPredicates.count( pred ) )
846 uncoveredPredicates.push_back( pred );
849 std::printf(
"\nUNCOVERED constraints:" );
851 for(
const char*
name : uncoveredConstraints )
852 std::printf(
" %s",
name );
854 std::printf(
"%s\n", uncoveredConstraints.empty() ?
" (none)" :
"" );
856 std::printf(
"UNCOVERED predicates:" );
858 for(
const wxString& pred : uncoveredPredicates )
859 std::printf(
" %s",
static_cast<const char*
>( pred.utf8_str() ) );
861 std::printf(
"%s\n\n", uncoveredPredicates.empty() ?
" (none)" :
"" );
863 wxFileName outFile( aOutDir, wxT(
"coverage.json" ) );
864 std::ofstream out( outFile.GetFullPath().fn_str() );
869 out <<
"{\n \"boards\": [\n";
871 for(
size_t i = 0; i < aRows.size(); ++i )
873 const COVERAGE_ROW& row = aRows[i];
875 out <<
" {\n \"board\": \"" << jsonEscape( row.board ) <<
"\",\n";
876 out <<
" \"constraints\": [";
886 out <<
"],\n \"predicates\": [";
890 for(
const wxString& pred : row.predicates )
892 out << ( first ?
"" :
", " ) <<
"\"" << jsonEscape( pred ) <<
"\"";
896 out <<
"]\n }" << ( i + 1 < aRows.size() ?
"," :
"" ) <<
"\n";
899 out <<
" ],\n \"uncovered_constraints\": [";
901 for(
size_t i = 0; i < uncoveredConstraints.size(); ++i )
902 out << ( i ?
", " :
"" ) <<
"\"" << uncoveredConstraints[i] <<
"\"";
904 out <<
"],\n \"uncovered_predicates\": [";
906 for(
size_t i = 0; i < uncoveredPredicates.size(); ++i )
907 out << ( i ?
", " :
"" ) <<
"\"" << jsonEscape( uncoveredPredicates[i] ) <<
"\"";
921 double evalOverheadMs = 0.0;
922 bool evalOverheadValid =
false;
924 bool underLoad =
false;
925 bool timedOut =
false;
926 double fraction = 1.0;
927 std::map<wxString, STAT> perProvider;
931void writeResultsJson(
const std::vector<RESULT_ROW>& aRows,
const wxString& aOutDir )
933 wxFileName outFile( aOutDir, wxT(
"results.json" ) );
934 std::ofstream out( outFile.GetFullPath().fn_str() );
941 for(
size_t i = 0; i < aRows.size(); ++i )
943 const RESULT_ROW& row = aRows[i];
946 out <<
" \"board\": \"" << jsonEscape( row.board ) <<
"\",\n";
947 out <<
" \"config\": \"" << jsonEscape( row.config ) <<
"\",\n";
948 out <<
" \"compile_ms\": " << row.compile.median <<
",\n";
949 out <<
" \"compile_mad\": " << row.compile.mad <<
",\n";
950 out <<
" \"cache_gen_ms\": " << row.cacheGen.median <<
",\n";
951 out <<
" \"cache_gen_mad\": " << row.cacheGen.mad <<
",\n";
952 out <<
" \"check_ms\": " << row.check.median <<
",\n";
953 out <<
" \"check_mad\": " << row.check.mad <<
",\n";
955 if( row.evalOverheadValid )
956 out <<
" \"eval_overhead_ms\": " << row.evalOverheadMs <<
",\n";
958 out <<
" \"eval_overhead_ms\": null,\n";
960 out <<
" \"n_violations\": " << row.violations <<
",\n";
961 out <<
" \"under_load\": " << ( row.underLoad ?
"true" :
"false" ) <<
",\n";
962 out <<
" \"timed_out\": " << ( row.timedOut ?
"true" :
"false" ) <<
",\n";
963 out <<
" \"percent_complete\": " << ( row.timedOut ? row.fraction * 100.0 : 100.0 )
965 out <<
" \"per_provider\": {";
969 for(
const auto& [
name, stat] : row.perProvider )
971 out << ( first ?
"\n" :
",\n" );
972 out <<
" \"" << jsonEscape(
name ) <<
"\": { \"median_ms\": " << stat.median
973 <<
", \"mad_ms\": " << stat.mad <<
" }";
977 out << ( first ?
"}" :
"\n }" ) <<
"\n";
978 out <<
" }" << ( i + 1 < aRows.size() ?
"," :
"" ) <<
"\n";
990void writeWorstOffenders(
const std::vector<RESULT_ROW>& aRows,
const wxString& aOutDir,
int aTopN )
992 std::vector<const RESULT_ROW*> byCompile;
993 std::vector<const RESULT_ROW*> byEval;
994 std::vector<const RESULT_ROW*> timedOut;
996 for(
const RESULT_ROW& row : aRows )
998 byCompile.push_back( &row );
1000 if( row.evalOverheadValid )
1001 byEval.push_back( &row );
1004 timedOut.push_back( &row );
1007 std::sort( byCompile.begin(), byCompile.end(),
1008 [](
const RESULT_ROW* a,
const RESULT_ROW* b )
1010 return a->compile.median > b->compile.median;
1013 std::sort( byEval.begin(), byEval.end(),
1014 [](
const RESULT_ROW* a,
const RESULT_ROW* b )
1016 return a->evalOverheadMs > b->evalOverheadMs;
1021 std::sort( timedOut.begin(), timedOut.end(),
1022 [](
const RESULT_ROW* a,
const RESULT_ROW* b )
1024 return a->fraction < b->fraction;
1027 auto emitList = [&](
const char* aLabel )
1029 std::printf(
"=== worst offenders by %s ===\n", aLabel );
1030 std::printf(
"%-28s %-22s %12s\n",
"board",
"config", aLabel );
1031 std::printf(
"%-28s %-22s %12s\n",
"----------------------------",
1032 "----------------------",
"------------" );
1035 emitList(
"compile_ms" );
1037 for(
int i = 0; i < aTopN && i < static_cast<int>( byCompile.size() ); ++i )
1039 const RESULT_ROW* row = byCompile[i];
1041 std::printf(
"%-28s %-22s %12.3f\n",
static_cast<const char*
>( row->board.utf8_str() ),
1042 static_cast<const char*
>( row->config.utf8_str() ), row->compile.median );
1045 std::printf(
"\n" );
1046 emitList(
"eval_overhead_ms" );
1048 for(
int i = 0; i < aTopN && i < static_cast<int>( byEval.size() ); ++i )
1050 const RESULT_ROW* row = byEval[i];
1052 std::printf(
"%-28s %-22s %12.3f\n",
static_cast<const char*
>( row->board.utf8_str() ),
1053 static_cast<const char*
>( row->config.utf8_str() ), row->evalOverheadMs );
1056 std::printf(
"\n" );
1058 if( !timedOut.empty() )
1060 std::printf(
"=== timed out (eval unbounded; ranked least-complete first) ===\n" );
1061 std::printf(
"%-28s %-22s %12s\n",
"board",
"config",
"percent" );
1062 std::printf(
"%-28s %-22s %12s\n",
"----------------------------",
1063 "----------------------",
"------------" );
1065 for(
const RESULT_ROW* row : timedOut )
1067 std::printf(
"%-28s %-22s %11.1f%%\n",
1068 static_cast<const char*
>( row->board.utf8_str() ),
1069 static_cast<const char*
>( row->config.utf8_str() ), row->fraction * 100.0 );
1072 std::printf(
"\n" );
1075 wxFileName outFile( aOutDir, wxT(
"worst_offenders.json" ) );
1076 std::ofstream out( outFile.GetFullPath().fn_str() );
1078 if( !out.is_open() )
1081 auto writeRanked = [&](
const char* aKey,
const std::vector<const RESULT_ROW*>& aList,
1084 out <<
" \"" << aKey <<
"\": [\n";
1086 int count = std::min<int>( aTopN,
static_cast<int>( aList.size() ) );
1088 for(
int i = 0; i < count; ++i )
1090 const RESULT_ROW* row = aList[i];
1091 double value = aUseEval ? row->evalOverheadMs : row->compile.median;
1093 out <<
" { \"board\": \"" << jsonEscape( row->board ) <<
"\", \"config\": \""
1094 << jsonEscape( row->config ) <<
"\", \"" << ( aUseEval ?
"eval_overhead_ms"
1096 <<
"\": " << value <<
" }" << ( i + 1 < count ?
"," :
"" ) <<
"\n";
1103 writeRanked(
"by_compile_ms", byCompile,
false );
1105 writeRanked(
"by_eval_overhead_ms", byEval,
true );
1108 out <<
" \"timed_out\": [\n";
1110 for(
size_t i = 0; i < timedOut.size(); ++i )
1112 const RESULT_ROW* row = timedOut[i];
1114 out <<
" { \"board\": \"" << jsonEscape( row->board ) <<
"\", \"config\": \""
1115 << jsonEscape( row->config ) <<
"\", \"percent_complete\": " << row->fraction * 100.0
1116 <<
" }" << ( i + 1 < timedOut.size() ?
"," :
"" ) <<
"\n";
1124wxString configTag( CACHE_MODE aCache, RULES_VARIANT aVariant,
int aThreads )
1126 return wxString::Format( wxT(
"%s/%s/t%d" ), aCache == CACHE_MODE::COLD ?
"cold" :
"warm",
1127 variantName( aVariant ), aThreads );
1135 wxInitialize( argc, argv );
1139 std::setlocale( LC_ALL,
"C" );
1143 std::setvbuf( stdout,
nullptr, _IOLBF, 0 );
1147 for(
int i = 1; i < argc; ++i )
1149 if( std::string( argv[i] ) ==
"--selftest" )
1164 static const wxCmdLineEntryDesc cmdLineDesc[] = {
1165 { wxCMD_LINE_SWITCH,
nullptr,
"selftest",
"run the trace-parser self-check and exit",
1166 wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
1167 { wxCMD_LINE_OPTION,
nullptr,
"board",
"ad-hoc board override (skips the corpus manifest)",
1168 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1169 { wxCMD_LINE_OPTION,
"r",
"rules",
"default-variant design rules file (.kicad_dru)",
1170 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1171 { wxCMD_LINE_OPTION,
nullptr,
"heavy-rules",
"heavy-variant design rules file (.kicad_dru)",
1172 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1173 { wxCMD_LINE_OPTION,
nullptr,
"rules-variant",
1174 "none|default|heavy (default: sweep all three)", wxCMD_LINE_VAL_STRING,
1175 wxCMD_LINE_PARAM_OPTIONAL },
1176 { wxCMD_LINE_SWITCH,
nullptr,
"rules-only",
1177 "time only InitEngine() compile in a loop, no checks", wxCMD_LINE_VAL_NONE,
1178 wxCMD_LINE_PARAM_OPTIONAL },
1179 { wxCMD_LINE_OPTION,
nullptr,
"threads",
"worker threads, 0=all (default 0)",
1180 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
1181 { wxCMD_LINE_OPTION,
nullptr,
"repeat",
"timed repeats after warm-up (default 5)",
1182 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
1183 { wxCMD_LINE_OPTION,
nullptr,
"cache",
"cold|warm|both (default both)",
1184 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1185 { wxCMD_LINE_OPTION,
nullptr,
"isolate",
1186 "ignore every provider but this one to attribute its eval cost",
1187 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1188 { wxCMD_LINE_OPTION,
nullptr,
"top-n",
"worst-offender list length (default 10)",
1189 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
1190 { wxCMD_LINE_OPTION,
nullptr,
"out",
"output directory for JSON artifacts (default cwd)",
1191 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1192 { wxCMD_LINE_OPTION,
nullptr,
"max-load",
1193 "abort when 1-min loadavg exceeds this (default ncores*0.5)", wxCMD_LINE_VAL_STRING,
1194 wxCMD_LINE_PARAM_OPTIONAL },
1195 { wxCMD_LINE_OPTION,
nullptr,
"timeout",
1196 "per-test deadline in seconds; 0 disables (default 60)", wxCMD_LINE_VAL_NUMBER,
1197 wxCMD_LINE_PARAM_OPTIONAL },
1198 { wxCMD_LINE_SWITCH,
nullptr,
"quick",
1199 "fast iteration set: small/synthetic boards, warm none+heavy, repeat 3",
1200 wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
1201 { wxCMD_LINE_OPTION,
nullptr,
"quick-max-mb",
1202 "in --quick, also keep real boards smaller than this many MB (default 0 = synthetic only)",
1203 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
1204 { wxCMD_LINE_PARAM,
nullptr,
nullptr,
"board.kicad_pcb", wxCMD_LINE_VAL_STRING,
1205 wxCMD_LINE_PARAM_OPTIONAL },
1209 wxCmdLineParser parser( argc, argv );
1210 parser.SetDesc( cmdLineDesc );
1211 parser.SetLogo(
"qa_drc_benchmark: time DRC rule compile + expression evaluation" );
1213 if( parser.Parse() != 0 )
1222 auto cleanupExit = [&](
int aCode )
1232 std::vector<CORPUS_ENTRY> entries;
1234 wxString adHocBoard;
1235 bool haveAdHocBoard = parser.Found(
"board", &adHocBoard );
1237 if( !haveAdHocBoard && parser.GetParamCount() > 0 )
1239 adHocBoard = parser.GetParam( 0 );
1240 haveAdHocBoard =
true;
1243 std::optional<wxString> cliDefaultRules;
1246 if( parser.Found(
"r", &rulesArg ) )
1247 cliDefaultRules = rulesArg;
1249 std::optional<wxString> cliHeavyRules;
1252 if( parser.Found(
"heavy-rules", &heavyArg ) )
1253 cliHeavyRules = heavyArg;
1255 if( haveAdHocBoard )
1258 wxFileName board( adHocBoard );
1259 board.MakeAbsolute();
1260 entry.
board = board.GetFullPath();
1262 if( cliDefaultRules )
1264 wxFileName rules( *cliDefaultRules );
1265 rules.MakeAbsolute();
1266 entry.
rules = rules.GetFullPath();
1269 entry.
tier = wxT(
"adhoc" );
1270 entries.push_back( entry );
1276 std::printf(
"KICAD_DRC_BENCH_CORPUS is unset or does not name a directory; "
1277 "nothing to benchmark.\n"
1278 "Set it to a corpus root containing corpus.json, or pass a board "
1279 "with --board <file.kicad_pcb>. Skipping.\n" );
1280 return cleanupExit( 0 );
1287 std::printf(
"error loading corpus: %s\n",
1288 static_cast<const char*
>( loadError.utf8_str() ) );
1289 return cleanupExit( 1 );
1292 if( entries.empty() )
1294 std::printf(
"corpus at %s has no entries; nothing to benchmark.\n",
1295 static_cast<const char*
>(
CORPUS::Root().utf8_str() ) );
1296 return cleanupExit( 0 );
1300 long threadsArg = 0;
1303 if( parser.Found(
"threads", &threadsArg ) )
1304 threads =
static_cast<int>( threadsArg );
1306 bool quick = parser.Found(
"quick" );
1308 double timeoutSec = 60.0;
1309 long timeoutArg = 60;
1311 if( parser.Found(
"timeout", &timeoutArg ) )
1312 timeoutSec =
static_cast<double>( std::max( 0
L, timeoutArg ) );
1316 double quickMaxMb = 0.0;
1317 long quickMaxArg = 0;
1319 if( parser.Found(
"quick-max-mb", &quickMaxArg ) )
1320 quickMaxMb =
static_cast<double>( std::max( 0
L, quickMaxArg ) );
1323 int repeat = quick ? 3 : 5;
1325 if( parser.Found(
"repeat", &repeatArg ) )
1326 repeat = std::max( 1,
static_cast<int>( repeatArg ) );
1331 if( parser.Found(
"top-n", &topNArg ) )
1332 topN = std::max( 1,
static_cast<int>( topNArg ) );
1334 std::optional<wxString> isolate;
1335 wxString isolateArg;
1337 if( parser.Found(
"isolate", &isolateArg ) )
1339 isolate = isolateArg;
1341 if( !providerErrorCodes().count( isolateArg ) )
1342 std::printf(
"warning: --isolate '%s' has no known error codes; the full provider "
1344 static_cast<const char*
>( isolateArg.utf8_str() ) );
1347 wxString outDir = wxFileName::GetCwd();
1350 if( parser.Found(
"out", &outArg ) )
1353 unsigned ncores = std::max( 1u, std::thread::hardware_concurrency() );
1354 double maxLoad = ncores * 0.5;
1355 wxString maxLoadArg;
1357 if( parser.Found(
"max-load", &maxLoadArg ) )
1358 maxLoadArg.ToCDouble( &maxLoad );
1362 double startLoad = readOneMinuteLoad();
1364 if( maxLoad > 0.0 && startLoad > maxLoad )
1366 std::printf(
"aborting: 1-min loadavg %.2f exceeds limit %.2f (cores=%u). "
1367 "Run on an idle machine or raise --max-load.\n",
1368 startLoad, maxLoad, ncores );
1369 return cleanupExit( 2 );
1372 std::vector<CACHE_MODE> cacheModes = { CACHE_MODE::COLD, CACHE_MODE::WARM };
1375 if( parser.Found(
"cache", &cacheArg ) )
1377 if( cacheArg == wxT(
"cold" ) )
1378 cacheModes = { CACHE_MODE::COLD };
1379 else if( cacheArg == wxT(
"warm" ) )
1380 cacheModes = { CACHE_MODE::WARM };
1381 else if( cacheArg != wxT(
"both" ) )
1383 std::printf(
"error: --cache must be cold|warm|both\n" );
1384 return cleanupExit( 1 );
1390 cacheModes = { CACHE_MODE::WARM };
1393 std::vector<RULES_VARIANT> variants = { RULES_VARIANT::NONE, RULES_VARIANT::DEFAULT,
1394 RULES_VARIANT::HEAVY };
1395 wxString variantArg;
1397 if( parser.Found(
"rules-variant", &variantArg ) )
1399 RULES_VARIANT v = RULES_VARIANT::DEFAULT;
1401 if( !parseVariant( variantArg, v ) )
1403 std::printf(
"error: --rules-variant must be none|default|heavy\n" );
1404 return cleanupExit( 1 );
1413 variants = { RULES_VARIANT::NONE, RULES_VARIANT::HEAVY };
1420 if( quick && !haveAdHocBoard )
1422 bool anyFlagged = std::any_of( entries.begin(), entries.end(),
1425 std::vector<CORPUS_ENTRY> kept;
1429 wxULongLong size = wxFileName::GetSize( entry.board );
1430 bool small = quickMaxMb > 0.0 && size != wxInvalidSize
1431 && size.ToDouble() < quickMaxMb * 1.0e6;
1432 bool flagged = anyFlagged ? entry.quick : ( entry.tier == wxT(
"C" ) );
1434 if( flagged || small )
1435 kept.push_back( entry );
1438 entries.swap( kept );
1441 std::printf(
"corpus: %s\n", haveAdHocBoard
1443 :
static_cast<const char*
>(
CORPUS::Root().utf8_str() ) );
1444 std::printf(
"boards: %zu%s\n", entries.size(), quick ?
" (quick set)" :
"" );
1445 std::printf(
"threads: %d (0=all)\n", threads );
1446 std::printf(
"repeat: %d\n", repeat );
1447 std::printf(
"timeout: %.0f s/test%s\n", timeoutSec, timeoutSec > 0.0 ?
"" :
" (disabled)" );
1448 std::printf(
"max-load: %.2f (start %.2f, cores %u)\n", maxLoad, startLoad, ncores );
1451 std::printf(
"isolate: %s\n",
static_cast<const char*
>( isolate->utf8_str() ) );
1453 std::printf(
"out: %s\n\n",
static_cast<const char*
>( outDir.utf8_str() ) );
1456 std::vector<COVERAGE_ROW> coverageRows;
1457 std::vector<RESULT_ROW> resultRows;
1459 bool rulesOnly = parser.Found(
"rules-only" );
1463 wxFileName boardName( entry.board );
1464 wxFileName projectName( boardName );
1472 if( projectName.Exists() )
1477 std::optional<wxString> entryDefaultRules;
1479 if( !entry.rules.IsEmpty() )
1480 entryDefaultRules = entry.rules;
1481 else if( cliDefaultRules )
1482 entryDefaultRules = cliDefaultRules;
1484 std::optional<wxString> entryHeavyRules = cliHeavyRules ? cliHeavyRules : entryDefaultRules;
1486 std::printf(
"### board: %s (tier %s)\n",
1487 static_cast<const char*
>( boardName.GetFullName().utf8_str() ),
1488 static_cast<const char*
>( entry.tier.utf8_str() ) );
1492 wxFileName coverageRules =
1493 resolveRules( boardName, entryDefaultRules, entryHeavyRules, RULES_VARIANT::DEFAULT );
1495 coverageRows.push_back(
1496 collectCoverage( boardName, manager, projectName, coverageRules ) );
1500 std::printf(
"%-10s %14s %12s\n",
"variant",
"compile_med",
"compile_mad" );
1501 std::printf(
"%-10s %14s %12s\n",
"----------",
"--------------",
"------------" );
1503 for( RULES_VARIANT variant : variants )
1505 wxFileName rulesFile =
1506 resolveRules( boardName, entryDefaultRules, entryHeavyRules, variant );
1509 runCompileOnly( boardName, manager, projectName, rulesFile, repeat );
1511 std::printf(
"%-10s %14.3f %12.3f\n", variantName( variant ), compile.median,
1514 RESULT_ROW resultRow;
1515 resultRow.board = boardName.GetFullName();
1516 resultRow.config = configTag( CACHE_MODE::COLD, variant, threads ) + wxT(
"/compile" );
1517 resultRow.compile = compile;
1518 resultRows.push_back( resultRow );
1521 std::printf(
"\n" );
1525 for( CACHE_MODE cache : cacheModes )
1527 const char* cacheLabel = cache == CACHE_MODE::COLD ?
"cold" :
"warm";
1529 std::printf(
"--- cache: %s ---\n", cacheLabel );
1530 std::printf(
"%-10s %12s %10s %12s %12s %12s %10s\n",
"variant",
"compile_ms",
"(mad)",
1531 "cache_gen",
"check_ms",
"eval_ovhd",
"violations" );
1532 std::printf(
"%-10s %12s %10s %12s %12s %12s %10s\n",
"----------",
"------------",
1533 "----------",
"------------",
"------------",
"------------",
1536 std::optional<double> noneCheck;
1538 for( RULES_VARIANT variant : variants )
1541 config.rulesVariant = variant;
1543 config.threads = threads;
1546 wxFileName rulesFile =
1547 resolveRules( boardName, entryDefaultRules, entryHeavyRules, variant );
1549 SWEEP_RESULT
result = runConfig( boardName, manager, projectName, rulesFile,
config,
1550 isolate, maxLoad, timeoutSec );
1560 if( variant == RULES_VARIANT::NONE && !
result.timedOut )
1561 noneCheck =
result.check.median;
1563 RESULT_ROW resultRow;
1564 resultRow.board = boardName.GetFullName();
1565 resultRow.config = configTag( cache, variant, threads );
1566 resultRow.compile =
result.compile;
1567 resultRow.cacheGen =
result.cacheGen;
1568 resultRow.check =
result.check;
1569 resultRow.violations =
result.violations;
1570 resultRow.underLoad =
result.underLoad;
1571 resultRow.timedOut =
result.timedOut;
1572 resultRow.fraction =
result.fraction;
1573 resultRow.perProvider =
result.providerStats;
1579 wxString ovhd = wxT(
"n/a" );
1583 ovhd = wxT(
"timeout" );
1585 else if( variant == RULES_VARIANT::NONE )
1587 resultRow.evalOverheadMs = 0.0;
1588 resultRow.evalOverheadValid =
true;
1589 ovhd = wxT(
"0.000" );
1591 else if( noneCheck )
1593 resultRow.evalOverheadMs =
result.check.median - *noneCheck;
1594 resultRow.evalOverheadValid =
true;
1595 ovhd = wxString::Format( wxT(
"%.3f" ), resultRow.evalOverheadMs );
1598 resultRows.push_back( resultRow );
1602 std::printf(
"%-10s %12.3f %10.3f %12s %12s %12s [TIMEOUT %.0f%%]\n",
1603 variantName( variant ),
result.compile.median,
result.compile.mad,
1604 "-",
"-",
static_cast<const char*
>( ovhd.utf8_str() ),
1605 result.fraction * 100.0 );
1609 std::printf(
"%-10s %12.3f %10.3f %12.3f %12.3f %12s %10d%s\n",
1610 variantName( variant ),
result.compile.median,
result.compile.mad,
1612 static_cast<const char*
>( ovhd.utf8_str() ),
result.violations,
1613 result.underLoad ?
" [UNDER_LOAD]" :
"" );
1617 std::printf(
"\n" );
1621 catch(
const std::exception& e )
1623 std::printf(
"error: board '%s' failed and was skipped: %s\n\n",
1624 static_cast<const char*
>( boardName.GetFullName().utf8_str() ), e.what() );
1629 emitCoverage( coverageRows, outDir );
1630 writeWorstOffenders( resultRows, outDir, topN );
1631 writeResultsJson( resultRows, outDir );
1633 std::printf(
"wrote results.json, worst_offenders.json, coverage.json to %s\n",
1634 static_cast<const char*
>( outDir.utf8_str() ) );
1636 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.
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.