Browse Source

Client-side caching and request changes

Changes to reduce load on the remote site:
* Uses a local LRU cache to store results.
* Has only one pending request outstanding at a time.
* Increase debounce timer to 1.25 seconds.
* After a short time, display '<Compiling>' to user.
* Shows compile time taken at the bottom.
dev/git-series/gccdum
Matt Godbolt 6 years ago
parent
commit
f61825b2be
  1. 3
      bower.json
  2. 81
      static/compiler.js
  3. 2
      static/editor.js
  4. 5
      static/explorer.css
  5. 1
      static/index.html
  6. 4
      static/main.js

3
bower.json

@ -27,6 +27,7 @@
"lz-string": "^1.4.4",
"clipboard": "^1.5.12",
"raven-js": "^3.7.0",
"es6-promise": "^4.0.5"
"es6-promise": "^4.0.5",
"lru-cache": "2.7.3"
}
}

81
static/compiler.js

@ -34,6 +34,7 @@ define(function (require) {
var FontScale = require('fontscale');
var Promise = require('es6-promise').Promise;
var Components = require('components');
var LruCache = require('lru-cache');
require('asm-mode');
require('selectize');
@ -41,6 +42,12 @@ define(function (require) {
var options = require('options');
var compilers = options.compilers;
var compilersById = _.object(_.pluck(compilers, "id"), compilers);
var Cache = new LruCache({
max: 200 * 1024,
length: function (n) {
return JSON.stringify(n).length;
}
});
function Compiler(hub, container, state) {
var self = this;
@ -57,9 +64,8 @@ define(function (require) {
this.source = "";
this.assembly = [];
this.lastResult = null;
this.lastRequestRespondedTo = "";
this.debouncedAjax = _.debounce($.ajax, 250);
this.pendingRequestSentAt = 0;
this.nextRequest = null;
this.domRoot.find(".compiler-picker").selectize({
sortField: 'name',
@ -71,9 +77,9 @@ define(function (require) {
}).on('change', function () {
self.onCompilerChange($(this).val());
});
var optionsChange = function () {
var optionsChange = _.debounce(function () {
self.onOptionsChange($(this).val());
};
}, 1250);
this.domRoot.find(".options")
.val(this.options)
.on("change", optionsChange)
@ -170,7 +176,6 @@ define(function (require) {
};
Compiler.prototype.compile = function () {
var self = this;
var request = {
source: this.source || "",
compiler: this.compiler ? this.compiler.id : "",
@ -179,32 +184,49 @@ define(function (require) {
};
if (!this.compiler) {
this.onCompileResponse(request, errorResult("Please select a compiler"));
this.onCompileResponse(request, errorResult("Please select a compiler"), false);
return;
}
var cacheableRequest = JSON.stringify(request);
if (cacheableRequest === this.lastRequestRespondedTo) return;
// only set the request timestamp after checking cache; else we'll always fetch
request.timestamp = Date.now();
this.sendCompile(request);
};
Compiler.prototype.sendCompile = function (request) {
if (this.pendingRequestSentAt) {
// If we have a request pending, then just store this request to do once the
// previous request completes.
this.nextRequest = request;
return;
}
var jsonRequest = JSON.stringify(request);
var cachedResult = Cache.get(jsonRequest);
if (cachedResult) {
this.onCompileResponse(request, cachedResult, true);
return;
}
this.debouncedAjax({
this.pendingRequestSentAt = Date.now();
// After a short delay, give the user some indication that we're working on their
// compilation.
var progress = setTimeout(_.bind(function() {
this.setAssembly(fakeAsm("<Compiling...>"));
}, this), 500);
$.ajax({
type: 'POST',
url: '/compile',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(request),
data: jsonRequest,
success: _.bind(function (result) {
clearTimeout(progress);
if (result.okToCache) {
this.lastRequestRespondedTo = cacheableRequest;
} else {
this.lastRequestRespondedTo = "";
Cache.set(jsonRequest, result);
}
self.onCompileResponse(request, result);
this.onCompileResponse(request, result, false);
}, this),
error: _.bind(function (xhr, e_status, error) {
this.lastRequestRespondedTo = "";
self.onCompileResponse(request, errorResult("Remote compilation failed: " + error));
clearTimeout(progress);
this.onCompileResponse(request, errorResult("Remote compilation failed: " + error), false);
}, this),
cache: false
});
@ -267,22 +289,30 @@ define(function (require) {
return [{text: text, source: null, fake: true}];
}
Compiler.prototype.onCompileResponse = function (request, result) {
Compiler.prototype.onCompileResponse = function (request, result, cached) {
this.lastResult = result;
var timeTaken = Date.now() - this.pendingRequestSentAt;
this.pendingRequestSentAt = 0;
if (this.nextRequest) {
var next = this.nextRequest;
this.nextRequest = null;
this.sendCompile(next);
}
ga('send', {
hitType: 'event',
eventCategory: 'Compile',
eventAction: request.compiler,
eventLabel: request.options
eventLabel: request.options,
eventValue: cached ? 1 : 0
});
ga('send', {
hitType: 'timing',
timingCategory: 'Compile',
timingVar: request.compiler,
timingValue: Date.now() - request.timestamp
timingValue: timeTaken
});
this.outputEditor.operation(_.bind(function () {
this.setAssembly(result.asm || fakeAsm("[no output]"));
this.setAssembly(result.asm || fakeAsm("<No output>"));
if (request.filters.binary) {
this.outputEditor.setOption('lineNumbers', false);
this.outputEditor.setOption('gutters', ['address', 'opcodes']);
@ -298,6 +328,11 @@ define(function (require) {
status.toggleClass('error', failed);
status.toggleClass('warning', warns);
status.parent().attr('title', allText);
if (cached) {
this.domRoot.find('.compile-time').text("- cached");
} else {
this.domRoot.find('.compile-time').text("- " + timeTaken + "ms");
}
this.eventHub.emit('compileResult', this.id, this.compiler, result);
};

2
static/editor.js

@ -103,7 +103,7 @@ define(function (require) {
// * Only actually triggering a change if the document text has changed from
// the previous emitted.
this.lastChangeEmitted = null;
var ChangeDebounceMs = 500;
var ChangeDebounceMs = 1250;
this.debouncedEmitChange = _.debounce(function () {
if (self.options.get().compileOnChange) self.maybeEmitChange();
}, ChangeDebounceMs);

5
static/explorer.css

@ -209,3 +209,8 @@ pre.content {
width: 100%;
height: 100%;
}
.compile-time {
font-size: x-small;
font-style: italic;
}

1
static/index.html

@ -150,6 +150,7 @@
<button><span class="glyphicon glyphicon-alert status"></span></button>
</div>
<span class="full-compiler-name"></span>
<span class="compile-time"></span>
</div>
</div>

4
static/main.js

@ -36,7 +36,8 @@ require.config({
lzstring: 'ext/lz-string/libs/lz-string',
clipboard: 'ext/clipboard/dist/clipboard',
'raven-js': 'ext/raven-js/dist/raven',
'es6-promise': 'ext/es6-promise/es6-promise'
'es6-promise': 'ext/es6-promise/es6-promise',
'lru-cache': 'ext/lru-cache/lib/lru-cache'
},
packages: [{
name: "codemirror",
@ -45,6 +46,7 @@ require.config({
}],
shim: {
underscore: {exports: '_'},
'lru-cache': {exports: 'LRUCache'},
bootstrap: ['jquery']
}
});

Loading…
Cancel
Save