start of coredump (#2050)
* start of coredump Signed-off-by: Jess Frazelle <github@jessfraz.com> * add local Signed-off-by: Jess Frazelle <github@jessfraz.com> * more coredummp Signed-off-by: Jess Frazelle <github@jessfraz.com> * add arch Signed-off-by: Jess Frazelle <github@jessfraz.com> * os info Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix app version Signed-off-by: Jess Frazelle <github@jessfraz.com> * more webrtc stats Signed-off-by: Jess Frazelle <github@jessfraz.com> * webrtc data Signed-off-by: Jess Frazelle <github@jessfraz.com> * webrtc stats Signed-off-by: Jess Frazelle <github@jessfraz.com> * add wasm function Signed-off-by: Jess Frazelle <github@jessfraz.com> * add coredump things Signed-off-by: Jess Frazelle <github@jessfraz.com> * add hotkey Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * clippy Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
@ -16,7 +16,7 @@
|
||||
"@open-rpc/client-js": "^1.8.1",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@replit/codemirror-interact": "^6.3.0",
|
||||
"@tauri-apps/api": "^2.0.0-beta.7",
|
||||
"@tauri-apps/api": "2.0.0-beta.7",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-http": "^2.0.0-beta.2",
|
||||
@ -57,6 +57,7 @@
|
||||
"toml": "^3.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.3",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"uuid": "^9.0.1",
|
||||
"vitest": "^1.4.0",
|
||||
"vscode-jsonrpc": "^8.1.0",
|
||||
@ -123,6 +124,7 @@
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"@types/three": "^0.160.0",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/wait-on": "^5.3.4",
|
||||
"@types/wicg-file-system-access": "^2023.10.5",
|
||||
|
@ -38,7 +38,7 @@ import {
|
||||
getSketchQuaternion,
|
||||
} from 'clientSideScene/sceneEntities'
|
||||
import { sketchOnExtrudedFace, startSketchOnDefault } from 'lang/modifyAst'
|
||||
import { Program, parse } from 'lang/wasm'
|
||||
import { Program, coreDump, parse } from 'lang/wasm'
|
||||
import { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import { TEST } from 'env'
|
||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||
@ -47,6 +47,8 @@ import toast from 'react-hot-toast'
|
||||
import { EditorSelection } from '@uiw/react-codemirror'
|
||||
import { Vector3 } from 'three'
|
||||
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -76,6 +78,8 @@ export const ModelingMachineProvider = ({
|
||||
const token = auth?.context?.token
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
useSetupEngineManager(streamRef, token, theme.current)
|
||||
const coreDumpManager = new CoreDumpManager(engineCommandManager)
|
||||
useHotkeys('meta + shift + .', () => coreDump(coreDumpManager, true))
|
||||
|
||||
const {
|
||||
isShiftDown,
|
||||
|
@ -69,6 +69,15 @@ type Timeout = ReturnType<typeof setTimeout>
|
||||
|
||||
type ClientMetrics = Models['ClientMetrics_type']
|
||||
|
||||
interface WebRTCClientMetrics extends ClientMetrics {
|
||||
rtc_frame_height: number
|
||||
rtc_frame_width: number
|
||||
rtc_packets_lost: number
|
||||
rtc_pli_count: number
|
||||
rtc_pause_count: number
|
||||
rtc_total_pauses_duration_sec: number
|
||||
}
|
||||
|
||||
type Value<T, U> = U extends undefined
|
||||
? { type: T; value: U }
|
||||
: U extends void
|
||||
@ -234,7 +243,7 @@ class EngineConnection {
|
||||
private onNewTrack: (track: NewTrackArgs) => void
|
||||
|
||||
// TODO: actual type is ClientMetrics
|
||||
private webrtcStatsCollector?: () => Promise<ClientMetrics>
|
||||
public webrtcStatsCollector?: () => Promise<WebRTCClientMetrics>
|
||||
private engineCommandManager: EngineCommandManager
|
||||
|
||||
constructor({
|
||||
@ -406,7 +415,7 @@ class EngineConnection {
|
||||
},
|
||||
}
|
||||
|
||||
this.webrtcStatsCollector = (): Promise<ClientMetrics> => {
|
||||
this.webrtcStatsCollector = (): Promise<WebRTCClientMetrics> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (mediaStream.getVideoTracks().length !== 1) {
|
||||
reject(new Error('too many video tracks to report'))
|
||||
@ -415,7 +424,7 @@ class EngineConnection {
|
||||
|
||||
let videoTrack = mediaStream.getVideoTracks()[0]
|
||||
void this.pc?.getStats(videoTrack).then((videoTrackStats) => {
|
||||
let client_metrics: ClientMetrics = {
|
||||
let client_metrics: WebRTCClientMetrics = {
|
||||
rtc_frames_decoded: 0,
|
||||
rtc_frames_dropped: 0,
|
||||
rtc_frames_received: 0,
|
||||
@ -424,6 +433,12 @@ class EngineConnection {
|
||||
rtc_jitter_sec: 0.0,
|
||||
rtc_keyframes_decoded: 0,
|
||||
rtc_total_freezes_duration_sec: 0.0,
|
||||
rtc_frame_height: 0,
|
||||
rtc_frame_width: 0,
|
||||
rtc_packets_lost: 0,
|
||||
rtc_pli_count: 0,
|
||||
rtc_pause_count: 0,
|
||||
rtc_total_pauses_duration_sec: 0.0,
|
||||
}
|
||||
|
||||
// TODO(paultag): Since we can technically have multiple WebRTC
|
||||
@ -449,6 +464,13 @@ class EngineConnection {
|
||||
videoTrackReport.keyFramesDecoded || 0
|
||||
client_metrics.rtc_total_freezes_duration_sec =
|
||||
videoTrackReport.totalFreezesDuration || 0
|
||||
client_metrics.rtc_frame_height =
|
||||
videoTrackReport.frameHeight || 0
|
||||
client_metrics.rtc_frame_width =
|
||||
videoTrackReport.frameWidth || 0
|
||||
client_metrics.rtc_packets_lost =
|
||||
videoTrackReport.packetsLost || 0
|
||||
client_metrics.rtc_pli_count = videoTrackReport.pliCount || 0
|
||||
} else if (videoTrackReport.type === 'transport') {
|
||||
// videoTrackReport.bytesReceived,
|
||||
// videoTrackReport.bytesSent,
|
||||
|
@ -10,6 +10,7 @@ import init, {
|
||||
ServerConfig,
|
||||
copilot_lsp_run,
|
||||
kcl_lsp_run,
|
||||
coredump,
|
||||
} from '../wasm-lib/pkg/wasm_lib'
|
||||
import { KCLError } from './errors'
|
||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||
@ -21,6 +22,9 @@ import type { Token } from '../wasm-lib/kcl/bindings/Token'
|
||||
import { Coords2d } from './std/sketch'
|
||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||
import { DEV } from 'env'
|
||||
import { AppInfo } from 'wasm-lib/kcl/bindings/AppInfo'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import openWindow from 'lib/openWindow'
|
||||
|
||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
||||
@ -311,3 +315,18 @@ export async function kclLspRun(config: ServerConfig, token: string) {
|
||||
// We can't restart here because a moved value, we should do this another way.
|
||||
}
|
||||
}
|
||||
|
||||
export async function coreDump(
|
||||
coreDumpManager: CoreDumpManager,
|
||||
openGithubIssue: boolean = false
|
||||
): Promise<AppInfo> {
|
||||
try {
|
||||
const dump: AppInfo = await coredump(coreDumpManager)
|
||||
if (openGithubIssue && dump.github_issue_url) {
|
||||
openWindow(dump.github_issue_url)
|
||||
}
|
||||
return dump
|
||||
} catch (e: any) {
|
||||
throw new Error(`Error getting core dump: ${e}`)
|
||||
}
|
||||
}
|
||||
|
114
src/lib/coredump.ts
Normal file
114
src/lib/coredump.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
import { WebrtcStats } from 'wasm-lib/kcl/bindings/WebrtcStats'
|
||||
import { OsInfo } from 'wasm-lib/kcl/bindings/OsInfo'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import {
|
||||
platform as tauriPlatform,
|
||||
arch as tauriArch,
|
||||
version as tauriKernelVersion,
|
||||
} from '@tauri-apps/plugin-os'
|
||||
import { APP_VERSION } from 'routes/Settings'
|
||||
import { UAParser } from 'ua-parser-js'
|
||||
|
||||
// This is a class for getting all the values from the JS world to pass to the Rust world
|
||||
// for a core dump.
|
||||
export class CoreDumpManager {
|
||||
engineCommandManager: EngineCommandManager
|
||||
|
||||
constructor(engineCommandManager: EngineCommandManager) {
|
||||
this.engineCommandManager = engineCommandManager
|
||||
}
|
||||
|
||||
// Get the version of the app from the package.json.
|
||||
version(): string {
|
||||
return APP_VERSION
|
||||
}
|
||||
|
||||
// Get the os information.
|
||||
getOsInfo(): Promise<string> {
|
||||
if (this.isTauri()) {
|
||||
return tauriArch()
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting arch: ${error}`)
|
||||
})
|
||||
.then((arch: string) => {
|
||||
return tauriPlatform()
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting platform: ${error}`)
|
||||
})
|
||||
.then((platform: string) => {
|
||||
return tauriKernelVersion()
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting kernel version: ${error}`)
|
||||
})
|
||||
.then((kernelVersion: string) => {
|
||||
const osinfo: OsInfo = {
|
||||
platform,
|
||||
arch,
|
||||
version: kernelVersion,
|
||||
}
|
||||
return JSON.stringify(osinfo)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const userAgent = window.navigator.userAgent || 'unknown browser'
|
||||
if (userAgent === 'unknown browser') {
|
||||
const osinfo: OsInfo = {
|
||||
platform: userAgent,
|
||||
arch: userAgent,
|
||||
version: userAgent,
|
||||
}
|
||||
return new Promise((resolve) => resolve(JSON.stringify(osinfo)))
|
||||
}
|
||||
|
||||
const parser = new UAParser(userAgent)
|
||||
const parserResults = parser.getResult()
|
||||
const osinfo: OsInfo = {
|
||||
platform: parserResults.os.name,
|
||||
arch: parserResults.cpu.architecture,
|
||||
version: parserResults.os.version,
|
||||
browser: userAgent,
|
||||
}
|
||||
return new Promise((resolve) => resolve(JSON.stringify(osinfo)))
|
||||
}
|
||||
|
||||
isTauri(): boolean {
|
||||
return isTauri()
|
||||
}
|
||||
|
||||
getWebrtcStats(): Promise<string> {
|
||||
if (!this.engineCommandManager.engineConnection) {
|
||||
throw new Error('Engine connection not initialized')
|
||||
}
|
||||
|
||||
if (!this.engineCommandManager.engineConnection.webrtcStatsCollector) {
|
||||
throw new Error('Engine webrtcStatsCollector not initialized')
|
||||
}
|
||||
|
||||
return this.engineCommandManager.engineConnection
|
||||
.webrtcStatsCollector()
|
||||
.catch((error: any) => {
|
||||
throw new Error(`Error getting webrtc stats: ${error}`)
|
||||
})
|
||||
.then((stats: any) => {
|
||||
const webrtcStats: WebrtcStats = {
|
||||
packets_lost: stats.rtc_packets_lost,
|
||||
frames_received: stats.rtc_frames_received,
|
||||
frame_width: stats.rtc_frame_width,
|
||||
frame_height: stats.rtc_frame_height,
|
||||
frame_rate: stats.rtc_frames_per_second,
|
||||
key_frames_decoded: stats.rtc_keyframes_decoded,
|
||||
frames_dropped: stats.rtc_frames_dropped,
|
||||
pause_count: stats.rtc_pause_count,
|
||||
total_pauses_duration: stats.rtc_total_pauses_duration_sec,
|
||||
freeze_count: stats.rtc_freeze_count,
|
||||
total_freezes_duration: stats.rtc_total_freezes_duration_sec,
|
||||
pli_count: stats.rtc_pli_count,
|
||||
jitter: stats.rtc_jitter_sec,
|
||||
}
|
||||
return JSON.stringify(webrtcStats)
|
||||
})
|
||||
}
|
||||
}
|
11
src/lib/openWindow.ts
Normal file
11
src/lib/openWindow.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { open as tauriOpen } from '@tauri-apps/plugin-shell'
|
||||
|
||||
// Open a new browser window tauri style or browser style.
|
||||
export default async function openWindow(url: string) {
|
||||
if (isTauri()) {
|
||||
await tauriOpen(url)
|
||||
} else {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
}
|
@ -37,8 +37,9 @@ import {
|
||||
shouldShowSettingInput,
|
||||
} from 'lib/settings/settingsUtils'
|
||||
|
||||
export const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
|
||||
|
||||
export const Settings = () => {
|
||||
const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
|
||||
const navigate = useNavigate()
|
||||
const close = () => navigate(location.pathname.replace(paths.SETTINGS, ''))
|
||||
const location = useLocation()
|
||||
|
8
src/wasm-lib/Cargo.lock
generated
8
src/wasm-lib/Cargo.lock
generated
@ -1373,6 +1373,12 @@ version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "git_rev"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60884563ea313b5037683cd5d44f1e14e9cb07b08543756242a65887f9cff48e"
|
||||
|
||||
[[package]]
|
||||
name = "gloo-utils"
|
||||
version = "0.2.0"
|
||||
@ -1864,7 +1870,9 @@ dependencies = [
|
||||
"databake",
|
||||
"derive-docs",
|
||||
"expectorate",
|
||||
"form_urlencoded",
|
||||
"futures",
|
||||
"git_rev",
|
||||
"gltf-json",
|
||||
"iai",
|
||||
"image",
|
||||
|
@ -19,7 +19,9 @@ clap = { version = "4.5.4", features = ["cargo", "derive", "env", "unicode"], op
|
||||
dashmap = "5.5.3"
|
||||
databake = { version = "0.1.7", features = ["derive"] }
|
||||
derive-docs = { version = "0.1.13", path = "../derive-docs" }
|
||||
form_urlencoded = "1.2.1"
|
||||
futures = { version = "0.3.30" }
|
||||
git_rev = "0.1.0"
|
||||
gltf-json = "1.4.0"
|
||||
kittycad = { workspace = true }
|
||||
kittycad-execution-plan-macros = { workspace = true }
|
||||
|
45
src/wasm-lib/kcl/src/coredump/local.rs
Normal file
45
src/wasm-lib/kcl/src/coredump/local.rs
Normal file
@ -0,0 +1,45 @@
|
||||
//! Functions for getting core dump information via local rust.
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::coredump::CoreDump;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CoreDumper {}
|
||||
|
||||
impl CoreDumper {
|
||||
pub fn new() -> Self {
|
||||
CoreDumper {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CoreDumper {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CoreDump for CoreDumper {
|
||||
fn version(&self) -> Result<String> {
|
||||
Ok(env!("CARGO_PKG_VERSION").to_string())
|
||||
}
|
||||
|
||||
async fn os(&self) -> Result<crate::coredump::OsInfo> {
|
||||
Ok(crate::coredump::OsInfo {
|
||||
platform: Some(std::env::consts::OS.to_string()),
|
||||
arch: Some(std::env::consts::ARCH.to_string()),
|
||||
version: None,
|
||||
browser: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_tauri(&self) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
async fn get_webrtc_stats(&self) -> Result<crate::coredump::WebrtcStats> {
|
||||
// TODO: we could actually implement this.
|
||||
Ok(crate::coredump::WebrtcStats::default())
|
||||
}
|
||||
}
|
151
src/wasm-lib/kcl/src/coredump/mod.rs
Normal file
151
src/wasm-lib/kcl/src/coredump/mod.rs
Normal file
@ -0,0 +1,151 @@
|
||||
//! Core dump related structures and functions.
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod local;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm;
|
||||
|
||||
use anyhow::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait CoreDump: Clone {
|
||||
fn version(&self) -> Result<String>;
|
||||
|
||||
async fn os(&self) -> Result<OsInfo>;
|
||||
|
||||
fn is_tauri(&self) -> Result<bool>;
|
||||
|
||||
async fn get_webrtc_stats(&self) -> Result<WebrtcStats>;
|
||||
|
||||
/// Dump the app info.
|
||||
async fn dump(&self) -> Result<AppInfo> {
|
||||
let webrtc_stats = self.get_webrtc_stats().await?;
|
||||
let os = self.os().await?;
|
||||
|
||||
let mut app_info = AppInfo {
|
||||
version: self.version()?,
|
||||
git_rev: git_rev::try_revision_string!().map_or_else(|| "unknown".to_string(), |s| s.to_string()),
|
||||
timestamp: chrono::Utc::now(),
|
||||
tauri: self.is_tauri()?,
|
||||
os,
|
||||
webrtc_stats,
|
||||
github_issue_url: None,
|
||||
};
|
||||
app_info.set_github_issue_url()?;
|
||||
|
||||
Ok(app_info)
|
||||
}
|
||||
}
|
||||
|
||||
/// The app info structure.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct AppInfo {
|
||||
/// The version of the app.
|
||||
pub version: String,
|
||||
/// The git revision of the app.
|
||||
pub git_rev: String,
|
||||
/// A timestamp of the core dump.
|
||||
#[ts(type = "string")]
|
||||
pub timestamp: chrono::DateTime<chrono::Utc>,
|
||||
/// If the app is running in tauri or the browser.
|
||||
pub tauri: bool,
|
||||
|
||||
/// The os info.
|
||||
pub os: OsInfo,
|
||||
|
||||
/// The webrtc stats.
|
||||
pub webrtc_stats: WebrtcStats,
|
||||
|
||||
/// A GitHub issue url to report the core dump.
|
||||
/// This gets prepoulated with all the core dump info.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub github_issue_url: Option<String>,
|
||||
}
|
||||
|
||||
impl AppInfo {
|
||||
/// Set the github issue url.
|
||||
pub fn set_github_issue_url(&mut self) -> Result<()> {
|
||||
let tauri_or_browser_label = if self.tauri { "tauri" } else { "browser" };
|
||||
let labels = ["coredump", "bug", tauri_or_browser_label];
|
||||
let body = format!(
|
||||
r#"[Insert a description of the issue here]
|
||||
|
||||
<details>
|
||||
<summary><b>Core Dump</b></summary>
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
</details>
|
||||
"#,
|
||||
serde_json::to_string_pretty(&self)?
|
||||
);
|
||||
let urlencoded: String = form_urlencoded::byte_serialize(body.as_bytes()).collect();
|
||||
|
||||
self.github_issue_url = Some(format!(
|
||||
r#"https://github.com/{}/{}/issues/new?body={}&labels={}"#,
|
||||
"KittyCAD",
|
||||
"modeling-app",
|
||||
urlencoded,
|
||||
labels.join(",")
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The os info structure.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct OsInfo {
|
||||
/// The platform the app is running on.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub platform: Option<String>,
|
||||
/// The architecture the app is running on.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub arch: Option<String>,
|
||||
/// The kernel version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<String>,
|
||||
/// Information about the browser.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub browser: Option<String>,
|
||||
}
|
||||
|
||||
/// The webrtc stats structure.
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct WebrtcStats {
|
||||
/// The packets lost.
|
||||
pub packets_lost: u32,
|
||||
/// The frames received.
|
||||
pub frames_received: u32,
|
||||
/// The frame width.
|
||||
pub frame_width: f32,
|
||||
/// The frame height.
|
||||
pub frame_height: f32,
|
||||
/// The frame rate.
|
||||
pub frame_rate: f32,
|
||||
/// The number of key frames decoded.
|
||||
pub key_frames_decoded: u32,
|
||||
/// The number of frames dropped.
|
||||
pub frames_dropped: u32,
|
||||
/// The pause count.
|
||||
pub pause_count: u32,
|
||||
/// The total pauses duration.
|
||||
pub total_pauses_duration: f32,
|
||||
/// The freeze count.
|
||||
pub freeze_count: u32,
|
||||
/// The total freezes duration.
|
||||
pub total_freezes_duration: f32,
|
||||
/// The pli count.
|
||||
pub pli_count: u32,
|
||||
/// Packet jitter for this synchronizing source, measured in seconds.
|
||||
pub jitter: f32,
|
||||
}
|
95
src/wasm-lib/kcl/src/coredump/wasm.rs
Normal file
95
src/wasm-lib/kcl/src/coredump/wasm.rs
Normal file
@ -0,0 +1,95 @@
|
||||
//! Functions for getting core dump information via wasm.
|
||||
|
||||
use anyhow::Result;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
use crate::{coredump::CoreDump, wasm::JsFuture};
|
||||
|
||||
#[wasm_bindgen(module = "/../../lib/coredump.ts")]
|
||||
extern "C" {
|
||||
#[derive(Debug, Clone)]
|
||||
pub type CoreDumpManager;
|
||||
|
||||
#[wasm_bindgen(method, js_name = version, catch)]
|
||||
fn version(this: &CoreDumpManager) -> Result<String, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = getOsInfo, catch)]
|
||||
fn get_os_info(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = isTauri, catch)]
|
||||
fn is_tauri(this: &CoreDumpManager) -> Result<bool, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = getWebrtcStats, catch)]
|
||||
fn get_webrtc_stats(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CoreDumper {
|
||||
manager: CoreDumpManager,
|
||||
}
|
||||
|
||||
impl CoreDumper {
|
||||
pub fn new(manager: CoreDumpManager) -> Self {
|
||||
CoreDumper { manager }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for CoreDumper {}
|
||||
unsafe impl Sync for CoreDumper {}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CoreDump for CoreDumper {
|
||||
fn version(&self) -> Result<String> {
|
||||
self.manager
|
||||
.version()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from version: {:?}", e))
|
||||
}
|
||||
|
||||
async fn os(&self) -> Result<crate::coredump::OsInfo> {
|
||||
let promise = self
|
||||
.manager
|
||||
.get_os_info()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get promise from get os info: {:?}", e))?;
|
||||
|
||||
let value = JsFuture::from(promise)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from os info: {:?}", e))?;
|
||||
|
||||
// Parse the value as a string.
|
||||
let s = value
|
||||
.as_string()
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get string from response from os info: `{:?}`", value))?;
|
||||
|
||||
let os: crate::coredump::OsInfo =
|
||||
serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse os info: {:?}", e))?;
|
||||
|
||||
Ok(os)
|
||||
}
|
||||
|
||||
fn is_tauri(&self) -> Result<bool> {
|
||||
self.manager
|
||||
.is_tauri()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from is tauri: {:?}", e))
|
||||
}
|
||||
|
||||
async fn get_webrtc_stats(&self) -> Result<crate::coredump::WebrtcStats> {
|
||||
let promise = self
|
||||
.manager
|
||||
.get_webrtc_stats()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get promise from get webrtc stats: {:?}", e))?;
|
||||
|
||||
let value = JsFuture::from(promise)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get response from webrtc stats: {:?}", e))?;
|
||||
|
||||
// Parse the value as a string.
|
||||
let s = value
|
||||
.as_string()
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get string from response from webrtc stats: `{:?}`", value))?;
|
||||
|
||||
let stats: crate::coredump::WebrtcStats =
|
||||
serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse webrtc stats: {:?}", e))?;
|
||||
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ pub use local::FileManager;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(test))]
|
||||
pub mod wasm;
|
||||
|
||||
use anyhow::Result;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(test))]
|
||||
|
@ -5,6 +5,7 @@
|
||||
#![recursion_limit = "1024"]
|
||||
|
||||
pub mod ast;
|
||||
pub mod coredump;
|
||||
pub mod docs;
|
||||
pub mod engine;
|
||||
pub mod errors;
|
||||
|
@ -7,7 +7,7 @@ use std::{
|
||||
|
||||
use futures::stream::TryStreamExt;
|
||||
use gloo_utils::format::JsValueSerdeExt;
|
||||
use kcl_lib::engine::EngineManager;
|
||||
use kcl_lib::{coredump::CoreDump, engine::EngineManager};
|
||||
use tower_lsp::{LspService, Server};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
@ -379,3 +379,16 @@ pub fn program_memory_init() -> Result<JsValue, String> {
|
||||
// gloo-serialize crate instead.
|
||||
JsValue::from_serde(&memory).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Get a coredump.
|
||||
#[wasm_bindgen]
|
||||
pub async fn coredump(core_dump_manager: kcl_lib::coredump::wasm::CoreDumpManager) -> Result<JsValue, String> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let core_dumper = kcl_lib::coredump::wasm::CoreDumper::new(core_dump_manager);
|
||||
let dump = core_dumper.dump().await.map_err(|e| e.to_string())?;
|
||||
|
||||
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
|
||||
// gloo-serialize crate instead.
|
||||
JsValue::from_serde(&dump).map_err(|e| e.to_string())
|
||||
}
|
||||
|
43
yarn.lock
43
yarn.lock
@ -2111,7 +2111,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.4.tgz#7688950f6e03f38b3bac73585f8f4cdd61be6aa6"
|
||||
integrity sha512-Nxtj28NYUo5iwYkpYslxmOPkdI2WkELU2e3UH9nbJm9Ydki2CQwJVGQxx4EANtdZcMNsEsUzRqaDTvEUYH1l6w==
|
||||
|
||||
"@tauri-apps/api@^2.0.0-beta.7":
|
||||
"@tauri-apps/api@2.0.0-beta.7":
|
||||
version "2.0.0-beta.7"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.7.tgz#a80a3ffc24e6ec8dbdc8131dc3e68d8f4342293d"
|
||||
integrity sha512-cM7SJQP4DBkLLMOdybLFYUURWn/tng2FEdAnXlu42f3NhFxKL4KVeeQTkuwlgC7ePwwwvDSqiXGiF+dKOadY7w==
|
||||
@ -2515,6 +2515,11 @@
|
||||
fflate "~0.6.10"
|
||||
meshoptimizer "~0.18.1"
|
||||
|
||||
"@types/ua-parser-js@^0.7.39":
|
||||
version "0.7.39"
|
||||
resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz#832c58e460c9435e4e34bb866e85e9146e12cdbb"
|
||||
integrity sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==
|
||||
|
||||
"@types/uuid@^9.0.8":
|
||||
version "9.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba"
|
||||
@ -8164,7 +8169,16 @@ string-natural-compare@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@ -8237,7 +8251,14 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@ -8646,6 +8667,11 @@ ua-parser-js@^1.0.35:
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.35.tgz#c4ef44343bc3db0a3cbefdf21822f1b1fc1ab011"
|
||||
integrity sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==
|
||||
|
||||
ua-parser-js@^1.0.37:
|
||||
version "1.0.37"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f"
|
||||
integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==
|
||||
|
||||
ufo@^1.1.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.2.0.tgz#28d127a087a46729133fdc89cb1358508b3f80ba"
|
||||
@ -9163,7 +9189,7 @@ workerpool@6.2.1:
|
||||
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
|
||||
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@ -9181,6 +9207,15 @@ wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
Reference in New Issue
Block a user