mirror of
https://github.com/ohwgiles/laminar.git
synced 2024-10-27 20:34:20 +00:00
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
This commit is contained in:
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…
Reference in New Issue
Block a user