GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Show connector errors in the profiler · feldera/feldera@ece8323

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • js-packages
    • common-ui
      • README.md
      • src/lib
        • ConnectorErrors.svelte
        • connectorMetrics.ts
        • index.ts
    • profiler-layout/src/lib
      • components
        • ConnectorErrors.svelte.spec.ts
        • SupportBundleViewerLayout.svelte.spec.ts
        • SupportBundleViewerLayout.svelte
        • tabs
          • ConnectorsTab.svelte.spec.ts
          • ConnectorsTab.svelte
          • MetricsTab.svelte
      • functions
        • countConnectorErrors.spec.ts
    • web-console/src/lib/components/pipelines/editor/performance
      • ConnectorErrors.svelte.spec.ts
      • ConnectorErrors.svelte
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎js-packages/common-ui/README.md‎

Copy file name to clipboardExpand all lines: js-packages/common-ui/README.md
+1-1Lines changed: 1 addition & 1 deletion
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { SegmentedControl, TabsPanel, PersistentContent, MonacoEditor } from 'co
2121
| Layout | `Drawer` shows a resizable side panel on wide screens (`InlineDrawer`) and an overlay on tablets and phones (`OverlayDrawer`). `InlineDropdown` toggles content under a header. `useIsMobile.svelte` has the screen-size media queries. |
2222
| Layout helpers | `PersistentContent` and `persistentRect.svelte` for content that must survive conditional re-renders (see those files for details). `ANSIDecoratedText` renders ANSI-colored log output. |
2323
| Buttons | `ClipboardCopyButton`. `ClickFeedback` shows a short confirmation state after a click. |
24-
| Pipeline views | `ConnectorMetricsTables` shows the connector statistics of each table and view. Use `aggregateConnectorMetrics` from `common-ui/connectorMetrics` to make its input from a `/stats` response. |
24+
| Pipeline views | `ConnectorMetricsTables` shows the connector statistics of each table and view. Use `aggregateConnectorMetrics` from `common-ui/connectorMetrics` to make its input from a `/stats` response. `ConnectorErrors` shows the error lists of one connector. The caller passes the connector status, from the API or from a support bundle. |
2525
| Health views | `StatusTimeline`, `EventLogList` and `HealthEventList` show a status history, its incidents and the events of one incident. `PipelineEventHistory` combines them for the monitor events of a pipeline, from the API or from a support bundle. |
2626

2727
Plain TypeScript modules that only the web console and the profiler use also
Collapse file

‎js-packages/common-ui/src/lib/ConnectorErrors.svelte‎

Copy file name to clipboard
+191Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<script lang="ts">
2+
import { formatDateTime } from 'common-lib/format'
3+
import Dayjs from 'dayjs'
4+
import { fade, slide } from 'svelte/transition'
5+
import type {
6+
ConnectorError,
7+
ConnectorErrorFilter,
8+
InputEndpointStatus,
9+
OutputEndpointStatus
10+
} from './connectorMetrics'
11+
import { getCaseDependentName } from './felderaRelation'
12+
import InlineDropdown from './InlineDropdown.svelte'
13+
import SegmentedControl from './SegmentedControl.svelte'
14+
import Tooltip from './Tooltip.svelte'
15+
16+
const inputFilterOptions = ['all', 'parse', 'transport'] as const
17+
const outputFilterOptions = ['all', 'encode', 'transport'] as const
18+
19+
let filterOptionLabels: Record<string, string> = {
20+
transport: 'Transport',
21+
parse: 'Parse',
22+
encode: 'Encode',
23+
all: 'All'
24+
}
25+
26+
let tagsFilter: ConnectorErrorFilter = $state('all')
27+
28+
let {
29+
relationName,
30+
connectorName,
31+
direction,
32+
filter = 'all',
33+
status,
34+
loading = false,
35+
onClose
36+
}: {
37+
relationName: string
38+
/** Endpoint name, prefixed with the relation name. */
39+
connectorName: string
40+
direction: 'input' | 'output'
41+
filter?: ConnectorErrorFilter
42+
/** The connector status with its error lists. `null` while it is not available. */
43+
status: InputEndpointStatus | OutputEndpointStatus | null
44+
loading?: boolean
45+
onClose: () => void
46+
} = $props()
47+
48+
const filterOptions = $derived(direction === 'input' ? inputFilterOptions : outputFilterOptions)
49+
const filterItems = $derived(
50+
filterOptions.map((value) => ({ value, label: filterOptionLabels[value] ?? value }))
51+
)
52+
53+
$effect(() => {
54+
tagsFilter = filter
55+
})
56+
57+
const strippedConnectorName = $derived(
58+
connectorName.slice(getCaseDependentName(relationName).name.length + 1)
59+
)
60+
61+
type DisplayItem = { kind: 'error'; error: ConnectorError } | { kind: 'gap'; count: number }
62+
63+
function withGaps(errors: ConnectorError[]): DisplayItem[] {
64+
const byTag = new Map<string, ConnectorError[]>()
65+
for (const e of errors) {
66+
const k = e.tag ?? 'other'
67+
const existing = byTag.get(k)
68+
if (existing) {
69+
existing.push(e)
70+
} else {
71+
byTag.set(k, [e])
72+
}
73+
}
74+
const result: DisplayItem[] = []
75+
for (const tagErrors of byTag.values()) {
76+
const sorted = [...tagErrors].sort((a, b) => a.index - b.index)
77+
for (let i = 0; i < sorted.length; i++) {
78+
if (i > 0) {
79+
const gap = sorted[i].index - sorted[i - 1].index - 1
80+
if (gap > 0) {
81+
result.push({ kind: 'gap', count: gap })
82+
}
83+
}
84+
result.push({ kind: 'error', error: sorted[i] })
85+
}
86+
}
87+
return result
88+
}
89+
90+
function getErrorArrays(
91+
s: InputEndpointStatus | OutputEndpointStatus
92+
): Record<string, ConnectorError[]> {
93+
if (direction === 'input') {
94+
return {
95+
parse: (s as InputEndpointStatus).parse_errors ?? [],
96+
transport: s.transport_errors ?? []
97+
}
98+
}
99+
return {
100+
encode: (s as OutputEndpointStatus).encode_errors ?? [],
101+
transport: s.transport_errors ?? []
102+
}
103+
}
104+
105+
const displayItems = $derived.by(() => {
106+
if (!status) {
107+
return [] as DisplayItem[]
108+
}
109+
const arrays = getErrorArrays(status)
110+
const combined =
111+
tagsFilter === 'all' ? Object.values(arrays).flat() : (arrays[tagsFilter] ?? [])
112+
return withGaps(combined).reverse()
113+
})
114+
</script>
115+
116+
<div class="bg-white-dark flex h-full flex-col gap-2 rounded px-4 pt-4">
117+
<div class="flex items-start justify-between">
118+
<div>
119+
<div class="font-medium">{relationName} · {strippedConnectorName}</div>
120+
</div>
121+
<button class="fd fd-x text-[20px]" onclick={onClose} aria-label="Close"></button>
122+
</div>
123+
<div class="flex flex-nowrap items-center gap-2">
124+
<SegmentedControl
125+
value={tagsFilter}
126+
onValueChange={(v) => (tagsFilter = v as ConnectorErrorFilter)}
127+
items={filterItems}
128+
class="-mt-2"
129+
/>
130+
<span class="text-surface-500">errors</span>
131+
</div>
132+
<div class="-mr-4 scrollbar flex-1 overflow-y-auto pr-4">
133+
{#if loading}
134+
<div class="p-2 text-surface-500">Loading…</div>
135+
{:else if displayItems.length === 0}
136+
<div class="p-2 text-surface-500">
137+
{#if tagsFilter === 'all'}
138+
No errors
139+
{:else}
140+
No errors of this type
141+
{/if}
142+
</div>
143+
{:else}
144+
{#each displayItems as item}
145+
{#if item.kind === 'gap'}
146+
<div class="px-2 py-1 text-center text-surface-500 italic">
147+
{#if item.count !== 1}
148+
{item.count} other errors skipped
149+
{:else}
150+
{item.count} other error skipped
151+
{/if}
152+
</div>
153+
{:else}
154+
<InlineDropdown>
155+
{#snippet header(isOpen, toggle)}
156+
<div
157+
class="flex w-full cursor-pointer items-center gap-2 py-2 pr-2"
158+
onclick={toggle}
159+
role="presentation"
160+
>
161+
<div
162+
class="fd fd-chevron-down text-[20px] transition-transform {isOpen
163+
? 'rotate-180'
164+
: ''}"
165+
></div>
166+
<span class="flex-1 overflow-hidden text-nowrap text-ellipsis">
167+
{#if !isOpen}
168+
<span out:fade={{ duration: 100 }}>{item.error.message}</span>
169+
{/if}
170+
</span>
171+
{#if status?.fatal_error != null && item.error.message === status.fatal_error}
172+
<span class="fd fd-circle-x text-error-500"></span>
173+
{/if}
174+
<span class="ml-auto">{Dayjs(item.error.timestamp).format('HH:mm:ss')}</span>
175+
<Tooltip placement="top">{formatDateTime(Dayjs(item.error.timestamp))}</Tooltip>
176+
</div>
177+
{/snippet}
178+
{#snippet content()}
179+
<div
180+
class="mr-16 -mb-7 ml-7 -translate-y-7 pb-2"
181+
transition:slide={{ duration: 150 }}
182+
>
183+
{item.error.message}
184+
</div>
185+
{/snippet}
186+
</InlineDropdown>
187+
{/if}
188+
{/each}
189+
{/if}
190+
</div>
191+
</div>
Collapse file

‎js-packages/common-ui/src/lib/connectorMetrics.ts‎

Copy file name to clipboardExpand all lines: js-packages/common-ui/src/lib/connectorMetrics.ts
+30Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ export type OutputEndpointMetrics = {
3939
transmitted_records: number
4040
}
4141

42+
/** An error reported by a connector. */
43+
export type ConnectorError = {
44+
/** Sequence number of the error. A jump between two errors of a tag means skipped errors. */
45+
index: number
46+
message: string
47+
tag?: string | null
48+
/** RFC3339 timestamp */
49+
timestamp: string
50+
}
51+
4252
export type InputEndpointStatus = {
4353
endpoint_name: string
4454
config: { stream: string }
@@ -47,6 +57,9 @@ export type InputEndpointStatus = {
4757
barrier: boolean
4858
health?: ConnectorHealth | null
4959
fatal_error?: string | null
60+
/** The most recent errors. Absent when the status was collected without connector errors. */
61+
parse_errors?: ConnectorError[] | null
62+
transport_errors?: ConnectorError[] | null
5063
}
5164

5265
export type OutputEndpointStatus = {
@@ -57,6 +70,9 @@ export type OutputEndpointStatus = {
5770
paused?: boolean
5871
health?: ConnectorHealth | null
5972
fatal_error?: string | null
73+
/** The most recent errors. Absent when the status was collected without connector errors. */
74+
encode_errors?: ConnectorError[] | null
75+
transport_errors?: ConnectorError[] | null
6076
}
6177

6278
/** The part of a `/stats` response that `aggregateConnectorMetrics` reads. */
@@ -100,6 +116,20 @@ export type ConnectorMetrics = {
100116
views: Map<string, AggregatedOutputEndpointMetrics>
101117
}
102118

119+
/** Sum of the error counters that the connector tables show, over all connectors. */
120+
export const countConnectorErrors = ({ tables, views }: ConnectorMetrics): number => {
121+
let count = 0
122+
for (const { aggregate } of tables.values()) {
123+
count +=
124+
(aggregate.metrics.num_parse_errors ?? 0) + (aggregate.metrics.num_transport_errors ?? 0)
125+
}
126+
for (const { aggregate } of views.values()) {
127+
count +=
128+
(aggregate.metrics.num_encode_errors ?? 0) + (aggregate.metrics.num_transport_errors ?? 0)
129+
}
130+
return count
131+
}
132+
103133
/**
104134
* Higher of two connector latencies, ignoring connectors without samples.
105135
*
Collapse file

‎js-packages/common-ui/src/lib/index.ts‎

Copy file name to clipboardExpand all lines: js-packages/common-ui/src/lib/index.ts
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export { default as ANSIDecoratedText } from './ANSIDecoratedText.svelte'
33
export { default as ClickFeedback } from './ClickFeedback.svelte'
44
export { default as ClipboardCopyButton } from './ClipboardCopyButton.svelte'
55
export { default as ConnectorMetricsTables } from './ConnectorMetricsTables.svelte'
6+
export { default as ConnectorErrors } from './ConnectorErrors.svelte'
67
export { default as Drawer } from './Drawer.svelte'
78
export { default as EventLogList } from './EventLogList.svelte'
89
export { default as HealthEventList } from './HealthEventList.svelte'

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.