Skip to content

Commit

Permalink
Merge pull request #356 from Yooooomi/perf-improvements
Browse files Browse the repository at this point in the history
Perf improvements
  • Loading branch information
Yooooomi authored Mar 1, 2024
2 parents 70cd64d + 0659ade commit bba31e0
Show file tree
Hide file tree
Showing 21 changed files with 617 additions and 550 deletions.
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

0 comments on commit bba31e0

Please sign in to comment.