aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBingqian Liu <bql@google.com>2024-04-09 16:00:25 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-04-09 16:00:25 +0000
commit64b0f318a3d31eab5f5210e2d03513ad6e975200 (patch)
tree7cd4ac551da08b841fc3a92e41f28677df8b68f9
parent5b3cddf26e007b7cf325872fcbf148b6097ebbe4 (diff)
parentd4de9c1add33ba3daa9e2b65232089e36e696873 (diff)
downloadperfetto-64b0f318a3d31eab5f5210e2d03513ad6e975200.tar.gz
Merge "ui: add loading to flamegraph panel" into main
-rw-r--r--ui/src/assets/details.scss69
-rw-r--r--ui/src/controller/flamegraph_controller.ts7
-rw-r--r--ui/src/frontend/flamegraph_panel.ts98
-rw-r--r--ui/src/frontend/globals.ts2
4 files changed, 97 insertions, 79 deletions
diff --git a/ui/src/assets/details.scss b/ui/src/assets/details.scss
index f3e529a6e..d09690a2c 100644
--- a/ui/src/assets/details.scss
+++ b/ui/src/assets/details.scss
@@ -173,43 +173,6 @@
width: 50%;
}
}
- &.flamegraph-profile {
- display: flex;
- justify-content: space-between;
- align-content: center;
- height: 30px;
- padding: 0;
- font-size: 12px;
- * {
- align-self: center;
- }
- .options {
- display: inline-flex;
- justify-content: space-around;
- }
- .details {
- display: inline-flex;
- justify-content: flex-end;
- }
- .title {
- justify-self: start;
- margin-left: 5px;
- font-size: 14px;
- margin-right: 10px;
- }
- .time {
- justify-self: end;
- margin-right: 10px;
- }
- .selected {
- justify-self: end;
- margin-right: 10px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- width: 200px;
- }
- }
}
table {
@@ -709,3 +672,35 @@
.pf-noselection {
height: 100%;
}
+
+.flamegraph-profile {
+ height: 100%;
+ // This is required to position locally-scoped (i.e. non-full-screen) modal
+ // dialogs within the panel, as they use position: absolute.
+ position: relative;
+
+ .time {
+ justify-self: end;
+ margin-right: 10px;
+ }
+ .selected {
+ justify-self: end;
+ margin-right: 10px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 200px;
+ }
+ .flamegraph-content {
+ overflow: auto;
+ height: 100%;
+
+ .loading-container {
+ font-size: larger;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ }
+ }
+}
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index a10252c99..b1c00a49a 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -300,6 +300,7 @@ export class FlamegraphController extends Controller<'main'> {
await this.args.engine.query(`select value from stats
where severity = 'error' and name = 'heap_graph_non_finalized_graph'`)
).firstRow({value: NUM}).value > 0;
+ flamegraphDetails.graphLoading = false;
publishFlamegraphDetails(flamegraphDetails);
}
@@ -317,8 +318,10 @@ export class FlamegraphController extends Controller<'main'> {
if (this.flamegraphDatasets.has(key)) {
currentData = this.flamegraphDatasets.get(key)!;
} else {
- // TODO(b/330703412): Show loading state.
-
+ publishFlamegraphDetails({
+ ...globals.flamegraphDetails,
+ graphLoading: true,
+ });
// Collecting data for drawing flamegraph for selected profile.
// Data needs to be in following format:
// id, name, parent_id, depth, total_size
diff --git a/ui/src/frontend/flamegraph_panel.ts b/ui/src/frontend/flamegraph_panel.ts
index fea7fb2f4..0f4cba28a 100644
--- a/ui/src/frontend/flamegraph_panel.ts
+++ b/ui/src/frontend/flamegraph_panel.ts
@@ -30,6 +30,8 @@ import {Button} from '../widgets/button';
import {Icon} from '../widgets/icon';
import {Modal, ModalAttrs} from '../widgets/modal';
import {Popup} from '../widgets/popup';
+import {EmptyState} from '../widgets/empty_state';
+import {Spinner} from '../widgets/spinner';
import {Flamegraph, NodeRendering} from './flamegraph';
import {globals} from './globals';
@@ -37,7 +39,9 @@ import {debounce} from './rate_limiters';
import {Router} from './router';
import {getCurrentTrace} from './sidebar';
import {convertTraceToPprofAndDownload} from './trace_converter';
+import {ButtonBar} from '../widgets/button';
import {DurationWidget} from './widgets/duration';
+import {DetailsShell} from '../widgets/details_shell';
const HEADER_HEIGHT = 30;
@@ -90,33 +94,31 @@ export class FlamegraphDetailsPanel implements m.ClassComponent {
? this.flamegraph.getHeight() + HEADER_HEIGHT
: 0;
return m(
- '.details-panel',
+ '.flamegraph-profile',
this.maybeShowModal(flamegraphDetails.graphIncomplete),
m(
- '.details-panel-heading.flamegraph-profile',
- {onclick: (e: MouseEvent) => e.stopPropagation()},
- [
- m('div.options', [
- m(
- 'div.title',
- this.getTitle(),
- this.profileType === ProfileType.MIXED_HEAP_PROFILE &&
+ DetailsShell,
+ {
+ fillParent: true,
+ title: m(
+ 'div.title',
+ this.getTitle(),
+ this.profileType === ProfileType.MIXED_HEAP_PROFILE &&
+ m(
+ Popup,
+ {
+ trigger: m(Icon, {icon: 'warning'}),
+ },
m(
- Popup,
- {
- trigger: m(Icon, {icon: 'warning'}),
- },
- m(
- '',
- {style: {width: '300px'}},
- 'This is a mixed java/native heap profile, free()s are not visualized. To visualize free()s, remove "all_heaps: true" from the config.',
- ),
+ '',
+ {style: {width: '300px'}},
+ 'This is a mixed java/native heap profile, free()s are not visualized. To visualize free()s, remove "all_heaps: true" from the config.',
),
- ':',
- ),
- this.getViewingOptionButtons(),
- ]),
- m('div.details', [
+ ),
+ ':',
+ ),
+ description: this.getViewingOptionButtons(),
+ buttons: [
m(
'div.selected',
`Selected function: ${toSelectedCallsite(
@@ -145,23 +147,39 @@ export class FlamegraphDetailsPanel implements m.ClassComponent {
this.downloadPprof();
},
}),
- ]),
- ],
- ),
- m(`canvas[ref=canvas]`, {
- style: `height:${height}px; width:100%`,
- onmousemove: (e: MouseEvent) => {
- const {offsetX, offsetY} = e;
- this.onMouseMove({x: offsetX, y: offsetY});
- },
- onmouseout: () => {
- this.onMouseOut();
+ ],
},
- onclick: (e: MouseEvent) => {
- const {offsetX, offsetY} = e;
- this.onMouseClick({x: offsetX, y: offsetY});
- },
- }),
+ m(
+ '.flamegraph-content',
+ flamegraphDetails.graphLoading
+ ? m(
+ '.loading-container',
+ m(
+ EmptyState,
+ {
+ icon: 'bar_chart',
+ title: 'Computing graph ...',
+ className: 'flamegraph-loading',
+ },
+ m(Spinner, {easing: true}),
+ ),
+ )
+ : m(`canvas[ref=canvas]`, {
+ style: `height:${height}px; width:100%`,
+ onmousemove: (e: MouseEvent) => {
+ const {offsetX, offsetY} = e;
+ this.onMouseMove({x: offsetX, y: offsetY});
+ },
+ onmouseout: () => {
+ this.onMouseOut();
+ },
+ onclick: (e: MouseEvent) => {
+ const {offsetX, offsetY} = e;
+ this.onMouseClick({x: offsetX, y: offsetY});
+ },
+ }),
+ ),
+ ),
);
} else {
return m(
@@ -260,7 +278,7 @@ export class FlamegraphDetailsPanel implements m.ClassComponent {
getViewingOptionButtons(): m.Children {
return m(
- 'div',
+ ButtonBar,
...FlamegraphDetailsPanel.selectViewingOptions(
assertExists(this.profileType),
),
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 1af13996a..5cecd7e26 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -160,6 +160,8 @@ export interface FlamegraphDetails {
// When heap_graph_non_finalized_graph has a count >0, we mark the graph
// as incomplete.
graphIncomplete?: boolean;
+ // About to show a new graph whose data is not ready yet.
+ graphLoading?: boolean;
}
export interface CpuProfileDetails {