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: 27 211 12.8 %
Date: 2023-01-19 00:18:29 Functions: 10 20 50.0 %

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

Generated by: LCOV version 1.13