/// Hook the load library functions void HookLoadLibrary() { LogTrace(traceENTER, ""); AMDT::BeginHook(); LONG error = AMDT::HookAPICall(&(PVOID&)Real_LoadLibraryA, Mine_LoadLibraryA); PsAssert(error == NO_ERROR); error = AMDT::HookAPICall(&(PVOID&)Real_LoadLibraryExA, Mine_LoadLibraryExA); PsAssert(error == NO_ERROR); error = AMDT::HookAPICall(&(PVOID&)Real_LoadLibraryW, Mine_LoadLibraryW); PsAssert(error == NO_ERROR); error = AMDT::HookAPICall(&(PVOID&)Real_LoadLibraryExW, Mine_LoadLibraryExW); PsAssert(error == NO_ERROR); error = AMDT::HookAPICall(&(PVOID&)Real_FreeLibrary, Mine_FreeLibrary); PsAssert(error == NO_ERROR); if (AMDT::EndHook() != NO_ERROR) { Log(logERROR, "HookLoadLibrary() failed\n"); } // Load the "dxgi.dll" at the beginning as we missed the dxgi loading sometimes. // TODO: Fix this: This function is called from DllMain(), and according to the // Microsoft documentation, LoadLibrary() should not be called from DllMain() LoadLibraryA("dxgi.dll"); LogTrace(traceEXIT, ""); return; }
//----------------------------------------------------------------------------- /// Release global shared memory region so it can be accessed by other processes. //----------------------------------------------------------------------------- void SharedGlobal::Unlock(void) { PsAssert(this != NULL); PsAssert(m_Mutex != NULL); m_Mutex->unlock(); }
//----------------------------------------------------------- // Stops capturing D3DPerf markers //----------------------------------------------------------- LONG CAPTURE_D3DPerfMarkers_Unhook() { LogTrace(traceENTER, ""); ScopeLock t(&s_mtx); if (dwAttached <= 0) { Log(logERROR, "Trying to Detach ID3D10Device twice!\n"); return -1; } s_pCaptureLayer = NULL; LONG error; AMDT::BeginHook(); if (Real_D3DPERF_BeginEvent != NULL) { error = AMDT::UnhookAPICall(&(PVOID&)Real_D3DPERF_BeginEvent, Mine_D3DPERF_BeginEvent); PsAssert(error == NO_ERROR); } if (Real_D3DPERF_EndEvent != NULL) { error = AMDT::UnhookAPICall(&(PVOID&)Real_D3DPERF_EndEvent, Mine_D3DPERF_EndEvent); PsAssert(error == NO_ERROR); } if (Real_D3DPERF_SetMarker != NULL) { error = AMDT::UnhookAPICall(&(PVOID&)Real_D3DPERF_SetMarker, Mine_D3DPERF_SetMarker); PsAssert(error == NO_ERROR); } if (Real_D3DPERF_SetRegion != NULL) { error = AMDT::UnhookAPICall(&(PVOID&)Real_D3DPERF_SetRegion, Mine_D3DPERF_SetRegion); PsAssert(error == NO_ERROR); } error = AMDT::EndHook(); if (error != NO_ERROR) { Log(logERROR, "failed\n"); } else { dwAttached--; } LogTrace(traceEXIT, ""); return error; }
//----------------------------------------------------------------------------- /// Lock global shared memory region for exclusive access. /// \return true if the shared memory could be locked; false otherwise //----------------------------------------------------------------------------- bool SharedGlobal::Lock(void) { PsAssert(this != NULL); PsAssert(m_Mutex != NULL); if (m_Mutex->lock() == false) { Log(logERROR, "Error occurred while waiting for Mutex :%d\n", osGetLastSystemError()); return false; } return true; }
//-------------------------------------------------------------------------- /// Returns the values of editable commands /// \return XML containing the values of editable commands //-------------------------------------------------------------------------- string CommandProcessor::GetEditableCommandValues() { stringstream strOut; // give derived classes a chance to update / add settings. strOut << GetDerivedSettings(); // now add this processors editable commands (Settings). CommandList::const_iterator objIter; for (objIter = m_Commands.begin(); objIter < m_Commands.end(); ++objIter) { CommandResponse* pObj = *objIter; PsAssert(pObj != NULL); if (pObj->GetEditableContent() != NOT_EDITABLE) { strOut << "<"; strOut << pObj->GetTagName(); strOut << " name='"; strOut << pObj->GetDisplayName(); strOut << "' url='"; strOut << GetFullPathString().asCharArray(); strOut << "'>"; strOut << pObj->GetEditableContentValue(); strOut << "</"; strOut << pObj->GetTagName(); strOut << ">"; } } return strOut.str(); }
//////////////////////////////////////////////////////////////////////// /// Writes a message to the console /// \param msg The message to display. //////////////////////////////////////////////////////////////////////// void ClientRequestThread::OutputScreenMessage(const char* msg) { PsAssert(s_output_criticalSection != NULL); s_output_criticalSection->enter(); LogConsole(logMESSAGE, "%s\n", msg); s_output_criticalSection->leave(); }
//////////////////////////////////////////////////////////////////////// /// Writes an error to the console. It clears the error before exiting. /// \param errmsg The message to display. //////////////////////////////////////////////////////////////////////// void ClientRequestThread::OutputScreenError(const char* errmsg) { PsAssert(s_output_criticalSection != NULL); s_output_criticalSection->enter(); LogConsole(logERROR, "%s - %i\n", errmsg, NetSocket::LastError()); s_output_criticalSection->leave(); }
void DestroyResponse(CommunicationID& rRequestID, Response** ppResponse) { PsAssert(ppResponse != NULL); if (*ppResponse == NULL) { return; } // protect the maps from being changed by other threads using the mutex ScopeLock lock(s_mutex); // remove from streaming map if ((*ppResponse)->m_bStreamingEnabled) { ResponseMap::iterator iter = g_streamingResponseMap.find(rRequestID); if (iter != g_streamingResponseMap.end()) { g_streamingResponseMap.erase(iter); } } SAFE_DELETE(*ppResponse); rRequestID = 0; }
//----------------------------------------------------------------------------- /// Set a path in the global data block. All paths are assumed to be PS_MAX_PATH size. /// /// \param offset The offset from the start of the structure to the path of interest. /// It is calculated automatically at compile time using the offsetof functionality /// through the SG_SET_PATH macro /// \param path The path to set at the specified offset /// \return true if the path could be set; false otherwise //----------------------------------------------------------------------------- bool SharedGlobal::SetPath(size_t offset, const char* path) { PsAssert(this != NULL); PsAssert(path != NULL); PsAssert(m_MapFile != NULL); PsAssert(m_MapFile->Get() != NULL); if (Lock()) { strcpy_s(&((char*)m_MapFile->Get())[offset], PS_MAX_PATH, path); Unlock(); return (true); } return (false); }
//-------------------------------------------------------------------------- /// Registers the specified CommandProcessor /// \param pTagName The XML tag name of the processor. /// \param pDisplayName The name to display in the client for this command /// \param pID The ID that should target a command to the CommandProcessor /// \param pTitlePrefix A customizable prefix that is prepended to the display name /// \param eDisplayMode Indicates whether or not to display the command in the client /// \param rComProc The commandProcessor that should be targeted when a /// command with the supplied ID is received //-------------------------------------------------------------------------- void CommandProcessor::AddProcessor(const char* pTagName, const char* pDisplayName, const char* pID, const char* pTitlePrefix, UIDisplayMode eDisplayMode, CommandProcessor& rComProc) { PsAssert(pTagName != NULL); PsAssert(pDisplayName != NULL); PsAssert(pID != NULL); rComProc.SetTagName(pTagName); rComProc.SetID(pID); rComProc.SetDisplayName(pDisplayName); rComProc.SetTitlePrefix(pTitlePrefix); rComProc.SetUIDisplayMode(eDisplayMode); rComProc.SetParent(this); m_Processors.push_back(&rComProc); }
//----------------------------------------------------------------------------- /// Verify, align, and store the new profiler results. /// \param pQueue The Queue used to collect the results to verify. /// \param results The vector of profiler results to verify. /// \param pTimestampPair A pair of calibration timestamps used to align CPU and GPU timestamps. /// \param threadID The ThreadId that the results are collected with. /// \param frameStartTime The start time of the frame as collected on the CPU. //----------------------------------------------------------------------------- void VktFrameProfilerLayer::VerifyAlignAndStoreResults( VktWrappedQueue* pQueue, std::vector<ProfilerResult>& results, CalibrationTimestampPair* pTimestampPair, UINT32 threadID, GPS_TIMESTAMP frameStartTime) { #if MANUAL_TIMESTAMP_CALIBRATION == 0 UNREFERENCED_PARAMETER(pTimestampPair); UNREFERENCED_PARAMETER(frameStartTime); #endif SampleIdToProfilerResultMap* pResultMap = FindOrCreateProfilerResultsMap(pQueue, threadID); PsAssert(pResultMap != nullptr); if (pResultMap != nullptr) { for (size_t resultIndex = 0; resultIndex < results.size(); ++resultIndex) { ProfilerResult& currentResult = results[resultIndex]; const UINT64 sampleId = currentResult.measurementInfo.idInfo.sampleId; // Verify that the timestamps retrieved from the profiler appear to be valid. if (ValidateProfilerResult(currentResult) == true) { #if MANUAL_TIMESTAMP_CALIBRATION // Now attempt to align the profiled GPU timestamps with the traced API calls on the CPU. bool bAlignedSuccessfully = AlignProfilerResultWithCPUTimeline(currentResult, pTimestampPair, frameStartTime); #else bool bAlignedSuccessfully = true; #endif // @TODO - determine if this is what we want to do // Make zero-duration case equal to 1 clock cycle if (currentResult.timestampResult.rawClocks.start == currentResult.timestampResult.rawClocks.end) { currentResult.timestampResult.rawClocks.end++; } if (bAlignedSuccessfully) { // Store the final adjusted profiler results if they're valid. ProfilerResult* pNewResult = new ProfilerResult; CopyProfilerResult(pNewResult, ¤tResult); (*pResultMap)[sampleId] = pNewResult; } else { Log(logERROR, "Command with SampleId %d failed to align with CPU timeline.\n", sampleId); } } } } }
//-------------------------------------------------------------------------- /// Registers the specified CommandObject /// \param eType The type of content that this command should be responded with /// \param pTagName The name to assign to nodes in XML in the CommandTree /// \param pDisplayName The name to give the CommandResponse object /// \param pURL The URL that should activate the CommandResponse object /// \param eDisplayMode Indicates whether or not to display the command in the client /// \param eIncludeFlag Indicates whether or not to include the command in the CommandTree /// \param rComObj The CommandResponse object that will be activated when /// the supplied URL is received as a command //-------------------------------------------------------------------------- void CommandProcessor::AddCommand(ContentType eType, const char* pTagName, const char* pDisplayName, const char* pURL, UIDisplayMode eDisplayMode, TreeInclude eIncludeFlag, CommandResponse& rComObj) { PsAssert(pTagName != NULL); PsAssert(pDisplayName != NULL); PsAssert(pURL != NULL); rComObj.SetTagName(pTagName); rComObj.SetDisplayName(pDisplayName); rComObj.SetURL(pURL); rComObj.SetContentType(eType); rComObj.SetUIDisplayMode(eDisplayMode); rComObj.SetTreeInclude(eIncludeFlag); m_Commands.push_back(&rComObj); }
//----------------------------------------------------------------------------- /// Get a path from the global data block. All paths are assumed to be PS_MAX_PATH size. /// /// \param offset The offset from the start of the structure to the path of interest. /// It is calculated automatically at compile time using the offsetof functionality /// through the SG_GET_PATH macro /// /// \return A shadow copy of the path if maintained in the class. This allows us to directly pass back /// a char * without concerns about locking/unlocking the shared memory region. //----------------------------------------------------------------------------- const char* SharedGlobal::GetPath(size_t offset) { PsAssert(this != NULL); PsAssert(m_MapFile != NULL); PsAssert(m_MapFile->Get() != NULL); char* src = & ((char*)m_MapFile->Get())[offset]; char* dst = & ((char*) & m_Shadow)[offset]; size_t nSize = sizeof((&m_Shadow)[offset]); if (Lock()) { // copy shared memory copy of string into local shadow copy memcpy_s(dst, nSize, src, PS_MAX_PATH); Unlock(); return (dst); } return (NULL); }
//----------------------------------------------------------------------------- /// Provides access to the single instance of this class in the process. If the /// class does not exist, it will initialize it and log any errors that occur. /// \return pointer to the instance of this class. //----------------------------------------------------------------------------- SharedGlobal* SharedGlobal::Instance(void) { static SharedGlobal* sg = new SharedGlobal; PsAssert(sg != NULL); if (!sg->m_bInitialized && !sg->Initialize()) { LogConsole(logERROR, "Unable to create SharedGlobal data\n"); delete sg; sg = NULL; } return (sg); }
/// Unhook the load library functions void UnhookLoadLibrary() { LogTrace(traceENTER, ""); AMDT::BeginHook(); LONG error = AMDT::UnhookAPICall(&(PVOID&)Real_LoadLibraryA, Mine_LoadLibraryA); PsAssert(error == NO_ERROR); error = AMDT::UnhookAPICall(&(PVOID&)Real_LoadLibraryExA, Mine_LoadLibraryExA); PsAssert(error == NO_ERROR); error = AMDT::UnhookAPICall(&(PVOID&)Real_LoadLibraryW, Mine_LoadLibraryW); PsAssert(error == NO_ERROR); error = AMDT::UnhookAPICall(&(PVOID&)Real_LoadLibraryExW, Mine_LoadLibraryExW); PsAssert(error == NO_ERROR); error = AMDT::UnhookAPICall(&(PVOID&)Real_FreeLibrary, Mine_FreeLibrary); PsAssert(error == NO_ERROR); if (AMDT::EndHook() != NO_ERROR) { Log(logERROR, "UnhookLoadLibrary() failed\n"); } // Restore Real functions to original values in case they aren't restored correctly by the unhook call Real_LoadLibraryA = LoadLibraryA; Real_LoadLibraryExA = LoadLibraryExA; Real_LoadLibraryW = LoadLibraryW; Real_LoadLibraryExW = LoadLibraryExW; Real_FreeLibrary = FreeLibrary; LogTrace(traceEXIT, ""); return; }
//-------------------------------------------------------------------------- /// Allows the application to send an error response for this command. /// These errors also get added to the log /// \param pError Error message to send //-------------------------------------------------------------------------- void CommandResponse::SendError(const char* pError, ...) { PsAssert(pError != NULL); char errString[ COMMAND_MAX_LENGTH ]; va_list arg_ptr; // we prepend "Error: to the string for the HTTP response - but not for the logfile // pLogString tracks the start of the formatted string to pass to the Logfile int nLen = sprintf_s(errString, COMMAND_MAX_LENGTH, "Error: "); if (nLen < 0) { Log(logERROR, "String length is less than 0\n"); return; } char* pLogString = &errString[nLen]; va_start(arg_ptr, pError); vsprintf_s(pLogString, COMMAND_MAX_LENGTH - nLen, pError, arg_ptr); va_end(arg_ptr); Log(logERROR, "%s\n", pLogString); // send the error back to all received requests for (std::list< CommunicationID >::const_iterator iRequestID = m_requestIDs.begin(); iRequestID != m_requestIDs.end(); ++iRequestID) { bool bResult = SendResponse(*iRequestID, "text/plain", errString, (unsigned int) strlen(errString), m_bStreamingEnabled); if (bResult == false) { Log(logERROR, "Failed to send error to request %u\n", *iRequestID); m_bStreamingEnabled = false; m_eResponseState = ERROR_SENDING_RESPONSE; } } if (m_bStreamingEnabled == false) { // we sent a response and the request was not for streaming data, so unset the request m_requestIDs.clear(); } }
//-------------------------------------------------------------------------- /// Processes the specified CommandObject to see if it is targeting any of /// the added Processors /// \param rIncomingCommand the incoming command that should be handled /// \return true if the command could be processed; false otherwise //-------------------------------------------------------------------------- bool CommandProcessor::ProcessProcessors(CommandObject& rIncomingCommand) { ProcessorList::const_iterator it; for (it = m_Processors.begin() ; it < m_Processors.end(); ++it) { CommandProcessor* pProc = *it; PsAssert(pProc != NULL); if (rIncomingCommand.IsCommand(pProc->GetID())) { pProc->Process(rIncomingCommand); return true; } } return false; }
//-------------------------------------------------------------------------- /// Returns the number of commands that are editable. /// \return Number of editable commands (0-n). //-------------------------------------------------------------------------- unsigned int CommandProcessor::GetEditableCount() { unsigned int nCount = 0; CommandList::const_iterator objIter; for (objIter = m_Commands.begin(); objIter < m_Commands.end(); ++objIter) { CommandResponse* pObj = *objIter; PsAssert(pObj != NULL); if (pObj->GetEditableContent() != NOT_EDITABLE) { nCount++; } } return nCount; }
//----------------------------------------------------------------------------- /// ShouldResponseBeSent /// /// indicates whether a response should be sent to the specified request based /// on whether or not the request is streaming and is rate limited. If the /// request is rate limited, but a response can be sent, the "last sent time" /// will be updated if bUpdateTime is true /// /// \param requestID id of the request that may or may not be rate limited /// \param bUpdateTime indicates whether or not the "last sent time" will be /// updated if the response is rate limited, but allowed to be sent /// /// \return true if a response should NOT be sent; false otherwise //----------------------------------------------------------------------------- bool ShouldResponseBeSent(CommunicationID requestID, bool bUpdateTime) { // protect the maps from being changed by other threads using the mutex ScopeLock lock(s_mutex); ResponseMap::iterator iterResponse = g_streamingResponseMap.find(requestID); if (iterResponse == g_streamingResponseMap.end()) { // don't limit the send because we don't even know it is streaming return false; } Response* pResponse = iterResponse->second; PsAssert(pResponse != NULL); // if this is a streaming request, only send if rate allows if (pResponse->m_bStreamingEnabled == true) { if (pResponse->m_dwMaxStreamsPerSecond == COMM_MAX_STREAM_RATE || pResponse->m_dwMaxStreamsPerSecond == 0) { return false; } DWORD dwCurrTime = g_streamTimer.GetAbsolute(); if (dwCurrTime - pResponse->m_dwLastSent >= 1000 / pResponse->m_dwMaxStreamsPerSecond) { if (bUpdateTime) { pResponse->m_dwLastSent = dwCurrTime; } return false; } else { return true; } } return false; }
//----------------------------------------------------------------------------- /// Get the map used to associate queue with its command lists. /// \param inQueue Input queue. /// \returns The CommandQueueToCommandListMap used to associate a Queue with the CommandLists it utilizes. //----------------------------------------------------------------------------- D3D12_COMMAND_LIST_TYPE DX12FrameProfilerLayer::GetCommandListTypeFromCommandQueue(Wrapped_ID3D12CommandQueue* inQueue) { // Use the metadata object to retrieve the cached CreateInfo, and then pull the type out. Start with "Direct". D3D12_COMMAND_LIST_TYPE commandListType = D3D12_COMMAND_LIST_TYPE_DIRECT; PsAssert(inQueue != nullptr); if (inQueue != nullptr) { DX12ObjectDatabaseProcessor* databaseProcessor = DX12ObjectDatabaseProcessor::Instance(); DX12WrappedObjectDatabase* objectDatabase = static_cast<DX12WrappedObjectDatabase*>(databaseProcessor->GetObjectDatabase()); IDX12InstanceBase* queueMetadata = objectDatabase->GetMetadataObject((IUnknown*)inQueue); Wrapped_ID3D12CommandQueueCreateInfo* queueCreateInfo = static_cast<Wrapped_ID3D12CommandQueueCreateInfo*>(queueMetadata->GetCreateInfoStruct()); D3D12_COMMAND_QUEUE_DESC* queueDesc = queueCreateInfo->GetDescription(); commandListType = queueDesc->Type; } return commandListType; }
//----------------------------------------------------------------------------- /// Set the internal flag that determines if GPU command profiling is enabled. /// \param inbProfilingEnabled The flag used to enable or disable profiling. //----------------------------------------------------------------------------- void DX12FrameProfilerLayer::SetProfilingEnabled(bool inbProfilingEnabled) { ModernAPIFrameProfilerLayer::SetProfilingEnabled(inbProfilingEnabled); for (CommandQueueToCommandListMap::iterator it = mCommandQueueTracker.begin(); it != mCommandQueueTracker.end(); ++it) { Wrapped_ID3D12Device* pDevice = static_cast<Wrapped_ID3D12Device*>(DX12Util::SafeGetDevice(it->first)); if (pDevice != nullptr) { HRESULT stateSet = E_FAIL; stateSet = pDevice->mRealDevice->SetStablePowerState(inbProfilingEnabled); PsAssert(stateSet == S_OK); pDevice->Release(); } } }
bool IsToken(char** sIn, const char* sTok) { size_t dwTokLen = strlen(sTok); size_t nStringLength = strlen(*sIn); if (_strnicmp(*sIn, sTok, dwTokLen) == 0) { // Check to see if we are going to increment the pointer beond the end of the array. PsAssert(dwTokLen <= nStringLength); if (dwTokLen > nStringLength) { Log(logERROR, "IsToken: buffer overrun. Str = %s, Tok = %s\n", *sIn, sTok); return false; } *sIn += dwTokLen; return true; } return false; }
//----------------------------------------------------------------------------- /// Verify, align, and store the new profiler results. /// \param pQueue The Queue used to collect the results to verify. /// \param results The vector of profiler results to verify. /// \param pTimestampPair A pair of calibration timestamps used to align CPU and GPU timestamps. /// \param threadID The ThreadId that the results are collected with. /// \param frameStartTime The start time of the frame as collected on the CPU. //----------------------------------------------------------------------------- void DX12FrameProfilerLayer::VerifyAlignAndStoreResults( Wrapped_ID3D12CommandQueue* pQueue, std::vector<ProfilerResult>& results, CalibrationTimestampPair* pTimestampPair, UINT32 threadID, GPS_TIMESTAMP frameStartTime) { SampleIdToProfilerResultMap* pResultMap = FindOrCreateProfilerResultsMap(pQueue, threadID); PsAssert(pResultMap != nullptr); if (pResultMap != nullptr) { for (size_t resultIndex = 0; resultIndex < results.size(); ++resultIndex) { ProfilerResult& currentResult = results[resultIndex]; const UINT64 sampleId = currentResult.measurementInfo.idInfo.mSampleId; // Verify that the timestamps retrieved from the profiler appear to be valid. if (ValidateProfilerResult(currentResult) == true) { // Now attempt to align the profiled GPU timestamps with the traced API calls on the CPU. bool bAlignedSuccessfully = AlignProfilerResultWithCPUTimeline(currentResult, pTimestampPair, frameStartTime); if (bAlignedSuccessfully) { // Store the final adjusted profiler results if they're valid. ProfilerResult* pNewResult = new ProfilerResult; CopyProfilerResult(pNewResult, ¤tResult); (*pResultMap)[sampleId] = pNewResult; } else { Log(logERROR, "Command with SampleId %d failed to align with CPU timeline.\n", sampleId); } } } } }
//-------------------------------------------------------------------------- /// Retrieve an existing SampleInfo instance, or create a new one, for the given ThreadId. /// \param inThreadId The current thread requesting a SampleInfo structure. /// \returns A SampleInfo instance to be used with the given ThreadId. //-------------------------------------------------------------------------- ModernAPIFrameProfilerLayer::SampleInfo* ModernAPIFrameProfilerLayer::GetSampleInfoForThread(DWORD inThreadId) { SampleInfo* pResult = nullptr; if (mSampleIdMap.find(inThreadId) != mSampleIdMap.end()) { // mSampleIdMap already has a key for the incoming thread. Update the current sampleInfo. pResult = mSampleIdMap[inThreadId]; } else { // We need to insert a new key into the mSampleIdMap. Need to lock it first. ScopeLock sampleLock(&mUniqueSampleIdMutex); pResult = new SampleInfo; mSampleIdMap[inThreadId] = pResult; } PsAssert(pResult != nullptr); return pResult; }
//-------------------------------------------------------------------------- /// \param pName the full name of this object //-------------------------------------------------------------------------- void CommandResponse::SetDisplayName(const char* pName) { PsAssert(pName != NULL); m_pDisplayName = pName; }
//-------------------------------------------------------------------------- /// Return a string with all of the logged API call data in line-delimited /// text format. This is used within the Timeline view in the client. /// \return A string of all of the logged API calls captured during frame render. //-------------------------------------------------------------------------- std::string MultithreadedTraceAnalyzerLayer::GetAPITraceTXT() { // A switch to determine at the last moment whether or not we should send our generated response back to the client. bool bWriteResponseString = false; // Concatenate all of the logged call lines into a single string that we can send to the client. std::stringstream traceString; std::map<DWORD, ThreadTraceData*>::iterator traceIter; for (traceIter = mThreadTraces.begin(); traceIter != mThreadTraces.end(); ++traceIter) { ThreadTraceData* currentTrace = traceIter->second; const TimingLog& currentTimer = currentTrace->mAPICallTimer; GPS_TIMESTAMP timeFrequency = currentTimer.GetTimeFrequency(); const GPS_TIMESTAMP frameStartTime = mFramestartTime; size_t numEntries = currentTrace->mLoggedCallVector.size(); // When using the updated trace format, include a preamble section for each traced thread. #if defined(CODEXL_GRAPHICS) // Write the trace type, API, ThreadID, and count of APIs traced. traceString << "//==API Trace==" << std::endl; traceString << "//API=" << GetAPIString() << std::endl; traceString << "//ThreadID=" << traceIter->first << std::endl; traceString << "//ThreadAPICount=" << numEntries << std::endl; #endif for (size_t entryIndex = 0; entryIndex < numEntries; ++entryIndex) { // Get each logged call by index. const CallsTiming& callTiming = currentTimer.GetTimingByIndex(entryIndex); double deltaStartTime, deltaEndTime; bool conversionResults = currentTimer.ConvertTimestampToDoubles(callTiming.m_startTime, callTiming.m_endTime, deltaStartTime, deltaEndTime, frameStartTime, &timeFrequency); // We should always be able to convert from GPS_TIMESTAMPs to doubles. PsAssert(conversionResults == true); (void)conversionResults; const APIEntry* callEntry = currentTrace->mLoggedCallVector[entryIndex]; // This exists as a sanity check. If a duration stretches past this point, we can be pretty sure something is messed up. // This signal value is basically random, with the goal of it being large enough to catch any obvious duration errors. if (deltaEndTime > 8000000000.0f) { const char* functionName = callEntry->GetAPIName(); Log(logWARNING, "The duration for APIEntry '%s' with index '%d' is suspicious. Tracing the application may have hung, producing inflated results.\n", functionName, entryIndex); } callEntry->AppendAPITraceLine(traceString, deltaStartTime, deltaEndTime); } bWriteResponseString = true; } // If for some reason we failed to write a valid response string, reply with a known failure signal so the client handles it properly. if (!bWriteResponseString) { traceString << "NODATA"; } return traceString.str(); }
//----------------------------------------------------------------------------- bool MakeResponse(CommunicationID requestID, Response** ppResponse) { // protect the maps from being changed by other threads using the mutex ScopeLock lock(s_mutex); PsAssert(ppResponse != NULL); // first see if we already have this ID as a streaming response ResponseMap::iterator iterResponse = g_streamingResponseMap.find(requestID); if (iterResponse != g_streamingResponseMap.end()) { *ppResponse = iterResponse->second; return true; } // otherwise we need to create a new response based on the original request // so get the request RequestMap::iterator iterRequest = g_requestMap.find(requestID); if (iterRequest == g_requestMap.end()) { // the original request couldn't be found, so return failure return false; } // need to create a new response if (PsNew(*ppResponse) == false) { return false; } HTTPRequestHeader* pRequest = iterRequest->second; PsAssert(pRequest != NULL); if (pRequest->GetReceivedOverSocket() == true) { (*ppResponse)->client_socket = pRequest->GetClientSocket(); } else { #if defined (_WIN32) (*ppResponse)->client_socket = NetSocket::CreateFromDuplicate(pRequest->GetProtoInfo()); #else // create a new socket and connect to the streamSocket on the server (*ppResponse)->client_socket = NetSocket::Create(); if ((*ppResponse)->client_socket != NULL) { osPortAddress portAddress((unsigned short)pRequest->GetPort()); (*ppResponse)->client_socket->Connect(portAddress); } #endif } if ((*ppResponse)->client_socket == NULL) { int Err = NetSocket::LastError(); Log(logERROR, "Could not create socket: NetSocket failed with error: %ld\n", Err); return false; } // see if this should be added as a streaming response gtASCIIString strUrl(pRequest->GetUrl()); int32 iStream = strUrl.find(STR_STREAM_TOKEN); if (iStream >= 0) { const char* pBuf = strUrl.asCharArray(); const char* pRate = &pBuf[ iStream + strlen(STR_STREAM_TOKEN)]; unsigned int uRate = 0; // try to get the rate from the command; if (sscanf_s(pRate, "%u", &uRate) < 1) { // default to max rate uRate = COMM_MAX_STREAM_RATE; } // set the response as streaming with the specified rate (*ppResponse)->m_bStreamingEnabled = true; (*ppResponse)->m_dwMaxStreamsPerSecond = uRate; g_streamingResponseMap[ requestID ] = *ppResponse; } else { // streaming requests need to be kept around so that // additional responses can be directed to the right place, // HOWEVER, non-streaming requests only get a single response // and we just created the response for it, so it is safe // to remove the request from the requestMap. This will // help keep communication quick as the number of incoming // requests grows. RemoveRequest(requestID); } return true; }
//-------------------------------------------------------------------------- /// Processes the specified CommandObject to see if it should activate any /// of the added Commands /// \param rIncomingCommand the incoming command that should be handled /// \return true if the command could be processed; false otherwise //-------------------------------------------------------------------------- bool CommandProcessor::ProcessCommands(CommandObject& rIncomingCommand) { CommandList::const_iterator it; for (it = m_Commands.begin() ; it < m_Commands.end(); ++it) { CommandResponse* pComm = *it; PsAssert(pComm != NULL); if (rIncomingCommand.IsCommand(pComm->GetURL())) { float dummy; pComm->m_bStreamingEnabled = rIncomingCommand.GetParam("Stream", dummy); if (pComm->GetParams(rIncomingCommand)) { // skip over the parsed parameters pComm->SkipParsedParams(rIncomingCommand); // only set the command to be active if // - the command is not editable (the client has requested data) or // - AutoReply is false (another part of the server wants to react / respond) or // - this is the last command and no response has been set yet if (pComm->GetEditableContent() == NOT_EDITABLE || pComm->GetEditableContentAutoReply() == false || (rIncomingCommand.HasAnotherCommand() == false && rIncomingCommand.GetResponseState() == NO_RESPONSE)) { pComm->SetActiveRequest(rIncomingCommand); } if (rIncomingCommand.HasAnotherCommand() == false) { rIncomingCommand.SetState(DELAYED_RESPONSE); if (pComm->GetEditableContent() != NOT_EDITABLE && rIncomingCommand.GetResponseState() != SENT_RESPONSE) { if (pComm->GetEditableContentAutoReply()) { // if this is an editable value, // send back a response pComm->Send("OK"); rIncomingCommand.SetState(SENT_RESPONSE); } } } return true; } else { // return variable's value if (pComm->GetEditableContent() != NOT_EDITABLE && rIncomingCommand.GetResponseState() != SENT_RESPONSE && pComm->GetEditableContentAutoReply() ) { pComm->SetActiveRequest(rIncomingCommand); rIncomingCommand.SetState(DELAYED_RESPONSE); pComm->Send(pComm->GetEditableContentValue().c_str()); rIncomingCommand.SetState(SENT_RESPONSE); return true; } return false; } } } return false; }
//-------------------------------------------------------------------------- /// Should be called by an API-specific FrameDebugger at each drawcall /// \param rDrawCall the API-specific DrawCall that is being executed //-------------------------------------------------------------------------- void FrameDebugger::OnDrawCall(IDrawCall& rDrawCall) { m_ulDrawCallCounter++; if (m_drawCallList.IsActive()) { m_LastDrawCall = &rDrawCall; gtASCIIString xmlString = rDrawCall.GetXML(); xmlString += XML("hash", rDrawCall.GetHash().asCharArray()); m_drawCallList += GetDrawCallXML(m_ulDrawCallCounter, xmlString.asCharArray()) ; } // Could collect drawcall XML here if desired if (IsDrawCallEnabled(m_ulDrawCallCounter)) { // Make sure to not render after the frame debugger's break point if (IsTargetDrawCall(m_ulDrawCallCounter) == false) { // execute the drawcall for the app rDrawCall.Execute(); } else { // have the pipeline process non-hud commands before drawing anything on HUD OnDrawCallAtBreakPointPreHUD(rDrawCall); // allow the API-specific FrameDebugger to decide whether or not to do the drawcall at the breakpoint DoDrawCallAtBreakPoint(rDrawCall); // Send the current draw call back to the client. if (m_CurrentDrawCall.IsActive()) { m_CurrentDrawCall.Send(rDrawCall.GetXML().asCharArray()); } bool bRes = BeginHUD(); PsAssert(bRes != false); if (bRes == false) { Log(logERROR, "BeginHUD() failed"); return; } // show the wireframe overlay if (m_bWireframeOverlay) { DoWireframeOverlay(rDrawCall, m_fWireframeOverlayColor[0], m_fWireframeOverlayColor[1], m_fWireframeOverlayColor[2], m_fWireframeOverlayColor[3]); } if (m_bAutoRenderTarget) { DoAutoRenderTarget(); } OnDrawCallAtBreakPoint(rDrawCall); EndHUD(); // skip the rest of the draws if HUD is being configured if (false == m_bConfigHUD) { // When the frame debugger is enabled we process the messages from inside the FD and no longer re-render the Frame Capture. This speeds up processing of messages // in apps that are running very slowly for (int ProcessedCommands = 0;;) { if (m_drawCallList.IsActive() == true || m_Stats.IsActive() == true) { break; } // have the pipeline process non-hud commands before drawing anything on HUD OnDrawCallAtBreakPointPreHUD(rDrawCall); OnDrawCallAtBreakPoint(rDrawCall); gtASCIIString SuCmd = PeekPendingRequests(); // Check for non FD command, if this changes then we have to re-render the HUD etc. So we break out if (SuCmd.length() != 0) { char* pCmd = (char*)SuCmd.asCharArray(); ProcessedCommands++; if (strstr(pCmd, "FD/Pipeline") == NULL) { LogConsole(logMESSAGE, "Processed in FD %3u\n", ProcessedCommands); break; } GetSinglePendingRequest(); } } } } } }
//-------------------------------------------------------------------------- /// Sets the current tag name. /// \param pTagName The tag name. //-------------------------------------------------------------------------- void CommandResponse::SetTagName(const char* pTagName) { PsAssert(pTagName != NULL); m_pTagName = pTagName; }