MissionProfiler.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. // Spirenkov Maxim
  2. #include "MissionProfiler.h"
  3. CREATE_CLASS(MissionProfiler)
  4. //Указатель на единственный экземпляр профайлера
  5. MissionProfiler * MissionProfiler::ptr = null;
  6. MissionProfiler::MissionProfiler() : folders(_FL_, 1024),
  7. records(_FL_, 4096),
  8. missions(_FL_, 16),
  9. safeStrings(_FL_, 16384),
  10. timers(_FL_, 256)
  11. {
  12. folders.Reserve(4096);
  13. records.Reserve(16384);
  14. missions.Reserve(16);
  15. safeStrings.Reserve(65536);
  16. for(long i = 0; i < findTableSize; i++)
  17. {
  18. findTable[i] = -1;
  19. }
  20. framesCounter = 0;
  21. profileTime = 0.0;
  22. profileTriMillionsTotal = 0.0;
  23. profileBatchesTotal = 0.0;
  24. profileTriCountMax = 0;
  25. profileBatchesMax = 0;
  26. detailLevel = 100;
  27. isStop = false;
  28. render = null;
  29. timers.Reserve(256);
  30. timersIdCounter = 0xf00d;
  31. }
  32. MissionProfiler::~MissionProfiler()
  33. {
  34. api->DelObjectExecution(this, &MissionProfiler::Update);
  35. Out();
  36. ptr = null;
  37. }
  38. //Начать измерения
  39. void MissionProfiler::Start(dword detail)
  40. {
  41. if(!ptr)
  42. {
  43. api->CreateObject("MissionProfiler");
  44. Assert(ptr);
  45. ptr->detailLevel = detail;
  46. }
  47. }
  48. //Закончить измерения и вывести отчёт
  49. void MissionProfiler::Stop()
  50. {
  51. if(ptr)
  52. {
  53. ptr->isStop = true;
  54. }
  55. }
  56. //Инициализация
  57. bool MissionProfiler::Init()
  58. {
  59. render = (IRender *)api->GetService("DX9Render");
  60. Assert(render);
  61. if(ptr)
  62. {
  63. return false;
  64. }
  65. ptr = this;
  66. api->SetObjectExecution(this, "mission", 0x7fffff, &MissionProfiler::Update);
  67. return true;
  68. }
  69. //Обновление глобальных параметров
  70. void __fastcall MissionProfiler::Update(float dltTime)
  71. {
  72. profileTime += api->GetNoScaleDeltaTime();
  73. IRender::PerfomanceInfo renderPI;
  74. render->GetPerfomanceInfo(renderPI);
  75. profileTriMillionsTotal += renderPI.dwPolyCount*0.000001;
  76. profileBatchesTotal += (double)renderPI.dwBatchCount;
  77. if(profileTriCountMax < renderPI.dwPolyCount) profileTriCountMax = renderPI.dwPolyCount;
  78. if(profileBatchesMax < renderPI.dwBatchCount) profileBatchesMax = renderPI.dwBatchCount;
  79. framesCounter++;
  80. if(isStop)
  81. {
  82. api->DelObjectExecution(this, &MissionProfiler::Update);
  83. delete this;
  84. }
  85. }
  86. //Добавить запись профайла
  87. void MissionProfiler::AddProfileRecord(const ProfileData & data)
  88. {
  89. AssertCoreThread
  90. //Проверяем базовые указатели
  91. Assert(data.mission);
  92. Assert(data.object);
  93. Assert(data.func);
  94. //Папка на объект
  95. ObjectFolder & folder = GetFolder(data.mission, data.object, data.objectID, data.objectType);
  96. //Запись объекта
  97. Record & record = GetRecord(folder, data.func, data.level);
  98. record.count++;
  99. if(record.max < data.time)
  100. {
  101. record.max = data.time;
  102. }
  103. record.sum += (double)data.time;
  104. }
  105. //Добавить запись профайла
  106. void MissionProfiler::AddProfileRegistry(const ProfileData & data)
  107. {
  108. AssertCoreThread
  109. //Проверяем базовые указатели
  110. Assert(data.mission);
  111. Assert(data.object);
  112. //Папка на объект
  113. ObjectFolder & folder = GetFolder(data.mission, data.object, data.objectID, data.objectType);
  114. if(data.func)
  115. {
  116. //Запись объекта
  117. Record & record = GetRecord(folder, data.func, data.level);
  118. record.regCount++;
  119. }else{
  120. folder.regCount++;
  121. }
  122. }
  123. //Добавить запись профайла
  124. void MissionProfiler::AddProfileUnregistryLevel(const ProfileData & data)
  125. {
  126. AssertCoreThread
  127. //Проверяем базовые указатели
  128. Assert(data.mission);
  129. Assert(data.object);
  130. Assert(data.func);
  131. //Папка на объект
  132. ObjectFolder & folder = GetFolder(data.mission, data.object, data.objectID, data.objectType);
  133. //Запись объекта
  134. Record & record = GetRecord(folder, data.func, data.level);
  135. record.unregCount++;
  136. }
  137. //Добавить запись профайла
  138. void MissionProfiler::AddProfileUnregistry(const ProfileData & data)
  139. {
  140. AssertCoreThread
  141. //Проверяем базовые указатели
  142. Assert(data.mission);
  143. Assert(data.object);
  144. //Папка на объект
  145. ObjectFolder & folder = GetFolder(data.mission, data.object, data.objectID, data.objectType);
  146. if(folder.record >= 0)
  147. {
  148. //Проходим по цепочке
  149. for(long i = folder.record; i >= 0; )
  150. {
  151. Record & rec = records[i];
  152. if(data.func)
  153. {
  154. if(rec.func == data.func)
  155. {
  156. rec.unregCount++;
  157. }
  158. }else{
  159. rec.unregCount++;
  160. }
  161. i = rec.next;
  162. }
  163. }else{
  164. folder.unregCount++;
  165. }
  166. }
  167. //Найти/добавить папку на объект
  168. MissionProfiler::ObjectFolder & MissionProfiler::GetFolder(IMission * mission, void * object, const char * objectId, const char * objectType)
  169. {
  170. //Ищем индекс миссии
  171. MissionInfo * miss = missions.GetBuffer();
  172. dword misSize = missions.Size();
  173. for(dword i = 0; i < misSize; i++)
  174. {
  175. if(miss[i].mission == mission)
  176. {
  177. break;
  178. }
  179. }
  180. long missionIndex = i;
  181. if(i >= misSize)
  182. {
  183. missionIndex = missions.Add();
  184. missions[missionIndex].mission = mission;
  185. missions[missionIndex].name = SafeString(mission->GetMissionName());
  186. }
  187. //Ищем папку записей на данный объект
  188. long num = (byte *)object - (byte *)null;
  189. long hash = num ^ (num >> 4) ^ (num >> findTableBase) ^ (num >> findTableBase*2);
  190. dword findIndex = hash & findTableMask;
  191. ObjectFolder * newFolder = null;
  192. if(findTable[findIndex] >= 0)
  193. {
  194. //Проходим по цепочке папок с данным хэшем
  195. long i = findTable[findIndex];
  196. while(true)
  197. {
  198. ObjectFolder & oh = folders[i];
  199. if(oh.object == object && oh.missionIndex == missionIndex)
  200. {
  201. return oh;
  202. }
  203. if(oh.next < 0)
  204. {
  205. //Не нашли записи, добавим новую
  206. newFolder = &folders[oh.next = folders.Add()];
  207. break;
  208. }
  209. i = oh.next;
  210. }
  211. }else{
  212. //Нет ещё записей в данной цепочке
  213. newFolder = &folders[findTable[findIndex] = folders.Add()];
  214. }
  215. if(newFolder)
  216. {
  217. newFolder->next = -1;
  218. newFolder->record = -1;
  219. newFolder->object = object;
  220. newFolder->missionIndex = missionIndex;
  221. newFolder->name = SafeString(objectId);
  222. newFolder->type = SafeString(objectType);
  223. newFolder->regCount = 0;
  224. newFolder->unregCount = 0;
  225. }
  226. return *newFolder;
  227. }
  228. //Найти/добавить запись к объекту
  229. MissionProfiler::Record & MissionProfiler::GetRecord(ObjectFolder & folder, void * func, long level)
  230. {
  231. Record * newRecord = null;
  232. if(folder.record >= 0)
  233. {
  234. //Проходим по цепочке
  235. long i = folder.record;
  236. while(true)
  237. {
  238. Record & rec = records[i];
  239. if(rec.func == func && rec.level == level)
  240. {
  241. return rec;
  242. }
  243. if(rec.next < 0)
  244. {
  245. i = records.Add();
  246. rec.next = i;
  247. newRecord = &records[i];
  248. break;
  249. }
  250. i = rec.next;
  251. }
  252. }else{
  253. //Добавляем новую запись
  254. long i = records.Add();
  255. folder.record = i;
  256. newRecord = &records[i];
  257. }
  258. //Заполняем параметры новой записи
  259. newRecord->next = -1;
  260. newRecord->func = func;
  261. newRecord->level = level;
  262. newRecord->count = 0;
  263. newRecord->max = 0;
  264. newRecord->sum = 0.0f;
  265. newRecord->regCount = 0;
  266. newRecord->unregCount = 0;
  267. return *newRecord;
  268. }
  269. //Вывести отчёт
  270. void MissionProfiler::Out()
  271. {
  272. array<SortObjects> sortList(_FL_, 4096);
  273. static const char * stopLine = "===========================================================================";
  274. api->Trace(stopLine);
  275. api->Trace("Mission object profile info start");
  276. api->Trace(stopLine);
  277. api->Trace("");
  278. //Выводим последовательно статистику для каждой миссии
  279. double full = 0.0;
  280. for(long m = 0; m < missions; m++)
  281. {
  282. api->Trace("Mission: %s", GetString(missions[m].name));
  283. api->Trace("");
  284. api->Trace(stopLine);
  285. api->Trace("");
  286. //Собираем статистику по типам
  287. sortList.Empty();
  288. double summary = 0.0;
  289. for(long i = 0; i < folders; i ++)
  290. {
  291. ObjectFolder & folder = folders[i];
  292. if(folder.missionIndex != m)
  293. {
  294. continue;
  295. }
  296. double middle = 0.0;
  297. for(long ri = folder.record; ri >= 0; )
  298. {
  299. Record & r = records[ri];
  300. if(r.count > 0)
  301. {
  302. middle += r.sum/r.count;
  303. }
  304. ri = r.next;
  305. }
  306. summary += middle;
  307. //Если добавлен такой тип то сумируем статистику
  308. const char * type = GetString(folders[i].type);
  309. for(long j = 0; j < sortList; j++)
  310. {
  311. SortObjects & so = sortList[j];
  312. if(string::IsEqual(type, GetString(folders[so.index].type)))
  313. {
  314. so.count++;
  315. so.middle += middle;
  316. if(so.max < middle)
  317. {
  318. so.max = middle;
  319. }
  320. break;
  321. }
  322. }
  323. if(j >= sortList)
  324. {
  325. //Добавляем новую запись на новый тип
  326. SortObjects & so = sortList[sortList.Add()];
  327. so.index = i;
  328. so.count = 1;
  329. so.middle = middle;
  330. so.max = middle;
  331. }
  332. }
  333. full += summary;
  334. //Сортируем
  335. sortList.QSort(&MissionProfiler::QSort);
  336. //Выводим статистику по типам
  337. api->Trace("Brake per type");
  338. api->Trace("");
  339. api->Trace("Brake percents objectType objects count average max");
  340. api->Trace("");
  341. long count = sortList.Size();
  342. if(detailLevel == 0)
  343. {
  344. if(count > 10) count = 10;
  345. }
  346. for(long i = 0; i < count; i++)
  347. {
  348. SortObjects & so = sortList[i];
  349. api->Trace("%5.0i %6.2f %30s %8d %15.0f %15.0f",
  350. i + 1,
  351. so.middle/summary*100.0,
  352. GetString(folders[so.index].type),
  353. so.count,
  354. so.middle/so.count + 0.5,
  355. so.max + 0.5);
  356. }
  357. if(detailLevel < 2)
  358. {
  359. continue;
  360. }
  361. //Собираем статистику по объектам в список
  362. sortList.Empty();
  363. for(long i = 0; i < folders; i ++)
  364. {
  365. ObjectFolder & folder = folders[i];
  366. if(folder.missionIndex != m)
  367. {
  368. continue;
  369. }
  370. SortObjects so;
  371. so.index = i;
  372. so.count = 0;
  373. so.middle = 0.0f;
  374. so.max = 0.0f;
  375. for(long ri = folder.record; ri >= 0; )
  376. {
  377. Record & r = records[ri];
  378. so.count++;
  379. if(r.count > 0)
  380. {
  381. so.middle += r.sum/r.count;
  382. so.max += r.max;
  383. }
  384. ri = r.next;
  385. }
  386. if(so.count > 0)
  387. {
  388. so.max /= so.count;
  389. }
  390. if(so.index >= 0)
  391. {
  392. sortList.Add(so);
  393. }
  394. }
  395. //!!!
  396. for(long i = 0; i < sortList; i++)
  397. {
  398. for(long j = i + 1; j < sortList; j++)
  399. {
  400. Assert(sortList[i].index != sortList[j].index);
  401. }
  402. }
  403. //Сортируем
  404. sortList.QSort(&MissionProfiler::QSort);
  405. //Выводим
  406. api->Trace("");
  407. api->Trace("Brake per object");
  408. api->Trace("");
  409. api->Trace("Brake percents objectID objectType average max");
  410. api->Trace("");
  411. for(long i = 0; i < sortList; i++)
  412. {
  413. SortObjects & so = sortList[i];
  414. char percents[16];
  415. Print(percents, sizeof(percents), "%1.2f", (double)so.middle/summary*100.0, false);
  416. api->Trace("%5.0i %s %45s %30s %19.1f %19.1f",
  417. i + 1,
  418. percents,
  419. GetString(folders[so.index].name),
  420. GetString(folders[so.index].type),
  421. so.middle + 0.05,
  422. so.max + 0.05);
  423. if(detailLevel > 3)
  424. {
  425. const char * separator = " ----------------------------------------------------------------------------------------------------------------------------------------------------";
  426. api->Trace(separator);
  427. dword totalRegCount = folders[so.index].regCount;
  428. dword totalUnregCount = folders[so.index].unregCount;
  429. for(long ri = folders[so.index].record; ri >= 0; ri = records[ri].next)
  430. {
  431. totalRegCount += records[ri].regCount;
  432. totalUnregCount += records[ri].unregCount;
  433. }
  434. char treg[12];
  435. Print(treg, sizeof(treg), "%1.0f", (double)totalRegCount, false);
  436. char tureg[12];
  437. Print(tureg, sizeof(tureg), "%1.0f", (double)totalUnregCount, false);
  438. char oreg[12];
  439. Print(oreg, sizeof(oreg), "%1.0f", (double)folders[so.index].regCount, false);
  440. char oureg[12];
  441. Print(oureg, sizeof(oureg), "%1.0f", (double)folders[so.index].unregCount, false);
  442. api->Trace(" | total reg: %s total unreg: %s obj reg: %s obj unreg: %s |", treg, tureg, oreg, oureg);
  443. if(folders[so.index].record >= 0)
  444. {
  445. api->Trace(separator);
  446. //Расписываем детально вызовы
  447. for(long ri = folders[so.index].record; ri >= 0; ri = records[ri].next)
  448. {
  449. Record & r = records[ri];
  450. char lpercents[10];
  451. double average;
  452. if(r.count >= 1)
  453. {
  454. average = r.sum/r.count;
  455. Print(lpercents, sizeof(lpercents), "%1.4f", average/summary*100.0, false);
  456. }else{
  457. average = 0.0;
  458. Print(lpercents, sizeof(lpercents), "%1.4f", 0.0, false);
  459. }
  460. char level[12];
  461. Print(level, sizeof(level), "%1.0f", (double)r.level, true);
  462. char count[15];
  463. Print(count, sizeof(count), "%1.0f", (double)r.count, false);
  464. char reg[12];
  465. Print(reg, sizeof(reg), "%1.0f", (double)r.regCount, false);
  466. char unreg[12];
  467. Print(unreg, sizeof(unreg), "%1.0f", (double)r.unregCount, false);
  468. api->Trace(" | %s level:%s count: %s reg: %s unreg: %s | %19.1f %19.1f",
  469. lpercents,
  470. level,
  471. count,
  472. reg,
  473. unreg,
  474. average + 0.049,
  475. r.max + 0.049);
  476. }
  477. }
  478. api->Trace(separator);
  479. }
  480. }
  481. }
  482. api->Trace("");
  483. api->Trace(stopLine);
  484. api->Trace("Avirage total ticks per frame: %.2f seconds", full);
  485. api->Trace("Profiling counter: %u frames", framesCounter);
  486. api->Trace("Profiling time: %5.2f seconds", profileTime);
  487. api->Trace("Avirage FPS: %5.2f", framesCounter/profileTime);
  488. if(framesCounter)
  489. {
  490. api->Trace("Avirage triangles count per frame: %8.1f", (profileTriMillionsTotal/framesCounter)*1000000.0);
  491. api->Trace("Avirage batches count per frame: %8.1f", profileBatchesTotal/framesCounter);
  492. api->Trace("Maximum triangles count: %u", profileTriCountMax);
  493. api->Trace("Maximum batches count: %u", profileBatchesMax);
  494. }
  495. api->Trace(stopLine);
  496. api->Trace("Mission object profile info end");
  497. api->Trace(stopLine);
  498. api->Trace("\n\n");
  499. }
  500. //Сортировка по объектам
  501. bool MissionProfiler::QSort(SortObjects const & grtElm, SortObjects const & lesElm)
  502. {
  503. return lesElm.middle < grtElm.middle;
  504. }
  505. //Сохранить строку
  506. __forceinline MissionProfiler::safestring MissionProfiler::SafeString(const char * str)
  507. {
  508. safestring retValue = safeStrings.Size();
  509. while(*str)
  510. {
  511. safeStrings.Add(*str++);
  512. }
  513. safeStrings.Add(0);
  514. return retValue;
  515. }
  516. //Получить сохранёную строку
  517. __forceinline const char * MissionProfiler::GetString(safestring str)
  518. {
  519. return &safeStrings[str];
  520. }
  521. //Печать чисел выровненых по левой границе
  522. __forceinline void MissionProfiler::Print(char * buffer, dword size, const char * format, double num, bool sign)
  523. {
  524. crt_snprintf(buffer, size, format, num);
  525. for(dword i = 0; i < size - 1; i++)
  526. {
  527. if(!buffer[i])
  528. {
  529. for(; i < size - 1; i++)
  530. {
  531. buffer[i] = ' ';
  532. }
  533. break;
  534. }
  535. }
  536. if(sign && buffer[0] != '-')
  537. {
  538. for(long i = size - 1; i > 0; i--)
  539. {
  540. buffer[i] = buffer[i - 1];
  541. }
  542. buffer[0] = ' ';
  543. }
  544. buffer[size - 1] = 0;
  545. }