Skip to content

Commit f7e6302

Browse files
authored
Add performance panel (#23)
User-Facing Changes Enabling the panel "Studio - Playback Performance". Description Integrate PlaybackPerformance files from version v1.75. These files will be implemented as a standard panel, rather than being activated only in debug mode, as was done in version v1.75.
1 parent 5e17ee4 commit f7e6302

File tree

4 files changed

+204
-0
lines changed

4 files changed

+204
-0
lines changed

packages/studio-base/src/i18n/en/panels.ts

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ export const panels = {
3434
ROSDiagnosticSummaryDescription: "Display a summary of all ROS DiagnosticArray messages.",
3535
stateTransitions: "State Transitions",
3636
stateTransitionsDescription: "Track when values change over time.",
37+
studioPlaybackPerformance: "Studio - Playback Performance",
38+
studioPlaybackPerformanceDescription:
39+
"Display playback and data-streaming performance statistics.",
3740
tab: "Tab",
3841
tabDescription: "Group panels together in a tabbed interface.",
3942
table: "Table",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/
4+
//
5+
// This file incorporates work covered by the following copyright and
6+
// permission notice:
7+
//
8+
// Copyright 2018-2021 Cruise LLC
9+
//
10+
// This source code is licensed under the Apache License, Version 2.0,
11+
// found at http://www.apache.org/licenses/LICENSE-2.0
12+
// You may not use this file except in compliance with the License.
13+
14+
import { StoryObj } from "@storybook/react";
15+
16+
import PanelSetup from "@foxglove/studio-base/stories/PanelSetup";
17+
18+
import PlaybackPerformance from "./index";
19+
20+
export default {
21+
title: "panels/PlaybackPerformance",
22+
};
23+
24+
export const SimpleExample: StoryObj = {
25+
render: () => {
26+
return (
27+
<PanelSetup>
28+
<PlaybackPerformance />
29+
</PanelSetup>
30+
);
31+
},
32+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/
4+
//
5+
// This file incorporates work covered by the following copyright and
6+
// permission notice:
7+
//
8+
// Copyright 2018-2021 Cruise LLC
9+
//
10+
// This source code is licensed under the Apache License, Version 2.0,
11+
// found at http://www.apache.org/licenses/LICENSE-2.0
12+
// You may not use this file except in compliance with the License.
13+
14+
import { Typography } from "@mui/material";
15+
import * as _ from "lodash-es";
16+
import { ReactElement } from "react";
17+
18+
import { subtract as subtractTimes, toSec } from "@foxglove/rostime";
19+
import { Immutable } from "@foxglove/studio";
20+
import { useMessagePipeline } from "@foxglove/studio-base/components/MessagePipeline";
21+
import Panel from "@foxglove/studio-base/components/Panel";
22+
import PanelToolbar from "@foxglove/studio-base/components/PanelToolbar";
23+
import { Sparkline, SparklinePoint } from "@foxglove/studio-base/components/Sparkline";
24+
import Stack from "@foxglove/studio-base/components/Stack";
25+
import { PlayerStateActiveData } from "@foxglove/studio-base/players/types";
26+
27+
const TIME_RANGE = 5000;
28+
29+
type PlaybackPerformanceItemProps = {
30+
points: SparklinePoint[];
31+
maximum: number;
32+
decimalPlaces: number;
33+
label: React.ReactNode;
34+
};
35+
36+
function PlaybackPerformanceItem(props: PlaybackPerformanceItemProps): ReactElement {
37+
return (
38+
<Stack direction="row" alignItems="center" gap={1}>
39+
<Sparkline
40+
points={props.points}
41+
maximum={props.maximum}
42+
width={100}
43+
height={30}
44+
timeRange={TIME_RANGE}
45+
/>
46+
<Stack>
47+
<Typography variant="body2">
48+
{(_.last(props.points) ?? { value: 0 }).value.toFixed(props.decimalPlaces)}
49+
{props.label}
50+
</Typography>
51+
<Typography variant="body2" color="text.secondary">
52+
{(_.sumBy(props.points, "value") / props.points.length).toFixed(props.decimalPlaces)} avg
53+
</Typography>
54+
</Stack>
55+
</Stack>
56+
);
57+
}
58+
59+
type UnconnectedPlaybackPerformanceProps = Immutable<{
60+
timestamp: number;
61+
activeData?: PlayerStateActiveData;
62+
}>;
63+
64+
function UnconnectedPlaybackPerformance({
65+
timestamp,
66+
activeData,
67+
}: UnconnectedPlaybackPerformanceProps): JSX.Element {
68+
const playbackInfo =
69+
React.useRef<Immutable<{ timestamp: number; activeData: PlayerStateActiveData } | undefined>>();
70+
const lastPlaybackInfo = playbackInfo.current;
71+
if (activeData && (!playbackInfo.current || playbackInfo.current.activeData !== activeData)) {
72+
playbackInfo.current = { timestamp, activeData };
73+
}
74+
75+
const perfPoints = React.useRef<{
76+
speed: SparklinePoint[];
77+
framerate: SparklinePoint[];
78+
bagTimeMs: SparklinePoint[];
79+
megabitsPerSecond: SparklinePoint[];
80+
}>({
81+
speed: [],
82+
framerate: [],
83+
bagTimeMs: [],
84+
megabitsPerSecond: [],
85+
});
86+
87+
if (
88+
activeData &&
89+
playbackInfo.current &&
90+
lastPlaybackInfo &&
91+
lastPlaybackInfo.activeData !== activeData
92+
) {
93+
const renderTimeMs = timestamp - lastPlaybackInfo.timestamp;
94+
if (
95+
lastPlaybackInfo.activeData.isPlaying &&
96+
activeData.isPlaying &&
97+
lastPlaybackInfo.activeData.lastSeekTime === activeData.lastSeekTime &&
98+
lastPlaybackInfo.activeData.currentTime !== activeData.currentTime
99+
) {
100+
const elapsedPlayerTime =
101+
toSec(subtractTimes(activeData.currentTime, lastPlaybackInfo.activeData.currentTime)) *
102+
1000;
103+
perfPoints.current.speed.push({ value: elapsedPlayerTime / renderTimeMs, timestamp });
104+
perfPoints.current.framerate.push({ value: 1000 / renderTimeMs, timestamp });
105+
perfPoints.current.bagTimeMs.push({ value: elapsedPlayerTime, timestamp });
106+
}
107+
const newBytesReceived =
108+
activeData.totalBytesReceived - lastPlaybackInfo.activeData.totalBytesReceived;
109+
const newMegabitsReceived = (8 * newBytesReceived) / 1e6;
110+
const megabitsPerSecond = newMegabitsReceived / (renderTimeMs / 1000);
111+
perfPoints.current.megabitsPerSecond.push({ value: megabitsPerSecond, timestamp });
112+
for (const points of Object.values(perfPoints.current)) {
113+
while (points[0] && points[0].timestamp < timestamp - TIME_RANGE) {
114+
points.shift();
115+
}
116+
}
117+
}
118+
119+
return (
120+
<Stack flex="auto">
121+
<PanelToolbar />
122+
<Stack flex="auto" justifyContent="center" gap={2} padding={1}>
123+
<PlaybackPerformanceItem
124+
points={perfPoints.current.speed}
125+
maximum={1.6}
126+
decimalPlaces={2}
127+
label={<>&times; realtime</>}
128+
/>
129+
<PlaybackPerformanceItem
130+
points={perfPoints.current.framerate}
131+
maximum={30}
132+
decimalPlaces={1}
133+
label="fps"
134+
/>
135+
<PlaybackPerformanceItem
136+
points={perfPoints.current.bagTimeMs}
137+
maximum={300}
138+
decimalPlaces={0}
139+
label="ms bag frame"
140+
/>
141+
<PlaybackPerformanceItem
142+
points={perfPoints.current.megabitsPerSecond}
143+
maximum={100}
144+
decimalPlaces={1}
145+
label="Mbps"
146+
/>
147+
</Stack>
148+
</Stack>
149+
);
150+
}
151+
152+
function PlaybackPerformance() {
153+
const timestamp = Date.now();
154+
const activeData = useMessagePipeline(
155+
React.useCallback(({ playerState }) => playerState.activeData, []),
156+
);
157+
return <UnconnectedPlaybackPerformance timestamp={timestamp} activeData={activeData} />;
158+
}
159+
160+
PlaybackPerformance.panelType = "PlaybackPerformance";
161+
PlaybackPerformance.defaultConfig = {};
162+
163+
export default Panel(PlaybackPerformance);

packages/studio-base/src/panels/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,10 @@ export const getBuiltin: (t: TFunction<"panels">) => PanelInfo[] = (t) => [
172172
module: async () => await import("./Tab"),
173173
hasCustomToolbar: true,
174174
},
175+
{
176+
title: t("studioPlaybackPerformance"),
177+
type: "PlaybackPerformance",
178+
description: t("studioPlaybackPerformanceDescription"),
179+
module: async () => await import("./PlaybackPerformance"),
180+
},
175181
];

0 commit comments

Comments
 (0)