Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: track cached response times #1232

Merged
merged 4 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions packages/gateway/src/durable-objects/gateway-metrics.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import {
responseTimeHistogram,
createResponseTimeHistogramObject,
} from '../utils/histogram.js'

/**
* @typedef {Object} GatewayMetrics
* @property {number} totalResponseTime total response time of the requests
Expand Down Expand Up @@ -86,7 +91,9 @@ export class GatewayMetrics0 {
}

// Get all the histogram buckets where the response time is smaller
const histogramCandidates = histogram.filter((h) => stats.responseTime < h)
const histogramCandidates = responseTimeHistogram.filter(
(h) => stats.responseTime < h
)
histogramCandidates.forEach((candidate) => {
gwHistogram[candidate] += 1
})
Expand All @@ -95,21 +102,14 @@ export class GatewayMetrics0 {
}
}

// We will count occurences per bucket where response time is less or equal than bucket value
export const histogram = [
300, 500, 750, 1000, 1500, 2000, 3000, 5000, 10000, 20000,
]

function createMetricsTracker() {
const h = histogram.map((h) => [h, 0])

/** @type {GatewayMetrics} */
const m = {
totalResponseTime: 0,
totalWinnerRequests: 0,
totalResponsesByStatus: {},
totalRequestsPreventedByReason: {},
responseTimeHistogram: Object.fromEntries(h),
responseTimeHistogram: createResponseTimeHistogramObject(),
}

return m
Expand Down
85 changes: 67 additions & 18 deletions packages/gateway/src/durable-objects/summary-metrics.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import {
responseTimeHistogram,
createResponseTimeHistogramObject,
} from '../utils/histogram.js'

/**
* @typedef {Object} SummaryMetrics
* @property {number} totalWinnerResponseTime total response time of the requests
* @property {number} totalWinnerSuccessfulRequests total number of successful requests
* @property {number} totalCachedResponseTime total response time to forward cached responses
* @property {number} totalCachedResponses total number of cached responses
* @property {BigInt} totalContentLengthBytes total content length of responses
* @property {BigInt} totalCachedContentLengthBytes total content length of cached responses
* @property {Record<string, number>} contentLengthHistogram
* @property {Record<string, number>} responseTimeHistogram
*
* @typedef {Object} ResponseWinnerStats
* @typedef {Object} FetchStats
* @property {number} responseTime number of milliseconds to get response
* @property {number} contentLength content length header content
*
* @typedef {Object} ContentLengthStats
* @property {number} contentLength content length header content
*/

// Key to track total time for winner gateway to respond
const TOTAL_WINNER_RESPONSE_TIME_ID = 'totalWinnerResponseTime'
// Key to track total successful requests
const TOTAL_WINNER_SUCCESSFUL_REQUESTS_ID = 'totalWinnerSuccessfulRequests'
// Key to track total time for forwarding cached response
const TOTAL_CACHED_RESPONSE_TIME_ID = 'totalCachedResponseTime'
// Key to track total cached requests
const TOTAL_CACHED_RESPONSES_ID = 'totalCachedResponses'
// Key to track total content length of responses
Expand All @@ -27,11 +33,13 @@ const TOTAL_CONTENT_LENGTH_BYTES_ID = 'totalContentLengthBytes'
const TOTAL_CACHED_CONTENT_LENGTH_BYTES_ID = 'totalCachedContentLengthBytes'
// Key to track content size histogram
const CONTENT_LENGTH_HISTOGRAM_ID = 'contentLengthHistogram'
// Key to track response time histogram
const RESPONSE_TIME_HISTOGRAM_ID = 'responseTimeHistogram'

/**
* Durable Object for keeping generic Metrics of gateway.nft.storage
* Durable Object for keeping summary metrics of gateway.nft.storage
*/
export class SummaryMetrics0 {
export class SummaryMetrics1 {
constructor(state) {
this.state = state

Expand All @@ -43,6 +51,9 @@ export class SummaryMetrics0 {
// Total successful requests
this.totalWinnerSuccessfulRequests =
(await this.state.storage.get(TOTAL_WINNER_SUCCESSFUL_REQUESTS_ID)) || 0
// Total cached response time
this.totalCachedResponseTime =
(await this.state.storage.get(TOTAL_CACHED_RESPONSE_TIME_ID)) || 0
// Total cached requests
this.totalCachedResponses =
(await this.state.storage.get(TOTAL_CACHED_RESPONSES_ID)) || 0
Expand All @@ -57,7 +68,11 @@ export class SummaryMetrics0 {
// Content length histogram
this.contentLengthHistogram =
(await this.state.storage.get(CONTENT_LENGTH_HISTOGRAM_ID)) ||
createHistogramObject()
createContentLengthHistogramObject()
// Response time histogram
this.responseTimeHistogram =
(await this.state.storage.get(RESPONSE_TIME_HISTOGRAM_ID)) ||
createResponseTimeHistogramObject()
})
}

Expand All @@ -74,11 +89,13 @@ export class SummaryMetrics0 {
JSON.stringify({
totalWinnerResponseTime: this.totalWinnerResponseTime,
totalWinnerSuccessfulRequests: this.totalWinnerSuccessfulRequests,
totalCachedResponseTime: this.totalCachedResponseTime,
totalCachedResponses: this.totalCachedResponses,
totalContentLengthBytes: this.totalContentLengthBytes.toString(),
totalCachedContentLengthBytes:
this.totalCachedContentLengthBytes.toString(),
contentLengthHistogram: this.contentLengthHistogram,
responseTimeHistogram: this.responseTimeHistogram,
})
)
default:
Expand All @@ -87,33 +104,36 @@ export class SummaryMetrics0 {
}

// POST
let data
/** @type {FetchStats} */
const data = await request.json()
switch (url.pathname) {
case '/metrics/winner':
/** @type {ResponseWinnerStats} */
data = await request.json()
await this._updateWinnerMetrics(data)
return new Response()
case '/metrics/cache':
/** @type {ContentLengthStats} */
data = await request.json()
await this._updateWinnerMetrics(data)
await this._updatedCacheMetrics(data)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😱 😱 😱 😱 😱 😱 😱 😱 😱 😱 😱 😱 😱 😱

return new Response()
default:
return new Response('Not found', { status: 404 })
}
}

/**
* @param {ContentLengthStats} stats
* @param {FetchStats} stats
*/
async _updatedCacheMetrics(stats) {
// Update metrics
this.totalCachedResponseTime += stats.responseTime
this.totalCachedResponses += 1
this.totalCachedContentLengthBytes += BigInt(stats.contentLength)
this._updateContentLengthMetrics(stats)
// Sabe updated metrics
this._updateResponseTimeHistogram(stats)
// Save updated metrics
await Promise.all([
this.state.storage.put(
TOTAL_CACHED_RESPONSE_TIME_ID,
this.totalCachedResponseTime
),
this.state.storage.put(
TOTAL_CACHED_RESPONSES_ID,
this.totalCachedResponses
Expand All @@ -130,17 +150,22 @@ export class SummaryMetrics0 {
CONTENT_LENGTH_HISTOGRAM_ID,
this.contentLengthHistogram
),
this.state.storage.put(
RESPONSE_TIME_HISTOGRAM_ID,
this.responseTimeHistogram
),
])
}

/**
* @param {ResponseWinnerStats} stats
* @param {FetchStats} stats
*/
async _updateWinnerMetrics(stats) {
// Updated Metrics
this.totalWinnerResponseTime += stats.responseTime
this.totalWinnerSuccessfulRequests += 1
this._updateContentLengthMetrics(stats)
this._updateResponseTimeHistogram(stats)
// Save updated Metrics
await Promise.all([
this.state.storage.put(
Expand All @@ -159,11 +184,15 @@ export class SummaryMetrics0 {
CONTENT_LENGTH_HISTOGRAM_ID,
this.contentLengthHistogram
),
this.state.storage.put(
RESPONSE_TIME_HISTOGRAM_ID,
this.responseTimeHistogram
),
])
}

/**
* @param {ContentLengthStats} stats
* @param {FetchStats} stats
*/
_updateContentLengthMetrics(stats) {
this.totalContentLengthBytes += BigInt(stats.contentLength)
Expand All @@ -183,9 +212,29 @@ export class SummaryMetrics0 {

this.contentLengthHistogram = tmpHistogram
}

/**
* @param {FetchStats} stats
*/
_updateResponseTimeHistogram(stats) {
const tmpHistogram = {
...this.responseTimeHistogram,
}

// Get all the histogram buckets where the response time is smaller
const histogramCandidates = responseTimeHistogram.filter(
(h) => stats.responseTime < h
)

histogramCandidates.forEach((candidate) => {
tmpHistogram[candidate] += 1
})

this.responseTimeHistogram = tmpHistogram
}
}

function createHistogramObject() {
function createContentLengthHistogramObject() {
const h = contentLengthHistogram.map((h) => [h, 0])
return Object.fromEntries(h)
}
Expand Down
13 changes: 9 additions & 4 deletions packages/gateway/src/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ import {
* @param {import('./index').Ctx} ctx
*/
export async function gatewayGet(request, env, ctx) {
const startTs = Date.now()
const cache = caches.default
let res = await cache.match(request.url)

if (res) {
// Update cache metrics in background
ctx.waitUntil(updateSummaryCacheMetrics(request, env, res))
const responseTime = Date.now() - startTs

ctx.waitUntil(updateSummaryCacheMetrics(request, env, res, responseTime))
return res
}

Expand Down Expand Up @@ -222,15 +225,17 @@ function getHeaders(request) {
* @param {Request} request
* @param {import('./env').Env} env
* @param {Response} response
* @param {number} responseTime
*/
async function updateSummaryCacheMetrics(request, env, response) {
async function updateSummaryCacheMetrics(request, env, response, responseTime) {
// Get durable object for gateway
const id = env.summaryMetricsDurable.idFromName(SUMMARY_METRICS_ID)
const stub = env.summaryMetricsDurable.get(id)

/** @type {import('./durable-objects/summary-metrics').ContentLengthStats} */
/** @type {import('./durable-objects/summary-metrics').FetchStats} */
const contentLengthStats = {
contentLength: Number(response.headers.get('content-length')),
responseTime,
}

await stub.fetch(
Expand Down Expand Up @@ -267,7 +272,7 @@ async function updateSummaryWinnerMetrics(request, env, gwResponse) {
const id = env.summaryMetricsDurable.idFromName(SUMMARY_METRICS_ID)
const stub = env.summaryMetricsDurable.get(id)

/** @type {import('./durable-objects/summary-metrics').ResponseWinnerStats} */
/** @type {import('./durable-objects/summary-metrics').FetchStats} */
const fetchStats = {
responseTime: gwResponse.responseTime,
contentLength: Number(gwResponse.response.headers.get('content-length')),
Expand Down
2 changes: 1 addition & 1 deletion packages/gateway/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { metricsGet } from './metrics.js'

// Export Durable Object namespace from the root module.
export { GatewayMetrics0 } from './durable-objects/gateway-metrics.js'
export { SummaryMetrics0 } from './durable-objects/summary-metrics.js'
export { SummaryMetrics1 } from './durable-objects/summary-metrics.js'
export { CidsTracker0 } from './durable-objects/cids.js'
export { GatewayRateLimits0 } from './durable-objects/gateway-rate-limits.js'

Expand Down
44 changes: 40 additions & 4 deletions packages/gateway/src/metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
SUMMARY_METRICS_ID,
HTTP_STATUS_SUCCESS,
} from './constants.js'
import { histogram } from './durable-objects/gateway-metrics.js'
import { responseTimeHistogram } from './utils/histogram.js'
import { contentLengthHistogram } from './durable-objects/summary-metrics.js'

/**
Expand Down Expand Up @@ -80,9 +80,26 @@ export async function metricsGet(request, env, ctx) {
})

const metrics = [
`# HELP nftgateway_summary_responses_total Total winner and cached responses returned.`,
`# TYPE nftgateway_summary_responses_total counter`,
`nftgateway_summary_responses_total{env="${env.ENV}"} ${
metricsCollected.summaryMetrics.totalWinnerSuccessfulRequests +
metricsCollected.summaryMetrics.totalCachedResponses
}`,
`# HELP nftgateway_summary_response_time_seconds_total Accumulated summary response time of winner responses and cached responses.`,
`# TYPE nftgateway_summary_response_time_seconds_total summary`,
`nftgateway_summary_response_time_seconds_total{env="${env.ENV}"} ${msToS(
metricsCollected.summaryMetrics.totalWinnerResponseTime +
metricsCollected.summaryMetrics.totalCachedResponseTime
)}`,
`# HELP nftgateway_cache_hit_responses_total Total cached responses returned.`,
`# TYPE nftgateway_cache_hit_responses_total counter`,
`nftgateway_cache_hit_responses_total{env="${env.ENV}"} ${metricsCollected.summaryMetrics.totalCachedResponses}`,
`# HELP nftgateway_cache_hit_response_time_seconds_total Accumulated cached response time.`,
`# TYPE nftgateway_cache_hit_response_time_seconds_total summary`,
`nftgateway_cache_hit_response_time_seconds_total{env="${env.ENV}"} ${msToS(
metricsCollected.summaryMetrics.totalCachedResponseTime
)}`,
`# HELP nftgateway_winner_requests_total Total winner requests.`,
`# TYPE nftgateway_winner_requests_total counter`,
`nftgateway_winner_requests_total{env="${env.ENV}"} ${metricsCollected.summaryMetrics.totalWinnerSuccessfulRequests}`,
Expand All @@ -91,6 +108,18 @@ export async function metricsGet(request, env, ctx) {
`nftgateway_winner_response_time_seconds_total{env="${env.ENV}"} ${msToS(
metricsCollected.summaryMetrics.totalWinnerResponseTime
)}`,
`# HELP nftgateway_summary_responses_per_time_total total of responses per response time bucket`,
`# TYPE nftgateway_summary_responses_per_time_total histogram`,
...responseTimeHistogram.map(
(t) =>
`nftgateway_summary_responses_per_time_total{le="${msToS(t)}",env="${
env.ENV
}"} ${metricsCollected.summaryMetrics.responseTimeHistogram[t]}`
),
`nftgateway_summary_responses_per_time_total{le="+Inf",env="${env.ENV}"} ${
metricsCollected.summaryMetrics.totalWinnerSuccessfulRequests +
metricsCollected.summaryMetrics.totalCachedResponses
}`,
`# HELP nftgateway_response_time_seconds_total Accumulated response time of each gateway.`,
`# TYPE nftgateway_response_time_seconds_total summary`,
...env.ipfsGateways.map(
Expand Down Expand Up @@ -184,11 +213,15 @@ export async function metricsGet(request, env, ctx) {
),
`# HELP nftgateway_requests_per_time_total total of requests per response time bucket`,
`# TYPE nftgateway_requests_per_time_total histogram`,
...histogram.map((t) => {
...responseTimeHistogram.map((t) => {
return env.ipfsGateways
.map(
(gw) =>
`nftgateway_requests_per_time_total{gateway="${gw}",le="${t}",env="${env.ENV}"} ${metricsCollected.ipfsGateways[gw].responseTimeHistogram[t]}`
`nftgateway_requests_per_time_total{gateway="${gw}",le="${msToS(
Copy link
Contributor Author

@vasco-santos vasco-santos Feb 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also made the le with seconds as we use seconds everywhere per Prometheus best practises

t
)}",env="${env.ENV}"} ${
metricsCollected.ipfsGateways[gw].responseTimeHistogram[t]
}`
)
.join('\n')
}),
Expand All @@ -208,7 +241,10 @@ export async function metricsGet(request, env, ctx) {
(t) =>
`nftgateway_responses_content_length_total{le="${t}",env="${env.ENV}"} ${metricsCollected.summaryMetrics.contentLengthHistogram[t]}`
),
`nftgateway_responses_content_length_total{le="+Inf",env="${env.ENV}"} ${metricsCollected.summaryMetrics.totalWinnerSuccessfulRequests}`,
`nftgateway_responses_content_length_total{le="+Inf",env="${env.ENV}"} ${
metricsCollected.summaryMetrics.totalWinnerSuccessfulRequests +
metricsCollected.summaryMetrics.totalCachedResponses
}`,
`# HELP nftgateway_responses_content_length_bytes_total Accumulated content length of delivered cached responses`,
`# TYPE nftgateway_responses_content_length_bytes_total summary`,
`nftgateway_responses_content_length_bytes_total{env="${env.ENV}"} ${metricsCollected.summaryMetrics.totalContentLengthBytes}`,
Expand Down
8 changes: 8 additions & 0 deletions packages/gateway/src/utils/histogram.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const responseTimeHistogram = [
50, 100, 200, 300, 500, 750, 1000, 1500, 2000, 3000, 5000, 10000, 20000,
]

export function createResponseTimeHistogramObject() {
const h = responseTimeHistogram.map((h) => [h, 0])
return Object.fromEntries(h)
}
Loading