|
|
@@ -3,6 +3,25 @@ const MIN_CHUNK_SIZE = 128 * 1024
|
|
|
|
|
|
const PDF_FILES = new Map()
|
|
|
|
|
|
+const METRICS = {
|
|
|
+ epoch: Date.now(),
|
|
|
+ cachedBytes: 0,
|
|
|
+ fetchedBytes: 0,
|
|
|
+ requestedBytes: 0,
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ * @param {number} size
|
|
|
+ * @param {number} cachedBytes
|
|
|
+ * @param {number} fetchedBytes
|
|
|
+ */
|
|
|
+function trackStats({ size, cachedBytes, fetchedBytes }) {
|
|
|
+ METRICS.cachedBytes += cachedBytes
|
|
|
+ METRICS.fetchedBytes += fetchedBytes
|
|
|
+ METRICS.requestedBytes += size
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* @param {FetchEvent} event
|
|
|
*/
|
|
|
@@ -18,6 +37,7 @@ function onFetch(event) {
|
|
|
if (ctx) {
|
|
|
return processPdfRequest(event, ctx)
|
|
|
}
|
|
|
+
|
|
|
// other request, ignore
|
|
|
}
|
|
|
|
|
|
@@ -27,8 +47,9 @@ function processCompileRequest(event) {
|
|
|
if (response.status !== 200) return response
|
|
|
|
|
|
return response.json().then(body => {
|
|
|
- handleCompileResponse(body)
|
|
|
- // The response body is consumed, serialize it again.
|
|
|
+ handleCompileResponse(response, body)
|
|
|
+ // Send the service workers metrics to the frontend.
|
|
|
+ body.serviceWorkerMetrics = METRICS
|
|
|
return new Response(JSON.stringify(body), response)
|
|
|
})
|
|
|
})
|
|
|
@@ -41,8 +62,12 @@ function processCompileRequest(event) {
|
|
|
* @param {Object} file
|
|
|
* @param {string} clsiServerId
|
|
|
* @param {string} compileGroup
|
|
|
+ * @param {Date} pdfCreatedAt
|
|
|
*/
|
|
|
-function processPdfRequest(event, { file, clsiServerId, compileGroup }) {
|
|
|
+function processPdfRequest(
|
|
|
+ event,
|
|
|
+ { file, clsiServerId, compileGroup, pdfCreatedAt }
|
|
|
+) {
|
|
|
if (!event.request.headers.has('Range') && file.size > MIN_CHUNK_SIZE) {
|
|
|
// skip probe request
|
|
|
const headers = new Headers()
|
|
|
@@ -96,6 +121,8 @@ function processPdfRequest(event, { file, clsiServerId, compileGroup }) {
|
|
|
})
|
|
|
)
|
|
|
const size = end - start
|
|
|
+ let cachedBytes = 0
|
|
|
+ let fetchedBytes = 0
|
|
|
const reAssembledBlob = new Uint8Array(size)
|
|
|
event.respondWith(
|
|
|
Promise.all(
|
|
|
@@ -109,6 +136,22 @@ function processPdfRequest(event, { file, clsiServerId, compileGroup }) {
|
|
|
}`
|
|
|
)
|
|
|
}
|
|
|
+ const blobFetchDate = getServerTime(response)
|
|
|
+ const blobSize = getResponseSize(response)
|
|
|
+ if (blobFetchDate && blobSize) {
|
|
|
+ const chunkSize =
|
|
|
+ Math.min(end, chunk.end) - Math.max(start, chunk.start)
|
|
|
+ // Example: 2MB PDF, 1MB image, 128KB PDF.js chunk.
|
|
|
+ // | pdf.js chunk |
|
|
|
+ // | A BIG IMAGE BLOB |
|
|
|
+ // | THE FULL PDF |
|
|
|
+ if (blobFetchDate < pdfCreatedAt) {
|
|
|
+ cachedBytes += chunkSize
|
|
|
+ } else {
|
|
|
+ // Blobs are fetched in bulk.
|
|
|
+ fetchedBytes += blobSize
|
|
|
+ }
|
|
|
+ }
|
|
|
return response.arrayBuffer()
|
|
|
})
|
|
|
.then(arrayBuffer => {
|
|
|
@@ -134,6 +177,8 @@ function processPdfRequest(event, { file, clsiServerId, compileGroup }) {
|
|
|
const insertPosition = Math.max(chunk.start - start, 0)
|
|
|
reAssembledBlob.set(new Uint8Array(arrayBuffer), insertPosition)
|
|
|
})
|
|
|
+
|
|
|
+ trackStats({ size, cachedBytes, fetchedBytes })
|
|
|
return new Response(reAssembledBlob, {
|
|
|
status: 206,
|
|
|
headers: {
|
|
|
@@ -152,16 +197,40 @@ function processPdfRequest(event, { file, clsiServerId, compileGroup }) {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ *
|
|
|
+ * @param {Response} response
|
|
|
+ */
|
|
|
+function getServerTime(response) {
|
|
|
+ const raw = response.headers.get('Date')
|
|
|
+ if (!raw) return undefined
|
|
|
+ return new Date(raw)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ * @param {Response} response
|
|
|
+ */
|
|
|
+function getResponseSize(response) {
|
|
|
+ const raw = response.headers.get('Content-Length')
|
|
|
+ if (!raw) return 0
|
|
|
+ return parseInt(raw, 10)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @param {Response} response
|
|
|
* @param {Object} body
|
|
|
*/
|
|
|
-function handleCompileResponse(body) {
|
|
|
+function handleCompileResponse(response, body) {
|
|
|
if (!body || body.status !== 'success') return
|
|
|
|
|
|
+ const pdfCreatedAt = getServerTime(response)
|
|
|
+
|
|
|
for (const file of body.outputFiles) {
|
|
|
if (file.path !== 'output.pdf') continue // not the pdf used for rendering
|
|
|
if (file.ranges) {
|
|
|
const { clsiServerId, compileGroup } = body
|
|
|
PDF_FILES.set(file.url, {
|
|
|
+ pdfCreatedAt,
|
|
|
file,
|
|
|
clsiServerId,
|
|
|
compileGroup,
|