Skip to content

Commit

Permalink
Merge branch 'main' into tag
Browse files Browse the repository at this point in the history
  • Loading branch information
jivey committed Oct 4, 2024
2 parents b0b7b30 + d4ae655 commit 1665d64
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 43 deletions.
14 changes: 10 additions & 4 deletions backend/src/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import multiprocessing
import os
import sys
import platform
from Bio import SeqIO
import psutil
import webview
Expand Down Expand Up @@ -114,16 +115,16 @@ def get_compute_stats(filename):
for record in SeqIO.parse(filename, "fasta"):
max_len = max(max_len, len(record.seq))

state = get_state()

required_memory = max_len * max_len
total_memory = psutil.virtual_memory().total
total_memory = state.platform["memory"]
total_cores = state.platform["cores"]

total_cores = multiprocessing.cpu_count()
return {
"total_cores": total_cores,
"recommended_cores": min(
max(total_cores - 1, 1), total_memory // required_memory
),
"total_memory": total_memory,
"required_memory": required_memory,
"available_memory": psutil.virtual_memory().available,
}
Expand Down Expand Up @@ -530,6 +531,11 @@ def manual_window():
get_state, set_state, reset_state = create_app_state(
debug=os.getenv("DEBUG", "false").lower() == "true",
tempdir_path=temp_dir.name,
platform=dict(
platform=platform.platform(),
cores=multiprocessing.cpu_count(),
memory=psutil.virtual_memory().total,
),
on_update=lambda _: update_client_state(window),
)

Expand Down
3 changes: 3 additions & 0 deletions backend/src/app_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"estimated_time",
"validation_error_id",
"compute_stats",
"platform",
],
)

Expand All @@ -53,6 +54,7 @@ def create_app_state(
estimated_time=None,
validation_error_id=None,
compute_stats=None,
platform=None,
):
default_state = AppState(
view=view,
Expand All @@ -71,6 +73,7 @@ def create_app_state(
estimated_time=estimated_time,
validation_error_id=validation_error_id,
compute_stats=compute_stats,
platform=platform,
)

state = default_state
Expand Down
16 changes: 12 additions & 4 deletions frontend/src/appState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,22 @@ export type AppState = {
estimated_time?: number;
validation_error_id?: keyof typeof messages;
compute_stats?: {
total_cores: number;
recommended_cores: number;
total_memory: number;
required_memory: number;
available_memory: number;
};
platform: {
cores: number;
memory: number;
platform: string;
};
client: {
dataView: "heatmap" | "plot";
performanceProfile: PerformanceProfile;
cluster_method: (typeof clusterMethods)[number];
compute_cores: number;
error?: Error;
errorInfo?: ErrorInfo;
error?: Error | null;
errorInfo?: ErrorInfo | PromiseRejectionEvent["reason"] | null;
saveFormat: SaveableImageFormat;
showExportModal: boolean;
};
Expand All @@ -67,6 +70,11 @@ export const initialAppState: AppState = {
compute_cores: 1,
performanceProfile: "recommended",
},
platform: {
cores: 1,
memory: 1,
platform: "unknown",
},
};

export type SetAppState = React.Dispatch<React.SetStateAction<AppState>>;
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const App = () => {
setShowDebugState(!showDebugState);
}
});
(window as any).APP_STATE = appState;
}

React.useEffect(() => {
Expand Down
134 changes: 110 additions & 24 deletions frontend/src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { ErrorInfo } from "react";
import { AppState, SetAppState } from "../appState";
import { AppState, SetAppState, initialAppState } from "../appState";
import { Dialog, Modal } from "react-aria-components";
import { formatBytes } from "../helpers";

interface Props {
appState: AppState;
Expand All @@ -10,6 +12,18 @@ interface Props {
export class ErrorBoundary extends React.Component<Props> {
constructor(props: Props) {
super(props);
this.handlePromiseRejection = this.handlePromiseRejection.bind(this);
}

override componentDidMount(): void {
window.addEventListener("unhandledrejection", this.handlePromiseRejection);
}

override componentWillUnmount(): void {
window.removeEventListener(
"unhandledrejection",
this.handlePromiseRejection,
);
}

override componentDidCatch(error: Error, errorInfo: ErrorInfo) {
Expand All @@ -30,41 +44,113 @@ export class ErrorBoundary extends React.Component<Props> {
};
}

getMailTo(error?: Error) {
let mailTo = `mailto:TODO@TODO?subject=SDT2%20crash`;
handlePromiseRejection(e: PromiseRejectionEvent) {
this.props.setAppState((previous) => {
return {
...previous,
client: {
...previous.client,
error: new Error(e.reason),
errorInfo: e.reason,
},
};
});
}

getMailTo(error?: Error, details = "") {
let mailTo = `mailto:[email protected]?subject=SDT2%20Issue`;

if (error) {
mailTo = `${mailTo}:%20${error.message}&body=${encodeURI(
error.stack || "",
)}`;
mailTo = `${mailTo}:%20${error.message}&body=${encodeURI(details)}`;
}

return mailTo;
}

getIssueUrl(error?: Error, details = "") {
const url = new URL("https://github.com/SDT-org/SDT2/issues/new");

if (!error) {
return url.toString();
}

url.searchParams.set("labels", "bug");
url.searchParams.set("title", error.message);
url.searchParams.set("body", details);

return url.toString();
}

override render() {
const error = this.props.appState.client.error;
const errorInfo = this.props.appState.client.errorInfo;
const platform = this.props.appState.platform;
const stats = this.props.appState.compute_stats;
const objectToHuman = (obj?: Object) =>
Object.entries(obj ?? {})
.map((v) => `${v[0].toUpperCase()}: ${v[1]}`)
.join("\n");

const errorDetails = [
errorInfo?.stack,
error?.stack,
errorInfo?.componentStack,
objectToHuman({ ...platform, memory: formatBytes(platform.memory) }),
objectToHuman({
...stats,
available_memory: formatBytes(stats?.available_memory || 0),
required_memory: formatBytes(stats?.required_memory || 0),
}),
]
.filter(Boolean)
.join("\n\n---\n\n");

const resetAppError = () =>
this.props.setAppState((previous) => {
return {
...previous,
client: {
...previous.client,
error: null,
errorInfo: null,
},
};
});

const resetApp = () => this.props.setAppState(initialAppState);

if (error) {
return (
<div className="app-main centered error-screen">
<h1>Something went wrong.</h1>
<p>
Please{" "}
<a href={this.getMailTo(error)} target="_blank">
send us an email
</a>{" "}
with the error details.
</p>
<details open={true}>
<summary>{error?.message.toString()}</summary>
<pre>{this.props.appState.client.errorInfo?.componentStack}</pre>
</details>
<details>
<summary>App State</summary>
<pre>{JSON.stringify(this.props.appState, null, 2)}</pre>
</details>
</div>
<Modal isDismissable isOpen={true} onOpenChange={resetAppError}>
<Dialog>
<div className="error-modal">
<h1>Something went wrong.</h1>
<p>
Please{" "}
<a href={this.getMailTo(error, errorDetails)} target="_blank">
send us an email
</a>{" "}
with the error details, or{" "}
<a href={this.getIssueUrl(error, errorDetails)} target="_blank">
open an issue
</a>
.
</p>
<details open={true}>
<summary>{error?.message.toString()}</summary>
<pre>{errorDetails}</pre>
</details>
<details>
<summary>App State</summary>
<pre>{JSON.stringify(this.props.appState, null, 2)}</pre>
</details>
</div>
<div className="actions space-between">
<button onClick={resetApp}>Reset</button>
<button onClick={resetAppError}>Continue</button>
</div>
</Dialog>
</Modal>
);
}

Expand Down
14 changes: 7 additions & 7 deletions frontend/src/components/Runner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ const RunnerSettings = ({

React.useEffect(() => {
if (appState.compute_stats) {
const stats = appState.compute_stats;
const stats = { ...appState.compute_stats, ...appState.platform };

setAppState((previous) => ({
...previous,
Expand All @@ -128,8 +128,8 @@ const RunnerSettings = ({

setComputeModes({
recommended: stats.recommended_cores,
best: stats.total_cores,
balanced: Math.floor(Math.max(stats.total_cores / 2, 1)),
best: stats.cores,
balanced: Math.floor(Math.max(stats.cores / 2, 1)),
low: 1,
});
}
Expand Down Expand Up @@ -228,7 +228,7 @@ const RunnerSettings = ({
<Slider
onChange={handleChangeComputeCores}
minValue={1}
maxValue={appState.compute_stats.total_cores}
maxValue={appState.platform.cores}
defaultValue={appState.client.compute_cores}
>
<Label>Cores</Label>
Expand All @@ -245,7 +245,7 @@ const RunnerSettings = ({
? appState.client.compute_cores
: computeModes[appState.client.performanceProfile]}
<span>/</span>
{appState.compute_stats?.total_cores}
{appState.platform.cores}
</span>
cores
</>
Expand All @@ -257,7 +257,7 @@ const RunnerSettings = ({
{appState.compute_stats &&
appState.client.compute_cores *
appState.compute_stats.required_memory >
appState.compute_stats.total_memory ? (
appState.platform.memory ? (
<div className="compute-forecast">
<p>
<b>Warning:</b> Analysing these sequences may cause system
Expand All @@ -275,7 +275,7 @@ const RunnerSettings = ({
appState.compute_stats.required_memory *
appState.client.compute_cores,
)}{" "}
/ {formatBytes(appState.compute_stats.total_memory)}{" "}
/ {formatBytes(appState.platform.memory)}{" "}
<small>
({formatBytes(appState.compute_stats.required_memory)} per
core)
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/Viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ export const Viewer = ({
])
.catch(() => {
setLoading(false);
alert("An error occured while processing this file.");
alert(
"An error occured while processing this file. Please ensure it is a valid, SDT-compatible file.",
);
window.pywebview.api.reset_state();
})
.finally(() => {
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -477,10 +477,13 @@ select, select option {
}
}

.error-screen {
height: 100vh;
.error-modal {
padding: 2rem;
justify-content: flex-start;
overflow: auto;
width: 80vw;
height: 80vh;
position: relative;

h1 {
color: red;
Expand All @@ -492,7 +495,7 @@ select, select option {
border: 0.1rem solid var(--foreground-color);
border-radius: 0.4rem;
padding: 1rem;
margin-top: 4rem;
margin-top: 2rem;
font-family: "Menlo", "Consolas", "monospace";

& + details {
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/styles/modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
display: flex;
gap: 1.6rem;
justify-content: flex-end;

&.space-between {
justify-content: space-between;
}
}
}

Expand Down

0 comments on commit 1665d64

Please sign in to comment.