LCOV - code coverage report
Current view: top level - source/ps - ProfileViewer.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 28 210 13.3 %
Date: 2022-06-14 00:41:00 Functions: 7 16 43.8 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "ProfileViewer.h"
      21             : 
      22             : #include "graphics/Canvas2D.h"
      23             : #include "graphics/FontMetrics.h"
      24             : #include "graphics/TextRenderer.h"
      25             : #include "gui/GUIMatrix.h"
      26             : #include "lib/external_libraries/libsdl.h"
      27             : #include "maths/Size2D.h"
      28             : #include "maths/Vector2D.h"
      29             : #include "ps/CLogger.h"
      30             : #include "ps/CStrInternStatic.h"
      31             : #include "ps/Filesystem.h"
      32             : #include "ps/Hotkey.h"
      33             : #include "ps/Profile.h"
      34             : #include "ps/Pyrogenesis.h"
      35             : #include "scriptinterface/Object.h"
      36             : 
      37             : #include <algorithm>
      38             : #include <ctime>
      39             : #include <fstream>
      40             : 
      41             : struct CProfileViewerInternals
      42             : {
      43             :     NONCOPYABLE(CProfileViewerInternals); // because of the ofstream
      44             : public:
      45          15 :     CProfileViewerInternals() {}
      46             : 
      47             :     /// Whether the profiling display is currently visible
      48             :     bool profileVisible;
      49             : 
      50             :     /// List of root tables
      51             :     std::vector<AbstractProfileTable*> rootTables;
      52             : 
      53             :     /// Path from a root table (path[0]) to the currently visible table (path[size-1])
      54             :     std::vector<AbstractProfileTable*> path;
      55             : 
      56             :     /// Helper functions
      57             :     void TableIsDeleted(AbstractProfileTable* table);
      58             :     void NavigateTree(int id);
      59             : 
      60             :     /// File for saved profile output (reset when the game is restarted)
      61             :     std::ofstream outputStream;
      62             : };
      63             : 
      64             : 
      65             : ///////////////////////////////////////////////////////////////////////////////////////////////
      66             : // AbstractProfileTable implementation
      67             : 
      68          10 : AbstractProfileTable::~AbstractProfileTable()
      69             : {
      70          10 :     if (CProfileViewer::IsInitialised())
      71             :     {
      72           5 :         g_ProfileViewer.m->TableIsDeleted(this);
      73             :     }
      74           5 : }
      75           0 : 
      76             : 
      77             : ///////////////////////////////////////////////////////////////////////////////////////////////
      78             : // CProfileViewer implementation
      79             : 
      80             : 
      81           0 : // AbstractProfileTable got deleted, make sure we have no dangling pointers
      82          10 : void CProfileViewerInternals::TableIsDeleted(AbstractProfileTable* table)
      83             : {
      84          10 :     for(int idx = (int)rootTables.size()-1; idx >= 0; --idx)
      85             :     {
      86           5 :         if (rootTables[idx] == table)
      87             :             rootTables.erase(rootTables.begin() + idx);
      88           5 :     }
      89             : 
      90             :     for(size_t idx = 0; idx < path.size(); ++idx)
      91             :     {
      92             :         if (path[idx] != table)
      93             :             continue;
      94             : 
      95             :         path.erase(path.begin() + idx, path.end());
      96           5 :         if (path.size() == 0)
      97             :             profileVisible = false;
      98          15 :     }
      99             : }
     100          10 : 
     101          25 : 
     102             : // Move into child tables or return to parent tables based on the given number
     103             : void CProfileViewerInternals::NavigateTree(int id)
     104          10 : {
     105             :     if (id == 0)
     106           0 :     {
     107             :         if (path.size() > 1)
     108             :             path.pop_back();
     109           0 :     }
     110           0 :     else
     111           0 :     {
     112             :         AbstractProfileTable* table = path[path.size() - 1];
     113           5 :         size_t numrows = table->GetNumberRows();
     114             : 
     115             :         for(size_t row = 0; row < numrows; ++row)
     116             :         {
     117           0 :             AbstractProfileTable* child = table->GetChild(row);
     118             : 
     119           0 :             if (!child)
     120             :                 continue;
     121           0 : 
     122           0 :             --id;
     123             :             if (id == 0)
     124             :             {
     125             :                 path.push_back(child);
     126           0 :                 break;
     127           0 :             }
     128             :         }
     129           0 :     }
     130             : }
     131           0 : 
     132             : 
     133           0 : // Construction/Destruction
     134           0 : CProfileViewer::CProfileViewer()
     135             : {
     136           0 :     m = new CProfileViewerInternals;
     137           0 :     m->profileVisible = false;
     138             : }
     139           0 : 
     140           0 : CProfileViewer::~CProfileViewer()
     141             : {
     142             :     delete m;
     143             : }
     144           0 : 
     145             : 
     146             : // Render
     147             : void CProfileViewer::RenderProfile(CCanvas2D& canvas)
     148           5 : {
     149             :     if (!m->profileVisible)
     150           5 :         return;
     151           5 : 
     152           5 :     if (m->path.empty())
     153             :     {
     154           5 :         m->profileVisible = false;
     155             :         return;
     156           5 :     }
     157           5 : 
     158             :     PROFILE3_GPU("profile viewer");
     159             : 
     160             :     AbstractProfileTable* table = m->path[m->path.size() - 1];
     161           0 :     const std::vector<ProfileColumn>& columns = table->GetColumns();
     162             :     size_t numrows = table->GetNumberRows();
     163           0 : 
     164           0 :     CStrIntern font_name("mono-stroke-10");
     165             :     CFontMetrics font(font_name);
     166           0 :     int lineSpacing = font.GetLineSpacing();
     167             : 
     168           0 :     // Render background.
     169           0 :     float estimateWidth = 50.0f;
     170             :     for (const ProfileColumn& column : columns)
     171             :         estimateWidth += static_cast<float>(column.width);
     172           0 : 
     173             :     float estimateHeight = 3 + static_cast<float>(numrows);
     174           0 :     if (m->path.size() > 1)
     175           0 :         estimateHeight += 2;
     176           0 :     estimateHeight *= lineSpacing;
     177             : 
     178           0 :     canvas.DrawRect(CRect(CSize2D(estimateWidth, estimateHeight)), CColor(0.0f, 0.0f, 0.0f, 0.5f));
     179           0 : 
     180           0 :     // Draw row backgrounds.
     181             :     for (size_t row = 0; row < numrows; ++row)
     182             :     {
     183           0 :         canvas.DrawRect(
     184           0 :             CRect(CVector2D(0.0f, lineSpacing * (2.0f + row) + 2.0f), CSize2D(estimateWidth, lineSpacing)),
     185           0 :             row % 2 ? CColor(1.0f, 1.0f, 1.0f, 0.1f): CColor(0.0f, 0.0f, 0.0f, 0.1f));
     186             :     }
     187           0 : 
     188           0 :     // Print table and column titles.
     189           0 :     CTextRenderer textRenderer;
     190           0 :     textRenderer.SetCurrentFont(font_name);
     191             :     textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 1.0f, 1.0f));
     192           0 :     textRenderer.PrintfAt(2.0f, lineSpacing, L"%hs", table->GetTitle().c_str());
     193             :     textRenderer.Translate(22.0f, lineSpacing*2.0f);
     194             : 
     195           0 :     float colX = 0.0f;
     196             :     for (size_t col = 0; col < columns.size(); ++col)
     197           0 :     {
     198           0 :         CStrW text = columns[col].title.FromUTF8();
     199           0 :         int w, h;
     200             :         font.CalculateStringSize(text.c_str(), w, h);
     201             : 
     202             :         float x = colX;
     203           0 :         if (col > 0) // right-align all but the first column
     204           0 :             x += columns[col].width - w;
     205           0 :         textRenderer.Put(x, 0.0f, text.c_str());
     206           0 : 
     207           0 :         colX += columns[col].width;
     208             :     }
     209             : 
     210           0 :     textRenderer.Translate(0.0f, lineSpacing);
     211             : 
     212           0 :     // Print rows
     213           0 :     int currentExpandId = 1;
     214           0 : 
     215             :     for (size_t row = 0; row < numrows; ++row)
     216           0 :     {
     217           0 :         if (table->IsHighlightRow(row))
     218           0 :             textRenderer.SetCurrentColor(CColor(1.0f, 0.5f, 0.5f, 1.0f));
     219           0 :         else
     220             :             textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 1.0f, 1.0f));
     221           0 : 
     222             :         if (table->GetChild(row))
     223             :         {
     224           0 :             textRenderer.PrintfAt(-15.0f, 0.0f, L"%d", currentExpandId);
     225             :             currentExpandId++;
     226             :         }
     227             : 
     228             :         float rowColX = 0.0f;
     229           0 :         for (size_t col = 0; col < columns.size(); ++col)
     230             :         {
     231           0 :             CStrW text = table->GetCellText(row, col).FromUTF8();
     232           0 :             int w, h;
     233             :             font.CalculateStringSize(text.c_str(), w, h);
     234           0 : 
     235             :             float x = rowColX;
     236           0 :             if (col > 0) // right-align all but the first column
     237             :                 x += columns[col].width - w;
     238           0 :             textRenderer.Put(x, 0.0f, text.c_str());
     239           0 : 
     240             :             rowColX += columns[col].width;
     241             :         }
     242           0 : 
     243           0 :         textRenderer.Translate(0.0f, lineSpacing);
     244             :     }
     245           0 : 
     246           0 :     textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 1.0f, 1.0f));
     247           0 : 
     248             :     if (m->path.size() > 1)
     249           0 :     {
     250           0 :         textRenderer.Translate(0.0f, lineSpacing);
     251           0 :         textRenderer.Put(-15.0f, 0.0f, L"0");
     252           0 :         textRenderer.Put(0.0f, 0.0f, L"back to parent");
     253             :     }
     254           0 : 
     255             :     canvas.DrawText(textRenderer);
     256             : }
     257           0 : 
     258             : 
     259             : // Handle input
     260           0 : InReaction CProfileViewer::Input(const SDL_Event_* ev)
     261             : {
     262           0 :     switch(ev->ev.type)
     263             :     {
     264           0 :     case SDL_KEYDOWN:
     265           0 :     {
     266           0 :         if (!m->profileVisible)
     267             :             break;
     268             : 
     269           0 :         int k = ev->ev.key.keysym.sym;
     270             :         if (k >= SDLK_0 && k <= SDLK_9)
     271             :         {
     272             :             m->NavigateTree(k - SDLK_0);
     273             :             return IN_HANDLED;
     274           0 :         }
     275             :         break;
     276           0 :     }
     277             :     case SDL_HOTKEYPRESS:
     278           0 :         std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
     279           0 : 
     280           0 :         if( hotkey == "profile.toggle" )
     281             :         {
     282             :             if (!m->profileVisible)
     283           0 :             {
     284           0 :                 if (m->rootTables.size())
     285             :                 {
     286           0 :                     m->profileVisible = true;
     287           0 :                     m->path.push_back(m->rootTables[0]);
     288             :                 }
     289             :             }
     290             :             else
     291           0 :             {
     292           0 :                 size_t i;
     293             : 
     294           0 :                 for(i = 0; i < m->rootTables.size(); ++i)
     295             :                 {
     296           0 :                     if (m->rootTables[i] == m->path[0])
     297             :                         break;
     298           0 :                 }
     299             :                 i++;
     300           0 : 
     301           0 :                 m->path.clear();
     302             :                 if (i < m->rootTables.size())
     303             :                 {
     304             :                     m->path.push_back(m->rootTables[i]);
     305             :                 }
     306             :                 else
     307             :                 {
     308           0 :                     m->profileVisible = false;
     309             :                 }
     310           0 :             }
     311             :             return( IN_HANDLED );
     312             :         }
     313           0 :         else if( hotkey == "profile.save" )
     314             :         {
     315           0 :             SaveToFile();
     316           0 :             return( IN_HANDLED );
     317             :         }
     318           0 :         break;
     319             :     }
     320             :     return( IN_PASS );
     321             : }
     322           0 : 
     323             : InReaction CProfileViewer::InputThunk(const SDL_Event_* ev)
     324             : {
     325           0 :     if (CProfileViewer::IsInitialised())
     326             :         return g_ProfileViewer.Input(ev);
     327           0 : 
     328             :     return IN_PASS;
     329           0 : }
     330             : 
     331             : 
     332           0 : // Add a table to the list of roots
     333             : void CProfileViewer::AddRootTable(AbstractProfileTable* table, bool front)
     334             : {
     335             :     if (front)
     336             :         m->rootTables.insert(m->rootTables.begin(), table);
     337           7 :     else
     338             :         m->rootTables.push_back(table);
     339          14 : }
     340           0 : 
     341             : namespace
     342             : {
     343             :     class WriteTable
     344             :     {
     345             :     public:
     346             :         WriteTable(std::ofstream& outputStream) : m_OutputStream(outputStream) {}
     347           5 :         WriteTable(const WriteTable& writeTable) = default;
     348             : 
     349           5 :         void operator() (AbstractProfileTable* table)
     350           0 :         {
     351             :             std::vector<CStr> data; // 2d array of (rows+head)*columns elements
     352           5 : 
     353           5 :             const std::vector<ProfileColumn>& columns = table->GetColumns();
     354             : 
     355             :             // Add column headers to 'data'
     356             :             for (std::vector<ProfileColumn>::const_iterator col_it = columns.begin();
     357             :                     col_it != columns.end(); ++col_it)
     358             :                 data.push_back(col_it->title);
     359             : 
     360             :             // Recursively add all profile data to 'data'
     361             :             WriteRows(1, table, data);
     362             : 
     363           0 :             // Calculate the width of each column ( = the maximum width of
     364             :             // any value in that column)
     365           0 :             std::vector<size_t> columnWidths;
     366             :             size_t cols = columns.size();
     367           0 :             for (size_t c = 0; c < cols; ++c)
     368             :             {
     369             :                 size_t max = 0;
     370           0 :                 for (size_t i = c; i < data.size(); i += cols)
     371           0 :                     max = std::max(max, data[i].length());
     372           0 :                 columnWidths.push_back(max);
     373             :             }
     374             : 
     375           0 :             // Output data as a formatted table:
     376             : 
     377             :             m_OutputStream << "\n\n" << table->GetTitle() << "\n";
     378             : 
     379           0 :             if (cols == 0) // avoid divide-by-zero
     380           0 :                 return;
     381           0 : 
     382             :             for (size_t r = 0; r < data.size()/cols; ++r)
     383           0 :             {
     384           0 :                 for (size_t c = 0; c < cols; ++c)
     385           0 :                     m_OutputStream << (c ? " | " : "\n")
     386           0 :                       << data[r*cols + c].Pad(PS_TRIM_RIGHT, columnWidths[c]);
     387             : 
     388             :                 // Add dividers under some rows. (Currently only the first, since
     389             :                 // that contains the column headers.)
     390             :                 if (r == 0)
     391           0 :                     for (size_t c = 0; c < cols; ++c)
     392             :                         m_OutputStream << (c ? "-|-" : "\n")
     393           0 :                           << CStr::Repeat("-", columnWidths[c]);
     394           0 :             }
     395             :         }
     396           0 : 
     397             :         void WriteRows(int indent, AbstractProfileTable* table, std::vector<CStr>& data)
     398           0 :         {
     399           0 :             const std::vector<ProfileColumn>& columns = table->GetColumns();
     400           0 : 
     401             :             for (size_t r = 0; r < table->GetNumberRows(); ++r)
     402             :             {
     403             :                 // Do pretty tree-structure indenting
     404           0 :                 CStr indentation = CStr::Repeat("| ", indent-1);
     405           0 :                 if (r+1 == table->GetNumberRows())
     406           0 :                     indentation += "'-";
     407           0 :                 else
     408             :                     indentation += "|-";
     409             : 
     410             :                 for (size_t c = 0; c < columns.size(); ++c)
     411           0 :                     if (c == 0)
     412             :                         data.push_back(indentation + table->GetCellText(r, c));
     413           0 :                     else
     414             :                         data.push_back(table->GetCellText(r, c));
     415           0 : 
     416             :                 if (table->GetChild(r))
     417             :                     WriteRows(indent+1, table->GetChild(r), data);
     418           0 :             }
     419           0 :         }
     420           0 : 
     421             :     private:
     422           0 :         std::ofstream& m_OutputStream;
     423             :         const WriteTable& operator=(const WriteTable&);
     424           0 :     };
     425           0 : 
     426           0 :     struct DumpTable
     427             :     {
     428           0 :         const ScriptInterface& m_ScriptInterface;
     429             :         JS::PersistentRooted<JS::Value> m_Root;
     430           0 :         DumpTable(const ScriptInterface& scriptInterface, JS::HandleValue root) :
     431           0 :             m_ScriptInterface(scriptInterface)
     432             :         {
     433           0 :             ScriptRequest rq(scriptInterface);
     434             :             m_Root.init(rq.cx, root);
     435             :         }
     436             : 
     437             :         // std::for_each requires a move constructor and the use of JS::PersistentRooted<T> apparently breaks a requirement for an
     438             :         // automatic move constructor
     439             :         DumpTable(DumpTable && original) :
     440             :             m_ScriptInterface(original.m_ScriptInterface)
     441             :         {
     442             :             ScriptRequest rq(m_ScriptInterface);
     443             :             m_Root.init(rq.cx, original.m_Root.get());
     444             :         }
     445             : 
     446             :         void operator() (AbstractProfileTable* table)
     447             :         {
     448             :             ScriptRequest rq(m_ScriptInterface);
     449             : 
     450             :             JS::RootedValue t(rq.cx);
     451             :             Script::CreateObject(
     452             :                 rq,
     453             :                 &t,
     454             :                 "cols", DumpCols(table),
     455             :                 "data", DumpRows(table));
     456             : 
     457             :             Script::SetProperty(rq, m_Root, table->GetTitle().c_str(), t);
     458             :         }
     459             : 
     460             :         std::vector<std::string> DumpCols(AbstractProfileTable* table)
     461             :         {
     462             :             std::vector<std::string> titles;
     463             : 
     464             :             const std::vector<ProfileColumn>& columns = table->GetColumns();
     465             : 
     466             :             for (size_t c = 0; c < columns.size(); ++c)
     467             :                 titles.push_back(columns[c].title);
     468             : 
     469             :             return titles;
     470             :         }
     471             : 
     472             :         JS::Value DumpRows(AbstractProfileTable* table)
     473             :         {
     474             :             ScriptRequest rq(m_ScriptInterface);
     475             : 
     476             :             JS::RootedValue data(rq.cx);
     477             :             Script::CreateObject(rq, &data);
     478             : 
     479             :             const std::vector<ProfileColumn>& columns = table->GetColumns();
     480             : 
     481             :             for (size_t r = 0; r < table->GetNumberRows(); ++r)
     482             :             {
     483             :                 JS::RootedValue row(rq.cx);
     484             :                 Script::CreateArray(rq, &row);
     485             : 
     486             :                 Script::SetProperty(rq, data, table->GetCellText(r, 0).c_str(), row);
     487             : 
     488             :                 if (table->GetChild(r))
     489             :                 {
     490             :                     JS::RootedValue childRows(rq.cx, DumpRows(table->GetChild(r)));
     491             :                     Script::SetPropertyInt(rq, row, 0, childRows);
     492             :                 }
     493             : 
     494             :                 for (size_t c = 1; c < columns.size(); ++c)
     495             :                     Script::SetPropertyInt(rq, row, c, table->GetCellText(r, c));
     496             :             }
     497             : 
     498             :             return data;
     499             :         }
     500             : 
     501             :     private:
     502             :         const DumpTable& operator=(const DumpTable&);
     503             :     };
     504             : 
     505             :     bool SortByName(AbstractProfileTable* a, AbstractProfileTable* b)
     506             :     {
     507             :         return (a->GetName() < b->GetName());
     508             :     }
     509             : }
     510             : 
     511             : void CProfileViewer::SaveToFile()
     512             : {
     513             :     // Open the file, if necessary. If this method is called several times,
     514             :     // the profile results will be appended to the previous ones from the same
     515             :     // run.
     516             :     if (! m->outputStream.is_open())
     517             :     {
     518             :         // Open the file. (It will be closed when the CProfileViewer
     519           0 :         // destructor is called.)
     520             :         OsPath path = psLogDir()/"profile.txt";
     521           0 :         m->outputStream.open(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
     522             : 
     523             :         if (m->outputStream.fail())
     524             :         {
     525           0 :             LOGERROR("Failed to open profile log file");
     526             :             return;
     527             :         }
     528             :         else
     529             :         {
     530           0 :             LOGMESSAGERENDER("Profiler snapshot saved to '%s'", path.string8());
     531             :         }
     532             :     }
     533             : 
     534           0 :     time_t t;
     535           0 :     time(&t);
     536             :     m->outputStream << "================================================================\n\n";
     537           0 :     m->outputStream << "PS profiler snapshot - " << asctime(localtime(&t));
     538             : 
     539           0 :     std::vector<AbstractProfileTable*> tables = m->rootTables;
     540           0 :     sort(tables.begin(), tables.end(), SortByName);
     541             :     for_each(tables.begin(), tables.end(), WriteTable(m->outputStream));
     542             : 
     543             :     m->outputStream << "\n\n================================================================\n";
     544           0 :     m->outputStream.flush();
     545             : }
     546             : 
     547             : void CProfileViewer::ShowTable(const CStr& table)
     548           0 : {
     549           0 :     m->path.clear();
     550           0 : 
     551           0 :     if (table.length() > 0)
     552             :     {
     553           0 :         for (size_t i = 0; i < m->rootTables.size(); ++i)
     554           0 :         {
     555           0 :             if (m->rootTables[i]->GetName() == table)
     556             :             {
     557           0 :                 m->path.push_back(m->rootTables[i]);
     558           0 :                 m->profileVisible = true;
     559             :                 return;
     560             :             }
     561           0 :         }
     562             :     }
     563           0 : 
     564             :     // No matching table found, so don't display anything
     565           0 :     m->profileVisible = false;
     566             : }

Generated by: LCOV version 1.13