improve frontend performance with large logs

frontend would furiously try to render as fast as it received data
chunks from the server. This causes a lot of extra load on the browser
renderer if the logs are large. Buffer and batch calls to rerender
to improve performance.

resolves #165
pull/167/head
Oliver Giles 2 years ago
parent bb087b72ee
commit efafda16ff

@ -187,8 +187,8 @@
</div> </div>
</div> </div>
<div class="console-log"> <div class="console-log">
<code v-html="log"></code> <code></code>
<span v-show="job.result == 'running'" v-html="runIcon('running')" style="display: block;"></span> <span v-show="!logComplete" v-html="runIcon('running')" style="display: block;"></span>
</div> </div>
</div></template> </div></template>

@ -643,7 +643,7 @@ const Run = templateId => {
const state = { const state = {
job: { artifacts: [], upstream: {} }, job: { artifacts: [], upstream: {} },
latestNum: null, latestNum: null,
log: '', logComplete: false,
}; };
const logFetcher = (vm, name, num) => { const logFetcher = (vm, name, num) => {
const abort = new AbortController(); const abort = new AbortController();
@ -651,11 +651,12 @@ const Run = templateId => {
// ATOW pipeThrough not supported in Firefox // ATOW pipeThrough not supported in Firefox
//const reader = res.body.pipeThrough(new TextDecoderStream).getReader(); //const reader = res.body.pipeThrough(new TextDecoderStream).getReader();
const reader = res.body.getReader(); const reader = res.body.getReader();
return function pump() { const target = document.getElementsByTagName('code')[0];
return reader.read().then(({done, value}) => { let logToRender = '';
value = utf8decoder.decode(value); let logComplete = false;
if (done) let tid = null;
return;
function updateUI() {
// output may contain private ANSI CSI escape sequence to point to // output may contain private ANSI CSI escape sequence to point to
// downstream jobs. ansi_up (correctly) discards unknown sequences, // downstream jobs. ansi_up (correctly) discards unknown sequences,
// so they must be matched before passing through ansi_up. ansi_up // so they must be matched before passing through ansi_up. ansi_up
@ -663,15 +664,39 @@ const Run = templateId => {
// to links after going through ansi_up. // to links after going through ansi_up.
// A better solution one day would be if ansi_up were to provide // A better solution one day would be if ansi_up were to provide
// a callback interface for handling unknown sequences. // a callback interface for handling unknown sequences.
state.log += ansi_up.ansi_to_html( // Also, update the DOM directly rather than using a binding through
value.replace(/\033\[\{([^:]+):(\d+)\033\\/g, (m, $1, $2) => // Vue, the performance is noticeably better with large logs.
target.innerHTML += ansi_up.ansi_to_html(
logToRender.replace(/\033\[\{([^:]+):(\d+)\033\\/g, (m, $1, $2) =>
'~~~~LAMINAR_RUN~'+$1+':'+$2+'~' '~~~~LAMINAR_RUN~'+$1+':'+$2+'~'
) )
).replace(/~~~~LAMINAR_RUN~([^:]+):(\d+)~/g, (m, $1, $2) => ).replace(/~~~~LAMINAR_RUN~([^:]+):(\d+)~/g, (m, $1, $2) =>
'<a href="jobs/'+$1+'" onclick="return LaminarApp.navigate(this.href);">'+$1+'</a>:'+ '<a href="jobs/'+$1+'" onclick="return LaminarApp.navigate(this.href);">'+$1+'</a>:'+
'<a href="jobs/'+$1+'/'+$2+'" onclick="return LaminarApp.navigate(this.href);">#'+$2+'</a>' '<a href="jobs/'+$1+'/'+$2+'" onclick="return LaminarApp.navigate(this.href);">#'+$2+'</a>'
); );
vm.$forceUpdate(); logToRender = '';
if (logComplete) {
// output finished
state.logComplete = true;
}
}
return function pump() {
return reader.read().then(({done, value}) => {
if (done) {
// do not set state.logComplete directly, because rendering
// may take some time, and we don't want the progress indicator
// to disappear before rendering is complete. Instead, delay
// it until after the log has been added to the DOM
logComplete = true;
return;
}
logToRender += utf8decoder.decode(value);
// sometimes logs can be very large, and we are calling pump()
// furiously to get all the data to the client. To prevent straining
// the client renderer, buffer the data and delay the UI updates.
clearTimeout(tid);
tid = setTimeout(updateUI, 125);
return pump(); return pump();
}); });
}(); }();
@ -694,7 +719,9 @@ const Run = templateId => {
state.job = data; state.job = data;
state.latestNum = data.latestNum; state.latestNum = data.latestNum;
state.jobsRunning = [data]; state.jobsRunning = [data];
state.log = ''; state.logComplete = false;
// DOM is used directly for performance
document.getElementsByTagName('code')[0].innerHTML = '';
if(this.logstream) if(this.logstream)
this.logstream.abort(); this.logstream.abort();
if(data.started) if(data.started)

Loading…
Cancel
Save