// CheckStatsTotal //------------------------------------------------------------------------------ void FBuildTest::CheckStatsTotal( const FBuildStats & stats, size_t numSeen, size_t numBuilt ) const { size_t actualNumSeen = stats.GetNodesProcessed(); TEST_ASSERT( actualNumSeen == numSeen ); size_t actualNumBuilt = stats.GetNodesBuilt(); TEST_ASSERT( actualNumBuilt == numBuilt ); }
// Generate //------------------------------------------------------------------------------ void Report::Generate( const FBuildStats & stats ) { Timer t; // pre-allocate a large string for output m_Output.SetReserved( MEGABYTE ); m_Output.SetLength( 0 ); // generate some common data used in reporting GetLibraryStats( stats ); // build the report CreateHeader(); CreateTitle(); CreateOverview( stats ); DoCPUTimeByType( stats ); DoCacheStats( stats ); DoCPUTimeByLibrary(); DoCPUTimeByItem( stats ); DoIncludes(); CreateFooter(); // patch in time take const float time = t.GetElapsed(); AStackString<> timeTakenBuffer; stats.FormatTime( time, timeTakenBuffer ); char * placeholder = m_Output.Find( "^^^^ " ); memcpy( placeholder, timeTakenBuffer.Get(), timeTakenBuffer.GetLength() ); }
// CheckStatsNode //------------------------------------------------------------------------------ void FBuildTest::CheckStatsNode( const FBuildStats & stats, size_t numSeen, size_t numBuilt, Node::Type nodeType ) const { const FBuildStats::Stats & nodeStats = stats.GetStatsFor( nodeType ); size_t actualNumSeen = nodeStats.m_NumProcessed; TEST_ASSERT( actualNumSeen == numSeen ); size_t actualNumBuilt = nodeStats.m_NumBuilt; TEST_ASSERT( actualNumBuilt == numBuilt ); }
// TestPCH //------------------------------------------------------------------------------ void TestPrecompiledHeaders::TestPCH() const { FBuildOptions options; options.m_ForceCleanBuild = true; options.m_UseCacheWrite = true; options.m_ShowSummary = true; // required to generate stats for node count checks #if defined( __WINDOWS__ ) AStackString<> obj( "../../../../ftmp/Test/PrecompiledHeaders/PCHUser.obj" ); #else AStackString<> obj( "../../../../ftmp/Test/PrecompiledHeaders/PCHUser.o" ); #endif AStackString<> pch( "../../../../ftmp/Test/PrecompiledHeaders/PrecompiledHeader.pch" ); AStackString<> lib( "../../../../ftmp/Test/PrecompiledHeaders/TestPCH.lib" ); EnsureFileDoesNotExist( obj ); EnsureFileDoesNotExist( pch ); EnsureFileDoesNotExist( lib ); FBuildStats stats = Build( options, false ); // don't use DB EnsureFileExists( obj ); EnsureFileExists( pch ); EnsureFileExists( lib ); // Check stats // Seen, Built, Type CheckStatsNode ( stats, 3, 2, Node::FILE_NODE ); // cpp + pch CheckStatsNode ( stats, 1, 1, Node::COMPILER_NODE ); CheckStatsNode ( stats, 2, 2, Node::OBJECT_NODE );// obj + pch obj CheckStatsNode ( stats, 1, 1, Node::LIBRARY_NODE ); CheckStatsNode ( stats, 1, 1, Node::DIRECTORY_LIST_NODE ); CheckStatsNode ( stats, 1, 1, Node::ALIAS_NODE ); CheckStatsNode ( stats, 1, 1, Node::DLL_NODE ); CheckStatsTotal( stats, 10, 9 ); // check we wrote all objects to the cache TEST_ASSERT( stats.GetStatsFor( Node::OBJECT_NODE ).m_NumCacheStores == 1 ); // only the main obj can be cached }
// Test //------------------------------------------------------------------------------ void TestCLR::Test() const { FBuildOptions options; options.m_ForceCleanBuild = true; options.m_UseCacheWrite = true; options.m_ShowSummary = true; // required to generate stats for node count checks EnsureFileDoesNotExist( "../../../../ftmp/Test/CLR/clr.lib" ); FBuildStats stats = Build( options, false, "CLR-Target" ); // dont' use DB EnsureFileExists( "../../../../ftmp/Test/CLR/clr.lib" ); // Check stats // Seen, Built, Type CheckStatsNode ( stats, 3, 1, Node::FILE_NODE ); // cpp CheckStatsNode ( stats, 1, 1, Node::COMPILER_NODE ); CheckStatsNode ( stats, 1, 1, Node::OBJECT_NODE ); CheckStatsNode ( stats, 1, 1, Node::LIBRARY_NODE ); CheckStatsNode ( stats, 1, 1, Node::ALIAS_NODE ); CheckStatsTotal( stats, 7, 5 ); TEST_ASSERT( stats.GetCacheStores() == 0 ); // cache not supported due to compiler bug }
// DoCPUTimeByItem //------------------------------------------------------------------------------ void Report::DoCPUTimeByItem( const FBuildStats & stats ) { DoSectionTitle( "CPU Time by Item", "cpuTimeByItem" ); DoTableStart(); // Headings Write( "<tr><th style=\"width:100px;\">Time</th><th style=\"width:100px;\">Type</th><th>Name</th></tr>\n" ); size_t numOutput = 0; // Result const Array< const Node * > & nodes = stats.GetNodesByTime(); for ( const Node ** it = nodes.Begin(); it != nodes.End(); ++ it ) { const Node * node = *it; float time = ( (float)node->GetProcessingTime() * 0.001f ); // ms to s const char * type = node->GetTypeName(); const char * name = node->GetName().Get(); // start collapsable section if ( numOutput == 10 ) { DoToggleSection( (uint32_t)nodes.GetSize() - 10 ); } Write( ( numOutput == 10 ) ? "<tr></tr><tr><td style=\"width:100px;\">%2.3fs</td><td style=\"width:100px;\">%s</td><td>%s</td></tr>\n" : "<tr><td>%2.3fs</td><td>%s</td><td>%s</td></tr>\n", time, type, name ); numOutput++; } DoTableStop(); if ( numOutput > 10 ) { Write( "</details>\n" ); } }
// GetLibraryStats //------------------------------------------------------------------------------ void Report::GetLibraryStats( const FBuildStats & stats ) { // gather library stats, sorted by CPU cost GetLibraryStatsRecurse( m_LibraryStats, stats.GetRootNode(), nullptr ); m_LibraryStats.SortDeref(); }
// DoCPUTimeByType //------------------------------------------------------------------------------ void Report::DoCPUTimeByType( const FBuildStats & stats ) { DoSectionTitle( "CPU Time by Node Type", "cpuTimeByNodeType" ); // Summary Pie Chart Array< PieItem > items( 32, true ); for ( size_t i=0; i < (size_t)Node::NUM_NODE_TYPES; ++i ) { const FBuildStats::Stats & nodeStats = stats.GetStatsFor( (Node::Type)i ); if ( nodeStats.m_NumProcessed == 0 ) { continue; } // label const char * typeName = Node::GetTypeName( Node::Type( i ) ); const float value = (float)( (double)nodeStats.m_ProcessingTimeMS / (double)1000 ); const uint32_t color = g_ReportNodeColors[ i ]; PieItem item( typeName, value, color, (void *)i ); items.Append( item ); } items.Sort(); // pie chart DoPieChart( items, " s" ); // table DoTableStart(); Write( "<tr><th width=80>Type</th><th width=80>Time</th><th width=80>Processed</th><th width=80>Built</th><th width=80>Cache Hits</th></tr>\n" ); for ( size_t i=0; i < items.GetSize(); ++i ) { Node::Type type = (Node::Type)(size_t)items[ i ].userData; const FBuildStats::Stats & nodeStats = stats.GetStatsFor( type ); if ( nodeStats.m_NumProcessed == 0 ) { continue; } const char * typeName = Node::GetTypeName( type ); const float value = (float)( (double)nodeStats.m_ProcessingTimeMS / (double)1000 ); const uint32_t processed = nodeStats.m_NumProcessed; const uint32_t built = nodeStats.m_NumBuilt; const uint32_t cacheHits = nodeStats.m_NumCacheHits; Write( "<tr><td>%s</td><td>%2.3fs</td><td>%u</td><td>%u</td>", typeName, value, processed, built, cacheHits ); if ( type == Node::OBJECT_NODE ) { // cacheable Write( "<td>%u</td></tr>\n", cacheHits ); } else { // non-cacheable Write( "<td>-</td></tr>\n" ); } } DoTableStop(); }
// CreateOverview //------------------------------------------------------------------------------ void Report::CreateOverview( const FBuildStats & stats ) { DoSectionTitle( "Overview", "overview" ); AStackString<> buffer; DoTableStart(); // Headings Write( "<tr><th width=150>Item</th><th>Details</th></tr>\n" ); // Full command line AStackString<> commandLineBuffer; Env::GetCmdLine( commandLineBuffer ); #if defined( __WINDOWS__ ) const char * exeExtension = strstr( commandLineBuffer.Get(), ".exe\"" ); const char * commandLine = exeExtension ? ( exeExtension + 5 ) : commandLineBuffer.Get(); // skip .exe + closing quote #else const char * commandLine = commandLineBuffer.Get(); #endif Write( "<tr><td width=80>Cmd Line Options</td><td>%s</td></tr>", commandLine ); // Target AStackString<> targets; const Node * rootNode = stats.GetRootNode(); if (rootNode->GetType() != Node::PROXY_NODE) { targets = rootNode->GetName(); } else { const Dependencies & childNodes = rootNode->GetStaticDependencies(); size_t num = childNodes.GetSize(); for ( size_t i=0; i<num; ++i ) { if ( i != 0 ) { targets += ", "; } const Node * child = childNodes[ i ].GetNode(); targets += child->GetName(); } } Write( "<tr><td>Target(s)</td><td>%s</td></tr>\n", targets.Get() ); // Result const bool buildOK = ( stats.GetRootNode()->GetState() == Node::UP_TO_DATE ); Write( "<tr><td>Result</td><td>%s</td></tr>\n", buildOK ? "OK" : "FAILED" ); // Real Time float totalBuildTime = stats.m_TotalBuildTime; stats.FormatTime( totalBuildTime, buffer ); Write( "<tr><td>Time</td><td>%s</td></tr>\n", buffer.Get() ); // Local CPU Time float totalLocalCPUInSeconds = (float)( (double)stats.m_TotalLocalCPUTimeMS / (double)1000 ); stats.FormatTime( totalLocalCPUInSeconds, buffer ); float localRatio = ( totalLocalCPUInSeconds / totalBuildTime ); Write( "<tr><td>CPU Time</td><td>%s (%2.1f:1)</td></tr>\n", buffer.Get(), localRatio ); // version info Write( "<tr><td>Version</td><td>%s %s</td></tr>\n", FBUILD_VERSION_STRING, FBUILD_VERSION_PLATFORM ); // report time char timeBuffer[ 256 ]; #if defined( __WINDOWS__ ) VERIFY( ::GetTimeFormat( LOCALE_NAME_USER_DEFAULT, // LCID Locale TIME_FORCE24HOURFORMAT, // DWORD dwFlags nullptr, // SYSTEMTIME *lpTime nullptr, // LPCTSTR lpFormat, timeBuffer, // LPTSTR lpTimeStr, 256 ) ); // int cchTime #elif defined( __APPLE__ ) timeBuffer[ 0 ] = '\000'; // TODO:MAC Implement GetTimeFormat in Report #elif defined( __LINUX__ ) timeBuffer[ 0 ] = '\000'; // TODO:LINUX Implement GetTimeFormat in Report #else #error Unknown platform #endif char dateBuffer[ 256 ]; #if defined( __WINDOWS__ ) VERIFY( ::GetDateFormat( LOCALE_NAME_USER_DEFAULT, // LCID Locale DATE_LONGDATE, // DWORD dwFlags nullptr, // SYSTEMTIME *lpTime nullptr, // LPCTSTR lpFormat, dateBuffer, // LPTSTR lpTimeStr, 256 ) ); // int cchTime #elif defined( __APPLE__ ) dateBuffer[ 0 ] = '\000'; // TODO:MAC Implement GetDateFormat in Report #elif defined( __LINUX__ ) dateBuffer[ 0 ] = '\000'; // TODO:LINUX Implement GetDateFormat in Report #else #error Unknown platform #endif // NOTE: leave space to patch in time taken later "^^^^ " Write( "<tr><td>Report Generated</td><td>^^^^ - %s %s</td></tr>\n", dateBuffer, timeBuffer ); DoTableStop(); }