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

Platform fetch performance improvements #857

Merged
merged 10 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 2 additions & 1 deletion backend/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

load_dotenv()

# UVICORN
# GUNICORN
DEV_PORT: Final = int(os.environ.get("VITE_BACKEND_DEV_PORT", "5000"))
DEV_HOST: Final = "0.0.0.0"
ROMM_HOST: Final = os.environ.get("ROMM_HOST", DEV_HOST)
GUNICORN_WORKERS: Final = int(os.environ.get("GUNICORN_WORKERS", 2))

# PATHS
ROMM_BASE_PATH: Final = os.environ.get("ROMM_BASE_PATH", "/romm")
Expand Down
4 changes: 1 addition & 3 deletions backend/endpoints/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,7 @@ def get_platform(request: Request, id: int) -> PlatformSchema:
PlatformSchema: Platform
"""

return PlatformSchema.from_orm_with_request(
db_platform_handler.get_platforms(id), request
)
return db_platform_handler.get_platforms(id)


@protected_route(router.put, "/platforms/{id}", ["platforms.write"])
Expand Down
14 changes: 2 additions & 12 deletions backend/endpoints/responses/platform.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from typing import Optional
from pydantic import BaseModel, Field
from fastapi import Request

from models.platform import Platform
from .firmware import FirmwareSchema

class PlatformSchema(BaseModel):
id: int
slug: str
Expand All @@ -14,16 +13,7 @@ class PlatformSchema(BaseModel):
name: str
logo_path: Optional[str] = ""
rom_count: int

firmware_files: list[FirmwareSchema] = Field(default_factory=list)
firmware: list[FirmwareSchema] = Field(default_factory=list)

class Config:
from_attributes = True

@classmethod
def from_orm_with_request(cls, db_platform: Platform, request: Request) -> "PlatformSchema":
platform = cls.model_validate(db_platform)
platform.firmware_files = [
FirmwareSchema.model_validate(f) for f in sorted(db_platform.firmware, key=lambda x: x.file_name)
]
return platform
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer needed since as it turns out performance is unaffected by loading firmware data as well

13 changes: 6 additions & 7 deletions backend/models/platform.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from sqlalchemy import Column, Integer, String, select, func
from sqlalchemy.orm import Mapped, relationship, column_property

from models.base import BaseModel
from models.rom import Rom
from models.firmware import Firmware
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import Mapped, relationship


class Platform(BaseModel):
Expand All @@ -24,11 +25,9 @@ class Platform(BaseModel):
"Firmware", lazy="selectin", back_populates="platform"
)

@property
def rom_count(self) -> int:
from handler.database import db_platform_handler

return db_platform_handler.get_rom_count(self.id)
rom_count = column_property(
select(func.count(Rom.id)).where(Rom.platform_id == id).scalar_subquery()
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Significantly faster since it runs in SQL land instead of making another DB query


def __repr__(self) -> str:
return self.name
2 changes: 1 addition & 1 deletion docker/init_scripts/init
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ start_bin_gunicorn () {
--bind=0.0.0.0:5000 \
--bind=unix:/tmp/gunicorn.sock \
--pid=/tmp/gunicorn.pid \
--workers 2 \
--workers ${GUNICORN_WORKERS:=2} \
main:app &
}

Expand Down
3 changes: 3 additions & 0 deletions env.template
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
ROMM_BASE_PATH=/path/to/romm_mock
VITE_BACKEND_DEV_PORT=5000

# Gunicorn (optional)
ROMM_HOST=localhost
GUNICORN_WORKERS=4 # (2 × CPU cores) + 1

# IGDB credentials
IGDB_CLIENT_ID=
Expand Down
35 changes: 33 additions & 2 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<script setup lang="ts">
import Notification from "@/components/Notification.vue";
import api from "@/services/api/index";
import userApi from "@/services/api/user";
import platformApi from "@/services/api/platform";
import socket from "@/services/socket";
import storeConfig from "@/stores/config";
import storeGalleryFilter from "@/stores/galleryFilter";
import storeHeartbeat from "@/stores/heartbeat";
import storeRoms, { type Rom } from "@/stores/roms";
import storePlatforms from "@/stores/platforms";
import storeAuth from "@/stores/auth";
import storeScanning from "@/stores/scanning";
import type { Events } from "@/types/emitter";
import { normalizeString } from "@/utils";
Expand All @@ -24,6 +28,8 @@ const emitter = inject<Emitter<Events>>("emitter");
// Props
const heartbeat = storeHeartbeat();
const configStore = storeConfig();
const auth = storeAuth();
const platformsStore = storePlatforms();

socket.on(
"scan:scanning_platform",
Expand Down Expand Up @@ -93,16 +99,41 @@ onBeforeUnmount(() => {
socket.off("scan:scanning_rom");
socket.off("scan:done");
socket.off("scan:done_ko");

document.removeEventListener("network-quiesced", fetchHomeData);
});

onBeforeMount(() => {
function fetchHomeData() {
document.removeEventListener("network-quiesced", fetchHomeData);

api.get("/heartbeat").then(({ data: data }) => {
heartbeat.set(data);
});

api.get("/config").then(({ data: data }) => {
configStore.set(data);
});
});

platformApi
.getPlatforms()
.then(({ data: platforms }) => {
platformsStore.set(platforms);
})
.catch((error) => {
console.error(error);
});

userApi
.fetchCurrentUser()
.then(({ data: user }) => {
auth.setUser(user);
})
.catch((error) => {
console.error(error);
});
}

document.addEventListener("network-quiesced", fetchHomeData)
</script>

<template>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/__generated__/models/PlatformSchema.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/src/components/Details/Emulation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function onFullScreenChange() {
label="BIOS"
v-model="biosRef"
:items="
props.platform.firmware_files?.map((f) => ({
props.platform.firmware?.map((f) => ({
title: f.file_name,
value: f,
})) ?? []
Expand Down
22 changes: 11 additions & 11 deletions frontend/src/components/Dialog/Platform/ViewFirmware.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function uploadFirmware() {
.then(({ data }) => {
const { uploaded, firmware } = data;
if (selectedPlatform.value) {
selectedPlatform.value.firmware_files = firmware;
selectedPlatform.value.firmware = firmware;
}

emitter?.emit("snackbarShow", {
Expand Down Expand Up @@ -75,8 +75,8 @@ function deleteFirmware() {
.deleteFirmware({ firmware: selectedFirmware.value, deleteFromFs: false })
.then(() => {
if (selectedPlatform.value) {
selectedPlatform.value.firmware_files =
selectedPlatform.value.firmware_files?.filter(
selectedPlatform.value.firmware =
selectedPlatform.value.firmware?.filter(
(firmware) => !selectedFirmware.value.includes(firmware)
);
}
Expand All @@ -91,20 +91,20 @@ function deleteFirmware() {
}

function allFirmwareSelected() {
if (selectedPlatform.value?.firmware_files?.length == 0) {
if (selectedPlatform.value?.firmware?.length == 0) {
return false;
}
return (
selectedFirmware.value.length ===
selectedPlatform.value?.firmware_files?.length
selectedPlatform.value?.firmware?.length
);
}

function selectAllFirmware() {
if (allFirmwareSelected()) {
selectedFirmware.value = [];
} else {
selectedFirmware.value = selectedPlatform.value?.firmware_files ?? [];
selectedFirmware.value = selectedPlatform.value?.firmware ?? [];
}
}
</script>
Expand Down Expand Up @@ -223,14 +223,14 @@ function selectAllFirmware() {
<v-card-text
class="my-4 py-0"
v-if="
selectedPlatform?.firmware_files != undefined &&
selectedPlatform?.firmware_files?.length > 0
selectedPlatform?.firmware != undefined &&
selectedPlatform?.firmware?.length > 0
"
>
<v-list rounded="0" class="pa-0">
<v-list-item
class="px-3"
v-for="firmware in selectedPlatform?.firmware_files ?? []"
v-for="firmware in selectedPlatform?.firmware ?? []"
:key="firmware.id"
>
<template v-slot:prepend>
Expand Down Expand Up @@ -286,8 +286,8 @@ function selectAllFirmware() {
variant="text"
:disabled="
!(
selectedPlatform?.firmware_files != undefined &&
selectedPlatform?.firmware_files?.length > 0
selectedPlatform?.firmware != undefined &&
selectedPlatform?.firmware?.length > 0
)
"
>
Expand Down
22 changes: 20 additions & 2 deletions frontend/src/services/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
import axios from "axios";
import cookie from "js-cookie";

import router from "@/plugins/router";
import { debounce } from "lodash";

const api = axios.create({ baseURL: "/api", timeout: 120000 });

const inflightRequests = new Set();

const networkQuiesced = debounce(() => {
document.dispatchEvent(new CustomEvent("network-quiesced"));
}, 300);

// Set CSRF header for all requests
api.interceptors.request.use((config) => {
inflightRequests.add(config.url);
networkQuiesced.cancel();

config.headers["x-csrftoken"] = cookie.get("csrftoken");
return config;
});

api.interceptors.response.use(
(response) => response,
(response) => {
inflightRequests.delete(response.config.url);

// If there are no more inflight requests, fetch home data
if (inflightRequests.size === 0) {
networkQuiesced();
}

return response;
},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does what it says; fires a custom event when all network requests are complete, which triggers loading things like the platforms sidebar and heartbeat data.

(error) => {
if (error.response?.status === 403) {
router.push({
Expand Down
26 changes: 1 addition & 25 deletions frontend/src/views/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,19 @@ import DeleteUserDialog from "@/components/Dialog/User/DeleteUser.vue";
import EditUserDialog from "@/components/Dialog/User/EditUser.vue";
import Drawer from "@/components/Drawer/Base.vue";
import platformApi from "@/services/api/platform";
import userApi from "@/services/api/user";
import storeAuth from "@/stores/auth";
import storePlatforms from "@/stores/platforms";
import storeScanning from "@/stores/scanning";
import type { Events } from "@/types/emitter";
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { inject, onMounted, ref } from "vue";
import { inject, ref } from "vue";
import { useDisplay } from "vuetify";

// Props
const { mdAndDown } = useDisplay();
const scanningStore = storeScanning();
const { scanning } = storeToRefs(scanningStore);
const platformsStore = storePlatforms();
const auth = storeAuth();
const refreshView = ref(0);

// Event listeners bus
Expand All @@ -47,27 +44,6 @@ emitter?.on("refreshDrawer", async () => {
emitter?.on("refreshView", async () => {
refreshView.value = refreshView.value + 1;
});

// Functions
onMounted(() => {
platformApi
.getPlatforms()
.then(({ data: platforms }) => {
platformsStore.set(platforms);
})
.catch((error) => {
console.error(error);
});

userApi
.fetchCurrentUser()
.then(({ data: user }) => {
auth.setUser(user);
})
.catch((error) => {
console.error(error);
});
});
</script>

<template>
Expand Down
Loading