Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit379f1f4

Browse files
committed
Display staartup logs in a virtual DOM for performance
1 parent34fde1a commit379f1f4

File tree

9 files changed

+223
-46
lines changed

9 files changed

+223
-46
lines changed

‎agent/agent.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -649,27 +649,46 @@ func (a *agent) runScript(ctx context.Context, lifecycle, script string) error {
649649
_=fileWriter.Close()
650650
}()
651651

652+
// Create pipes for startup logs reader and writer
652653
startupLogsReader,startupLogsWriter:=io.Pipe()
654+
655+
// Close the pipes when the function returns
653656
deferfunc() {
654657
_=startupLogsReader.Close()
655658
_=startupLogsWriter.Close()
656659
}()
660+
661+
// Create a multi-writer for startup logs and file writer
657662
writer:=io.MultiWriter(startupLogsWriter,fileWriter)
658663

664+
// Initialize variables for log management
659665
queuedLogs:=make([]agentsdk.StartupLog,0)
660666
varflushLogsTimer*time.Timer
661667
varlogMutex sync.Mutex
662668
varlogsSendingbool
669+
670+
// sendLogs function uploads the queued logs to the server
663671
sendLogs:=func() {
672+
// Lock logMutex and check if logs are already being sent
664673
logMutex.Lock()
665674
iflogsSending {
666675
logMutex.Unlock()
667676
return
668677
}
669-
logsSending=true
678+
ifflushLogsTimer!=nil {
679+
flushLogsTimer.Stop()
680+
}
681+
iflen(queuedLogs)==0 {
682+
logMutex.Unlock()
683+
return
684+
}
685+
// Move the current queued logs to logsToSend and clear the queue
670686
logsToSend:=queuedLogs
687+
logsSending=true
671688
queuedLogs=make([]agentsdk.StartupLog,0)
672689
logMutex.Unlock()
690+
691+
// Retry uploading logs until successful or a specific error occurs
673692
forr:=retry.New(time.Second,5*time.Second);r.Wait(ctx); {
674693
err:=a.client.PatchStartupLogs(ctx, agentsdk.PatchStartupLogs{
675694
Logs:logsToSend,
@@ -686,25 +705,30 @@ func (a *agent) runScript(ctx context.Context, lifecycle, script string) error {
686705
}
687706
a.logger.Error(ctx,"upload startup logs",slog.Error(err),slog.F("to_send",logsToSend))
688707
}
708+
// Reset logsSending flag
689709
logMutex.Lock()
690710
logsSending=false
691711
logMutex.Unlock()
692712
}
713+
// queueLog function appends a log to the queue and triggers sendLogs if necessary
693714
queueLog:=func(log agentsdk.StartupLog) {
694715
logMutex.Lock()
695716
deferlogMutex.Unlock()
717+
718+
// Append log to the queue
696719
queuedLogs=append(queuedLogs,log)
697-
iflen(queuedLogs)>25 {
720+
721+
// If there are more than 100 logs, send them immediately
722+
iflen(queuedLogs)>100 {
698723
gosendLogs()
699724
return
700725
}
726+
// Reset or set the flushLogsTimer to trigger sendLogs after 100 milliseconds
701727
ifflushLogsTimer!=nil {
702728
flushLogsTimer.Reset(100*time.Millisecond)
703729
return
704730
}
705-
flushLogsTimer=time.AfterFunc(100*time.Millisecond,func() {
706-
sendLogs()
707-
})
731+
flushLogsTimer=time.AfterFunc(100*time.Millisecond,sendLogs)
708732
}
709733
err=a.trackConnGoroutine(func() {
710734
scanner:=bufio.NewScanner(startupLogsReader)

‎coderd/workspaceagents.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,12 @@ func (api *API) patchWorkspaceAgentStartupLogs(rw http.ResponseWriter, r *http.R
238238
if!httpapi.Read(ctx,rw,r,&req) {
239239
return
240240
}
241-
241+
iflen(req.Logs)==0 {
242+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
243+
Message:"No logs provided.",
244+
})
245+
return
246+
}
242247
createdAt:=make([]time.Time,0)
243248
output:=make([]string,0)
244249
outputLength:=0
@@ -342,11 +347,11 @@ func (api *API) patchWorkspaceAgentStartupLogs(rw http.ResponseWriter, r *http.R
342347
func (api*API)workspaceAgentStartupLogs(rw http.ResponseWriter,r*http.Request) {
343348
// This mostly copies how provisioner job logs are streamed!
344349
var (
345-
ctx=r.Context()
346-
agent=httpmw.WorkspaceAgentParam(r)
347-
logger=api.Logger.With(slog.F("workspace_agent_id",agent.ID))
348-
follow=r.URL.Query().Has("follow")
349-
afterRaw=r.URL.Query().Get("after")
350+
ctx=r.Context()
351+
workspaceAgent=httpmw.WorkspaceAgentParam(r)
352+
logger=api.Logger.With(slog.F("workspace_agent_id",workspaceAgent.ID))
353+
follow=r.URL.Query().Has("follow")
354+
afterRaw=r.URL.Query().Get("after")
350355
)
351356

352357
varafterint64
@@ -366,7 +371,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques
366371
}
367372

368373
logs,err:=api.Database.GetWorkspaceAgentStartupLogsAfter(ctx, database.GetWorkspaceAgentStartupLogsAfterParams{
369-
AgentID:agent.ID,
374+
AgentID:workspaceAgent.ID,
370375
CreatedAfter:after,
371376
})
372377
iferrors.Is(err,sql.ErrNoRows) {
@@ -412,7 +417,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques
412417
iferr!=nil {
413418
return
414419
}
415-
ifagent.LifecycleState==database.WorkspaceAgentLifecycleStateReady {
420+
ifworkspaceAgent.LifecycleState==database.WorkspaceAgentLifecycleStateReady {
416421
// The startup script has finished running, so we can close the connection.
417422
return
418423
}
@@ -434,7 +439,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques
434439
}
435440

436441
closeSubscribe,err:=api.Pubsub.Subscribe(
437-
agentsdk.StartupLogsNotifyChannel(agent.ID),
442+
agentsdk.StartupLogsNotifyChannel(workspaceAgent.ID),
438443
func(ctx context.Context,message []byte) {
439444
ifendOfLogs.Load() {
440445
return
@@ -448,7 +453,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques
448453

449454
ifjlMsg.CreatedAfter!=0 {
450455
logs,err:=api.Database.GetWorkspaceAgentStartupLogsAfter(ctx, database.GetWorkspaceAgentStartupLogsAfterParams{
451-
AgentID:agent.ID,
456+
AgentID:workspaceAgent.ID,
452457
CreatedAfter:jlMsg.CreatedAfter,
453458
})
454459
iferr!=nil {
@@ -461,7 +466,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques
461466
ifjlMsg.EndOfLogs {
462467
endOfLogs.Store(true)
463468
logs,err:=api.Database.GetWorkspaceAgentStartupLogsAfter(ctx, database.GetWorkspaceAgentStartupLogsAfterParams{
464-
AgentID:agent.ID,
469+
AgentID:workspaceAgent.ID,
465470
CreatedAfter:lastSentLogID.Load(),
466471
})
467472
iferr!=nil {

‎site/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@
7373
"react-markdown":"8.0.3",
7474
"react-router-dom":"6.4.1",
7575
"react-syntax-highlighter":"15.5.0",
76+
"react-virtualized-auto-sizer":"^1.0.7",
77+
"react-window":"^1.8.8",
7678
"remark-gfm":"3.0.1",
7779
"rollup-plugin-visualizer":"5.9.0",
7880
"sourcemapped-stacktrace":"1.1.11",
@@ -102,6 +104,8 @@
102104
"@types/react-dom":"18.0.6",
103105
"@types/react-helmet":"6.1.5",
104106
"@types/react-syntax-highlighter":"15.5.5",
107+
"@types/react-virtualized-auto-sizer":"^1.0.1",
108+
"@types/react-window":"^1.8.5",
105109
"@types/semver":"7.3.12",
106110
"@types/ua-parser-js":"0.7.36",
107111
"@types/uuid":"8.3.4",

‎site/src/components/DeploymentBanner/DeploymentBannerView.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ export const DeploymentBannerView: FC<DeploymentBannerViewProps> = ({
192192
<Tooltiptitle="A countdown until stats are fetched again. Click to refresh!">
193193
<Button
194194
className={`${styles.value}${styles.refreshButton}`}
195-
title="Refresh"
196195
onClick={()=>{
197196
if(fetchStats){
198197
fetchStats()

‎site/src/components/Logs/Logs.tsx

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,33 @@ export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
5151
)
5252
}
5353

54+
exportconstlogLineHeight=20
55+
56+
exportconstLogLine:FC<{
57+
line:Line
58+
hideTimestamp?:boolean
59+
number?:number
60+
style?:React.CSSProperties
61+
}>=({ line, hideTimestamp, number, style})=>{
62+
conststyles=useStyles({
63+
lineNumbers:Boolean(number),
64+
})
65+
66+
return(
67+
<divclassName={combineClasses([styles.line,line.level])}style={style}>
68+
{!hideTimestamp&&(
69+
<>
70+
<spanclassName={styles.time}>
71+
{number ?number :dayjs(line.time).format(`HH:mm:ss.SSS`)}
72+
</span>
73+
<spanclassName={styles.space}>&nbsp;&nbsp;&nbsp;&nbsp;</span>
74+
</>
75+
)}
76+
<span>{line.output}</span>
77+
</div>
78+
)
79+
}
80+
5481
constuseStyles=makeStyles<
5582
Theme,
5683
{
@@ -59,19 +86,20 @@ const useStyles = makeStyles<
5986
>((theme)=>({
6087
root:{
6188
minHeight:156,
62-
background:theme.palette.background.default,
63-
color:theme.palette.text.primary,
64-
fontFamily:MONOSPACE_FONT_FAMILY,
6589
fontSize:13,
66-
wordBreak:"break-all",
6790
padding:theme.spacing(2,0),
6891
borderRadius:theme.shape.borderRadius,
6992
overflowX:"auto",
93+
background:theme.palette.background.default,
7094
},
7195
scrollWrapper:{
7296
width:"fit-content",
7397
},
7498
line:{
99+
wordBreak:"break-all",
100+
color:theme.palette.text.primary,
101+
fontFamily:MONOSPACE_FONT_FAMILY,
102+
height:logLineHeight,
75103
// Whitespace is significant in terminal output for alignment
76104
whiteSpace:"pre-line",
77105
padding:theme.spacing(0,3),

‎site/src/components/Resources/AgentRow.tsx

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,23 @@ import { Skeleton } from "@material-ui/lab"
88
import{useMachine}from"@xstate/react"
99
import{AppLinkSkeleton}from"components/AppLink/AppLinkSkeleton"
1010
import{Maybe}from"components/Conditionals/Maybe"
11-
import{Line,Logs}from"components/Logs/Logs"
11+
import{LogLine,logLineHeight}from"components/Logs/Logs"
1212
import{PortForwardButton}from"components/PortForwardButton/PortForwardButton"
1313
import{VSCodeDesktopButton}from"components/VSCodeDesktopButton/VSCodeDesktopButton"
14-
import{FC,useEffect,useMemo,useRef,useState}from"react"
14+
import{
15+
FC,
16+
useCallback,
17+
useEffect,
18+
useLayoutEffect,
19+
useMemo,
20+
useRef,
21+
useState
22+
}from"react"
1523
import{useTranslation}from"react-i18next"
1624
import{PrismasSyntaxHighlighter}from"react-syntax-highlighter"
1725
import{darcula}from"react-syntax-highlighter/dist/cjs/styles/prism"
26+
importAutoSizerfrom"react-virtualized-auto-sizer"
27+
import{FixedSizeListasList,ListOnScrollProps}from"react-window"
1828
import{workspaceAgentLogsMachine}from"xServices/workspaceAgentLogs/workspaceAgentLogsXService"
1929
import{Workspace,WorkspaceAgent}from"../../api/typesGenerated"
2030
import{AppLink}from"../AppLink/AppLink"
@@ -70,24 +80,49 @@ export const AgentRow: FC<AgentRowProps> = ({
7080
sendLogsEvent("FETCH_STARTUP_LOGS")
7181
}
7282
},[sendLogsEvent,showStartupLogs])
83+
constlogListRef=useRef<List>(null)
84+
constlogListDivRef=useRef<HTMLDivElement>(null)
7385
conststartupLogs=useMemo(()=>{
74-
constlogs=
75-
logsMachine.context.startupLogs?.map(
76-
(log):Line=>({
77-
level:"info",
78-
output:log.output,
79-
time:log.created_at,
80-
}),
81-
)||[]
86+
constallLogs=logsMachine.context.startupLogs||[]
87+
88+
constlogs=[...allLogs]
8289
if(agent.startup_logs_overflowed){
8390
logs.push({
91+
id:-1,
8492
level:"error",
8593
output:"Startup logs exceeded the max size of 1MB!",
8694
time:newDate().toISOString(),
8795
})
8896
}
8997
returnlogs
9098
},[logsMachine.context.startupLogs,agent.startup_logs_overflowed])
99+
const[bottomOfLogs,setBottomOfLogs]=useState(true)
100+
useLayoutEffect(()=>{
101+
if(bottomOfLogs&&logListRef.current){
102+
logListRef.current.scrollToItem(startupLogs.length-1,"end")
103+
}
104+
},[showStartupLogs,startupLogs,logListRef,bottomOfLogs])
105+
consthandleLogScroll=useCallback(
106+
(props:ListOnScrollProps)=>{
107+
if(
108+
props.scrollOffset===0||
109+
props.scrollUpdateWasRequested||
110+
!logListDivRef.current
111+
){
112+
return
113+
}
114+
// The parent holds the height of the list!
115+
constparent=logListDivRef.current.parentElement
116+
if(!parent){
117+
return
118+
}
119+
constdistanceFromBottom=
120+
logListDivRef.current.scrollHeight-
121+
(props.scrollOffset+parent.clientHeight)
122+
setBottomOfLogs(distanceFromBottom<logLineHeight)
123+
},
124+
[logListDivRef],
125+
)
91126

92127
return(
93128
<Stack
@@ -269,8 +304,30 @@ export const AgentRow: FC<AgentRowProps> = ({
269304
)}
270305
</Stack>
271306
</Stack>
307+
272308
{showStartupLogs&&(
273-
<LogsclassName={styles.startupLogs}lineNumberslines={startupLogs}/>
309+
<AutoSizerdisableHeight>
310+
{({ width})=>(
311+
<List
312+
ref={logListRef}
313+
innerRef={logListDivRef}
314+
height={256}
315+
itemCount={startupLogs.length}
316+
itemSize={logLineHeight}
317+
width={width}
318+
className={styles.startupLogs}
319+
onScroll={handleLogScroll}
320+
>
321+
{({ index, style})=>(
322+
<LogLine
323+
line={startupLogs[index]}
324+
number={index+1}
325+
style={style}
326+
/>
327+
)}
328+
</List>
329+
)}
330+
</AutoSizer>
274331
)}
275332
</Stack>
276333
)
@@ -312,8 +369,7 @@ const useStyles = makeStyles((theme) => ({
312369

313370
startupLogs:{
314371
maxHeight:256,
315-
display:"flex",
316-
flexDirection:"column-reverse",
372+
background:theme.palette.background.default,
317373
},
318374

319375
startupScriptPopover:{

‎site/src/components/Workspace/Workspace.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Avatar } from "components/Avatar/Avatar"
33
import{AgentRow}from"components/Resources/AgentRow"
44
import{
55
ActiveTransition,
6-
WorkspaceBuildProgress
6+
WorkspaceBuildProgress,
77
}from"components/WorkspaceBuildProgress/WorkspaceBuildProgress"
88
import{WorkspaceStatusBadge}from"components/WorkspaceStatusBadge/WorkspaceStatusBadge"
99
import{FC}from"react"
@@ -15,7 +15,7 @@ import { Margins } from "../Margins/Margins"
1515
import{
1616
PageHeader,
1717
PageHeaderSubtitle,
18-
PageHeaderTitle
18+
PageHeaderTitle,
1919
}from"../PageHeader/PageHeader"
2020
import{Resources}from"../Resources/Resources"
2121
import{Stack}from"../Stack/Stack"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp