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:
Jess Frazelle
2024-04-09 18:05:36 -07:00
committed by GitHub
parent d18e35b7ea
commit 959433e357
16 changed files with 535 additions and 11 deletions

View File

@ -16,7 +16,7 @@
"@open-rpc/client-js": "^1.8.1", "@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6", "@react-hook/resize-observer": "^1.2.6",
"@replit/codemirror-interact": "^6.3.0", "@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-dialog": "^2.0.0-beta.2",
"@tauri-apps/plugin-fs": "^2.0.0-beta.2", "@tauri-apps/plugin-fs": "^2.0.0-beta.2",
"@tauri-apps/plugin-http": "^2.0.0-beta.2", "@tauri-apps/plugin-http": "^2.0.0-beta.2",
@ -57,6 +57,7 @@
"toml": "^3.0.0", "toml": "^3.0.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.4.3", "typescript": "^5.4.3",
"ua-parser-js": "^1.0.37",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"vitest": "^1.4.0", "vitest": "^1.4.0",
"vscode-jsonrpc": "^8.1.0", "vscode-jsonrpc": "^8.1.0",
@ -123,6 +124,7 @@
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/react-modal": "^3.16.3", "@types/react-modal": "^3.16.3",
"@types/three": "^0.160.0", "@types/three": "^0.160.0",
"@types/ua-parser-js": "^0.7.39",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@types/wait-on": "^5.3.4", "@types/wait-on": "^5.3.4",
"@types/wicg-file-system-access": "^2023.10.5", "@types/wicg-file-system-access": "^2023.10.5",

View File

@ -38,7 +38,7 @@ import {
getSketchQuaternion, getSketchQuaternion,
} from 'clientSideScene/sceneEntities' } from 'clientSideScene/sceneEntities'
import { sketchOnExtrudedFace, startSketchOnDefault } from 'lang/modifyAst' 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 { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst'
import { TEST } from 'env' import { TEST } from 'env'
import { exportFromEngine } from 'lib/exportFromEngine' import { exportFromEngine } from 'lib/exportFromEngine'
@ -47,6 +47,8 @@ import toast from 'react-hot-toast'
import { EditorSelection } from '@uiw/react-codemirror' import { EditorSelection } from '@uiw/react-codemirror'
import { Vector3 } from 'three' import { Vector3 } from 'three'
import { quaternionFromUpNForward } from 'clientSideScene/helpers' import { quaternionFromUpNForward } from 'clientSideScene/helpers'
import { CoreDumpManager } from 'lib/coredump'
import { useHotkeys } from 'react-hotkeys-hook'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -76,6 +78,8 @@ export const ModelingMachineProvider = ({
const token = auth?.context?.token const token = auth?.context?.token
const streamRef = useRef<HTMLDivElement>(null) const streamRef = useRef<HTMLDivElement>(null)
useSetupEngineManager(streamRef, token, theme.current) useSetupEngineManager(streamRef, token, theme.current)
const coreDumpManager = new CoreDumpManager(engineCommandManager)
useHotkeys('meta + shift + .', () => coreDump(coreDumpManager, true))
const { const {
isShiftDown, isShiftDown,

View File

@ -69,6 +69,15 @@ type Timeout = ReturnType<typeof setTimeout>
type ClientMetrics = Models['ClientMetrics_type'] 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 Value<T, U> = U extends undefined
? { type: T; value: U } ? { type: T; value: U }
: U extends void : U extends void
@ -234,7 +243,7 @@ class EngineConnection {
private onNewTrack: (track: NewTrackArgs) => void private onNewTrack: (track: NewTrackArgs) => void
// TODO: actual type is ClientMetrics // TODO: actual type is ClientMetrics
private webrtcStatsCollector?: () => Promise<ClientMetrics> public webrtcStatsCollector?: () => Promise<WebRTCClientMetrics>
private engineCommandManager: EngineCommandManager private engineCommandManager: EngineCommandManager
constructor({ constructor({
@ -406,7 +415,7 @@ class EngineConnection {
}, },
} }
this.webrtcStatsCollector = (): Promise<ClientMetrics> => { this.webrtcStatsCollector = (): Promise<WebRTCClientMetrics> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (mediaStream.getVideoTracks().length !== 1) { if (mediaStream.getVideoTracks().length !== 1) {
reject(new Error('too many video tracks to report')) reject(new Error('too many video tracks to report'))
@ -415,7 +424,7 @@ class EngineConnection {
let videoTrack = mediaStream.getVideoTracks()[0] let videoTrack = mediaStream.getVideoTracks()[0]
void this.pc?.getStats(videoTrack).then((videoTrackStats) => { void this.pc?.getStats(videoTrack).then((videoTrackStats) => {
let client_metrics: ClientMetrics = { let client_metrics: WebRTCClientMetrics = {
rtc_frames_decoded: 0, rtc_frames_decoded: 0,
rtc_frames_dropped: 0, rtc_frames_dropped: 0,
rtc_frames_received: 0, rtc_frames_received: 0,
@ -424,6 +433,12 @@ class EngineConnection {
rtc_jitter_sec: 0.0, rtc_jitter_sec: 0.0,
rtc_keyframes_decoded: 0, rtc_keyframes_decoded: 0,
rtc_total_freezes_duration_sec: 0.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 // TODO(paultag): Since we can technically have multiple WebRTC
@ -449,6 +464,13 @@ class EngineConnection {
videoTrackReport.keyFramesDecoded || 0 videoTrackReport.keyFramesDecoded || 0
client_metrics.rtc_total_freezes_duration_sec = client_metrics.rtc_total_freezes_duration_sec =
videoTrackReport.totalFreezesDuration || 0 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') { } else if (videoTrackReport.type === 'transport') {
// videoTrackReport.bytesReceived, // videoTrackReport.bytesReceived,
// videoTrackReport.bytesSent, // videoTrackReport.bytesSent,

View File

@ -10,6 +10,7 @@ import init, {
ServerConfig, ServerConfig,
copilot_lsp_run, copilot_lsp_run,
kcl_lsp_run, kcl_lsp_run,
coredump,
} from '../wasm-lib/pkg/wasm_lib' } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors' import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' 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 { Coords2d } from './std/sketch'
import { fileSystemManager } from 'lang/std/fileSystemManager' import { fileSystemManager } from 'lang/std/fileSystemManager'
import { DEV } from 'env' 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 { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Value } from '../wasm-lib/kcl/bindings/Value' 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. // 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
View 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
View 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')
}
}

View File

@ -37,8 +37,9 @@ import {
shouldShowSettingInput, shouldShowSettingInput,
} from 'lib/settings/settingsUtils' } from 'lib/settings/settingsUtils'
export const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
export const Settings = () => { export const Settings = () => {
const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
const navigate = useNavigate() const navigate = useNavigate()
const close = () => navigate(location.pathname.replace(paths.SETTINGS, '')) const close = () => navigate(location.pathname.replace(paths.SETTINGS, ''))
const location = useLocation() const location = useLocation()

View File

@ -1373,6 +1373,12 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "git_rev"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60884563ea313b5037683cd5d44f1e14e9cb07b08543756242a65887f9cff48e"
[[package]] [[package]]
name = "gloo-utils" name = "gloo-utils"
version = "0.2.0" version = "0.2.0"
@ -1864,7 +1870,9 @@ dependencies = [
"databake", "databake",
"derive-docs", "derive-docs",
"expectorate", "expectorate",
"form_urlencoded",
"futures", "futures",
"git_rev",
"gltf-json", "gltf-json",
"iai", "iai",
"image", "image",

View File

@ -19,7 +19,9 @@ clap = { version = "4.5.4", features = ["cargo", "derive", "env", "unicode"], op
dashmap = "5.5.3" dashmap = "5.5.3"
databake = { version = "0.1.7", features = ["derive"] } databake = { version = "0.1.7", features = ["derive"] }
derive-docs = { version = "0.1.13", path = "../derive-docs" } derive-docs = { version = "0.1.13", path = "../derive-docs" }
form_urlencoded = "1.2.1"
futures = { version = "0.3.30" } futures = { version = "0.3.30" }
git_rev = "0.1.0"
gltf-json = "1.4.0" gltf-json = "1.4.0"
kittycad = { workspace = true } kittycad = { workspace = true }
kittycad-execution-plan-macros = { workspace = true } kittycad-execution-plan-macros = { workspace = true }

View 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())
}
}

View 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,
}

View 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)
}
}

View File

@ -8,6 +8,7 @@ pub use local::FileManager;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[cfg(not(test))] #[cfg(not(test))]
pub mod wasm; pub mod wasm;
use anyhow::Result; use anyhow::Result;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[cfg(not(test))] #[cfg(not(test))]

View File

@ -5,6 +5,7 @@
#![recursion_limit = "1024"] #![recursion_limit = "1024"]
pub mod ast; pub mod ast;
pub mod coredump;
pub mod docs; pub mod docs;
pub mod engine; pub mod engine;
pub mod errors; pub mod errors;

View File

@ -7,7 +7,7 @@ use std::{
use futures::stream::TryStreamExt; use futures::stream::TryStreamExt;
use gloo_utils::format::JsValueSerdeExt; use gloo_utils::format::JsValueSerdeExt;
use kcl_lib::engine::EngineManager; use kcl_lib::{coredump::CoreDump, engine::EngineManager};
use tower_lsp::{LspService, Server}; use tower_lsp::{LspService, Server};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -379,3 +379,16 @@ pub fn program_memory_init() -> Result<JsValue, String> {
// gloo-serialize crate instead. // gloo-serialize crate instead.
JsValue::from_serde(&memory).map_err(|e| e.to_string()) 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())
}

View File

@ -2111,7 +2111,7 @@
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.4.tgz#7688950f6e03f38b3bac73585f8f4cdd61be6aa6" resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.4.tgz#7688950f6e03f38b3bac73585f8f4cdd61be6aa6"
integrity sha512-Nxtj28NYUo5iwYkpYslxmOPkdI2WkELU2e3UH9nbJm9Ydki2CQwJVGQxx4EANtdZcMNsEsUzRqaDTvEUYH1l6w== 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" version "2.0.0-beta.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.7.tgz#a80a3ffc24e6ec8dbdc8131dc3e68d8f4342293d" resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.7.tgz#a80a3ffc24e6ec8dbdc8131dc3e68d8f4342293d"
integrity sha512-cM7SJQP4DBkLLMOdybLFYUURWn/tng2FEdAnXlu42f3NhFxKL4KVeeQTkuwlgC7ePwwwvDSqiXGiF+dKOadY7w== integrity sha512-cM7SJQP4DBkLLMOdybLFYUURWn/tng2FEdAnXlu42f3NhFxKL4KVeeQTkuwlgC7ePwwwvDSqiXGiF+dKOadY7w==
@ -2515,6 +2515,11 @@
fflate "~0.6.10" fflate "~0.6.10"
meshoptimizer "~0.18.1" 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": "@types/uuid@^9.0.8":
version "9.0.8" version "9.0.8"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" 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" resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== 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" version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -8237,7 +8251,14 @@ string_decoder@~1.1.1:
dependencies: dependencies:
safe-buffer "~5.1.0" 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" version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 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" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.35.tgz#c4ef44343bc3db0a3cbefdf21822f1b1fc1ab011"
integrity sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA== 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: ufo@^1.1.2:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.2.0.tgz#28d127a087a46729133fdc89cb1358508b3f80ba" 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" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== 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" version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -9181,6 +9207,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0" string-width "^4.1.0"
strip-ansi "^6.0.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: wrap-ansi@^8.1.0:
version "8.1.0" version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"