75 lines
3.7 KiB
Groovy
75 lines
3.7 KiB
Groovy
import org.gradle.api.logging.Logging
|
|
import org.gradle.internal.operations.BuildOperationListener
|
|
import org.gradle.internal.operations.BuildOperationListenerManager
|
|
import org.gradle.internal.operations.OperationProgressDetails
|
|
import org.gradle.internal.resource.ExternalResourceReadBuildOperationType
|
|
import java.util.concurrent.ConcurrentHashMap
|
|
import java.util.concurrent.Executors
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
// The Docker images pin Gradle. Observe its download events without changing requests,
|
|
// dependency verification or caching; plain console output otherwise hides these events.
|
|
def logger = Logging.getLogger('felis.download')
|
|
def downloads = new ConcurrentHashMap()
|
|
def bytes = { long value ->
|
|
value >= 1024 * 1024 ? String.format(Locale.ROOT, '%.1f MiB', value / (1024.0 * 1024)) :
|
|
String.format(Locale.ROOT, '%.1f KiB', value / 1024.0)
|
|
}
|
|
def seconds = { long since -> TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - since) }
|
|
def listener = [
|
|
started: { operation, event ->
|
|
if (!(operation.details instanceof ExternalResourceReadBuildOperationType.Details)) return
|
|
def uri = URI.create(operation.details.location)
|
|
if (!(uri.scheme in ['http', 'https'])) return
|
|
def now = System.nanoTime()
|
|
def name = "${uri.path.tokenize('/').last()} (${uri.host})"
|
|
downloads[operation.id] = [name: name, bytes: 0L, total: 0L, started: now,
|
|
changed: now, sampled: now, sampledBytes: 0L]
|
|
logger.lifecycle("[felis] Downloading ${name}")
|
|
},
|
|
progress: { id, event ->
|
|
def download = downloads[id]
|
|
if (download == null || !(event.details instanceof OperationProgressDetails)) return
|
|
if (event.details.units != 'bytes') return
|
|
synchronized (download) {
|
|
if (event.details.progress > download.bytes) download.changed = System.nanoTime()
|
|
download.bytes = event.details.progress
|
|
download.total = event.details.total
|
|
}
|
|
},
|
|
finished: { operation, event ->
|
|
def download = downloads.remove(operation.id)
|
|
if (download == null) return
|
|
def outcome = event.failure != null ? 'Download failed' :
|
|
event.result?.missing ? 'Not found' : 'Downloaded'
|
|
logger.lifecycle("[felis] ${outcome} ${download.name} (${seconds(download.started)}s)")
|
|
}
|
|
] as BuildOperationListener
|
|
|
|
def manager = gradle.services.get(BuildOperationListenerManager)
|
|
manager.addListener(listener)
|
|
def timer = Executors.newSingleThreadScheduledExecutor({ task ->
|
|
def thread = new Thread(task, 'felis-download-progress')
|
|
thread.daemon = true
|
|
thread
|
|
} as java.util.concurrent.ThreadFactory)
|
|
timer.scheduleAtFixedRate({
|
|
downloads.values().each { download ->
|
|
synchronized (download) {
|
|
def now = System.nanoTime()
|
|
def rate = (long) ((download.bytes - download.sampledBytes) * 1e9 / (now - download.sampled))
|
|
def total = download.total > 0 ? bytes(download.total) : 'unknown size'
|
|
def percent = download.total > 0 ? " (${Math.min(100, (100L * download.bytes).intdiv(download.total))}%)" : ''
|
|
def idle = seconds(download.changed)
|
|
def waiting = idle >= 30 ? " | no new data for ${idle}s; waiting for the download" : ''
|
|
logger.lifecycle("[felis] ${download.name}: ${bytes(download.bytes)} / ${total}${percent} | ${bytes(rate)}/s | ${seconds(download.started)}s elapsed${waiting}")
|
|
download.sampled = now
|
|
download.sampledBytes = download.bytes
|
|
}
|
|
}
|
|
} as Runnable, 10, 10, TimeUnit.SECONDS)
|
|
gradle.buildFinished {
|
|
timer.shutdownNow()
|
|
manager.removeListener(listener)
|
|
}
|