Skip to content

Commit bba31e0

Browse files
authored
Merge pull request #356 from Yooooomi/perf-improvements
Perf improvements
2 parents 70cd64d + 0659ade commit bba31e0

File tree

21 files changed

+617
-550
lines changed

21 files changed

+617
-550
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,4 @@ dist
105105
docker-compose-personal.yml
106106
client/public/index.html
107107
client/public/variables.js
108+
db_data

apps/client/src/App.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import PlaylistDialog from "./components/PlaylistDialog";
2828
import TrackStats from "./scenes/TrackStats";
2929
import LongestSessions from "./scenes/LongestSessions";
3030
import AlbumStats from "./scenes/AlbumStats";
31+
import Benchmarks from "./scenes/Benchmarks";
3132

3233
function App() {
3334
const dark = useSelector(selectDarkMode);
@@ -169,6 +170,14 @@ function App() {
169170
</PrivateRoute>
170171
}
171172
/>
173+
<Route
174+
path="/benchmarks"
175+
element={
176+
<PrivateRoute>
177+
<Benchmarks />
178+
</PrivateRoute>
179+
}
180+
/>
172181
</Routes>
173182
</Layout>
174183
</BrowserRouter>

apps/client/src/components/ImplementedCharts/BestOfHour/BestOfHour.tsx

+10-40
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,14 @@ function getElementName(
3333
>["data"][number],
3434
id: string,
3535
) {
36-
if ("tracks" in result) {
37-
return result.tracks.find(t => t.track.id === id)?.track.name;
38-
}
39-
if ("albums" in result) {
40-
return result.albums.find(t => t.album.id === id)?.album.name;
41-
}
42-
if ("artists" in result) {
43-
return result.artists.find(t => t.artist.id === id)?.artist.name;
44-
}
45-
return "";
36+
return result.full_items[id]?.name;
4637
}
4738

4839
function getElementData(
4940
result: UnboxPromise<ReturnType<(typeof elementToCall)[Element]>>["data"],
5041
index: number,
5142
) {
52-
const foundIndex = result.findIndex(r => r._id === index);
43+
const foundIndex = result.findIndex(r => r.hour === index);
5344
if (foundIndex === -1) {
5445
return { x: index };
5546
}
@@ -61,34 +52,13 @@ function getElementData(
6152

6253
const { total } = found;
6354

64-
if ("tracks" in found) {
65-
return found.tracks.reduce<StackedBarProps["data"][number]>(
66-
(acc, curr) => {
67-
acc[curr.track.id] = Math.floor((curr.count / total) * 1000) / 10;
68-
return acc;
69-
},
70-
{ x: index },
71-
);
72-
}
73-
if ("albums" in found) {
74-
return found.albums.reduce<StackedBarProps["data"][number]>(
75-
(acc, curr) => {
76-
acc[curr.album.id] = Math.floor((curr.count / total) * 1000) / 10;
77-
return acc;
78-
},
79-
{ x: index },
80-
);
81-
}
82-
if ("artists" in found) {
83-
return found.artists.reduce<StackedBarProps["data"][number]>(
84-
(acc, curr) => {
85-
acc[curr.artist.id] = Math.floor((curr.count / total) * 1000) / 10;
86-
return acc;
87-
},
88-
{ x: index },
89-
);
90-
}
91-
return { x: index };
55+
return found.items.reduce<StackedBarProps["data"][number]>(
56+
(acc, curr) => {
57+
acc[curr.itemId] = Math.floor((curr.total / total) * 1000) / 10;
58+
return acc;
59+
},
60+
{ x: index },
61+
);
9262
}
9363

9464
function formatX(value: any) {
@@ -116,7 +86,7 @@ export default function BestOfHour({ className }: BestOfHourProps) {
11686

11787
const tooltipValue = useCallback<ValueFormatter<typeof data>>(
11888
(payload, value, root) => {
119-
const foundIndex = result?.findIndex(r => r._id === payload.x);
89+
const foundIndex = result?.findIndex(r => r.hour === payload.x);
12090
if (!result || foundIndex === undefined || foundIndex === -1) {
12191
return null;
12292
}

apps/client/src/components/InlineArtist/InlineArtist.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export default function InlineArtist<T extends HTMLTag = "div">({
1313
...other
1414
}: InlineArtistProps<T>) {
1515
return (
16-
// eslint-disable-next-line react/jsx-props-no-spreading
1716
<Text title={artist.name} {...other}>
1817
<Link to={`/artist/${artist.id}`} className={s.root}>
1918
{artist.name}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
import { useSelector } from "react-redux";
2+
import {
3+
Button,
4+
IconButton,
5+
Table,
6+
TableCell,
7+
TableHead,
8+
TableRow,
9+
} from "@mui/material";
10+
import { PlayArrow } from "@mui/icons-material";
11+
import { useState } from "react";
12+
import Header from "../../components/Header";
13+
import {
14+
selectRawIntervalDetail,
15+
selectUser,
16+
} from "../../services/redux/modules/user/selector";
17+
import { api } from "../../services/apis/api";
18+
import { Timesplit } from "../../services/types";
19+
import TitleCard from "../../components/TitleCard";
20+
import Text from "../../components/Text";
21+
22+
interface Request<T> {
23+
title: string;
24+
prepare?: () => Promise<T>;
25+
request: (prepared: T) => Promise<any>;
26+
}
27+
28+
const NOT_FINISHED_REQUEST = -1;
29+
const FAILED_REQUEST = -2;
30+
31+
export default function Benchmarks() {
32+
const { interval } = useSelector(selectRawIntervalDetail);
33+
34+
const [elapsedTime, setElapsedTime] = useState<Record<string, number>>({});
35+
36+
const user = useSelector(selectUser);
37+
38+
if (!user) {
39+
return null;
40+
}
41+
42+
const NB = 30;
43+
const OFFSET = 0;
44+
45+
const requests = [
46+
{
47+
title: "Get tracks",
48+
request: () => api.getTracks(interval.start, interval.end, 20, OFFSET),
49+
},
50+
{
51+
title: "Get most listened",
52+
request: () =>
53+
api.mostListened(interval.start, interval.end, Timesplit.all),
54+
},
55+
{
56+
title: "Get most listened artists",
57+
request: () =>
58+
api.mostListenedArtist(interval.start, interval.end, Timesplit.all),
59+
},
60+
{
61+
title: "Get songs per",
62+
request: () => api.songsPer(interval.start, interval.end, Timesplit.all),
63+
},
64+
{
65+
title: "Get time per",
66+
request: () => api.timePer(interval.start, interval.end, Timesplit.all),
67+
},
68+
{
69+
title: "Get feat ratio",
70+
request: () => api.featRatio(interval.start, interval.end, Timesplit.all),
71+
},
72+
{
73+
title: "Get album date ratio",
74+
request: () =>
75+
api.albumDateRatio(interval.start, interval.end, Timesplit.all),
76+
},
77+
{
78+
title: "Get popularity per",
79+
request: () =>
80+
api.popularityPer(interval.start, interval.end, Timesplit.all),
81+
},
82+
{
83+
title: "Get different artists per",
84+
request: () =>
85+
api.differentArtistsPer(interval.start, interval.end, Timesplit.all),
86+
},
87+
{
88+
title: "Get time per hour of day",
89+
request: () => api.timePerHourOfDay(interval.start, interval.end),
90+
},
91+
{
92+
title: "Get best songs",
93+
request: () => api.getBestSongs(interval.start, interval.end, NB, OFFSET),
94+
},
95+
{
96+
title: "Get best artists",
97+
request: () =>
98+
api.getBestArtists(interval.start, interval.end, NB, OFFSET),
99+
},
100+
{
101+
title: "Get best albums",
102+
request: () =>
103+
api.getBestAlbums(interval.start, interval.end, NB, OFFSET),
104+
},
105+
{
106+
title: "Get best songs of hour",
107+
request: () => api.getBestSongsOfHour(interval.start, interval.end),
108+
},
109+
{
110+
title: "Get best albums of hour",
111+
request: () => api.getBestAlbumsOfHour(interval.start, interval.end),
112+
},
113+
{
114+
title: "Get best artists of hour",
115+
request: () => api.getBestArtistsOfHour(interval.start, interval.end),
116+
},
117+
{
118+
title: "Get longest sessions",
119+
request: () => api.getLongestSessions(interval.start, interval.end),
120+
},
121+
{
122+
title: "Get artist page",
123+
prepare: async () => {
124+
const { data: bestArtists } = await api.getBestArtists(
125+
interval.start,
126+
interval.end,
127+
1,
128+
0,
129+
);
130+
const [bestArtist] = bestArtists;
131+
return bestArtist?.artist.id;
132+
},
133+
request: async (bestArtistId: string) => api.getArtistStats(bestArtistId),
134+
},
135+
{
136+
title: "Get album page",
137+
prepare: async () => {
138+
const { data: bestAlbums } = await api.getBestAlbums(
139+
interval.start,
140+
interval.end,
141+
1,
142+
0,
143+
);
144+
const [bestAlbum] = bestAlbums;
145+
return bestAlbum?.album.id;
146+
},
147+
request: async (bestAlbumId: string) => api.getAlbumStats(bestAlbumId),
148+
},
149+
{
150+
title: "Get track page",
151+
prepare: async () => {
152+
const { data: bestTracks } = await api.getBestSongs(
153+
interval.start,
154+
interval.end,
155+
1,
156+
0,
157+
);
158+
const [bestTrack] = bestTracks;
159+
return bestTrack?.track.id;
160+
},
161+
request: async (bestTrackId: string) => api.getTrackStats(bestTrackId),
162+
},
163+
] as const satisfies Request<any>[];
164+
165+
const run = async (req: Request<any>) => {
166+
setElapsedTime(prev => ({ ...prev, [req.title]: NOT_FINISHED_REQUEST }));
167+
let prepared = undefined;
168+
if (req.prepare) {
169+
prepared = await req.prepare();
170+
}
171+
if (!prepared && req.prepare) {
172+
setElapsedTime(prev => ({ ...prev, [req.title]: FAILED_REQUEST }));
173+
return;
174+
}
175+
let start = Date.now();
176+
const { data: result } = await req.request(prepared);
177+
console.log("Result", result);
178+
const end = Date.now();
179+
setElapsedTime(prev => ({ ...prev, [req.title]: end - start }));
180+
};
181+
182+
const runAll = async () => {
183+
// We need to run the requests in sequence to get a more
184+
// accurate measure of the time it takes to run each request separately.
185+
for (const req of requests) {
186+
await run(req);
187+
}
188+
};
189+
190+
return (
191+
<div>
192+
<Header title="Benchmarks" subtitle="Analyze server queries time" />
193+
<TitleCard
194+
title="Benchmarks"
195+
right={
196+
<Button variant="contained" onClick={runAll}>
197+
Run All
198+
</Button>
199+
}>
200+
<Table>
201+
<TableHead>
202+
<TableCell>Request</TableCell>
203+
<TableCell align="right">Elapsed time</TableCell>
204+
<TableCell
205+
padding="none"
206+
align="right"
207+
style={{ paddingRight: 24 }}>
208+
Actions
209+
</TableCell>
210+
</TableHead>
211+
{requests.map(req => {
212+
let elapsed;
213+
const requestTimeElapsed = elapsedTime[req.title];
214+
if (requestTimeElapsed === NOT_FINISHED_REQUEST) {
215+
elapsed = <i>Loading...</i>;
216+
} else if (requestTimeElapsed === FAILED_REQUEST) {
217+
elapsed = <i>Failed</i>;
218+
} else if (requestTimeElapsed) {
219+
elapsed = `${requestTimeElapsed}ms`;
220+
}
221+
222+
return (
223+
<TableRow key={req.title}>
224+
<TableCell component="th" scope="row">
225+
<Text>{req.title}</Text>
226+
</TableCell>
227+
<TableCell align="right">
228+
<Text>{elapsed}</Text>
229+
</TableCell>
230+
<TableCell
231+
padding="none"
232+
align="right"
233+
style={{ paddingRight: 12 }}>
234+
<IconButton color="success" onClick={() => run(req)}>
235+
<PlayArrow />
236+
</IconButton>
237+
</TableCell>
238+
</TableRow>
239+
);
240+
})}
241+
</Table>
242+
</TitleCard>
243+
</div>
244+
);
245+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "./Benchmarks";

0 commit comments

Comments
 (0)