diff --git a/app/components/summary-item.hbs b/app/components/summary-item.hbs
new file mode 100644
index 0000000000..31209b95ac
--- /dev/null
+++ b/app/components/summary-item.hbs
@@ -0,0 +1,21 @@
+
+
+
+ <@list.cell class="list-cell-main js-render-main-cell js-render-profile-name" style={{this.nameStyle}}>
+ {{@model.name}}
+ @list.cell>
+
+<@list.cell class="list-cell-main js-render-main-cell list-cell-value_numeric js-render-profile-duration">
+ {{@model.initial-render}}
+ @list.cell>
+
+<@list.cell class="list-cell-main js-render-main-cell list-cell-value_numeric js-render-profile-duration">
+ {{@model.avg-re-render}}
+ @list.cell>
+
+ <@list.cell class="list-cell-main js-render-main-cell list-cell-value_numeric js-render-profile-timestamp">
+ {{@model.render-count}}
+ @list.cell>
+
+
+
diff --git a/app/components/summary-item.ts b/app/components/summary-item.ts
new file mode 100644
index 0000000000..c3bcc0dc07
--- /dev/null
+++ b/app/components/summary-item.ts
@@ -0,0 +1,17 @@
+import Component from '@glimmer/component';
+
+interface SummaryItemArgs {
+ model: {
+ name: string;
+ 'initial-render': number;
+ 'avg-re-render': number;
+ 'render-count': number;
+ };
+ list: unknown; // or just leave this out
+}
+
+export default class SummaryItem extends Component {
+ get row() {
+ return this.args.model;
+ }
+}
diff --git a/app/components/summary-render-table.hbs b/app/components/summary-render-table.hbs
new file mode 100644
index 0000000000..203a4321e8
--- /dev/null
+++ b/app/components/summary-render-table.hbs
@@ -0,0 +1,10 @@
+
+
+ {{#each this.rows as |row|}}
+
+ {{/each}}
+
+
diff --git a/app/components/summary-render-table.ts b/app/components/summary-render-table.ts
new file mode 100644
index 0000000000..c3a4714312
--- /dev/null
+++ b/app/components/summary-render-table.ts
@@ -0,0 +1,88 @@
+import Component from '@glimmer/component';
+
+import summarySchema from '../schemas/summary-render-tree';
+
+import escapeRegExp from '../utils/escape-reg-exp';
+
+import type { RenderTreeModel } from '../routes/render-tree';
+
+import isEmpty from '@ember/utils/lib/is_empty';
+
+interface SummaryRenderArgs {
+ profiles: RenderTreeModel['profiles'];
+ searchValue: string;
+}
+
+// TODO handle for recursive cases also
+
+export default class SummaryRenderTable extends Component {
+ get schema() {
+ return summarySchema;
+ }
+
+ get escapedSearch() {
+ return escapeRegExp(this.args.searchValue?.toLowerCase());
+ }
+
+ get rows() {
+ const profiles = this.args.profiles ?? [];
+
+ if (profiles.length === 0) {
+ return [];
+ }
+
+ // Flatten children (actual components)
+ const allComponents = profiles.flatMap((p) => p.children ?? []);
+
+ const grouped: Record<
+ string,
+ {
+ initial: number | null;
+ reRenders: number[];
+ }
+ > = {};
+
+ allComponents.forEach((profile) => {
+ const name = profile.name;
+
+ const time = profile.time; // precise ms
+
+ if (!grouped[name]) {
+ grouped[name] = { initial: null, reRenders: [] };
+ }
+
+ if (grouped[name].initial === null) {
+ // First time we see this component → initial render
+ grouped[name].initial = time;
+ } else {
+ // All later times → re-renders
+ grouped[name].reRenders.push(time);
+ }
+ });
+
+ return Object.entries(grouped)
+ .map(([name, data]) => {
+ const avgReRender = data.reRenders.length
+ ? data.reRenders.reduce((a, b) => a + b, 0) / data.reRenders.length
+ : 0;
+
+ const count = data.reRenders.length + (data.initial ? 1 : 0);
+ return {
+ name,
+ 'initial-render': data.initial ? Number(data.initial.toFixed(2)) : 0,
+ 'avg-re-render': Number(avgReRender.toFixed(2)),
+ 'render-count': count,
+ };
+ })
+ .filter((item) => {
+ if (isEmpty(this.escapedSearch)) {
+ return true;
+ }
+
+ const regExp = new RegExp(this.escapedSearch as string);
+ return !!item.name.toLowerCase().match(regExp);
+ })
+ .sort((a, b) => b['initial-render'] - a['initial-render'])
+ .slice(0, 5);
+ }
+}
diff --git a/app/routes/render-tree.ts b/app/routes/render-tree.ts
index 9ff1eae978..606d39ebba 100644
--- a/app/routes/render-tree.ts
+++ b/app/routes/render-tree.ts
@@ -10,6 +10,7 @@ import type RenderTreeController from '../controllers/render-tree';
import TabRoute from './tab';
export interface Profile {
+ time: number;
children: Array;
name: string;
}
diff --git a/app/schemas/summary-render-tree.ts b/app/schemas/summary-render-tree.ts
new file mode 100644
index 0000000000..16d4101b77
--- /dev/null
+++ b/app/schemas/summary-render-tree.ts
@@ -0,0 +1,30 @@
+/**
+ * Summary render performance schema.
+ */
+export default {
+ columns: [
+ {
+ id: 'name',
+ name: 'Component',
+ visible: true,
+ },
+ {
+ id: 'initial-render',
+ name: 'Initial Render Time (ms)',
+ visible: true,
+ numeric: true,
+ },
+ {
+ id: 'avg-re-render',
+ name: 'Avg Re-Render Time (ms)',
+ visible: true,
+ numeric: true,
+ },
+ {
+ id: 'render-count',
+ name: 'Render Count',
+ visible: true,
+ numeric: true,
+ },
+ ],
+};
diff --git a/app/templates/render-tree.hbs b/app/templates/render-tree.hbs
index 318b10d0cf..95835dd122 100644
--- a/app/templates/render-tree.hbs
+++ b/app/templates/render-tree.hbs
@@ -38,7 +38,10 @@
{{else}}
-
+
+
+ {{!--
{{/each}}
-
+ --}}
{{/if}}
\ No newline at end of file
diff --git a/tests/integration/components/summary-item-test.ts b/tests/integration/components/summary-item-test.ts
new file mode 100644
index 0000000000..9758f56b3d
--- /dev/null
+++ b/tests/integration/components/summary-item-test.ts
@@ -0,0 +1,26 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-inspector/tests/helpers';
+import { render } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+
+module('Integration | Component | summary-item', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.set('myAction', function(val) { ... });
+
+ await render(hbs``);
+
+ assert.dom().hasText('');
+
+ // Template block usage:
+ await render(hbs`
+
+ template block text
+
+ `);
+
+ assert.dom().hasText('template block text');
+ });
+});
diff --git a/tests/integration/components/summary-render-table-test.ts b/tests/integration/components/summary-render-table-test.ts
new file mode 100644
index 0000000000..7fd9ec3e1e
--- /dev/null
+++ b/tests/integration/components/summary-render-table-test.ts
@@ -0,0 +1,26 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-inspector/tests/helpers';
+import { render } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+
+module('Integration | Component | summary-render-table', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.set('myAction', function(val) { ... });
+
+ await render(hbs``);
+
+ assert.dom().hasText('');
+
+ // Template block usage:
+ await render(hbs`
+
+ template block text
+
+ `);
+
+ assert.dom().hasText('template block text');
+ });
+});