Skip to content

Commit a216fb0

Browse files
committed
fix: strip node name from events and log lines
also improve handling of timestamps in log lines
1 parent 26a3234 commit a216fb0

File tree

3 files changed

+46
-25
lines changed

3 files changed

+46
-25
lines changed

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

+15-13
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import React from "react"
1818
import prettyMillis from "pretty-ms"
1919
import { Box, Spacer, Text } from "ink"
2020

21-
import type { GridSpec, UpdatePayload, LogLineUpdate, WorkersUpdate, Worker } from "./types.js"
21+
import type { GridSpec, UpdatePayload, LogLineUpdate, TimestampedLine, WorkersUpdate, Worker } from "./types.js"
2222

2323
import Grid from "./Grid.js"
2424
import Timeline from "./Timeline.js"
@@ -210,21 +210,25 @@ export default class Dashboard extends React.PureComponent<Props, State> {
210210
return this.ago(Date.now() - millis)
211211
}
212212

213+
/**
214+
* The controller (controller/dashboard/utilization/Live) leaves a
215+
* {timestamp} breadcrumb in the raw line text, so that we,as the
216+
* view, can inject a "5m ago" text, while preserving the ansi
217+
* formatting that surrounds the timestamp.
218+
*/
219+
private readonly textForLine = ({ line, timestamp }: TimestampedLine) => {
220+
const txt = line.replace("{timestamp}", () => this.agos(timestamp))
221+
return <Text key={txt}>{txt}</Text>
222+
}
223+
213224
/** Render log lines and events */
214225
private footer() {
215226
if (!this.events && !this.logLine) {
216227
return <React.Fragment />
217228
} else {
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-
})
229+
const eventRows = (this.events || []).map(this.textForLine)
226230

227-
const logLineRows = (this.logLine ? this.logLine : []).map((line) => <Text key={line}>{line}</Text>)
231+
const logLineRows = (this.logLine ? this.logLine : []).map(this.textForLine)
228232

229233
return (
230234
<React.Fragment>
@@ -295,9 +299,7 @@ export default class Dashboard extends React.PureComponent<Props, State> {
295299
return (
296300
<Box flexDirection="column">
297301
{this.grids()}
298-
<Box marginTop={1}>
299-
<Timeline gridModels={this.gridModels} workers={this.state?.workers} />
300-
</Box>
302+
<Timeline gridModels={this.gridModels} workers={this.state?.workers} />
301303
</Box>
302304
)
303305
}

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,19 @@ export type Worker = {
3737
lastUpdate: number
3838
}
3939

40+
export type TimestampedLine = { line: string; timestamp: number }
41+
4042
export type LogLineUpdate = {
4143
/** Log lines */
42-
logLine: string[]
44+
logLine: TimestampedLine[]
4345
}
4446

4547
export type WorkersUpdate = {
4648
/** Per-worker status info */
4749
workers: Worker[]
4850

4951
/** Lines of raw event lines to be displayed */
50-
events?: { line: string; timestamp: number }[]
52+
events?: TimestampedLine[]
5153
}
5254

5355
/** Model that allows the controllers to pass updated `Worker` info */

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

+27-10
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type Event = { line: string; stateRank: number; timestamp: number }
3737
* we received it. Perhaps suboptimal, but we cannot guarantee that
3838
* random log lines from applications are timestamped.
3939
*/
40-
type LogLineRecord = { id: string; logLine: string; localMillis: number }
40+
type LogLineRecord = { id: string; line: string; timestamp: number }
4141

4242
/**
4343
* Maintain a model of live data from a given set of file streams
@@ -81,9 +81,11 @@ export default class Live {
8181
tail.stream.on("data", (data) => {
8282
if (data) {
8383
if (tail.kind === "logs") {
84+
// handle a log line
8485
this.pushLineAndPublish(stripColors(data), cb)
8586
}
8687

88+
// otherwise, treat it as an event
8789
const line = stripAnsi(data)
8890
const cols = line.split(/\s+/)
8991

@@ -162,14 +164,28 @@ export default class Live {
162164
return line.replace(/\s*(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z)\s*/, "{timestamp}")
163165
}
164166

167+
/** Strip out the worker name from `line` */
168+
private stripWorkerName(line: string) {
169+
return line
170+
.replace(/pod\/\S+-torchx-\S+ /, "") // worker name in torchx
171+
.replace(/pod\/ray-(head|worker)-\S+ /, "") // worker name in ray
172+
.replace(/\* /, "") // wildcard worker name (codeflare)
173+
}
174+
175+
private stripPageClear(line: string) {
176+
// eslint-disable-next-line no-control-regex
177+
return line.replace(/\x1b\x5B\[2J/g, "")
178+
}
179+
180+
private prepareLineForUI(line: string) {
181+
return this.stripPageClear(this.stripWorkerName(this.timestamped(line)))
182+
}
183+
165184
private readonly lookup: Record<string, Event> = {}
166185
/** Add `line` to our heap `this.events` */
167186
private pushEvent(line: string, metric: WorkerState, timestamp: number) {
168-
const key = this.timestamped(line)
169-
.replace(/pod\/torchx-\S+ /, "") // worker name in torchx
170-
.replace(/pod\/ray-(head|worker)-\S+ /, "") // worker name in ray
171-
.replace(/\* /, "") // wildcard worker name (codeflare)
172-
.replace(/\x1b\x5B\[2J/g, "") // eslint-disable-line no-control-regex
187+
const key = this.prepareLineForUI(line)
188+
173189
// ^^^ [2J is part of clear screen; we don't want those to flow through
174190

175191
const rec = {
@@ -202,7 +218,7 @@ export default class Live {
202218
private readonly workerIdPattern = new RegExp("^(" + ansiRegex().source + ")?\\[([^\\]]+)\\]")
203219

204220
private readonly timeSorter = (a: LogLineRecord, b: LogLineRecord): number => {
205-
return a.localMillis - b.localMillis
221+
return a.timestamp - b.timestamp
206222
}
207223

208224
private readonly idSorter = (a: LogLineRecord, b: LogLineRecord): number => {
@@ -217,16 +233,17 @@ export default class Live {
217233
const match = logLine.match(this.workerIdPattern)
218234
const id = match ? match[2] : "notsure"
219235
if (id) {
220-
this.logLine[id] = { id, logLine, localMillis: Date.now() }
236+
const idx = logLine.lastIndexOf(" ")
237+
const timestamp = idx < 0 ? undefined : this.asMillisSinceEpoch(stripAnsi(logLine.slice(idx + 1)))
238+
this.logLine[id] = { id, line: this.prepareLineForUI(logLine), timestamp: timestamp || Date.now() }
221239

222240
// display the k most recent logLines per worker, ordering the display by worker id
223241
const k = 4
224242
cb({
225243
logLine: Object.values(this.logLine)
226244
.sort(this.timeSorter) // so we can pick off the most recent
227245
.slice(0, k) // now pick off the k most recent
228-
.sort(this.idSorter) // sort those k by worker id, so there is a consistent ordering in the UI
229-
.map((_) => _.logLine), // and display just the logLine
246+
.sort(this.idSorter), // sort those k by worker id, so there is a consistent ordering in the UI
230247
})
231248
}
232249
}

0 commit comments

Comments
 (0)