Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perf improvements #356

Merged
merged 18 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,4 @@ dist
docker-compose-personal.yml
client/public/index.html
client/public/variables.js
db_data
9 changes: 9 additions & 0 deletions apps/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import PlaylistDialog from "./components/PlaylistDialog";
import TrackStats from "./scenes/TrackStats";
import LongestSessions from "./scenes/LongestSessions";
import AlbumStats from "./scenes/AlbumStats";
import Benchmarks from "./scenes/Benchmarks";

function App() {
const dark = useSelector(selectDarkMode);
Expand Down Expand Up @@ -169,6 +170,14 @@ function App() {
</PrivateRoute>
}
/>
<Route
path="/benchmarks"
element={
<PrivateRoute>
<Benchmarks />
</PrivateRoute>
}
/>
</Routes>
</Layout>
</BrowserRouter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,14 @@ function getElementName(
>["data"][number],
id: string,
) {
if ("tracks" in result) {
return result.tracks.find(t => t.track.id === id)?.track.name;
}
if ("albums" in result) {
return result.albums.find(t => t.album.id === id)?.album.name;
}
if ("artists" in result) {
return result.artists.find(t => t.artist.id === id)?.artist.name;
}
return "";
return result.full_items[id]?.name;
}

function getElementData(
result: UnboxPromise<ReturnType<(typeof elementToCall)[Element]>>["data"],
index: number,
) {
const foundIndex = result.findIndex(r => r._id === index);
const foundIndex = result.findIndex(r => r.hour === index);
if (foundIndex === -1) {
return { x: index };
}
Expand All @@ -61,34 +52,13 @@ function getElementData(

const { total } = found;

if ("tracks" in found) {
return found.tracks.reduce<StackedBarProps["data"][number]>(
(acc, curr) => {
acc[curr.track.id] = Math.floor((curr.count / total) * 1000) / 10;
return acc;
},
{ x: index },
);
}
if ("albums" in found) {
return found.albums.reduce<StackedBarProps["data"][number]>(
(acc, curr) => {
acc[curr.album.id] = Math.floor((curr.count / total) * 1000) / 10;
return acc;
},
{ x: index },
);
}
if ("artists" in found) {
return found.artists.reduce<StackedBarProps["data"][number]>(
(acc, curr) => {
acc[curr.artist.id] = Math.floor((curr.count / total) * 1000) / 10;
return acc;
},
{ x: index },
);
}
return { x: index };
return found.items.reduce<StackedBarProps["data"][number]>(
(acc, curr) => {
acc[curr.itemId] = Math.floor((curr.total / total) * 1000) / 10;
return acc;
},
{ x: index },
);
}

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

const tooltipValue = useCallback<ValueFormatter<typeof data>>(
(payload, value, root) => {
const foundIndex = result?.findIndex(r => r._id === payload.x);
const foundIndex = result?.findIndex(r => r.hour === payload.x);
if (!result || foundIndex === undefined || foundIndex === -1) {
return null;
}
Expand Down
1 change: 0 additions & 1 deletion apps/client/src/components/InlineArtist/InlineArtist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export default function InlineArtist<T extends HTMLTag = "div">({
...other
}: InlineArtistProps<T>) {
return (
// eslint-disable-next-line react/jsx-props-no-spreading
<Text title={artist.name} {...other}>
<Link to={`/artist/${artist.id}`} className={s.root}>
{artist.name}
Expand Down
245 changes: 245 additions & 0 deletions apps/client/src/scenes/Benchmarks/Benchmarks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
import { useSelector } from "react-redux";
import {
Button,
IconButton,
Table,
TableCell,
TableHead,
TableRow,
} from "@mui/material";
import { PlayArrow } from "@mui/icons-material";
import { useState } from "react";
import Header from "../../components/Header";
import {
selectRawIntervalDetail,
selectUser,
} from "../../services/redux/modules/user/selector";
import { api } from "../../services/apis/api";
import { Timesplit } from "../../services/types";
import TitleCard from "../../components/TitleCard";
import Text from "../../components/Text";

interface Request<T> {
title: string;
prepare?: () => Promise<T>;
request: (prepared: T) => Promise<any>;
}

const NOT_FINISHED_REQUEST = -1;
const FAILED_REQUEST = -2;

export default function Benchmarks() {
const { interval } = useSelector(selectRawIntervalDetail);

const [elapsedTime, setElapsedTime] = useState<Record<string, number>>({});

const user = useSelector(selectUser);

if (!user) {
return null;
}

const NB = 30;
const OFFSET = 0;

const requests = [
{
title: "Get tracks",
request: () => api.getTracks(interval.start, interval.end, 20, OFFSET),
},
{
title: "Get most listened",
request: () =>
api.mostListened(interval.start, interval.end, Timesplit.all),
},
{
title: "Get most listened artists",
request: () =>
api.mostListenedArtist(interval.start, interval.end, Timesplit.all),
},
{
title: "Get songs per",
request: () => api.songsPer(interval.start, interval.end, Timesplit.all),
},
{
title: "Get time per",
request: () => api.timePer(interval.start, interval.end, Timesplit.all),
},
{
title: "Get feat ratio",
request: () => api.featRatio(interval.start, interval.end, Timesplit.all),
},
{
title: "Get album date ratio",
request: () =>
api.albumDateRatio(interval.start, interval.end, Timesplit.all),
},
{
title: "Get popularity per",
request: () =>
api.popularityPer(interval.start, interval.end, Timesplit.all),
},
{
title: "Get different artists per",
request: () =>
api.differentArtistsPer(interval.start, interval.end, Timesplit.all),
},
{
title: "Get time per hour of day",
request: () => api.timePerHourOfDay(interval.start, interval.end),
},
{
title: "Get best songs",
request: () => api.getBestSongs(interval.start, interval.end, NB, OFFSET),
},
{
title: "Get best artists",
request: () =>
api.getBestArtists(interval.start, interval.end, NB, OFFSET),
},
{
title: "Get best albums",
request: () =>
api.getBestAlbums(interval.start, interval.end, NB, OFFSET),
},
{
title: "Get best songs of hour",
request: () => api.getBestSongsOfHour(interval.start, interval.end),
},
{
title: "Get best albums of hour",
request: () => api.getBestAlbumsOfHour(interval.start, interval.end),
},
{
title: "Get best artists of hour",
request: () => api.getBestArtistsOfHour(interval.start, interval.end),
},
{
title: "Get longest sessions",
request: () => api.getLongestSessions(interval.start, interval.end),
},
{
title: "Get artist page",
prepare: async () => {
const { data: bestArtists } = await api.getBestArtists(
interval.start,
interval.end,
1,
0,
);
const [bestArtist] = bestArtists;
return bestArtist?.artist.id;
},
request: async (bestArtistId: string) => api.getArtistStats(bestArtistId),
},
{
title: "Get album page",
prepare: async () => {
const { data: bestAlbums } = await api.getBestAlbums(
interval.start,
interval.end,
1,
0,
);
const [bestAlbum] = bestAlbums;
return bestAlbum?.album.id;
},
request: async (bestAlbumId: string) => api.getAlbumStats(bestAlbumId),
},
{
title: "Get track page",
prepare: async () => {
const { data: bestTracks } = await api.getBestSongs(
interval.start,
interval.end,
1,
0,
);
const [bestTrack] = bestTracks;
return bestTrack?.track.id;
},
request: async (bestTrackId: string) => api.getTrackStats(bestTrackId),
},
] as const satisfies Request<any>[];

const run = async (req: Request<any>) => {
setElapsedTime(prev => ({ ...prev, [req.title]: NOT_FINISHED_REQUEST }));
let prepared = undefined;
if (req.prepare) {
prepared = await req.prepare();
}
if (!prepared && req.prepare) {
setElapsedTime(prev => ({ ...prev, [req.title]: FAILED_REQUEST }));
return;
}
let start = Date.now();
const { data: result } = await req.request(prepared);
console.log("Result", result);
const end = Date.now();
setElapsedTime(prev => ({ ...prev, [req.title]: end - start }));
};

const runAll = async () => {
// We need to run the requests in sequence to get a more
// accurate measure of the time it takes to run each request separately.
for (const req of requests) {
await run(req);
}
};

return (
<div>
<Header title="Benchmarks" subtitle="Analyze server queries time" />
<TitleCard
title="Benchmarks"
right={
<Button variant="contained" onClick={runAll}>
Run All
</Button>
}>
<Table>
<TableHead>
<TableCell>Request</TableCell>
<TableCell align="right">Elapsed time</TableCell>
<TableCell
padding="none"
align="right"
style={{ paddingRight: 24 }}>
Actions
</TableCell>
</TableHead>
{requests.map(req => {
let elapsed;
const requestTimeElapsed = elapsedTime[req.title];
if (requestTimeElapsed === NOT_FINISHED_REQUEST) {
elapsed = <i>Loading...</i>;
} else if (requestTimeElapsed === FAILED_REQUEST) {
elapsed = <i>Failed</i>;
} else if (requestTimeElapsed) {
elapsed = `${requestTimeElapsed}ms`;
}

return (
<TableRow key={req.title}>
<TableCell component="th" scope="row">
<Text>{req.title}</Text>
</TableCell>
<TableCell align="right">
<Text>{elapsed}</Text>
</TableCell>
<TableCell
padding="none"
align="right"
style={{ paddingRight: 12 }}>
<IconButton color="success" onClick={() => run(req)}>
<PlayArrow />
</IconButton>
</TableCell>
</TableRow>
);
})}
</Table>
</TitleCard>
</div>
);
}
1 change: 1 addition & 0 deletions apps/client/src/scenes/Benchmarks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./Benchmarks";
Loading