nuxt-devtools-observatory 0.1.31 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -46
- package/client/.env.example +1 -0
- package/client/dist/assets/index-BqKYgjVB.js +20 -0
- package/client/dist/assets/index-bs1JBJ2u.css +1 -0
- package/client/dist/index.html +2 -2
- package/client/src/App.vue +4 -0
- package/client/src/components/Flamegraph.vue +7 -8
- package/client/src/components/SpanInspector.vue +1 -1
- package/client/src/components/TraceFilter.vue +0 -2
- package/client/src/components/WaterfallView.vue +1 -1
- package/client/src/composables/composable-search.ts +127 -0
- package/client/src/composables/trace-render-aggregation.ts +263 -0
- package/client/src/composables/useExportImport.ts +63 -0
- package/client/src/composables/useTraceFilter.ts +1 -5
- package/client/src/composables/useVirtualizationConfig.ts +40 -0
- package/client/src/composables/useVirtualizationFlags.ts +129 -0
- package/client/src/stores/observatory.ts +9 -1
- package/client/src/views/ComposableTracker.vue +273 -97
- package/client/src/views/FetchDashboard.vue +181 -16
- package/client/src/views/ProvideInjectGraph.vue +41 -18
- package/client/src/views/RenderHeatmap.vue +392 -76
- package/client/src/views/TraceViewer.vue +797 -14
- package/client/src/views/TransitionTimeline.vue +112 -19
- package/dist/module.d.mts +5 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +12 -23
- package/dist/runtime/composables/composable-registry.d.ts +19 -0
- package/dist/runtime/composables/composable-registry.js +63 -5
- package/dist/runtime/composables/render-registry.js +23 -13
- package/dist/runtime/instrumentation/asyncData.d.ts +1 -1
- package/dist/runtime/instrumentation/fetch.d.ts +7 -1
- package/dist/runtime/instrumentation/fetch.js +22 -1
- package/dist/runtime/nitro/fetch-capture.d.ts +1 -2
- package/dist/runtime/nitro/fetch-capture.js +85 -7
- package/dist/runtime/nitro/ssr-trace-store.d.ts +85 -0
- package/dist/runtime/nitro/ssr-trace-store.js +84 -0
- package/dist/runtime/plugin.js +48 -1
- package/dist/runtime/test-bridge.d.ts +18 -0
- package/dist/runtime/test-bridge.js +86 -0
- package/dist/runtime/tracing/trace.d.ts +1 -1
- package/package.json +18 -3
- package/client/.env +0 -17
- package/client/dist/assets/index-BuMXDBO9.js +0 -17
- package/client/dist/assets/index-CwcspZ6w.css +0 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface ObservatoryExportFile<T> {
|
|
2
|
+
type: 'observatory-traces' | 'observatory-renders'
|
|
3
|
+
version: '1'
|
|
4
|
+
exportedAt: number
|
|
5
|
+
count: number
|
|
6
|
+
data: T[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function exportJson(filename: string, envelope: ObservatoryExportFile<unknown>): void {
|
|
10
|
+
const json = JSON.stringify(envelope, null, 2)
|
|
11
|
+
const blob = new Blob([json], { type: 'application/json' })
|
|
12
|
+
const url = URL.createObjectURL(blob)
|
|
13
|
+
const anchor = document.createElement('a')
|
|
14
|
+
anchor.href = url
|
|
15
|
+
anchor.download = filename
|
|
16
|
+
anchor.click()
|
|
17
|
+
URL.revokeObjectURL(url)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function importJson(): Promise<unknown> {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const input = document.createElement('input')
|
|
23
|
+
input.type = 'file'
|
|
24
|
+
input.accept = '.json,application/json'
|
|
25
|
+
|
|
26
|
+
input.addEventListener('change', () => {
|
|
27
|
+
const file = input.files?.[0]
|
|
28
|
+
|
|
29
|
+
if (!file) {
|
|
30
|
+
reject(new Error('No file selected'))
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const reader = new FileReader()
|
|
35
|
+
|
|
36
|
+
reader.addEventListener('load', () => {
|
|
37
|
+
try {
|
|
38
|
+
resolve(JSON.parse(reader.result as string))
|
|
39
|
+
} catch {
|
|
40
|
+
reject(new Error('Invalid JSON file'))
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
reader.addEventListener('error', () => reject(new Error('Failed to read file')))
|
|
45
|
+
reader.readAsText(file)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Reject if the dialog is cancelled (focus returns without a change event)
|
|
49
|
+
window.addEventListener(
|
|
50
|
+
'focus',
|
|
51
|
+
() => {
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
if (!input.files?.length) {
|
|
54
|
+
reject(new Error('cancelled'))
|
|
55
|
+
}
|
|
56
|
+
}, 300)
|
|
57
|
+
},
|
|
58
|
+
{ once: true }
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
input.click()
|
|
62
|
+
})
|
|
63
|
+
}
|
|
@@ -65,11 +65,7 @@ export function useTraceFilter() {
|
|
|
65
65
|
return false
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function matchesDurationFilter(
|
|
69
|
-
trace: TraceEntry,
|
|
70
|
-
min: number,
|
|
71
|
-
max: number
|
|
72
|
-
): boolean {
|
|
68
|
+
function matchesDurationFilter(trace: TraceEntry, min: number, max: number): boolean {
|
|
73
69
|
const hasExplicitDurationFilter = min > 0 || max < Infinity
|
|
74
70
|
|
|
75
71
|
if (trace.durationMs === undefined) {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { computed, ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
const DEFAULT_ROW_HEIGHT = 34
|
|
4
|
+
const DEFAULT_OVERSCAN = 10
|
|
5
|
+
const MIN_OVERSCAN = 2
|
|
6
|
+
const MAX_OVERSCAN = 40
|
|
7
|
+
|
|
8
|
+
function clampOverscan(value: number): number {
|
|
9
|
+
if (!Number.isFinite(value)) {
|
|
10
|
+
return DEFAULT_OVERSCAN
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return Math.max(MIN_OVERSCAN, Math.min(MAX_OVERSCAN, Math.round(value)))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type VirtualizationPreset = {
|
|
17
|
+
rowHeight: number
|
|
18
|
+
overscan: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useVirtualizationConfig(initial?: Partial<VirtualizationPreset>) {
|
|
22
|
+
const rowHeight = ref(initial?.rowHeight ?? DEFAULT_ROW_HEIGHT)
|
|
23
|
+
const overscan = ref(clampOverscan(initial?.overscan ?? DEFAULT_OVERSCAN))
|
|
24
|
+
|
|
25
|
+
const preset = computed<VirtualizationPreset>(() => ({
|
|
26
|
+
rowHeight: Math.max(20, rowHeight.value),
|
|
27
|
+
overscan: clampOverscan(overscan.value),
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
rowHeight,
|
|
32
|
+
overscan,
|
|
33
|
+
preset,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const VIRTUALIZATION_DEFAULTS = {
|
|
38
|
+
rowHeight: DEFAULT_ROW_HEIGHT,
|
|
39
|
+
overscan: DEFAULT_OVERSCAN,
|
|
40
|
+
} as const
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { computed, ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
type VirtualizationScreen = 'heatmap' | 'traces' | 'composables' | 'fetch' | 'transitions'
|
|
4
|
+
|
|
5
|
+
type VirtualizationFlags = {
|
|
6
|
+
enabled: boolean
|
|
7
|
+
heatmap: boolean
|
|
8
|
+
traces: boolean
|
|
9
|
+
composables: boolean
|
|
10
|
+
fetch: boolean
|
|
11
|
+
transitions: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const defaultFlags: VirtualizationFlags = {
|
|
15
|
+
enabled: false,
|
|
16
|
+
heatmap: false,
|
|
17
|
+
traces: false,
|
|
18
|
+
composables: false,
|
|
19
|
+
fetch: false,
|
|
20
|
+
transitions: false,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const flags = ref<VirtualizationFlags>({ ...defaultFlags })
|
|
24
|
+
|
|
25
|
+
let initialized = false
|
|
26
|
+
|
|
27
|
+
function parseBooleanParam(value: string | null): boolean | null {
|
|
28
|
+
if (value == null) {
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (value === '1' || value === 'true' || value === 'on') {
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (value === '0' || value === 'false' || value === 'off') {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readFromStorage(): VirtualizationFlags {
|
|
44
|
+
return { ...defaultFlags }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function applyQueryOverrides(current: VirtualizationFlags): VirtualizationFlags {
|
|
48
|
+
if (typeof window === 'undefined') {
|
|
49
|
+
return current
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const params = new URLSearchParams(window.location.search)
|
|
53
|
+
const globalParam = parseBooleanParam(params.get('virt'))
|
|
54
|
+
|
|
55
|
+
const next = { ...current }
|
|
56
|
+
|
|
57
|
+
if (globalParam != null) {
|
|
58
|
+
next.enabled = globalParam
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const perScreen: Array<[VirtualizationScreen, string]> = [
|
|
62
|
+
['heatmap', 'virtHeatmap'],
|
|
63
|
+
['traces', 'virtTraces'],
|
|
64
|
+
['composables', 'virtComposables'],
|
|
65
|
+
['fetch', 'virtFetch'],
|
|
66
|
+
['transitions', 'virtTransitions'],
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
for (const [key, paramName] of perScreen) {
|
|
70
|
+
const override = parseBooleanParam(params.get(paramName))
|
|
71
|
+
|
|
72
|
+
if (override != null) {
|
|
73
|
+
next[key] = override
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return next
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function init() {
|
|
81
|
+
if (initialized) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
initialized = true
|
|
86
|
+
const stored = readFromStorage()
|
|
87
|
+
const merged = applyQueryOverrides(stored)
|
|
88
|
+
flags.value = merged
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function setAllEnabled(value: boolean) {
|
|
92
|
+
const next = {
|
|
93
|
+
...flags.value,
|
|
94
|
+
enabled: value,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
flags.value = next
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function setScreenEnabled(screen: VirtualizationScreen, value: boolean) {
|
|
101
|
+
const next = {
|
|
102
|
+
...flags.value,
|
|
103
|
+
[screen]: value,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
flags.value = next
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function useVirtualizationFlags() {
|
|
110
|
+
init()
|
|
111
|
+
|
|
112
|
+
const effective = computed(() => ({
|
|
113
|
+
enabled: flags.value.enabled,
|
|
114
|
+
heatmap: flags.value.enabled && flags.value.heatmap,
|
|
115
|
+
traces: flags.value.enabled && flags.value.traces,
|
|
116
|
+
composables: flags.value.enabled && flags.value.composables,
|
|
117
|
+
fetch: flags.value.enabled && flags.value.fetch,
|
|
118
|
+
transitions: flags.value.enabled && flags.value.transitions,
|
|
119
|
+
}))
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
flags,
|
|
123
|
+
effective,
|
|
124
|
+
setAllEnabled,
|
|
125
|
+
setScreenEnabled,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type { VirtualizationFlags, VirtualizationScreen }
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { ref } from 'vue'
|
|
2
2
|
import { useDevtoolsClient, onDevtoolsClientConnected } from '@nuxt/devtools-kit/iframe-client'
|
|
3
3
|
import type { ObservatorySnapshot, ObservatoryServerFunctions, ObservatoryClientFunctions } from '@observatory/types/rpc'
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
FetchEntry,
|
|
6
|
+
ProvideEntry,
|
|
7
|
+
InjectEntry,
|
|
8
|
+
ComposableEntry,
|
|
9
|
+
RenderEntry,
|
|
10
|
+
TransitionEntry,
|
|
11
|
+
TraceEntry,
|
|
12
|
+
} from '@observatory/types/snapshot'
|
|
5
13
|
|
|
6
14
|
type ProvideInjectSnapshot = { provides: ProvideEntry[]; injects: InjectEntry[] }
|
|
7
15
|
|