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 = {
339bool applyIsolate(
BOARD* aBoard,
const wxString& aProvider )
341 auto it = providerErrorCodes().find( aProvider );
343 if( it == providerErrorCodes().
end() )
346 std::set<int> keep( it->second.begin(), it->second.end() );
352 if( !keep.count( code ) )
373 explicit BENCH_PROGRESS(
double aTimeoutSec ) :
374 PROGRESS_REPORTER_BASE( 1 ),
375 m_enabled( aTimeoutSec > 0.0 ),
376 m_deadline( std::chrono::steady_clock::now()
377 + std::chrono::duration_cast<std::chrono::steady_clock::duration>(
378 std::chrono::duration<double>( aTimeoutSec ) ) )
382 bool TimedOut()
const {
return m_timedOut.load(); }
385 bool updateUI()
override
387 if( m_enabled && std::chrono::steady_clock::now() >= m_deadline )
389 m_timedOut.store(
true );
390 m_cancelled.store(
true );
400 std::chrono::steady_clock::time_point m_deadline;
401 std::atomic_bool m_timedOut{
false };
405double timeCompile(
BOARD* aBoard,
const wxFileName& aRulesFile )
407 std::shared_ptr<DRC_ENGINE> engine =
413 engine->InitEngine( aRulesFile );
416 return timer.
msecs();
424RUN_SAMPLE timeRun(
BOARD* aBoard,
const wxFileName& aRulesFile,
double aTimeoutSec )
428 std::shared_ptr<DRC_ENGINE> engine =
433 std::atomic<int> violationCount{ 0 };
435 engine->SetViolationHandler(
436 [&](
const std::shared_ptr<DRC_ITEM>& aItem,
const VECTOR2I& aPos,
int aLayer,
437 const std::function<
void(
PCB_MARKER* )>& aCreateMarker )
439 violationCount.fetch_add( 1, std::memory_order_relaxed );
443 engine->InitEngine( aRulesFile );
445 sample.compileMs = compileTimer.
msecs();
449 size_t totalProviders = engine->GetTestProviders().size();
454 wxLog::AddTraceMask( wxT(
"KICAD_DRC_PROFILE" ) );
457 wxLog* prevTarget = wxLog::SetActiveTarget( profileLog );
462 BENCH_PROGRESS progress( aTimeoutSec );
463 engine->SetProgressReporter( &progress );
471 catch(
const std::exception& e )
473 std::printf(
"error during RunTests: %s\n", e.what() );
478 engine->SetProgressReporter(
nullptr );
479 wxLog::SetActiveTarget( prevTarget );
483 sample.timedOut = progress.TimedOut();
485 if( sample.timedOut )
486 sample.fraction = totalProviders > 0
487 ?
static_cast<double>( sample.providerMs.size() )
488 /
static_cast<double>( totalProviders )
494 double engineTotal = profileLog->
TotalMs();
496 sample.checkMs = engineTotal > 0.0 ? engineTotal : checkTimer.
msecs();
498 double providerSum = 0.0;
500 for(
const auto& [
name, ms] : sample.providerMs )
505 sample.cacheGenMs = std::max( 0.0, sample.checkMs - providerSum );
507 sample.violations = violationCount.load( std::memory_order_relaxed );
520 bool underLoad =
false;
525 bool timedOut =
false;
526 double fraction = 1.0;
527 std::map<wxString, STAT> providerStats;
539SWEEP_RESULT runConfig(
const wxFileName& aBoardName,
SETTINGS_MANAGER& aManager,
540 const wxFileName& aProjectName,
const wxFileName& aRulesFile,
541 const BENCH_CONFIG& aConfig,
const std::optional<wxString>& aIsolate,
542 double aMaxLoad,
double aTimeoutSec )
547 applyThreadConfig( aConfig.threads );
549 std::vector<double> compileSamples;
550 std::vector<double> checkSamples;
551 std::vector<double> cacheSamples;
552 std::map<wxString, std::vector<double>> providerSamples;
554 std::unique_ptr<BOARD> warmBoard;
556 if( aConfig.cache == CACHE_MODE::WARM )
558 warmBoard = loadBoard( aBoardName, aManager, aProjectName );
564 applyIsolate( warmBoard.get(), *aIsolate );
567 for(
int i = 0; i <= aConfig.repeat; ++i )
569 BOARD* board = warmBoard.get();
571 std::unique_ptr<BOARD> coldBoard;
573 if( aConfig.cache == CACHE_MODE::COLD )
575 coldBoard = loadBoard( aBoardName, aManager, aProjectName );
581 applyIsolate( coldBoard.get(), *aIsolate );
583 board = coldBoard.get();
588 if( aMaxLoad > 0.0 && i > 0 )
590 double load = readOneMinuteLoad();
592 if( load > aMaxLoad )
596 RUN_SAMPLE sample = timeRun( board, aRulesFile, aTimeoutSec );
601 if( sample.timedOut )
604 result.fraction = sample.fraction;
606 if( compileSamples.empty() )
607 compileSamples.push_back( sample.compileMs );
615 compileSamples.push_back( sample.compileMs );
616 checkSamples.push_back( sample.checkMs );
617 cacheSamples.push_back( sample.cacheGenMs );
618 result.violations = sample.violations;
620 for(
const auto& [
name, ms] : sample.providerMs )
621 providerSamples[
name].push_back( ms );
626 warmBoard->SetProject(
nullptr );
629 result.compile = computeStat( compileSamples );
630 result.check = computeStat( checkSamples );
631 result.cacheGen = computeStat( cacheSamples );
633 for(
auto& [
name, samples] : providerSamples )
634 result.providerStats[
name] = computeStat( samples );
647STAT runCompileOnly(
const wxFileName& aBoardName,
SETTINGS_MANAGER& aManager,
648 const wxFileName& aProjectName,
const wxFileName& aRulesFile,
int aRepeat )
650 std::unique_ptr<BOARD> board = loadBoard( aBoardName, aManager, aProjectName );
655 std::vector<double> samples;
657 for(
int i = 0; i <= aRepeat; ++i )
659 double ms = timeCompile( board.get(), aRulesFile );
662 samples.push_back( ms );
665 board->SetProject(
nullptr );
667 return computeStat( samples );
671const char* variantName( RULES_VARIANT aVariant )
675 case RULES_VARIANT::NONE:
return "none";
676 case RULES_VARIANT::DEFAULT:
return "default";
677 case RULES_VARIANT::HEAVY:
return "heavy";
684bool parseVariant(
const wxString& aArg, RULES_VARIANT& aVariant )
686 if( aArg == wxT(
"none" ) )
687 aVariant = RULES_VARIANT::NONE;
688 else if( aArg == wxT(
"default" ) )
689 aVariant = RULES_VARIANT::DEFAULT;
690 else if( aArg == wxT(
"heavy" ) )
691 aVariant = RULES_VARIANT::HEAVY;
700wxString slurp(
const wxFileName& aFile )
702 std::ifstream in( aFile.GetFullPath().fn_str() );
705 return wxEmptyString;
707 std::stringstream buffer;
708 buffer << in.rdbuf();
710 return wxString::FromUTF8( buffer.str().c_str() );
715std::string jsonEscape(
const wxString& aStr )
717 std::string utf8( aStr.utf8_str() );
719 out.reserve( utf8.size() + 8 );
725 case '"': out +=
"\\\"";
break;
726 case '\\': out +=
"\\\\";
break;
727 case '\n': out +=
"\\n";
break;
728 case '\r': out +=
"\\r";
break;
729 case '\t': out +=
"\\t";
break;
730 default: out += c;
break;
742 std::set<DRC_CONSTRAINT_T> constraints;
743 std::set<wxString> predicates;
756COVERAGE_ROW collectCoverage(
const wxFileName& aBoardName,
SETTINGS_MANAGER& aManager,
757 const wxFileName& aProjectName,
const wxFileName& aRulesFile )
760 row.board = aBoardName.GetFullName();
762 std::unique_ptr<BOARD> board = loadBoard( aBoardName, aManager, aProjectName );
767 std::shared_ptr<DRC_ENGINE> engine =
768 std::make_shared<DRC_ENGINE>( board.get(), &board->GetDesignSettings() );
770 board->GetDesignSettings().m_DRCEngine = engine;
771 engine->InitEngine( aRulesFile );
775 if( engine->HasRulesForConstraintType( type ) )
776 row.constraints.insert( type );
779 if( aRulesFile.IsOk() && aRulesFile.Exists() )
782 row.predicates.insert( pred );
785 board->SetProject(
nullptr );
795void emitCoverage(
const std::vector<COVERAGE_ROW>& aRows,
const wxString& aOutDir )
797 std::set<DRC_CONSTRAINT_T> coveredConstraints;
798 std::set<wxString> coveredPredicates;
800 for(
const COVERAGE_ROW& row : aRows )
802 coveredConstraints.insert( row.constraints.begin(), row.constraints.end() );
803 coveredPredicates.insert( row.predicates.begin(), row.predicates.end() );
806 std::printf(
"=== coverage matrix ===\n" );
807 std::printf(
"%-28s %s\n",
"board",
"constraints / predicates" );
808 std::printf(
"%-28s %s\n",
"----------------------------",
809 "-------------------------------------" );
811 for(
const COVERAGE_ROW& row : aRows )
825 for(
const wxString& pred : row.predicates )
830 preds += std::string( pred.utf8_str() );
833 std::printf(
"%-28s C[%s] P[%s]\n",
834 static_cast<const char*
>( row.board.utf8_str() ), cons.c_str(),
838 std::vector<const char*> uncoveredConstraints;
842 if( !coveredConstraints.count( type ) )
846 std::vector<wxString> uncoveredPredicates;
850 if( !coveredPredicates.count( pred ) )
851 uncoveredPredicates.push_back( pred );
854 std::printf(
"\nUNCOVERED constraints:" );
856 for(
const char*
name : uncoveredConstraints )
857 std::printf(
" %s",
name );
859 std::printf(
"%s\n", uncoveredConstraints.empty() ?
" (none)" :
"" );
861 std::printf(
"UNCOVERED predicates:" );
863 for(
const wxString& pred : uncoveredPredicates )
864 std::printf(
" %s",
static_cast<const char*
>( pred.utf8_str() ) );
866 std::printf(
"%s\n\n", uncoveredPredicates.empty() ?
" (none)" :
"" );
868 wxFileName outFile( aOutDir, wxT(
"coverage.json" ) );
869 std::ofstream out( outFile.GetFullPath().fn_str() );
874 out <<
"{\n \"boards\": [\n";
876 for(
size_t i = 0; i < aRows.size(); ++i )
878 const COVERAGE_ROW& row = aRows[i];
880 out <<
" {\n \"board\": \"" << jsonEscape( row.board ) <<
"\",\n";
881 out <<
" \"constraints\": [";
891 out <<
"],\n \"predicates\": [";
895 for(
const wxString& pred : row.predicates )
897 out << ( first ?
"" :
", " ) <<
"\"" << jsonEscape( pred ) <<
"\"";
901 out <<
"]\n }" << ( i + 1 < aRows.size() ?
"," :
"" ) <<
"\n";
904 out <<
" ],\n \"uncovered_constraints\": [";
906 for(
size_t i = 0; i < uncoveredConstraints.size(); ++i )
907 out << ( i ?
", " :
"" ) <<
"\"" << uncoveredConstraints[i] <<
"\"";
909 out <<
"],\n \"uncovered_predicates\": [";
911 for(
size_t i = 0; i < uncoveredPredicates.size(); ++i )
912 out << ( i ?
", " :
"" ) <<
"\"" << jsonEscape( uncoveredPredicates[i] ) <<
"\"";
926 double evalOverheadMs = 0.0;
927 bool evalOverheadValid =
false;
929 bool underLoad =
false;
930 bool timedOut =
false;
931 double fraction = 1.0;
932 std::map<wxString, STAT> perProvider;
936void writeResultsJson(
const std::vector<RESULT_ROW>& aRows,
const wxString& aOutDir )
938 wxFileName outFile( aOutDir, wxT(
"results.json" ) );
939 std::ofstream out( outFile.GetFullPath().fn_str() );
946 for(
size_t i = 0; i < aRows.size(); ++i )
948 const RESULT_ROW& row = aRows[i];
951 out <<
" \"board\": \"" << jsonEscape( row.board ) <<
"\",\n";
952 out <<
" \"config\": \"" << jsonEscape( row.config ) <<
"\",\n";
953 out <<
" \"compile_ms\": " << row.compile.median <<
",\n";
954 out <<
" \"compile_mad\": " << row.compile.mad <<
",\n";
955 out <<
" \"cache_gen_ms\": " << row.cacheGen.median <<
",\n";
956 out <<
" \"cache_gen_mad\": " << row.cacheGen.mad <<
",\n";
957 out <<
" \"check_ms\": " << row.check.median <<
",\n";
958 out <<
" \"check_mad\": " << row.check.mad <<
",\n";
960 if( row.evalOverheadValid )
961 out <<
" \"eval_overhead_ms\": " << row.evalOverheadMs <<
",\n";
963 out <<
" \"eval_overhead_ms\": null,\n";
965 out <<
" \"n_violations\": " << row.violations <<
",\n";
966 out <<
" \"under_load\": " << ( row.underLoad ?
"true" :
"false" ) <<
",\n";
967 out <<
" \"timed_out\": " << ( row.timedOut ?
"true" :
"false" ) <<
",\n";
968 out <<
" \"percent_complete\": " << ( row.timedOut ? row.fraction * 100.0 : 100.0 )
970 out <<
" \"per_provider\": {";
974 for(
const auto& [
name, stat] : row.perProvider )
976 out << ( first ?
"\n" :
",\n" );
977 out <<
" \"" << jsonEscape(
name ) <<
"\": { \"median_ms\": " << stat.median
978 <<
", \"mad_ms\": " << stat.mad <<
" }";
982 out << ( first ?
"}" :
"\n }" ) <<
"\n";
983 out <<
" }" << ( i + 1 < aRows.size() ?
"," :
"" ) <<
"\n";
995void writeWorstOffenders(
const std::vector<RESULT_ROW>& aRows,
const wxString& aOutDir,
int aTopN )
997 std::vector<const RESULT_ROW*> byCompile;
998 std::vector<const RESULT_ROW*> byEval;
999 std::vector<const RESULT_ROW*> timedOut;
1001 for(
const RESULT_ROW& row : aRows )
1003 byCompile.push_back( &row );
1005 if( row.evalOverheadValid )
1006 byEval.push_back( &row );
1009 timedOut.push_back( &row );
1012 std::sort( byCompile.begin(), byCompile.end(),
1013 [](
const RESULT_ROW* a,
const RESULT_ROW* b )
1015 return a->compile.median > b->compile.median;
1018 std::sort( byEval.begin(), byEval.end(),
1019 [](
const RESULT_ROW* a,
const RESULT_ROW* b )
1021 return a->evalOverheadMs > b->evalOverheadMs;
1026 std::sort( timedOut.begin(), timedOut.end(),
1027 [](
const RESULT_ROW* a,
const RESULT_ROW* b )
1029 return a->fraction < b->fraction;
1032 auto emitList = [&](
const char* aLabel )
1034 std::printf(
"=== worst offenders by %s ===\n", aLabel );
1035 std::printf(
"%-28s %-22s %12s\n",
"board",
"config", aLabel );
1036 std::printf(
"%-28s %-22s %12s\n",
"----------------------------",
1037 "----------------------",
"------------" );
1040 emitList(
"compile_ms" );
1042 for(
int i = 0; i < aTopN && i < static_cast<int>( byCompile.size() ); ++i )
1044 const RESULT_ROW* row = byCompile[i];
1046 std::printf(
"%-28s %-22s %12.3f\n",
static_cast<const char*
>( row->board.utf8_str() ),
1047 static_cast<const char*
>( row->config.utf8_str() ), row->compile.median );
1050 std::printf(
"\n" );
1051 emitList(
"eval_overhead_ms" );
1053 for(
int i = 0; i < aTopN && i < static_cast<int>( byEval.size() ); ++i )
1055 const RESULT_ROW* row = byEval[i];
1057 std::printf(
"%-28s %-22s %12.3f\n",
static_cast<const char*
>( row->board.utf8_str() ),
1058 static_cast<const char*
>( row->config.utf8_str() ), row->evalOverheadMs );
1061 std::printf(
"\n" );
1063 if( !timedOut.empty() )
1065 std::printf(
"=== timed out (eval unbounded; ranked least-complete first) ===\n" );
1066 std::printf(
"%-28s %-22s %12s\n",
"board",
"config",
"percent" );
1067 std::printf(
"%-28s %-22s %12s\n",
"----------------------------",
1068 "----------------------",
"------------" );
1070 for(
const RESULT_ROW* row : timedOut )
1072 std::printf(
"%-28s %-22s %11.1f%%\n",
1073 static_cast<const char*
>( row->board.utf8_str() ),
1074 static_cast<const char*
>( row->config.utf8_str() ), row->fraction * 100.0 );
1077 std::printf(
"\n" );
1080 wxFileName outFile( aOutDir, wxT(
"worst_offenders.json" ) );
1081 std::ofstream out( outFile.GetFullPath().fn_str() );
1083 if( !out.is_open() )
1086 auto writeRanked = [&](
const char* aKey,
const std::vector<const RESULT_ROW*>& aList,
1089 out <<
" \"" << aKey <<
"\": [\n";
1091 int count = std::min<int>( aTopN,
static_cast<int>( aList.size() ) );
1093 for(
int i = 0; i < count; ++i )
1095 const RESULT_ROW* row = aList[i];
1096 double value = aUseEval ? row->evalOverheadMs : row->compile.median;
1098 out <<
" { \"board\": \"" << jsonEscape( row->board ) <<
"\", \"config\": \""
1099 << jsonEscape( row->config ) <<
"\", \"" << ( aUseEval ?
"eval_overhead_ms"
1101 <<
"\": " << value <<
" }" << ( i + 1 < count ?
"," :
"" ) <<
"\n";
1108 writeRanked(
"by_compile_ms", byCompile,
false );
1110 writeRanked(
"by_eval_overhead_ms", byEval,
true );
1113 out <<
" \"timed_out\": [\n";
1115 for(
size_t i = 0; i < timedOut.size(); ++i )
1117 const RESULT_ROW* row = timedOut[i];
1119 out <<
" { \"board\": \"" << jsonEscape( row->board ) <<
"\", \"config\": \""
1120 << jsonEscape( row->config ) <<
"\", \"percent_complete\": " << row->fraction * 100.0
1121 <<
" }" << ( i + 1 < timedOut.size() ?
"," :
"" ) <<
"\n";
1129wxString configTag( CACHE_MODE aCache, RULES_VARIANT aVariant,
int aThreads )
1131 return wxString::Format( wxT(
"%s/%s/t%d" ), aCache == CACHE_MODE::COLD ?
"cold" :
"warm",
1132 variantName( aVariant ), aThreads );
1140 wxInitialize( argc, argv );
1144 std::setlocale( LC_ALL,
"C" );
1148 std::setvbuf( stdout,
nullptr, _IOLBF, 0 );
1152 for(
int i = 1; i < argc; ++i )
1154 if( std::string( argv[i] ) ==
"--selftest" )
1169 static const wxCmdLineEntryDesc cmdLineDesc[] = {
1170 { wxCMD_LINE_SWITCH,
nullptr,
"selftest",
"run the trace-parser self-check and exit",
1171 wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
1172 { wxCMD_LINE_OPTION,
nullptr,
"board",
"ad-hoc board override (skips the corpus manifest)",
1173 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1174 { wxCMD_LINE_OPTION,
"r",
"rules",
"default-variant design rules file (.kicad_dru)",
1175 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1176 { wxCMD_LINE_OPTION,
nullptr,
"heavy-rules",
"heavy-variant design rules file (.kicad_dru)",
1177 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1178 { wxCMD_LINE_OPTION,
nullptr,
"rules-variant",
1179 "none|default|heavy (default: sweep all three)", wxCMD_LINE_VAL_STRING,
1180 wxCMD_LINE_PARAM_OPTIONAL },
1181 { wxCMD_LINE_SWITCH,
nullptr,
"rules-only",
1182 "time only InitEngine() compile in a loop, no checks", wxCMD_LINE_VAL_NONE,
1183 wxCMD_LINE_PARAM_OPTIONAL },
1184 { wxCMD_LINE_OPTION,
nullptr,
"threads",
"worker threads, 0=all (default 0)",
1185 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
1186 { wxCMD_LINE_OPTION,
nullptr,
"repeat",
"timed repeats after warm-up (default 5)",
1187 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
1188 { wxCMD_LINE_OPTION,
nullptr,
"cache",
"cold|warm|both (default both)",
1189 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1190 { wxCMD_LINE_OPTION,
nullptr,
"isolate",
1191 "ignore every provider but this one to attribute its eval cost",
1192 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1193 { wxCMD_LINE_OPTION,
nullptr,
"top-n",
"worst-offender list length (default 10)",
1194 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
1195 { wxCMD_LINE_OPTION,
nullptr,
"out",
"output directory for JSON artifacts (default cwd)",
1196 wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
1197 { wxCMD_LINE_OPTION,
nullptr,
"max-load",
1198 "abort when 1-min loadavg exceeds this (default ncores*0.5)", wxCMD_LINE_VAL_STRING,
1199 wxCMD_LINE_PARAM_OPTIONAL },
1200 { wxCMD_LINE_OPTION,
nullptr,
"timeout",
1201 "per-test deadline in seconds; 0 disables (default 60)", wxCMD_LINE_VAL_NUMBER,
1202 wxCMD_LINE_PARAM_OPTIONAL },
1203 { wxCMD_LINE_SWITCH,
nullptr,
"quick",
1204 "fast iteration set: small/synthetic boards, warm none+heavy, repeat 3",
1205 wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
1206 { wxCMD_LINE_OPTION,
nullptr,
"quick-max-mb",
1207 "in --quick, also keep real boards smaller than this many MB (default 0 = synthetic only)",
1208 wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
1209 { wxCMD_LINE_PARAM,
nullptr,
nullptr,
"board.kicad_pcb", wxCMD_LINE_VAL_STRING,
1210 wxCMD_LINE_PARAM_OPTIONAL },
1214 wxCmdLineParser parser( argc, argv );
1215 parser.SetDesc( cmdLineDesc );
1216 parser.SetLogo(
"qa_drc_benchmark: time DRC rule compile + expression evaluation" );
1218 if( parser.Parse() != 0 )
1227 auto cleanupExit = [&](
int aCode )
1237 std::vector<CORPUS_ENTRY> entries;
1239 wxString adHocBoard;
1240 bool haveAdHocBoard = parser.Found(
"board", &adHocBoard );
1242 if( !haveAdHocBoard && parser.GetParamCount() > 0 )
1244 adHocBoard = parser.GetParam( 0 );
1245 haveAdHocBoard =
true;
1248 std::optional<wxString> cliDefaultRules;
1251 if( parser.Found(
"r", &rulesArg ) )
1252 cliDefaultRules = rulesArg;
1254 std::optional<wxString> cliHeavyRules;
1257 if( parser.Found(
"heavy-rules", &heavyArg ) )
1258 cliHeavyRules = heavyArg;
1260 if( haveAdHocBoard )
1263 wxFileName board( adHocBoard );
1264 board.MakeAbsolute();
1265 entry.
board = board.GetFullPath();
1267 if( cliDefaultRules )
1269 wxFileName rules( *cliDefaultRules );
1270 rules.MakeAbsolute();
1271 entry.
rules = rules.GetFullPath();
1274 entry.
tier = wxT(
"adhoc" );
1275 entries.push_back( entry );
1281 std::printf(
"KICAD_DRC_BENCH_CORPUS is unset or does not name a directory; "
1282 "nothing to benchmark.\n"
1283 "Set it to a corpus root containing corpus.json, or pass a board "
1284 "with --board <file.kicad_pcb>. Skipping.\n" );
1285 return cleanupExit( 0 );
1292 std::printf(
"error loading corpus: %s\n",
1293 static_cast<const char*
>( loadError.utf8_str() ) );
1294 return cleanupExit( 1 );
1297 if( entries.empty() )
1299 std::printf(
"corpus at %s has no entries; nothing to benchmark.\n",
1300 static_cast<const char*
>(
CORPUS::Root().utf8_str() ) );
1301 return cleanupExit( 0 );
1305 long threadsArg = 0;
1308 if( parser.Found(
"threads", &threadsArg ) )
1309 threads =
static_cast<int>( threadsArg );
1311 bool quick = parser.Found(
"quick" );
1313 double timeoutSec = 60.0;
1314 long timeoutArg = 60;
1316 if( parser.Found(
"timeout", &timeoutArg ) )
1317 timeoutSec =
static_cast<double>( std::max( 0
L, timeoutArg ) );
1321 double quickMaxMb = 0.0;
1322 long quickMaxArg = 0;
1324 if( parser.Found(
"quick-max-mb", &quickMaxArg ) )
1325 quickMaxMb =
static_cast<double>( std::max( 0
L, quickMaxArg ) );
1328 int repeat = quick ? 3 : 5;
1330 if( parser.Found(
"repeat", &repeatArg ) )
1331 repeat = std::max( 1,
static_cast<int>( repeatArg ) );
1336 if( parser.Found(
"top-n", &topNArg ) )
1337 topN = std::max( 1,
static_cast<int>( topNArg ) );
1339 std::optional<wxString> isolate;
1340 wxString isolateArg;
1342 if( parser.Found(
"isolate", &isolateArg ) )
1344 isolate = isolateArg;
1346 if( !providerErrorCodes().count( isolateArg ) )
1347 std::printf(
"warning: --isolate '%s' has no known error codes; the full provider "
1349 static_cast<const char*
>( isolateArg.utf8_str() ) );
1352 wxString outDir = wxFileName::GetCwd();
1355 if( parser.Found(
"out", &outArg ) )
1358 unsigned ncores = std::max( 1u, std::thread::hardware_concurrency() );
1359 double maxLoad = ncores * 0.5;
1360 wxString maxLoadArg;
1362 if( parser.Found(
"max-load", &maxLoadArg ) )
1363 maxLoadArg.ToCDouble( &maxLoad );
1367 double startLoad = readOneMinuteLoad();
1369 if( maxLoad > 0.0 && startLoad > maxLoad )
1371 std::printf(
"aborting: 1-min loadavg %.2f exceeds limit %.2f (cores=%u). "
1372 "Run on an idle machine or raise --max-load.\n",
1373 startLoad, maxLoad, ncores );
1374 return cleanupExit( 2 );
1377 std::vector<CACHE_MODE> cacheModes = { CACHE_MODE::COLD, CACHE_MODE::WARM };
1380 if( parser.Found(
"cache", &cacheArg ) )
1382 if( cacheArg == wxT(
"cold" ) )
1383 cacheModes = { CACHE_MODE::COLD };
1384 else if( cacheArg == wxT(
"warm" ) )
1385 cacheModes = { CACHE_MODE::WARM };
1386 else if( cacheArg != wxT(
"both" ) )
1388 std::printf(
"error: --cache must be cold|warm|both\n" );
1389 return cleanupExit( 1 );
1395 cacheModes = { CACHE_MODE::WARM };
1398 std::vector<RULES_VARIANT> variants = { RULES_VARIANT::NONE, RULES_VARIANT::DEFAULT,
1399 RULES_VARIANT::HEAVY };
1400 wxString variantArg;
1402 if( parser.Found(
"rules-variant", &variantArg ) )
1404 RULES_VARIANT v = RULES_VARIANT::DEFAULT;
1406 if( !parseVariant( variantArg, v ) )
1408 std::printf(
"error: --rules-variant must be none|default|heavy\n" );
1409 return cleanupExit( 1 );
1418 variants = { RULES_VARIANT::NONE, RULES_VARIANT::HEAVY };
1425 if( quick && !haveAdHocBoard )
1427 bool anyFlagged = std::any_of( entries.begin(), entries.end(),
1430 std::vector<CORPUS_ENTRY> kept;
1434 wxULongLong size = wxFileName::GetSize( entry.board );
1435 bool small = quickMaxMb > 0.0 && size != wxInvalidSize
1436 && size.ToDouble() < quickMaxMb * 1.0e6;
1437 bool flagged = anyFlagged ? entry.quick : ( entry.tier == wxT(
"C" ) );
1439 if( flagged || small )
1440 kept.push_back( entry );
1443 entries.swap( kept );
1446 std::printf(
"corpus: %s\n", haveAdHocBoard
1448 :
static_cast<const char*
>(
CORPUS::Root().utf8_str() ) );
1449 std::printf(
"boards: %zu%s\n", entries.size(), quick ?
" (quick set)" :
"" );
1450 std::printf(
"threads: %d (0=all)\n", threads );
1451 std::printf(
"repeat: %d\n", repeat );
1452 std::printf(
"timeout: %.0f s/test%s\n", timeoutSec, timeoutSec > 0.0 ?
"" :
" (disabled)" );
1453 std::printf(
"max-load: %.2f (start %.2f, cores %u)\n", maxLoad, startLoad, ncores );
1456 std::printf(
"isolate: %s\n",
static_cast<const char*
>( isolate->utf8_str() ) );
1458 std::printf(
"out: %s\n\n",
static_cast<const char*
>( outDir.utf8_str() ) );
1461 std::vector<COVERAGE_ROW> coverageRows;
1462 std::vector<RESULT_ROW> resultRows;
1464 bool rulesOnly = parser.Found(
"rules-only" );
1468 wxFileName boardName( entry.board );
1469 wxFileName projectName( boardName );
1477 if( projectName.Exists() )
1482 std::optional<wxString> entryDefaultRules;
1484 if( !entry.rules.IsEmpty() )
1485 entryDefaultRules = entry.rules;
1486 else if( cliDefaultRules )
1487 entryDefaultRules = cliDefaultRules;
1489 std::optional<wxString> entryHeavyRules = cliHeavyRules ? cliHeavyRules : entryDefaultRules;
1491 std::printf(
"### board: %s (tier %s)\n",
1492 static_cast<const char*
>( boardName.GetFullName().utf8_str() ),
1493 static_cast<const char*
>( entry.tier.utf8_str() ) );
1497 wxFileName coverageRules =
1498 resolveRules( boardName, entryDefaultRules, entryHeavyRules, RULES_VARIANT::DEFAULT );
1500 coverageRows.push_back(
1501 collectCoverage( boardName, manager, projectName, coverageRules ) );
1505 std::printf(
"%-10s %14s %12s\n",
"variant",
"compile_med",
"compile_mad" );
1506 std::printf(
"%-10s %14s %12s\n",
"----------",
"--------------",
"------------" );
1508 for( RULES_VARIANT variant : variants )
1510 wxFileName rulesFile =
1511 resolveRules( boardName, entryDefaultRules, entryHeavyRules, variant );
1514 runCompileOnly( boardName, manager, projectName, rulesFile, repeat );
1516 std::printf(
"%-10s %14.3f %12.3f\n", variantName( variant ), compile.median,
1519 RESULT_ROW resultRow;
1520 resultRow.board = boardName.GetFullName();
1521 resultRow.config = configTag( CACHE_MODE::COLD, variant, threads ) + wxT(
"/compile" );
1522 resultRow.compile = compile;
1523 resultRows.push_back( resultRow );
1526 std::printf(
"\n" );
1530 for( CACHE_MODE cache : cacheModes )
1532 const char* cacheLabel = cache == CACHE_MODE::COLD ?
"cold" :
"warm";
1534 std::printf(
"--- cache: %s ---\n", cacheLabel );
1535 std::printf(
"%-10s %12s %10s %12s %12s %12s %10s\n",
"variant",
"compile_ms",
"(mad)",
1536 "cache_gen",
"check_ms",
"eval_ovhd",
"violations" );
1537 std::printf(
"%-10s %12s %10s %12s %12s %12s %10s\n",
"----------",
"------------",
1538 "----------",
"------------",
"------------",
"------------",
1541 std::optional<double> noneCheck;
1543 for( RULES_VARIANT variant : variants )
1546 config.rulesVariant = variant;
1548 config.threads = threads;
1551 wxFileName rulesFile =
1552 resolveRules( boardName, entryDefaultRules, entryHeavyRules, variant );
1554 SWEEP_RESULT
result = runConfig( boardName, manager, projectName, rulesFile,
config,
1555 isolate, maxLoad, timeoutSec );
1565 if( variant == RULES_VARIANT::NONE && !
result.timedOut )
1566 noneCheck =
result.check.median;
1568 RESULT_ROW resultRow;
1569 resultRow.board = boardName.GetFullName();
1570 resultRow.config = configTag( cache, variant, threads );
1571 resultRow.compile =
result.compile;
1572 resultRow.cacheGen =
result.cacheGen;
1573 resultRow.check =
result.check;
1574 resultRow.violations =
result.violations;
1575 resultRow.underLoad =
result.underLoad;
1576 resultRow.timedOut =
result.timedOut;
1577 resultRow.fraction =
result.fraction;
1578 resultRow.perProvider =
result.providerStats;
1584 wxString ovhd = wxT(
"n/a" );
1588 ovhd = wxT(
"timeout" );
1590 else if( variant == RULES_VARIANT::NONE )
1592 resultRow.evalOverheadMs = 0.0;
1593 resultRow.evalOverheadValid =
true;
1594 ovhd = wxT(
"0.000" );
1596 else if( noneCheck )
1598 resultRow.evalOverheadMs =
result.check.median - *noneCheck;
1599 resultRow.evalOverheadValid =
true;
1600 ovhd = wxString::Format( wxT(
"%.3f" ), resultRow.evalOverheadMs );
1603 resultRows.push_back( resultRow );
1607 std::printf(
"%-10s %12.3f %10.3f %12s %12s %12s [TIMEOUT %.0f%%]\n",
1608 variantName( variant ),
result.compile.median,
result.compile.mad,
1609 "-",
"-",
static_cast<const char*
>( ovhd.utf8_str() ),
1610 result.fraction * 100.0 );
1614 std::printf(
"%-10s %12.3f %10.3f %12.3f %12.3f %12s %10d%s\n",
1615 variantName( variant ),
result.compile.median,
result.compile.mad,
1617 static_cast<const char*
>( ovhd.utf8_str() ),
result.violations,
1618 result.underLoad ?
" [UNDER_LOAD]" :
"" );
1622 std::printf(
"\n" );
1626 catch(
const std::exception& e )
1628 std::printf(
"error: board '%s' failed and was skipped: %s\n\n",
1629 static_cast<const char*
>( boardName.GetFullName().utf8_str() ), e.what() );
1634 emitCoverage( coverageRows, outDir );
1635 writeWorstOffenders( resultRows, outDir, topN );
1636 writeResultsJson( resultRows, outDir );
1638 std::printf(
"wrote results.json, worst_offenders.json, coverage.json to %s\n",
1639 static_cast<const char*
>( outDir.utf8_str() ) );
1641 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_DIFF_PAIR_GAP_OUT_OF_RANGE
@ DRCE_SILK_EDGE_CLEARANCE
@ DRCE_SILK_MASK_CLEARANCE
@ 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_DIFF_PAIR_UNCOUPLED_LENGTH_TOO_LONG
@ DRCE_MICROVIA_DRILL_OUT_OF_RANGE
@ DRCE_MALFORMED_COURTYARD
@ DRCE_FOOTPRINT_TYPE_MISMATCH
@ DRCE_NONMIRRORED_TEXT_ON_BACK_LAYER
@ DRCE_DRILLED_HOLES_COLOCATED
@ DRCE_LENGTH_OUT_OF_RANGE
@ DRCE_PAD_TH_WITH_NO_HOLE
@ 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.