mirror of
				https://github.com/ohwgiles/laminar.git
				synced 2025-06-13 12:54:29 +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 class="console-log">
 | 
			
		||||
   <code v-html="log"></code>
 | 
			
		||||
   <span v-show="job.result == 'running'" v-html="runIcon('running')" style="display: block;"></span>
 | 
			
		||||
   <code></code>
 | 
			
		||||
   <span v-show="!logComplete" v-html="runIcon('running')" style="display: block;"></span>
 | 
			
		||||
  </div>
 | 
			
		||||
 </div></template>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -643,7 +643,7 @@ const Run = templateId => {
 | 
			
		||||
  const state = {
 | 
			
		||||
    job: { artifacts: [], upstream: {} },
 | 
			
		||||
    latestNum: null,
 | 
			
		||||
    log: '',
 | 
			
		||||
    logComplete: false,
 | 
			
		||||
  };
 | 
			
		||||
  const logFetcher = (vm, name, num) => {
 | 
			
		||||
    const abort = new AbortController();
 | 
			
		||||
@ -651,11 +651,12 @@ const Run = templateId => {
 | 
			
		||||
      // ATOW pipeThrough not supported in Firefox
 | 
			
		||||
      //const reader = res.body.pipeThrough(new TextDecoderStream).getReader();
 | 
			
		||||
      const reader = res.body.getReader();
 | 
			
		||||
      return function pump() {
 | 
			
		||||
        return reader.read().then(({done, value}) => {
 | 
			
		||||
          value = utf8decoder.decode(value);
 | 
			
		||||
          if (done)
 | 
			
		||||
            return;
 | 
			
		||||
      const target = document.getElementsByTagName('code')[0];
 | 
			
		||||
      let logToRender = '';
 | 
			
		||||
      let logComplete = false;
 | 
			
		||||
      let tid = null;
 | 
			
		||||
 | 
			
		||||
      function updateUI() {
 | 
			
		||||
        // output may contain private ANSI CSI escape sequence to point to
 | 
			
		||||
        // downstream jobs. ansi_up (correctly) discards unknown sequences,
 | 
			
		||||
        // 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.
 | 
			
		||||
        // A better solution one day would be if ansi_up were to provide
 | 
			
		||||
        // a callback interface for handling unknown sequences.
 | 
			
		||||
          state.log += ansi_up.ansi_to_html(
 | 
			
		||||
            value.replace(/\033\[\{([^:]+):(\d+)\033\\/g, (m, $1, $2) =>
 | 
			
		||||
        // Also, update the DOM directly rather than using a binding through
 | 
			
		||||
        // 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+'~'
 | 
			
		||||
          )
 | 
			
		||||
        ).replace(/~~~~LAMINAR_RUN~([^:]+):(\d+)~/g, (m, $1, $2) =>
 | 
			
		||||
          '<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>'
 | 
			
		||||
        );
 | 
			
		||||
          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();
 | 
			
		||||
        });
 | 
			
		||||
      }();
 | 
			
		||||
@ -694,7 +719,9 @@ const Run = templateId => {
 | 
			
		||||
        state.job = data;
 | 
			
		||||
        state.latestNum = data.latestNum;
 | 
			
		||||
        state.jobsRunning = [data];
 | 
			
		||||
        state.log = '';
 | 
			
		||||
        state.logComplete = false;
 | 
			
		||||
        // DOM is used directly for performance
 | 
			
		||||
        document.getElementsByTagName('code')[0].innerHTML = '';
 | 
			
		||||
        if(this.logstream)
 | 
			
		||||
          this.logstream.abort();
 | 
			
		||||
        if(data.started)
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user