Skip to content

Commit 188aae6

Browse files
committed
fix: when showing loglines, make sure to show most recent
1 parent 5b186f8 commit 188aae6

File tree

3 files changed

+71
-18
lines changed

3 files changed

+71
-18
lines changed

Diff for: plugins/plugin-codeflare-dashboard/src/components/Dashboard/index.tsx

+27-13
Original file line numberDiff line numberDiff line change
@@ -215,21 +215,35 @@ export default class Dashboard extends React.PureComponent<Props, State> {
215215
if (!this.events && !this.logLine) {
216216
return <React.Fragment />
217217
} else {
218-
const rows = (this.events || [])
219-
.map(({ line, timestamp }) => {
220-
// the controller (controller/dashboard/utilization/Live)
221-
// leaves a {timestamp} breadcrumb in the raw line text, so
222-
// that we,as the view, can inject a "5m ago" text, while
223-
// preserving the ansi formatting that surrounds the timestamp
224-
const txt = line.replace("{timestamp}", () => this.agos(timestamp))
225-
return <Text key={txt}>{txt}</Text>
226-
})
227-
.concat((this.logLine ? [this.logLine] : []).map((line) => <Text key={line}>{line}</Text>))
218+
const eventRows = (this.events || []).map(({ line, timestamp }) => {
219+
// the controller (controller/dashboard/utilization/Live)
220+
// leaves a {timestamp} breadcrumb in the raw line text, so
221+
// that we,as the view, can inject a "5m ago" text, while
222+
// preserving the ansi formatting that surrounds the timestamp
223+
const txt = line.replace("{timestamp}", () => this.agos(timestamp))
224+
return <Text key={txt}>{txt}</Text>
225+
})
226+
227+
const logLineRows = (this.logLine ? this.logLine : []).map((line) => <Text key={line}>{line}</Text>)
228228

229229
return (
230-
<Box marginTop={1} flexDirection="column">
231-
{rows}
232-
</Box>
230+
<React.Fragment>
231+
{eventRows.length === 0 ? (
232+
<React.Fragment />
233+
) : (
234+
<Box marginTop={1} flexDirection="column">
235+
{eventRows}
236+
</Box>
237+
)}
238+
239+
{logLineRows.length === 0 ? (
240+
<React.Fragment />
241+
) : (
242+
<Box marginTop={1} flexDirection="column">
243+
{logLineRows}
244+
</Box>
245+
)}
246+
</React.Fragment>
233247
)
234248
}
235249
}

Diff for: plugins/plugin-codeflare-dashboard/src/components/Dashboard/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export type Worker = {
3939

4040
export type LogLineUpdate = {
4141
/** Log lines */
42-
logLine: string
42+
logLine: string[]
4343
}
4444

4545
export type WorkersUpdate = {

Diff for: plugins/plugin-codeflare-dashboard/src/controller/dashboard/status/Live.ts

+43-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import Heap from "heap"
18+
import ansiRegex from "ansi-regex"
1819
import stripAnsi from "strip-ansi"
1920
import type { TextProps } from "ink"
2021

@@ -28,6 +29,15 @@ import { rankFor, stateFor } from "./states.js"
2829

2930
type Event = { line: string; stateRank: number; timestamp: number }
3031

32+
/**
33+
* Keep track of a local timestamp so we can prioritize and show the
34+
* most recent; this is a "local" timestamp in that it does not
35+
* indicate when the event was created on the server, but rather when
36+
* we received it. Perhaps suboptimal, but we cannot guarantee that
37+
* random log lines from applications are timestamped.
38+
*/
39+
type LogLineRecord = { id: string; logLine: string; localMillis: number }
40+
3141
/**
3242
* Maintain a model of live data from a given set of file streams
3343
* (`tails`), and pump it into the given `cb` callback.
@@ -41,7 +51,7 @@ export default class Live {
4151
private static readonly MAX_HEAP = 1000
4252

4353
/** Model of logLines. TODO circular buffer and obey options.lines */
44-
// private logLine = ""
54+
private logLine: Record<string, LogLineRecord> = {}
4555

4656
/** Model of the lines of output */
4757
private readonly events = new Heap<Event>((a, b) => {
@@ -144,11 +154,15 @@ export default class Live {
144154
.sort((a, b) => a.timestamp - b.timestamp)
145155
}
146156

157+
/** Replace any timestamps with a placeholder, so that the UI can use a "5m ago" style */
158+
private timestamped(line: string) {
159+
return line.replace(/\s*(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z)\s*/, "{timestamp}")
160+
}
161+
147162
private readonly lookup: Record<string, Event> = {}
148163
/** Add `line` to our heap `this.events` */
149164
private pushEvent(line: string, metric: WorkerState, timestamp: number) {
150-
const key = line
151-
.replace(/\s*(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z)\s*/, "{timestamp}")
165+
const key = this.timestamped(line)
152166
.replace(/pod\/torchx-\S+ /, "") // worker name in torchx
153167
.replace(/pod\/ray-(head|worker)-\S+ /, "") // worker name in ray
154168
.replace(/\* /, "") // wildcard worker name (codeflare)
@@ -181,12 +195,37 @@ export default class Live {
181195
}
182196
}
183197

198+
/** This helps us parse out a [W5] style prefix for loglines, so we can intuit the worker id of the log line */
199+
private readonly workerIdPattern = new RegExp("^(" + ansiRegex().source + ")?\\[([^\\]]+)\\]")
200+
201+
private readonly timeSorter = (a: LogLineRecord, b: LogLineRecord): number => {
202+
return a.localMillis - b.localMillis
203+
}
204+
205+
private readonly idSorter = (a: LogLineRecord, b: LogLineRecord): number => {
206+
return a.id.localeCompare(b.id)
207+
}
208+
184209
/** Add the given `line` to our logLines model and pass the updated model to `cb` */
185210
private pushLineAndPublish(logLine: string, cb: OnData) {
186211
if (logLine) {
187212
// here we avoid a flood of React renders by batching them up a
188213
// bit; i thought react 18 was supposed to help with this. hmm.
189-
cb({ logLine })
214+
const match = logLine.match(this.workerIdPattern)
215+
const id = match ? match[2] : "notsure"
216+
if (id) {
217+
this.logLine[id] = { id, logLine, localMillis: Date.now() }
218+
219+
// display the k most recent logLines per worker, ordering the display by worker id
220+
const k = 4
221+
cb({
222+
logLine: Object.values(this.logLine)
223+
.sort(this.timeSorter) // so we can pick off the most recent
224+
.slice(0, k) // now pick off the k most recent
225+
.sort(this.idSorter) // sort those k by worker id, so there is a consistent ordering in the UI
226+
.map((_) => _.logLine), // and display just the logLine
227+
})
228+
}
190229
}
191230
}
192231

0 commit comments

Comments
 (0)