GeezerVison is showing github.com — Show the UDF files in the profiler · feldera/feldera@bc20408
File treeExpand file treeCollapse file tree Open diff view settings web-console/src/routes/(system)/(authenticated)/(authorized)/profile-viewer
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: js-packages/profiler-app/src/App.svelte +5-4Lines changed: 5 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 6 | 6 | type GlobalMetrics, | 7 | 7 | getSuitableProfiles, | 8 | 8 | processProfileFiles, | | 9 | + type SourceFile, | 9 | 10 | SupportBundleViewerLayout, | 10 | 11 | type ZipItem | 11 | 12 | } from 'profiler-layout' | | 24 | 25 | let profileData: { | 25 | 26 | profile: JsonProfiles | undefined | 26 | 27 | dataflow: Dataflow | undefined | 27 | | - sources: string[] | undefined | 28 | 28 | logText: string | undefined | 29 | 29 | globalMetrics: GlobalMetrics | undefined | 30 | 30 | connectorStatus: ConnectorStatus | undefined | 31 | 31 | runtimeConfig: unknown | 32 | 32 | pipelineEvents: PipelineMonitorEvent[] | undefined | | 33 | + sourceFiles: SourceFile[] | undefined | 33 | 34 | } | null = $state(null) | 34 | 35 |
| 35 | 36 | let fileInput: HTMLInputElement | null = $state(null) | | 50 | 51 | profileData = { | 51 | 52 | profile: data.profile, | 52 | 53 | dataflow: data.dataflow, | 53 | | - sources: data.sources, | 54 | 54 | logText: data.logText, | 55 | 55 | globalMetrics: data.globalMetrics, | 56 | 56 | connectorStatus: data.connectorStatus, | 57 | 57 | runtimeConfig: data.runtimeConfig, | 58 | | - pipelineEvents: data.pipelineEvents | | 58 | + pipelineEvents: data.pipelineEvents, | | 59 | + sourceFiles: data.sourceFiles | 59 | 60 | } | 60 | 61 | } | 61 | 62 |
| | 120 | 121 | <SupportBundleViewerLayout | 121 | 122 | profileData={profileData.profile} | 122 | 123 | dataflowData={profileData.dataflow} | 123 | | - programCode={profileData.sources} | 124 | 124 | logText={profileData.logText} | 125 | 125 | globalMetrics={profileData.globalMetrics} | 126 | 126 | connectorStatus={profileData.connectorStatus} | 127 | 127 | runtimeConfig={profileData.runtimeConfig} | 128 | 128 | pipelineEvents={profileData.pipelineEvents} | | 129 | + sourceFiles={profileData.sourceFiles} | 129 | 130 | triageResults={new TriageResults()} | 130 | 131 | {profileFiles} | 131 | 132 | {selectedTimestamp} | |
Collapse file Copy file name to clipboardExpand all lines: js-packages/profiler-app/vite.config.ts +4-5Lines changed: 4 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -12,14 +12,13 @@ export default defineConfig({ | 12 | 12 | tailwindcss(), | 13 | 13 | svelte(), | 14 | 14 | svg(), | 15 | | - // Trim monaco to the bare minimum needed for read-only SQL viewing, and | 16 | | - // inline the workers so `viteSingleFile` produces a true self-contained bundle. | 17 | | - // The SqlCodeView (consumed via profiler-layout) renders SQL with line numbers, | 18 | | - // range highlighting via decorations, and a small lookup-cycling search. | | 15 | + // Bundle only the Monaco languages and features that the read-only SourceCodeView of | | 16 | + // profiler-layout uses, and inline the workers so that `viteSingleFile` makes one | | 17 | + // self-contained file. SourceCodeView shows TOML with the `graphql` grammar. | 19 | 18 | // The plugin neuters `vs/language/<x>/` dynamic imports for any language not | 20 | 19 | // listed here, so MonacoEditor's optional JSON-schema field costs nothing. | 21 | 20 | monaco({ | 22 | | - languages: ['sql'], | | 21 | + languages: ['sql', 'rust', 'graphql'], | 23 | 22 | features: ['browser', 'clipboard', 'find', 'wordHighlighter'], | 24 | 23 | inlineWorkers: true | 25 | 24 | }), | |
Collapse file Copy file name to clipboardExpand all lines: js-packages/profiler-layout/README.md +1-1Lines changed: 1 addition & 1 deletion - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -28,7 +28,7 @@ Runthe build to test changes in consuming apps. | 28 | 28 | - **`ProfilerDiagram`** - Circuit graph renderer | 29 | 29 | - **`ProfilerTooltip`** - Node metrics details display | 30 | 30 | - **`ProfileTimestampSelector`** - Timestamp navigation dropdown | 31 | | -- **`SqlCodeView`**, **`BundleLogsView`**, **`TriageResultsView`** - The panels the layout hosts | | 31 | +- **`SourceCodeView`**, **`BundleLogsView`**, **`TriageResultsView`** - The panels the layout hosts | 32 | 32 |
| 33 | 33 | ## Exported Utilities | 34 | 34 |
| |
Collapse file Copy file name to clipboardExpand all lines: js-packages/profiler-layout/src/lib/components/SourceCodeView.svelte +13-10Lines changed: 13 additions & 10 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 14 | 14 | import { MonacoEditor, setSelections } from 'common-ui' | 15 | 15 | import * as monaco from 'monaco-editor' | 16 | 16 | import type { SourcePositionRange } from 'profiler-lib' | | 17 | + import { untrack } from 'svelte' | | 18 | + import type { SourceFile } from '../functions/processZipBundle' | 17 | 19 |
| 18 | 20 | interface Props { | 19 | | - code: string | | 21 | + /** The view reads `name` and `language` once, at mount. `name` is the URI of the Monaco | | 22 | + * model, so two mounted views must show different files. */ | | 23 | + file: SourceFile | 20 | 24 | highlightRanges?: SourcePositionRange[] | 21 | 25 | } | 22 | 26 |
| 23 | | - let { code, highlightRanges = [] }: Props = $props() | | 27 | + let { file, highlightRanges = [] }: Props = $props() | 24 | 28 |
| 25 | 29 | let editor: monaco.editor.IStandaloneCodeEditor | undefined = $state() | 26 | 30 | let isDarkMode = $state(false) | | 34 | 38 | return () => observer.disconnect() | 35 | 39 | }) | 36 | 40 |
| 37 | | - // This component is mounted once (hoisted into a <PersistentContent> overlay by | 38 | | - // SupportBundleViewerLayout), so it owns a single model on a fixed URI. The model's contents are | 39 | | - // refreshed whenever `code` changes — including when a new support bundle loads a different | 40 | | - // program — and the model is disposed when the component unmounts. | 41 | | - const modelUri = monaco.Uri.parse('inmemory://profiler/program.sql') | 42 | | - const model = monaco.editor.createModel('', 'sql', modelUri) | | 41 | + // The view updates its model when `file.code` changes, for example when a new bundle loads. It | | 42 | + // disposes the model when it unmounts. | | 43 | + const { name, language } = untrack(() => file) | | 44 | + const modelUri = monaco.Uri.parse(`inmemory://profiler/${name}`) | | 45 | + const model = monaco.editor.createModel('', language, modelUri) | 43 | 46 |
| 44 | 47 | $effect(() => { | 45 | | - if (model.getValue() !== code) { | 46 | | - model.setValue(code) | | 48 | + if (model.getValue() !== file.code) { | | 49 | + model.setValue(file.code) | 47 | 50 | } | 48 | 51 | }) | 49 | 52 |
| |
Collapse file Copy file name to clipboard +41Lines changed: 41 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +import * as monaco from 'monaco-editor' | | 2 | +import { describe, expect, it } from 'vitest' | | 3 | +import { render } from 'vitest-browser-svelte' | | 4 | +import SourceCodeView from './SourceCodeView.svelte' | | 5 | + | | 6 | +const modelOf = (name: string) => | | 7 | + monaco.editor.getModel(monaco.Uri.parse(`inmemory://profiler/${name}`)) | | 8 | + | | 9 | +describe('SourceCodeView', () => { | | 10 | + it('shows a file in the language of the file', () => { | | 11 | + const view = render(SourceCodeView, { | | 12 | + file: { name: 'udf.rs', language: 'rust', code: 'pub fn f() {}' } | | 13 | + }) | | 14 | + expect(modelOf('udf.rs')?.getLanguageId()).toBe('rust') | | 15 | + expect(modelOf('udf.rs')?.getValue()).toBe('pub fn f() {}') | | 16 | + view.unmount() | | 17 | + expect(modelOf('udf.rs')).toBeNull() | | 18 | + }) | | 19 | + | | 20 | + it('shows the new code when the file changes', async () => { | | 21 | + const file = { name: 'udf.rs', language: 'rust', code: 'pub fn f() {}' } as const | | 22 | + const view = render(SourceCodeView, { file }) | | 23 | + await view.rerender({ file: { ...file, code: 'pub fn g() {}' } }) | | 24 | + expect(modelOf('udf.rs')?.getValue()).toBe('pub fn g() {}') | | 25 | + view.unmount() | | 26 | + }) | | 27 | + | | 28 | + it('gives each file its own model, so two files can be mounted at the same time', () => { | | 29 | + // A tab switch can mount the next file before it unmounts the previous file. | | 30 | + const sql = render(SourceCodeView, { | | 31 | + file: { name: 'program.sql', language: 'sql', code: 'SELECT 1;' } | | 32 | + }) | | 33 | + const udf = render(SourceCodeView, { | | 34 | + file: { name: 'udf.rs', language: 'rust', code: 'pub fn f() {}' } | | 35 | + }) | | 36 | + expect(modelOf('program.sql')?.getValue()).toBe('SELECT 1;') | | 37 | + expect(modelOf('udf.rs')?.getValue()).toBe('pub fn f() {}') | | 38 | + sql.unmount() | | 39 | + udf.unmount() | | 40 | + }) | | 41 | +}) |
|