Files
microsoft-ui-xaml/controls/dev/TableView/TableView_Telemetry.cpp
T
Saharsh JaiswalandCopilot 927f474d48 TableView TiP: blame by exception type and count contained app faults
Review round 3. Attribution matched the last app HRESULT against a
control-wide counter, so a nested callback could overwrite it and an
unrelated exception with the same HRESULT could match. App code's
exceptions are now tagged where TableView calls it (CallerError, and
CallerOutOfMemory for an allocation failure, which stays a std::bad_alloc),
and the catch that decides an outcome reads the type. SelectionModel calls
TableView back across the ABI, which drops the C++ type, so the call into
it tags the exception again by its error object.

Column code is TableView's unless app code ran it: the editing hooks are
internal, so only a CellEditingTemplate's content (while the editor is
prepared) is the app's there, and only an app-derived column class owns its
display factory and sort member path. Expansion is always tracked and its
catch reads the tag; only a request that changes one group or node runs a
test.

A filter over a large source no longer drops into the small bucket: the
large flag counts the source before the filter, and the metrics and
TableView_Instance carry both counts. Initialize closes as
unloaded_before_render when the table is unloaded with rows and a body
viewport before any row was laid out. An app filter predicate, sort or group
key selector or custom comparer that throws is still contained, and now
writes one recoverable TableView_Error per operation with FaultCount.

Internal pipeline builds fail when Tabular falls back to the TiP stub. All
versions start at 1, since none has shipped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 53d996ee-1cb6-468a-bfc9-e11af03b1f80
2026-10-23 02:13:31 +05:30

71 lines
3.3 KiB
C++

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
#include "pch.h"
#include "common.h"
#include "TableView.h"
#include "TableViewSource.h"
namespace
{
uint32_t ColumnKindOf(winrt::TableViewColumn const& column)
{
uint32_t kinds{};
if (column.try_as<winrt::TableViewTemplateColumn>()) { kinds |= TableViewTelemetry::ColumnKind::Template; }
else if (column.try_as<winrt::TableViewTextColumn>()) { kinds |= TableViewTelemetry::ColumnKind::Text; }
if (TableViewDetails::IsAppDerivedColumn(column)) { kinds |= TableViewTelemetry::ColumnKind::AppDerived; }
return kinds;
}
}
// TableView_Instance's two sample points: Loaded and Unloaded. Reads TableView's own state; no app code runs beyond
// the columns' QueryInterface and class name, and an app-supplied Columns vector.
void TableView::SampleInstanceTelemetry() noexcept
{
if (!m_telemetrySession.active || !TableViewTelemetry::ShouldSample()) { return; }
try
{
TableViewTelemetry::Sample sample;
sample.sourceKinds = m_telemetrySourceKinds;
sample.rightToLeft = FlowDirection() == winrt::FlowDirection::RightToLeft;
sample.templateIncomplete = m_templateIncomplete;
bool anySortable{};
bool anyEditable{};
if (auto const columns = Columns())
{
for (auto const& column : columns)
{
if (!column) { continue; }
sample.columnKinds |= ColumnKindOf(column);
anySortable = anySortable || column.CanSort();
anyEditable = anyEditable || !column.IsReadOnly();
}
}
if (CanUserSortColumns() && anySortable) { sample.enabledFeatures |= TableViewTelemetry::Feature::Sorting; }
if (!IsReadOnly() && anyEditable) { sample.enabledFeatures |= TableViewTelemetry::Feature::Editing; }
if (auto const source = m_activeSource.get())
{
auto const* const sourceImpl = winrt::get_self<::TableViewSource>(source);
if (sourceImpl->HasFilter()) { sample.enabledFeatures |= TableViewTelemetry::Feature::Filtering; }
if (sourceImpl->HasGroupBy()) { sample.enabledFeatures |= TableViewTelemetry::Feature::Grouping; }
if (sourceImpl->HasParentBy()) { sample.enabledFeatures |= TableViewTelemetry::Feature::Hierarchy; }
if (sourceImpl->IsLiveShaping()) { sample.enabledFeatures |= TableViewTelemetry::Feature::LiveShaping; }
sample.sourceRows = sourceImpl->SourceRowCount();
sample.dataRows = sourceImpl->DataRowCount();
if (sample.dataRows == 0)
{
TableViewTelemetry::NoteContent(m_telemetrySession, TableViewTelemetry::ContentShown::Empty);
}
}
if (auto const repeater = m_rowsRepeater.get())
{
// A cleared container stays the repeater's child in its recycle pool, so this is the containers created.
sample.peakRealizedRows = static_cast<uint32_t>(winrt::VisualTreeHelper::GetChildrenCount(repeater));
}
TableViewTelemetry::AddSample(m_telemetrySession, sample);
}
catch (...)
{
// Telemetry never throws into the app; a sample that could not be read leaves the session as it was.
}
}