Created
March 28, 2019 04:10
-
-
Save ldaley/c8538870b40a01328dc01af899ed2fb2 to your computer and use it in GitHub Desktop.
Gradle Enterprise Export API - Build configuration time
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Build configuration time</title> | |
</head> | |
<body> | |
<script> | |
// The address of your Gradle Enterprise server | |
const GRADLE_ENTERPRISE_SERVER_URL = 'http://localhost:5050'; | |
// The point in time from which builds should be processed. | |
// Values can be 'now', or a number of milliseconds since the UNIX epoch. | |
// The time is the point in time that the build was published to the server. | |
const PROCESS_FROM = '0'; | |
// How many builds to process at one time. | |
// If running with very fast network connection to the server, | |
// this number can be increased for better throughput. | |
const MAX_CONCURRENT_BUILDS_TO_PROCESS = 6; | |
// A build event handler that counts how many tasks of the build were cacheable. | |
class ConfigurationTimeCounter { | |
constructor(build) { | |
this.buildId = build.buildId; | |
this.loadProjectStarted = {}; | |
this.time = {}; | |
this.currentStartedAt = 0; | |
this.buildPath = null; | |
} | |
onLoadProjectsStarted(e) { | |
this.loadProjectStarted[e.data.id] = e; | |
} | |
onLoadProjectsFinished(e) { | |
const started = this.loadProjectStarted[e.data.id]; | |
if (started.data.buildPath === ':' || started.data.buildPath.indexOf(':buildSrc') >= 0) { | |
this.buildPath = started.data.buildPath; | |
this.currentStartedAt = e.timestamp; | |
} | |
} | |
onTaskStarted(e) { | |
if (!this.time[this.buildPath]) { | |
this.finish(e); | |
} | |
} | |
onBuildFinished(e) { | |
if (!this.time[this.buildPath]) { | |
this.finish(e); | |
} | |
} | |
finish(e) { | |
this.time[this.buildPath] = (this.time[this.buildPath] || 0) + e.timestamp - this.currentStartedAt; | |
} | |
complete() { | |
document.write(`<p>Build ${this.buildId} ${JSON.stringify(this.time)}</p>`,); | |
} | |
} | |
// The event handlers to use to process builds. | |
const BUILD_EVENT_HANDLERS = [ConfigurationTimeCounter]; | |
// Code below is a generic utility for interacting with the Export API. | |
class BuildProcessor { | |
constructor(gradleEnterpriseServerUrl, maxConcurrentBuildsToProcess, eventHandlerClasses) { | |
this.gradleEnterpriseServerUrl = gradleEnterpriseServerUrl; | |
this.eventHandlerClasses = eventHandlerClasses; | |
this.allHandledEventTypes = this.getAllHandledEventTypes(); | |
this.pendingBuilds = []; | |
this.numBuildsInProcess = 0; | |
this.maxConcurrentBuildsToProcess = maxConcurrentBuildsToProcess; | |
} | |
start(startTime) { | |
const buildStreamUrl = this.createBuildStreamUrl(startTime); | |
const buildStream = new EventSource(buildStreamUrl); | |
buildStream.onopen = event => console.log(`Build stream '${buildStreamUrl}' open`); | |
buildStream.onerror = event => console.error('Build stream error', event); | |
buildStream.addEventListener('Build', event => { | |
this.enqueue(JSON.parse(event.data)); | |
}); | |
} | |
enqueue(build) { | |
this.pendingBuilds.push(build); | |
this.processPendingBuilds(); | |
} | |
processPendingBuilds() { | |
if (this.pendingBuilds.length > 0 && this.numBuildsInProcess < this.maxConcurrentBuildsToProcess) { | |
this.processBuild(this.pendingBuilds.shift()); | |
} | |
} | |
createBuildStreamUrl(startTime) { | |
return `${this.gradleEnterpriseServerUrl}/build-export/v1/builds/since/${startTime}?stream`; | |
} | |
// Inspect the methods on the handler class to find any event handlers that start with 'on' followed by the event type like 'onBuildStarted'. | |
// Then take the part of the method name after the 'on' to get the event type. | |
getHandledEventTypesForHandlerClass(handlerClass) { | |
return Object.getOwnPropertyNames(handlerClass.prototype) | |
.filter(methodName => methodName.startsWith('on')) | |
.map(methodName => methodName.substring(2)); | |
} | |
getAllHandledEventTypes() { | |
return new Set(this.eventHandlerClasses.reduce((eventTypes, handlerClass) => eventTypes.concat(this.getHandledEventTypesForHandlerClass(handlerClass)), [])); | |
} | |
createBuildEventStreamUrl(buildId) { | |
const types = [...this.allHandledEventTypes].join(','); | |
return `${this.gradleEnterpriseServerUrl}/build-export/v1/build/${buildId}/events?eventTypes=${types}`; | |
} | |
// Creates a map of event type -> handler instance for each event type supported by one or more handlers. | |
createBuildEventHandlers(build) { | |
return this.eventHandlerClasses.reduce((handlers, handlerClass) => { | |
const addHandler = (type, handler) => handlers[type] ? handlers[type].push(handler) : handlers[type] = [handler]; | |
const handler = new handlerClass.prototype.constructor(build); | |
this.getHandledEventTypesForHandlerClass(handlerClass).forEach(eventType => addHandler(eventType, handler)); | |
if (Object.getOwnPropertyNames(handlerClass.prototype).includes('complete')) { | |
addHandler('complete', handler); | |
} | |
return handlers; | |
}, {}); | |
} | |
processBuild(build) { | |
this.numBuildsInProcess++; | |
const buildEventHandlers = this.createBuildEventHandlers(build); | |
const buildEventStream = new EventSource(this.createBuildEventStreamUrl(build.buildId)); | |
let buildEventStreamOpen = true; | |
buildEventStream.addEventListener('BuildEvent', event => { | |
const buildEventPayload = JSON.parse(event.data); | |
const { eventType } = buildEventPayload.type; | |
if (this.allHandledEventTypes.has(eventType)) { | |
buildEventHandlers[eventType].forEach(handler => handler[`on${eventType}`](buildEventPayload)); | |
} | |
}); | |
// there isn't an onclose event that we can listen to, but an error event does get triggered | |
// when the end of the stream is reached so use that as a rough proxy for the end of the stream | |
buildEventStream.onerror = event => { | |
if (buildEventStreamOpen) { | |
this.finishedProcessingBuild(); | |
// Call the 'complete()' method on any handler that has it. | |
if (buildEventHandlers.complete) { | |
buildEventHandlers.complete.forEach(handler => handler.complete()); | |
} | |
buildEventStream.close(); | |
buildEventStreamOpen = false; | |
} | |
} | |
} | |
finishedProcessingBuild() { | |
this.numBuildsInProcess--; | |
setTimeout(() => this.processPendingBuilds(), 0); // process the next set of pending builds, if any | |
} | |
} | |
new BuildProcessor( | |
GRADLE_ENTERPRISE_SERVER_URL, | |
MAX_CONCURRENT_BUILDS_TO_PROCESS, | |
BUILD_EVENT_HANDLERS | |
).start(PROCESS_FROM); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment