/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include "StandaloneTools_precompiled.h" #include #include #include #include "ProfilerDataPanel.hxx" #include #include "ProfilerDataAggregator.hxx" #include #include "Source/Driller/StripChart.hxx" #include "Source/Driller/DrillerAggregator.hxx" #include "Source/Driller/Profiler/ProfilerOperationTelemetryEvent.h" #include "ProfilerEvents.h" #include #include #include #include #include #include namespace Driller { enum { PDM_FUNCTIONNAME = 0, PDM_COMMENT, PDM_EXCLUSIVE_TIME, PDM_INCLUSIVE_TIME, PDM_EXCLUSIVE_PCT, PDM_INCLUSIVE_PCT, PDM_CALLS, PDM_CHILDREN_TIME, PDM_ACCUMULATED_TIME, PDM_CHILDREN_CALLS, PDM_ACCUMULATED_CALLS, PDM_THREAD_ID, PDM_TIME_TOTAL }; static const char* PDM_TIME_STRING[] = { "Function", "Comment", "Excl. Time (Micro)", "Incl. Time (Micro)", "Excl. Pct", "Incl. Pct", "Calls", "Child Time (Micro)", "Total Time (Micro)", "Child Calls", "Total Calls", "Thread ID" }; enum { PDM_NUMERIC_DATA_ROLE = Qt::UserRole + 1 }; enum { PDM_VALUE_FUNCTIONNAME = 0, PDM_VALUE_COMMENT, PDM_VALUE_1, PDM_VALUE_2, PDM_VALUE_3, PDM_VALUE_4, PDM_VALUE_5, PDM_VALUE_THREAD_ID, PDM_VALUE_TOTAL }; static const char* PDM_VALUE_STRING[] = { "Function", "Comment", "Value 1", "Value 2", "Value 3", "Value 4", "Value 5", "Thread ID" }; int ProfilerDataModel::m_colorIndexTracker = 0; class ProfilerFilterModel : public QSortFilterProxyModel { public: AZ_CLASS_ALLOCATOR(ProfilerFilterModel, AZ::SystemAllocator, 0); ProfilerFilterModel(QObject* pParent) : QSortFilterProxyModel(pParent) { setFilterCaseSensitivity(Qt::CaseSensitive); setDynamicSortFilter(false); } protected: virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const { switch (left.column()) { case PDM_FUNCTIONNAME: case PDM_COMMENT: return QSortFilterProxyModel::lessThan(left, right); break; default: AZ::u64 leftNumber = 0; AZ::u64 rightNumber = 0; // inner switch sanity check that we only pull numbers from numeric fields and default the rest to 0<0 false switch (left.column()) { case PDM_INCLUSIVE_TIME: case PDM_EXCLUSIVE_TIME: case PDM_INCLUSIVE_PCT: case PDM_EXCLUSIVE_PCT: case PDM_CHILDREN_TIME: case PDM_ACCUMULATED_TIME: case PDM_CALLS: case PDM_CHILDREN_CALLS: case PDM_ACCUMULATED_CALLS: case PDM_THREAD_ID: QVariant retrievedColumnLeft = sourceModel()->data(left, PDM_NUMERIC_DATA_ROLE); QVariant retrievedColumnRight = sourceModel()->data(right, PDM_NUMERIC_DATA_ROLE); leftNumber = retrievedColumnLeft.toULongLong(); rightNumber = retrievedColumnRight.toULongLong(); break; } return leftNumber < rightNumber; break; } } }; class ProfilerValueFilterModel : public QSortFilterProxyModel { public: AZ_CLASS_ALLOCATOR(ProfilerValueFilterModel, AZ::SystemAllocator, 0); ProfilerValueFilterModel(QObject* pParent) : QSortFilterProxyModel(pParent) { setFilterCaseSensitivity(Qt::CaseSensitive); setDynamicSortFilter(false); } protected: virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const { switch (left.column()) { case PDM_FUNCTIONNAME: case PDM_COMMENT: return QSortFilterProxyModel::lessThan(left, right); break; default: AZ::u64 leftNumber = 0; AZ::u64 rightNumber = 0; // inner switch sanity check that we only pull numbers from numeric fields and default the rest to 0<0 false switch (left.column()) { case PDM_VALUE_1: case PDM_VALUE_2: case PDM_VALUE_3: case PDM_VALUE_4: case PDM_VALUE_5: case PDM_VALUE_THREAD_ID: QVariant retrievedColumnLeft = sourceModel()->data(left, PDM_NUMERIC_DATA_ROLE); QVariant retrievedColumnRight = sourceModel()->data(right, PDM_NUMERIC_DATA_ROLE); leftNumber = retrievedColumnLeft.toULongLong(); rightNumber = retrievedColumnRight.toULongLong(); break; } return leftNumber < rightNumber; break; } } }; ProfilerAxisFormatter::ProfilerAxisFormatter(QObject* pParent, int whichTypeOfRegister) : QAbstractAxisFormatter(pParent) { m_lastAxisValueForScaling = 1.0f; m_whatKindOfRegister = whichTypeOfRegister; } QString ProfilerAxisFormatter::formatMicroseconds(float value) { // data is in microseconds! // so how big is the division size? if (m_lastAxisValueForScaling > 100000.0f) // greater than a 0.1 second per division { if (m_lastAxisValueForScaling > 1000000.0f) // whole seconds { return QObject::tr("%1s").arg(QString::number(value / 1000000.0f, 'f', 0)); } else { return QObject::tr("%1s").arg(QString::number(value / 1000000.0f, 'f', 1)); } } else if (m_lastAxisValueForScaling > 100.0f) // greater than a 0.1 millisecond per division { if (m_lastAxisValueForScaling > 1000.0f) // whole milliseconds { return QObject::tr("%1%2").arg(QString::number(value / 1000.0f, 'f', 0)).arg("ms"); } else { return QObject::tr("%1%2").arg(QString::number(value / 1000.0f, 'f', 1)).arg("ms"); } } else if (m_lastAxisValueForScaling > 1.0f) { return QObject::tr("%1%2s").arg((int)value).arg(QChar(0x00b5)); } else { return QObject::tr("%1%2s").arg(QString::number((double)value, 'f', 2)).arg(QChar(0x00b5)); } } QString ProfilerAxisFormatter::convertAxisValueToText(Charts::AxisType axis, float value, float /*minDisplayedValue*/, float /*maxDisplayedValue*/, float divisionSize) { if (axis == Charts::AxisType::Vertical) { if (m_whatKindOfRegister == (int)Profiler::RegisterInfo::PRT_TIME) { m_lastAxisValueForScaling = divisionSize; return formatMicroseconds(value); } else { return QString::number(value); } } else { return QString::number((int)value); } }; /////////////////////// // ProfilerDataWidget /////////////////////// ProfilerDataWidget::ProfilerDataWidget(QWidget* parent) : AzToolsFramework::QTreeViewWithStateSaving(parent) , m_dataModel(NULL) { m_cachedChart = NULL; m_cachedColumn = PDM_EXCLUSIVE_TIME; m_autoZoom = true; m_cachedFlatView = false; m_cachedDeltaData = true; m_manualZoomMin = 2000000000.0f; m_manualZoomMax = -2000000000.0f; m_iLastHighlightedChannel = -1; setFocusPolicy(Qt::StrongFocus); // required for key press handling setEnabled(true); setSortingEnabled(true); sortByColumn(0, Qt::AscendingOrder); header()->setSectionResizeMode(QHeaderView::Interactive); header()->setSectionsMovable(true); header()->setStretchLastSection(false); setUniformRowHeights(true); connect(this, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(OnDoubleClicked(const QModelIndex &))); } ProfilerDataWidget::~ProfilerDataWidget() { //m_stateSaver->Detach(); if (m_dataModel) { delete m_dataModel; } } void ProfilerDataWidget::SetViewType(int viewType) { m_viewType = viewType; if (m_viewType == Profiler::RegisterInfo::PRT_TIME) { m_dataModel = new ProfilerDataModel(); if (m_dataModel) { m_filterModel = aznew ProfilerFilterModel(this); m_filterModel->setSourceModel(m_dataModel); setModel(m_filterModel); } } if (m_viewType == Profiler::RegisterInfo::PRT_VALUE) { m_dataModel = new ProfilerCounterDataModel(); if (m_dataModel) { m_filterModel = aznew ProfilerValueFilterModel(this); m_filterModel->setSourceModel(m_dataModel); setModel(m_filterModel); } } AZ_Assert(m_dataModel, "SetViewType has an invalid argument, profiler data model cannot be created!"); } void ProfilerDataWidget::OnChartTypeMenu() { QAction* qa = qobject_cast(sender()); if (qa) { OnChartTypeMenu(qa->objectName()); } } void ProfilerDataWidget::OnChartTypeMenu(QString typeStr) { ProfilerOperationTelemetryEvent chartTypeChanged; int newValue = 0; if (m_viewType == Profiler::RegisterInfo::PRT_TIME) { newValue = PDM_EXCLUSIVE_TIME; chartTypeChanged.SetAttribute("ChartTimeType", typeStr.toStdString().c_str()); if (typeStr == "Incl.Time") { newValue = PDM_INCLUSIVE_TIME; } if (typeStr == "Excl.Time") { newValue = PDM_EXCLUSIVE_TIME; } if (typeStr == "Calls") { newValue = PDM_CALLS; } if (typeStr == "Acc.Time") { newValue = PDM_ACCUMULATED_TIME; } if (typeStr == "Acc.Calls") { newValue = PDM_ACCUMULATED_CALLS; } } else if (m_viewType == Profiler::RegisterInfo::PRT_VALUE) { newValue = PDM_VALUE_1; chartTypeChanged.SetAttribute("ChartValueType", typeStr.toStdString().c_str()); if (typeStr == "Value 1") { newValue = PDM_VALUE_1; } if (typeStr == "Value 2") { newValue = PDM_VALUE_2; } if (typeStr == "Value 3") { newValue = PDM_VALUE_3; } if (typeStr == "Value 4") { newValue = PDM_VALUE_4; } if (typeStr == "Value 5") { newValue = PDM_VALUE_5; } } chartTypeChanged.Log(); m_cachedColumn = newValue; RedrawChart(); } void ProfilerDataWidget::BeginDataModelUpdate() { PauseTreeViewSaving(); m_dataModel->BeginAddRegisters(); } void ProfilerDataWidget::EndDataModelUpdate() { m_dataModel->EndAddRegisters(); // this will capture any changes/zoom to the chart by the user if ((!m_autoZoom) && (m_cachedChart)) { m_cachedChart->GetWindowRange(Charts::AxisType::Vertical, m_manualZoomMin, m_manualZoomMax); } UnpauseTreeViewSaving(); ApplyTreeViewSnapshot(); } void ProfilerDataWidget::OnExpandAll() { expandAll(); CaptureTreeViewSnapshot(); // expand all doesn't signal, this captures the fully open tree // now that column organization is being stored in user settings // I'm disabling this so the user isn't surprised by his view suddenly changing //resizeColumnToContents( 0 ); } void ProfilerDataWidget::OnHideSelected() { m_iLastHighlightedChannel = -1; QModelIndexList list = selectionModel()->selectedIndexes(); foreach (QModelIndex index, list) { if (index.column() == 0) { QModelIndex sourceIndex = m_filterModel->mapToSource(index); const Driller::ProfilerDrillerUpdateRegisterEvent* tp = (Driller::ProfilerDrillerUpdateRegisterEvent*)(sourceIndex.internalPointer()); if (tp) { if (m_dataModel->m_enabledChartingMap.find(tp->GetRegister()->GetInfo().m_id) != m_dataModel->m_enabledChartingMap.end()) { m_dataModel->m_enabledChartingMap[ tp->GetRegister()->GetInfo().m_id ] = 0; QModelIndex column0(m_dataModel->index(index.row(), 0)); emit dataChanged(column0, column0); } } } } update(); RedrawChart(); } void ProfilerDataWidget::OnShowSelected() { QModelIndexList list = selectionModel()->selectedIndexes(); foreach (QModelIndex index, list) { if (index.column() == 0) { QModelIndex sourceIndex = m_filterModel->mapToSource(index); const Driller::ProfilerDrillerUpdateRegisterEvent* tp = (Driller::ProfilerDrillerUpdateRegisterEvent*)(sourceIndex.internalPointer()); if (tp) { if (m_dataModel->m_enabledChartingMap.find(tp->GetRegister()->GetInfo().m_id) != m_dataModel->m_enabledChartingMap.end()) { m_dataModel->m_enabledChartingMap[ tp->GetRegister()->GetInfo().m_id ] = 1; QModelIndex column0(m_dataModel->index(index.row(), 0)); emit dataChanged(column0, column0); } } } } update(); RedrawChart(); } void ProfilerDataWidget::OnInvertHidden() { AZStd::map::iterator iter = m_dataModel->m_enabledChartingMap.begin(); while (iter != m_dataModel->m_enabledChartingMap.end()) { int current = iter->second; iter->second = !current; ++iter; } update(); RedrawChart(); } void ProfilerDataWidget::OnHideAll() { m_iLastHighlightedChannel = -1; AZStd::map::iterator iter = m_dataModel->m_enabledChartingMap.begin(); while (iter != m_dataModel->m_enabledChartingMap.end()) { iter->second = 0; ++iter; } update(); RedrawChart(); } void ProfilerDataWidget::OnShowAll() { AZStd::map::iterator iter = m_dataModel->m_enabledChartingMap.begin(); while (iter != m_dataModel->m_enabledChartingMap.end()) { iter->second = 1; ++iter; } update(); RedrawChart(); } void ProfilerDataWidget::OnAutoZoomChange(bool newValue) { if (!newValue) { m_autoZoom = false; m_cachedChart->GetWindowRange(Charts::AxisType::Vertical, m_manualZoomMin, m_manualZoomMax); } else { m_autoZoom = true; m_manualZoomMin = 2000000000.0f; m_manualZoomMax = -2000000000.0f; } update(); RedrawChart(); } void ProfilerDataWidget::OnFlatView(bool isOn) { m_cachedFlatView = isOn; m_dataModel->SetFlatView(m_cachedFlatView); update(); RedrawChart(); } void ProfilerDataWidget::OnDeltaData(bool isOn) { m_cachedDeltaData = isOn; m_dataModel->SetDeltaData(m_cachedDeltaData); update(); RedrawChart(); } void ProfilerDataWidget::OnDoubleClicked(const QModelIndex& index) { if (index.isValid()) { QModelIndex sourceIndex = m_filterModel->mapToSource(index); const Driller::ProfilerDrillerUpdateRegisterEvent* tp = (Driller::ProfilerDrillerUpdateRegisterEvent*)(sourceIndex.internalPointer()); if (tp) { if (m_dataModel->m_enabledChartingMap.find(tp->GetRegister()->GetInfo().m_id) != m_dataModel->m_enabledChartingMap.end()) { int current = m_dataModel->m_enabledChartingMap.find(tp->GetRegister()->GetInfo().m_id)->second; m_dataModel->m_enabledChartingMap[ tp->GetRegister()->GetInfo().m_id ] = !current; QModelIndex column0(m_dataModel->index(index.row(), 0)); emit dataChanged(column0, column0); // no matter where we clicked, update only column 0 RedrawChart(); } } } } void ProfilerDataWidget::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { QTreeView::selectionChanged(selected, deselected); } void ProfilerDataWidget::RedrawChart() { if (m_cachedChart) { m_iLastHighlightedChannel = -1; m_cachedChart->Reset(); m_cachedChart->AddAxis("Frame", static_cast(m_cachedStartFrame), static_cast(m_cachedStartFrame + m_cachedDisplayRange), true, true); m_cachedChart->AddAxis("", m_manualZoomMin, m_manualZoomMax, false, false); m_cachedChart->SetDataDirty(); } } void ProfilerDataWidget::ConfigureChart(StripChart::DataStrip* chart, FrameNumberType atFrame, int howFar, FrameNumberType frameCount) { (void)frameCount; // this can be NULL if (chart) { // stored against the need to update on local selection changes, and used internally if (m_cachedChart != chart) { m_cachedChart = chart; ProfilerAxisFormatter* prf = aznew ProfilerAxisFormatter(this, (int)m_viewType); m_cachedChart->SetAxisTextFormatter(prf); m_formatter = prf; m_cachedChart->AttachDataSourceWidget(this); } m_cachedDisplayRange = howFar; m_cachedCurrentFrame = atFrame; m_cachedEndFrame = atFrame; m_cachedStartFrame = AZStd::GetMax(atFrame - m_cachedDisplayRange, 0); RedrawChart(); } } void ProfilerDataWidget::ProvideData(StripChart::DataStrip* chart) { PlotTimeHistory(chart); } void ProfilerDataWidget::onMouseOverNothing(float primaryAxisValue, float dependentAxisValue) { (void)primaryAxisValue; (void)dependentAxisValue; if (m_iLastHighlightedChannel != -1) { m_cachedChart->SetChannelHighlight(m_iLastHighlightedChannel, false); m_iLastHighlightedChannel = -1; m_dataModel->SetHighlightedRegisterID(0); } } void ProfilerDataWidget::onMouseOverDataPoint(int channelID, AZ::u64 sampleID, float primaryAxisValue, float dependentAxisValue) { if (!m_cachedChart) { return; } (void)primaryAxisValue; auto found = m_ChannelsToRegisters.find(channelID); if (found == m_ChannelsToRegisters.end()) { return; } const Driller::ProfilerDrillerNewRegisterEvent* currentRegister = found->second; if (!currentRegister) { return; } if (m_iLastHighlightedChannel != -1) { m_cachedChart->SetChannelHighlight(m_iLastHighlightedChannel, false); m_iLastHighlightedChannel = -1; m_dataModel->SetHighlightedRegisterID(0); } m_iLastHighlightedChannel = channelID; m_cachedChart->SetChannelHighlight(m_iLastHighlightedChannel, true); auto foundChannel = m_ChannelsToRegisters.find(channelID); if (foundChannel != m_ChannelsToRegisters.end()) { m_dataModel->SetHighlightedRegisterID(foundChannel->second->GetInfo().m_id); } const Driller::ProfilerDrillerUpdateRegisterEvent* previousRegister = currentRegister->GetLastSample(); while ((previousRegister) && (previousRegister->GetGlobalEventId() != (unsigned int)sampleID)) { previousRegister = previousRegister->GetPreviousSample(); } if (previousRegister) { QString tooltip; QString identifier = tr("%1(%2) %3") .arg(currentRegister->GetInfo().m_function ? currentRegister->GetInfo().m_function : "???") .arg(currentRegister->GetInfo().m_line) .arg(currentRegister->GetInfo().m_name ? QString("'%1'").arg(currentRegister->GetInfo().m_name) : ""); if (previousRegister->GetRegister()->GetInfo().m_type == Profiler::RegisterInfo::PRT_TIME) { QString displayValue; switch (m_cachedColumn) { case PDM_INCLUSIVE_TIME: displayValue = tr("Inclusive: %1").arg(m_formatter->formatMicroseconds(dependentAxisValue)); break; case PDM_EXCLUSIVE_TIME: displayValue = tr("Exclusive: %1").arg(m_formatter->formatMicroseconds(dependentAxisValue)); break; case PDM_CALLS: displayValue = tr("%1 calls").arg((int)dependentAxisValue); break; case PDM_ACCUMULATED_TIME: displayValue = tr("Accumulated: %1%").arg(m_formatter->formatMicroseconds(dependentAxisValue)); break; case PDM_ACCUMULATED_CALLS: displayValue = tr("%1 accumulated calls").arg((int)dependentAxisValue); break; } tooltip = QString("%1: %2").arg(identifier).arg(displayValue); if (QApplication::activeWindow() == this->parent()) { QToolTip::showText(m_cachedChart->mapToGlobal(QPoint(0, 0)), tooltip, m_cachedChart); } } else { // value register: } } } void ProfilerDataWidget::PlotTimeHistory(StripChart::DataStrip* chart) { m_ChannelsToRegisters.clear(); if (chart) { float maxVerticalValue = 0; chart->SetMarkerColor(QColor(255, 0, 0)); chart->SetMarkerPosition(static_cast(m_cachedCurrentFrame)); chart->StartBatchDataAdd(); for (const Driller::ProfilerDrillerUpdateRegisterEvent* currentRegister : m_dataModel->m_profilerDrillerUpdateRegisterEvents) { const Driller::ProfilerDrillerUpdateRegisterEvent* previousRegister = currentRegister->GetPreviousSample(); if (previousRegister) { if (m_dataModel->m_enabledChartingMap.find(currentRegister->GetRegister()->GetInfo().m_id)->second) { FrameNumberType localAtFrame = m_cachedCurrentFrame; FrameNumberType localHowFar = m_cachedDisplayRange; int channelID = -1; const Driller::ProfilerDrillerNewRegisterEvent* reg = currentRegister->GetRegister(); if (reg) { if ((reg->GetInfo().m_name) && (strlen(reg->GetInfo().m_name) > 0)) { channelID = chart->AddChannel(reg->GetInfo().m_name); } else if ((reg->GetInfo().m_function) && (strlen(reg->GetInfo().m_function) > 0)) { channelID = chart->AddChannel(tr("%1(%2)").arg(reg->GetInfo().m_function).arg(reg->GetInfo().m_line)); } else { channelID = chart->AddChannel(tr("Unknown Register:%1").arg(reg->GetInfo().m_id)); } chart->SetChannelStyle(channelID, StripChart::Channel::STYLE_CONNECTED_LINE); chart->SetChannelColor(channelID, m_dataModel->m_colorMap.find(currentRegister->GetRegister()->GetInfo().m_id)->second); } else { channelID = chart->AddChannel("NULL"); chart->SetChannelStyle(channelID, StripChart::Channel::STYLE_CONNECTED_LINE); chart->SetChannelColor(channelID, m_dataModel->m_colorMap.find(0)->second); } // If we don't have a valid channel ID skip over. if (!chart->IsValidChannelId(channelID)) { continue; } m_ChannelsToRegisters[channelID] = reg; while (localAtFrame >= 0 && localHowFar >= 0) { float sample = 0.0f; if (m_viewType == Profiler::RegisterInfo::PRT_TIME) { switch (m_cachedColumn) { case PDM_INCLUSIVE_TIME: sample = (float)(currentRegister->GetData().m_timeData.m_time - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_time)); break; case PDM_EXCLUSIVE_TIME: sample = (float)(((currentRegister->GetData().m_timeData.m_time - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_time)) - (currentRegister->GetData().m_timeData.m_childrenTime - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_childrenTime)))); break; case PDM_CALLS: sample = (float)(currentRegister->GetData().m_timeData.m_calls - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_calls)); break; case PDM_ACCUMULATED_TIME: sample = (float)(currentRegister->GetData().m_timeData.m_time); break; case PDM_ACCUMULATED_CALLS: sample = (float)(currentRegister->GetData().m_timeData.m_calls); break; } } else if (m_viewType == Profiler::RegisterInfo::PRT_VALUE) { AZ::u64 columnNumber = 0; AZ::u64 columnDelta = 0; switch (m_cachedColumn) { case PDM_VALUE_1: columnNumber = currentRegister->GetData().m_valueData.m_value1; break; case PDM_VALUE_2: columnNumber = currentRegister->GetData().m_valueData.m_value2; break; case PDM_VALUE_3: columnNumber = currentRegister->GetData().m_valueData.m_value3; break; case PDM_VALUE_4: columnNumber = currentRegister->GetData().m_valueData.m_value4; break; case PDM_VALUE_5: columnNumber = currentRegister->GetData().m_valueData.m_value5; break; case PDM_VALUE_THREAD_ID: columnNumber = currentRegister->GetRegister()->GetInfo().m_threadId; break; } if (m_cachedDeltaData) { switch (m_cachedColumn) { case PDM_VALUE_1: columnDelta = (previousRegister == NULL ? 0 : previousRegister->GetData().m_valueData.m_value1); break; case PDM_VALUE_2: columnDelta = (previousRegister == NULL ? 0 : previousRegister->GetData().m_valueData.m_value2); break; case PDM_VALUE_3: columnDelta = (previousRegister == NULL ? 0 : previousRegister->GetData().m_valueData.m_value3); break; case PDM_VALUE_4: columnDelta = (previousRegister == NULL ? 0 : previousRegister->GetData().m_valueData.m_value4); break; case PDM_VALUE_5: columnDelta = (previousRegister == NULL ? 0 : previousRegister->GetData().m_valueData.m_value5); break; } } sample = (float)(columnNumber - columnDelta); } maxVerticalValue = AZStd::GetMax(sample, maxVerticalValue); chart->AddBatchedData(channelID, currentRegister->GetGlobalEventId(), (float)localAtFrame, sample); currentRegister = previousRegister; if (!currentRegister) { break; } previousRegister = currentRegister->GetPreviousSample(); --localAtFrame; --localHowFar; } } } } // Always assume 0 as the minimum chart->SetWindowRange(Charts::AxisType::Vertical, 0, maxVerticalValue); // Adding one here so I can actually see the scrubber mark. float minValue = 0.0f; float maxValue = 0.0f; if (chart->GetAxisRange(Charts::AxisType::Horizontal, minValue, maxValue)) { chart->SetWindowRange(Charts::AxisType::Horizontal, minValue, maxValue + 0.5f); } chart->EndBatchDataAdd(); } if (m_autoZoom) { chart->ZoomExtents(Charts::AxisType::Vertical); } else { chart->ZoomManual(Charts::AxisType::Vertical, m_manualZoomMin, m_manualZoomMax); } } //------------------------------------------------------------------------ ProfilerDataModel::ProfilerDataModel() { m_SourceAggregator = NULL; m_cachedFlatView = false; m_highlightedRegisterID = 0; } ProfilerDataModel::~ProfilerDataModel() { EmptyTheEventCache(); m_colorMap.clear(); m_iconMap.clear(); m_enabledChartingMap.clear(); } QVariant ProfilerDataModel::headerData (int section, Qt::Orientation orientation, int role) const { (void)orientation; if (role == Qt::DisplayRole) { return QVariant(PDM_TIME_STRING[section]); } return QVariant(); } QVariant ProfilerDataModel::data (const QModelIndex& index, int role) const { if (index.isValid() && m_SourceAggregator && m_SourceAggregator->IsValid()) { const Driller::ProfilerDrillerUpdateRegisterEvent* registerEvent = static_cast(index.internalPointer()); if (role == Qt::BackgroundRole) { if (m_highlightedRegisterID != 0) { if (registerEvent->GetRegisterId() == m_highlightedRegisterID) { return QVariant::fromValue(QColor(94, 94, 178, 255)); } } } // a color swatch to match register to chart, or black if not drawn to the chart if (role == Qt::DecorationRole && index.column() == 0 /*COLOR SWATCH*/) { if (registerEvent->GetRegister()) { if (m_enabledChartingMap.find(registerEvent->GetRegister()->GetInfo().m_id) != m_enabledChartingMap.end()) { if (m_enabledChartingMap.find(registerEvent->GetRegister()->GetInfo().m_id)->second) { return QVariant(m_iconMap.find(registerEvent->GetRegister()->GetInfo().m_id)->second); } else { return QVariant(m_iconMap.find(0)->second); } } else { return QVariant(m_iconMap.find(0)->second); } } } if (role == Qt::DisplayRole || role == PDM_NUMERIC_DATA_ROLE) { const Driller::ProfilerDrillerUpdateRegisterEvent* currentRegister = registerEvent; const Driller::ProfilerDrillerUpdateRegisterEvent* previousRegister = currentRegister->GetPreviousSample(); if (role == Qt::DisplayRole) { switch (index.column()) { case PDM_FUNCTIONNAME: if (currentRegister->GetRegister()) { AZStd::string name = AZStd::string::format("%s(%d)" , currentRegister->GetRegister()->GetInfo().m_function ? currentRegister->GetRegister()->GetInfo().m_function : "N/A" , currentRegister->GetRegister()->GetInfo().m_line); return QVariant(name.c_str()); } else { return QVariant("N/A"); } break; case PDM_COMMENT: return QVariant(QString(currentRegister->GetRegister() ? currentRegister->GetRegister()->GetInfo().m_name ? currentRegister->GetRegister()->GetInfo().m_name : "" : "")); break; } } AZ::u64 columnNumber = 0; switch (index.column()) { case PDM_INCLUSIVE_TIME: columnNumber = currentRegister->GetData().m_timeData.m_time - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_time); break; case PDM_EXCLUSIVE_TIME: columnNumber = (currentRegister->GetData().m_timeData.m_time - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_time)) - (currentRegister->GetData().m_timeData.m_childrenTime - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_childrenTime)); break; case PDM_INCLUSIVE_PCT: columnNumber = currentRegister->GetData().m_timeData.m_time - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_time); break; case PDM_EXCLUSIVE_PCT: columnNumber = (currentRegister->GetData().m_timeData.m_time - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_time)) - (currentRegister->GetData().m_timeData.m_childrenTime - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_childrenTime)); break; case PDM_CHILDREN_TIME: columnNumber = currentRegister->GetData().m_timeData.m_childrenTime - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_childrenTime); break; case PDM_ACCUMULATED_TIME: columnNumber = currentRegister->GetData().m_timeData.m_time; break; case PDM_CALLS: columnNumber = currentRegister->GetData().m_timeData.m_calls - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_calls); break; case PDM_CHILDREN_CALLS: columnNumber = currentRegister->GetData().m_timeData.m_childrenCalls - (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_childrenCalls); break; case PDM_ACCUMULATED_CALLS: columnNumber = currentRegister->GetData().m_timeData.m_calls; break; case PDM_THREAD_ID: columnNumber = currentRegister->GetRegister()->GetInfo().m_threadId; break; } if (role == Qt::DisplayRole) { if (index.column() == PDM_INCLUSIVE_PCT || index.column() == PDM_EXCLUSIVE_PCT) { float percent = (float)columnNumber / (float)m_totalTime; return QVariant(QString::number(percent * 100.0f, 'f', 2)); } if (index.column() == PDM_THREAD_ID) { return QVariant(QString("%1").arg(columnNumber)); } else { return QVariant(QString("%L1").arg(columnNumber)); } } else if (role == PDM_NUMERIC_DATA_ROLE) { return QVariant(columnNumber); } return QVariant(); } } return QVariant(); } Qt::ItemFlags ProfilerDataModel::flags (const QModelIndex& index) const { if (!index.isValid()) { return Qt::ItemFlags(); } return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QModelIndex ProfilerDataModel::index (int row, int column, const QModelIndex& parent) const { if (hasIndex(row, column, parent) && m_SourceAggregator && m_SourceAggregator->IsValid()) { if (m_cachedFlatView) { // look up the rowTh data item return createIndex(row, column, (void*)(m_profilerDrillerUpdateRegisterEvents[row])); } if (!parent.isValid()) { // look up the rowTh data item with no parent itself int foundCount = 0; DVVector::const_iterator iter = m_profilerDrillerUpdateRegisterEvents.begin(); while (iter != m_profilerDrillerUpdateRegisterEvents.end()) { if ((*iter)->GetData().m_timeData.m_lastParentRegisterId == 0) { if (foundCount == row) { return createIndex(row, column, (void*)(*iter)); } ++foundCount; } ++iter; } } else { Driller::ProfilerDrillerUpdateRegisterEvent* registerEvent = static_cast(parent.internalPointer()); // look up the rowTh data item with pt as a parent int foundCount = 0; DVVector::const_iterator iter = m_profilerDrillerUpdateRegisterEvents.begin(); while (iter != m_profilerDrillerUpdateRegisterEvents.end()) { if ((*iter)->GetData().m_timeData.m_lastParentRegisterId == registerEvent->GetRegister()->GetInfo().m_id) { if (foundCount == row) { return createIndex(row, column, (void*)(*iter)); } ++foundCount; } ++iter; } } } return QModelIndex(); } QModelIndex ProfilerDataModel::parent (const QModelIndex& index) const { if (m_cachedFlatView) { return QModelIndex(); } if (index.isValid() && m_SourceAggregator && m_SourceAggregator->IsValid()) { Driller::ProfilerDrillerUpdateRegisterEvent* childItem = static_cast(index.internalPointer()); DVVector::const_iterator mainIter = m_profilerDrillerUpdateRegisterEvents.begin(); while (mainIter != m_profilerDrillerUpdateRegisterEvents.end()) { // 0th frame the updates haven't been linked to their registers yet, that happens on 1st // this is a guard against that if ((*mainIter)->GetRegister()) { if ((*mainIter)->GetRegister()->GetInfo().m_id == childItem->GetData().m_timeData.m_lastParentRegisterId) { // row() should be the parent's row in it's own parent int parentRow = 0; DVVector::const_iterator parentIter = m_profilerDrillerUpdateRegisterEvents.begin(); while (parentIter != m_profilerDrillerUpdateRegisterEvents.end()) { if ((*parentIter)->GetRegister()->GetData().m_timeData.m_lastParentRegisterId == (*mainIter)->GetData().m_timeData.m_lastParentRegisterId) { if (*parentIter == *mainIter) { return createIndex(parentRow, 0, (void*)(*mainIter)); } ++parentRow; } ++parentIter; } } } ++mainIter; } } return QModelIndex(); } int ProfilerDataModel::rowCount (const QModelIndex& parent) const { int foundCount = 0; if (m_SourceAggregator && m_SourceAggregator->IsValid()) { if (m_cachedFlatView && !parent.isValid()) { foundCount = (int)m_profilerDrillerUpdateRegisterEvents.size(); } else if (m_cachedFlatView) { foundCount = 0; } else if (!parent.isValid()) { // count data items with no parent id DVVector::const_iterator iter = m_profilerDrillerUpdateRegisterEvents.begin(); while (iter != m_profilerDrillerUpdateRegisterEvents.end()) { if ((*iter)->GetData().m_timeData.m_lastParentRegisterId == 0) { ++foundCount; } ++iter; } } else { Driller::ProfilerDrillerUpdateRegisterEvent* registerEvent = static_cast(parent.internalPointer()); // count data items with pt as a parent DVVector::const_iterator iter = m_profilerDrillerUpdateRegisterEvents.begin(); while (iter != m_profilerDrillerUpdateRegisterEvents.end()) { // 0th frame the updates haven't been linked to their registers yet, that happens on 1st // this is a guard against that if (registerEvent->GetRegister()) { if ((*iter)->GetData().m_timeData.m_lastParentRegisterId == registerEvent->GetRegister()->GetInfo().m_id) { ++foundCount; } } ++iter; } } } return foundCount; } int ProfilerDataModel::columnCount (const QModelIndex& /*parent*/) const { return PDM_TIME_TOTAL; } void ProfilerDataModel::EmptyTheEventCache() { m_profilerDrillerUpdateRegisterEvents.clear(); } void ProfilerDataModel::BeginAddRegisters() { beginResetModel(); EmptyTheEventCache(); m_totalTime = 0; } void ProfilerDataModel::AddRegister(const Driller::ProfilerDrillerUpdateRegisterEvent* newData) { if (newData->GetRegister()) { if (newData->GetRegister()->GetInfo().m_type == Profiler::RegisterInfo::PRT_TIME) { m_profilerDrillerUpdateRegisterEvents.push_back(newData); const Driller::ProfilerDrillerUpdateRegisterEvent* currentRegister = newData; const Driller::ProfilerDrillerUpdateRegisterEvent* previousRegister = currentRegister->GetPreviousSample(); const AZ::u64 previousRegisterTime = (previousRegister == nullptr ? 0 : previousRegister->GetData().m_timeData.m_time); const AZ::u64 registerDeltaTime = currentRegister->GetData().m_timeData.m_time - previousRegisterTime; const AZ::u64 previousChildTime = (previousRegister == NULL ? 0 : previousRegister->GetData().m_timeData.m_childrenTime); const AZ::u64 childDeltaTime = currentRegister->GetData().m_timeData.m_childrenTime - previousChildTime; AZ::u64 t = registerDeltaTime - childDeltaTime; m_totalTime += t; } } } void ProfilerDataModel::EndAddRegisters() { Recolor(); endResetModel(); } void ProfilerDataModel::SetAggregator(Driller::ProfilerDataAggregator* aggregator) { m_SourceAggregator = aggregator; } QColor ProfilerDataModel::GetColorByIndex(int colorIdx, int maxNumColors) { QColor col; float sat = .9f; float val = .9f; col.setHsvF((float)(colorIdx % maxNumColors) / (float(maxNumColors)), sat, val); return col; } // lazy build of a mapping between Event ID# and QColor for chart display(s) void ProfilerDataModel::Recolor() { // these two numbers used to cycle broadly around the color wheel // so that proximal entries are never too similar in hue const int magicNumber = 32; // magic number const int magicIncrement = 5; // black for disabled on the chart if (m_colorMap.find(0) == m_colorMap.end()) { QPixmap pixmap(16, 16); QPainter painter(&pixmap); painter.setBrush(Qt::black); painter.drawRect(0, 0, 16, 16); QIcon itemIcon(pixmap); m_iconMap[ 0 ] = itemIcon; } DVVector::const_iterator iter = m_profilerDrillerUpdateRegisterEvents.begin(); while (iter != m_profilerDrillerUpdateRegisterEvents.end()) { if ((*iter)->GetRegister()) { if (m_colorMap.find((*iter)->GetRegister()->GetInfo().m_id) == m_colorMap.end()) { // charting map and color map always in lockstep m_enabledChartingMap[ (*iter)->GetRegister()->GetInfo().m_id ] = 1; QColor qc = GetColorByIndex(m_colorIndexTracker, magicNumber); m_colorMap[ (*iter)->GetRegister()->GetInfo().m_id ] = qc; QPixmap pixmap(16, 16); QPainter painter(&pixmap); painter.setBrush(qc); painter.drawRect(0, 0, 16, 16); QIcon itemIcon(pixmap); m_iconMap[ (*iter)->GetRegister()->GetInfo().m_id ] = itemIcon; m_colorIndexTracker += magicIncrement; } } ++iter; } } void ProfilerDataModel::SetFlatView(bool on) { emit layoutAboutToBeChanged(); m_cachedFlatView = on; emit layoutChanged(); } void ProfilerDataModel::SetDeltaData(bool on) { emit layoutAboutToBeChanged(); m_cachedDeltaData = on; emit layoutChanged(); } void ProfilerDataModel::SetHighlightedRegisterID(AZ::u64 regid) { if (m_highlightedRegisterID != regid) { m_highlightedRegisterID = regid; emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } } //------------------------------------------------------------------------ ProfilerCounterDataModel::ProfilerCounterDataModel() { } ProfilerCounterDataModel::~ProfilerCounterDataModel() { } void ProfilerCounterDataModel::AddRegister(const Driller::ProfilerDrillerUpdateRegisterEvent* newData) { if (newData->GetRegister()) { if (newData->GetRegister()->GetInfo().m_type == Profiler::RegisterInfo::PRT_VALUE) { m_profilerDrillerUpdateRegisterEvents.push_back(newData); } } } QVariant ProfilerCounterDataModel::headerData (int section, Qt::Orientation orientation, int role) const { (void)orientation; if (role == Qt::DisplayRole) { return QVariant(PDM_VALUE_STRING[section]); } return QVariant(); } QVariant ProfilerCounterDataModel::data (const QModelIndex& index, int role) const { if (index.isValid() && m_SourceAggregator && m_SourceAggregator->IsValid()) { const Driller::ProfilerDrillerUpdateRegisterEvent* registerEvent = static_cast(index.internalPointer()); if (role == Qt::BackgroundRole) { if (m_highlightedRegisterID != 0) { if (registerEvent->GetRegisterId() == m_highlightedRegisterID) { return QVariant::fromValue(QColor(94, 94, 178, 255)); } } } // a color swatch to match register to chart, or black if not drawn to the chart if (role == Qt::DecorationRole && index.column() == 0 /*COLOR SWATCH*/) { if (registerEvent->GetRegister()) { if (m_enabledChartingMap.find(registerEvent->GetRegister()->GetInfo().m_id) != m_enabledChartingMap.end()) { if (m_enabledChartingMap.find(registerEvent->GetRegister()->GetInfo().m_id)->second) { return QVariant(m_iconMap.find(registerEvent->GetRegister()->GetInfo().m_id)->second); } else { return QVariant(m_iconMap.find(0)->second); } } else { return QVariant(m_iconMap.find(0)->second); } } } if (role == Qt::DisplayRole || role == PDM_NUMERIC_DATA_ROLE) { const Driller::ProfilerDrillerUpdateRegisterEvent* currentRegister = registerEvent; const Driller::ProfilerDrillerUpdateRegisterEvent* previousRegister = currentRegister->GetPreviousSample(); if (role == Qt::DisplayRole) { switch (index.column()) { case PDM_FUNCTIONNAME: if (currentRegister->GetRegister()) { AZStd::string name = AZStd::string::format("%s(%d)" , currentRegister->GetRegister()->GetInfo().m_function ? currentRegister->GetRegister()->GetInfo().m_function : "N/A" , currentRegister->GetRegister()->GetInfo().m_line); return QVariant(name.c_str()); } else { return QVariant("N/A"); } break; case PDM_COMMENT: return QVariant(QString(currentRegister->GetRegister() ? currentRegister->GetRegister()->GetInfo().m_name ? currentRegister->GetRegister()->GetInfo().m_name : "" : "")); break; } } AZ::u64 columnNumber = 0; AZ::u64 columnDelta = 0; switch (index.column()) { case PDM_VALUE_1: columnNumber = currentRegister->GetData().m_valueData.m_value1; break; case PDM_VALUE_2: columnNumber = currentRegister->GetData().m_valueData.m_value2; break; case PDM_VALUE_3: columnNumber = currentRegister->GetData().m_valueData.m_value3; break; case PDM_VALUE_4: columnNumber = currentRegister->GetData().m_valueData.m_value4; break; case PDM_VALUE_5: columnNumber = currentRegister->GetData().m_valueData.m_value5; break; case PDM_VALUE_THREAD_ID: columnNumber = currentRegister->GetRegister()->GetInfo().m_threadId; break; } if (m_cachedDeltaData) { switch (index.column()) { case PDM_VALUE_1: columnDelta = (previousRegister == NULL ? 0 : previousRegister->GetData().m_valueData.m_value1); break; case PDM_VALUE_2: columnDelta = (previousRegister == NULL ? 0 : previousRegister->GetData().m_valueData.m_value2); break; case PDM_VALUE_3: columnDelta = (previousRegister == NULL ? 0 : previousRegister->GetData().m_valueData.m_value3); break; case PDM_VALUE_4: columnDelta = (previousRegister == NULL ? 0 : previousRegister->GetData().m_valueData.m_value4); break; case PDM_VALUE_5: columnDelta = (previousRegister == NULL ? 0 : previousRegister->GetData().m_valueData.m_value5); break; } } columnNumber -= columnDelta; if (role == Qt::DisplayRole) { if (index.column() == PDM_VALUE_THREAD_ID) { return QVariant(QString("%1").arg(columnNumber)); } else { return QVariant(QString("%L1").arg(columnNumber)); } } else if (role == PDM_NUMERIC_DATA_ROLE) { return QVariant(columnNumber); } return QVariant(); } } return QVariant(); } int ProfilerCounterDataModel::columnCount (const QModelIndex& /*parent*/) const { return PDM_VALUE_TOTAL; } }