Files
XCEngine/engine/third_party/physx/snippets/snippetprofilerconverter/SnippetProfilerConverter.cpp

547 lines
14 KiB
C++

// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
// ***********************************************************************************************
// This snippet converts raw profiling data to a json file format that can be loaded and viewed
// in Google Chrome.
//
// SnippetProfilerConverter is a simple command-line tool supporting the following options::
//
// --srcFile=<filename> Specify the raw profiler source file
// --dstFile=<filename> Specify the destination json file
//
// ***********************************************************************************************
#include "extensions/PxDefaultProfiler.h"
#include "foundation/PxString.h"
#include <iostream>
#include <stdio.h>
#include <vector>
#include <map>
#include <algorithm>
#include "string.h"
using namespace physx;
#define TENS_NANO_SECONDS_TO_MICRO_SECONDS 10.0 * 0.001
struct EventData
{
bool valid;
PxDefaultProfilerThread thread;
PxDefaultProfilerHeader header;
PxDefaultProfilerEvent event;
PxDefaultProfilerValueEvent value;
bool operator<(const EventData& rhs) const
{
return event.time < rhs.event.time;
}
};
struct NameData
{
PxDefaultProfilerName profilerName;
char* name;
};
// Parse the file into local data structures.
std::vector<EventData> gEventList;
std::map<PxU64, NameData> gNameMap;
PxU64 gInitialTime = 0;
struct CmdLineParameters
{
const char* srcFile;
const char* dstFile;
CmdLineParameters() : srcFile(NULL), dstFile(NULL)
{
}
};
static bool match(const char* opt, const char* ref)
{
std::string s1(opt);
std::string s2(ref);
return !s1.compare(0, s2.length(), s2);
}
static void printHelpMsg()
{
printf("SnippetProfilerConverter usage:\n"
"SnippetProfilerConverter "
"--srcFile=<filename> "
"--dstFile=<filename> \n");
printf("--srcFile=<filename>\n");
printf(" Name of the PhysX profiler input file to convert.\n");
printf("--dstFile=<filename>\n");
printf(" Name of the JSON output file to create.\n");
}
static bool parseCommandLine(CmdLineParameters& result, int argc, const char* const* argv)
{
if (argc <= 1)
{
printHelpMsg();
return false;
}
#define GET_PARAMETER(v, s) \
{ \
v = argv[i] + strlen(s); \
if( v == NULL ) \
{ \
printf("[ERROR] \"%s\" should have extra parameter\n", argv[i]);\
printHelpMsg(); \
return false; \
} \
}
for (int i = 0; i < argc; ++i)
{
if (argv[i][0] != '-' || argv[i][1] != '-')
{
if (i > 0)
{
printf("[ERROR] Unknown command line parameter \"%s\"\n", argv[i]);
printHelpMsg();
return false;
}
continue;
}
if (match(argv[i], "--srcFile="))
{
GET_PARAMETER(result.srcFile, "--srcFile=");
}
else if (match(argv[i], "--dstFile="))
{
GET_PARAMETER(result.dstFile, "--dstFile=");
}
else
{
printf("[ERROR] Unknown command line parameter \"%s\"\n", argv[i]);
printHelpMsg();
return false;
}
}
if (!result.srcFile || !result.dstFile)
{
printf("[ERROR] Missing args!! \n");
printHelpMsg();
return false;
}
return true;
}
bool freadCheck(void* buffer, size_t size, FILE* f)
{
size_t bytesRead = fread(buffer, 1, size, f);
if(bytesRead != size)
{
return false;
}
return true;
}
double convertTimeToMicroSeconds(PxU64 stopTime, PxU64 startTime = 0)
{
PxU64 initialTime = 0;
if(startTime == 0)
{
initialTime = gInitialTime;
}
return (double)(stopTime - initialTime - startTime) * TENS_NANO_SECONDS_TO_MICRO_SECONDS;
}
int snippetMain(int argc, const char* const* argv)
{
CmdLineParameters result;
if(!parseCommandLine(result, argc, argv))
{
return 1;
}
FILE* f = fopen(result.srcFile, "rb");
if(!f)
{
fprintf(stderr, "Could not open input file, \"%s\"!\n", result.srcFile);
exit(1);
}
// Read the version info.
PxDefaultProfilerVersionInfo versionInfo;
freadCheck(&versionInfo, sizeof(versionInfo), f);
if(versionInfo.major > PX_PROFILER_VERSION_MAJOR)
{
fprintf(stderr, "This tool cannot load the file. It is version %d.%d and this tool can only read versions %d.%d and earlier.\n",
versionInfo.major, versionInfo.minor,
PX_PROFILER_VERSION_MAJOR, PX_PROFILER_VERSION_MINOR);
exit(1);
}
else if(versionInfo.major == PX_PROFILER_VERSION_MAJOR)
{
if(versionInfo.minor > PX_PROFILER_VERSION_MINOR)
{
fprintf(stderr, "This tool cannot load the file. It is version %d.%d and this tool can only read versions %d.%d and earlier.\n",
versionInfo.major, versionInfo.minor,
PX_PROFILER_VERSION_MAJOR, PX_PROFILER_VERSION_MINOR);
exit(1);
}
}
// Loop through all of the events on each thread.
PxDefaultProfilerHeader header;
PxDefaultProfilerThread thread;
EventData event;
NameData name;
bool success = true;
thread.threadId = 0;
while(success)
{
success = freadCheck(&header, sizeof(header), f);
switch(header.type)
{
case PxDefaultProfilerDataType::eTHREAD_BLOCK:
{
success = freadCheck(&thread, sizeof(thread), f);
break;
}
case PxDefaultProfilerDataType::eZONE_START:
case PxDefaultProfilerDataType::eZONE_END:
case PxDefaultProfilerDataType::eZONE_START_CROSS_THREAD:
case PxDefaultProfilerDataType::eZONE_END_CROSS_THREAD:
case PxDefaultProfilerDataType::eFRAME:
{
event.valid = true;
event.header = header;
// The thread always precedes any event.
event.thread = thread;
success = freadCheck(&event.event, sizeof(event.event), f);
gEventList.push_back(event);
break;
}
case PxDefaultProfilerDataType::eNAME_REGISTRATION:
{
success = freadCheck(&name.profilerName, sizeof(name.profilerName), f);
if(gNameMap.find(name.profilerName.key) == gNameMap.end())
{
name.name = new char[name.profilerName.size];
success = freadCheck(name.name, name.profilerName.size, f);
// Add the name to the name map.
gNameMap[name.profilerName.key] = name;
}
else
{
fseek(f, name.profilerName.size, SEEK_CUR);
}
break;
}
case PxDefaultProfilerDataType::eVALUE_INT:
case PxDefaultProfilerDataType::eVALUE_FLOAT:
{
event.valid = true;
event.header = header;
// The thread always precedes any event.
event.thread = thread;
success = freadCheck(&event.value, sizeof(event.value), f);
gEventList.push_back(event);
break;
}
default:
break;
}
}
fclose(f);
// Sort all of the events by time.
std::sort(gEventList.begin(), gEventList.end());
// All times are relative to the first time in the list.
if(gEventList.size() > 0)
{
gInitialTime = gEventList[0].event.time;
}
// Create the output file.
FILE* w = fopen(result.dstFile, "wt");
if(!w)
{
fprintf(stderr, "Could not open output file, \"%s\"!\n", result.dstFile);
exit(1);
}
fprintf(w, "[\n");
bool firstLine = true;
// Process all of the intra threaded zones first.
for(PxU32 i = 0; i < gEventList.size(); i++)
{
EventData startEvent = gEventList[i];
if(startEvent.valid && startEvent.header.type == PxDefaultProfilerDataType::eZONE_START)
{
PxU32 j;
double startTimeInMicroSeconds = convertTimeToMicroSeconds(startEvent.event.time);
for(j = i + 1; j < gEventList.size(); j++)
{
EventData endEvent = gEventList[j];
if(endEvent.valid &&
endEvent.header.type == PxDefaultProfilerDataType::eZONE_END &&
startEvent.thread.threadId == endEvent.thread.threadId &&
startEvent.event.nameKey == endEvent.event.nameKey)
{
// Found a matching pair.
double durationInMicroSeconds = convertTimeToMicroSeconds(endEvent.event.time, startEvent.event.time);
if(firstLine == false)
{
fprintf(w, ",\n");
}
firstLine = false;
fprintf(w,
"{\"name\":\"%s\",\"ph\":\"X\",\"pid\":%llu,\"tid\":%llu,\"ts\":%g,\"dur\":%g,\"args\":{\"sceneId\":\"%llu\"}}",
gNameMap[startEvent.event.nameKey].name,
(unsigned long long)0,
(unsigned long long)startEvent.thread.threadId,
startTimeInMicroSeconds,
durationInMicroSeconds,
(unsigned long long)startEvent.event.contextId);
// Don't process these zones again.
startEvent.valid = false;
endEvent.valid = false;
break;
}
}
if(j == gEventList.size())
{
printf("Zone Start %s on thread %llu at time %g is missing a corresponding Zone End.\n",
gNameMap[startEvent.event.nameKey].name, (unsigned long long)startEvent.thread.threadId, startTimeInMicroSeconds);
}
}
}
// Process the remaining cross-threaded zones.
for(PxU32 i = 0; i < gEventList.size(); i++)
{
EventData startEvent = gEventList[i];
if(startEvent.valid && startEvent.header.type == PxDefaultProfilerDataType::eZONE_START_CROSS_THREAD)
{
PxU32 j;
double startTimeInMicroSeconds = convertTimeToMicroSeconds(startEvent.event.time);
for(j = i + 1; j < gEventList.size(); j++)
{
EventData endEvent = gEventList[j];
if(endEvent.valid &&
endEvent.header.type == PxDefaultProfilerDataType::eZONE_END_CROSS_THREAD &&
startEvent.event.nameKey == endEvent.event.nameKey)
{
// Found a matching pair.
double durationInMicroSeconds = convertTimeToMicroSeconds(endEvent.event.time, startEvent.event.time);
if(firstLine == false)
{
fprintf(w, ",\n");
}
firstLine = false;
fprintf(w,
"{\"name\":\"%s\",\"ph\":\"X\",\"pid\":%llu,\"tid\":%llu,\"ts\":%g,\"dur\":%g,\"args\":{\"sceneId\":\"%llu\"}}",
gNameMap[startEvent.event.nameKey].name,
(unsigned long long)0,
(unsigned long long)startEvent.thread.threadId,
startTimeInMicroSeconds,
durationInMicroSeconds,
(unsigned long long)startEvent.event.contextId);
// Don't process these zones again.
startEvent.valid = false;
endEvent.valid = false;
break;
}
}
if(j == gEventList.size())
{
printf("Cross thread Zone Start %s on thread %llu at time %g is missing a corresponding Zone End.\n",
gNameMap[startEvent.event.nameKey].name, (unsigned long long)startEvent.thread.threadId, startTimeInMicroSeconds);
}
}
}
// Write all of the int values.
for(PxU32 i = 0; i < gEventList.size(); i++)
{
EventData valueEvent = gEventList[i];
if(valueEvent.header.type == PxDefaultProfilerDataType::eVALUE_INT)
{
if(firstLine == false)
{
fprintf(w, ",\n");
}
firstLine = false;
double timeInMicrosSeconds = convertTimeToMicroSeconds(valueEvent.event.time);
fprintf(w,
"{\"name\":\"%s\",\"ph\":\"C\",\"pid\":%llu,\"tid\":%llu,\"ts\":%g,\"args\":{\"%s\":%d}}",
gNameMap[valueEvent.event.nameKey].name,
(unsigned long long)0,
(unsigned long long)valueEvent.thread.threadId,
timeInMicrosSeconds,
gNameMap[valueEvent.event.nameKey].name,
valueEvent.value.intValue);
}
}
// Write all of the float values.
for(PxU32 i = 0; i < gEventList.size(); i++)
{
EventData valueEvent = gEventList[i];
if(valueEvent.header.type == PxDefaultProfilerDataType::eVALUE_FLOAT)
{
if(firstLine == false)
{
fprintf(w, ",\n");
}
firstLine = false;
double timeInMicrosSeconds = convertTimeToMicroSeconds(valueEvent.event.time);
fprintf(w,
"{\"name\":\"%s\",\"ph\":\"C\",\"pid\":%llu,\"tid\":%llu,\"ts\":%g,\"args\":{\"%s\":%g}}",
gNameMap[valueEvent.event.nameKey].name,
(unsigned long long)0,
(unsigned long long)valueEvent.thread.threadId,
timeInMicrosSeconds,
gNameMap[valueEvent.event.nameKey].name,
(double)valueEvent.value.floatValue);
}
}
// Write all of the frames.
for(PxU32 i = 0; i < gEventList.size(); i++)
{
EventData frameEvent = gEventList[i];
if(frameEvent.header.type == PxDefaultProfilerDataType::eFRAME)
{
if(firstLine == false)
{
fprintf(w, ",\n");
}
firstLine = false;
double timeInMicrosSeconds = convertTimeToMicroSeconds(frameEvent.event.time);
fprintf(w,
"{\"name\":\"%s\",\"ph\":\"i\",\"pid\":%llu,\"tid\":%llu,\"ts\":%g,\"s\":\"t\",\"bp\":\"e\"},\n",
gNameMap[frameEvent.event.nameKey].name,
(unsigned long long)0,
(unsigned long long)frameEvent.thread.threadId,
timeInMicrosSeconds);
fprintf(w,
"{\"name\":\"Frame %s (%llu)\",\"ph\":\"i\",\"pid\":%llu,\"tid\":%llu,\"ts\":%g,\"s\":\"t\",\"bp\":\"e\"}",
gNameMap[frameEvent.event.nameKey].name,
(unsigned long long)frameEvent.event.contextId,
(unsigned long long)0,
(unsigned long long)frameEvent.thread.threadId,
timeInMicrosSeconds);
}
}
fprintf(w, "\n]\n");
fclose(w);
// Clean up.
std::map<PxU64, NameData>::iterator iterator;
for(iterator = gNameMap.begin(); iterator != gNameMap.end(); iterator++)
{
delete[] iterator->second.name;
}
return 1;
}