Skip to content

Latest commit

 

History

History
474 lines (439 loc) · 12.9 KB

index.md

File metadata and controls

474 lines (439 loc) · 12.9 KB
toc
import { LineGraph } from './components/line-graph.js'
import { Histogram } from './components/histogram.js'
import { todayInFormat, getDateXDaysAgo } from './utils/date-utils.js'
import { combine, move, clone } from './utils/ratios-utils.js'
const SparkRates = FileAttachment('./data/spark-rsr.json').json()
const SparkNonZeroRates = FileAttachment(
  './data/spark-rsr-non-zero.json',
).json()
const SparkMinerRates = FileAttachment('./data/spark-miners-rsr.json').json()
const SparkMinerRetrievalTimings = FileAttachment(
  './data/spark-miners-retrieval-timings.json',
).json()
const SparkRetrievalResultCodes = FileAttachment(
  './data/spark-retrieval-result-codes.json',
).json()
const SparkMinerRsrSummaries = FileAttachment(
  './data/spark-miner-rsr-summaries.json',
).json()
const SparkRetrievalTimes = FileAttachment(
  './data/spark-retrieval-timings.json',
).json()
const SparkClientRates = FileAttachment('./data/spark-clients-rsr.json').json()
const sparkMinerRetrievalTimingsMap = SparkMinerRetrievalTimings.reduce(
  (acc, record) => {
    acc[record.miner_id] = record
    return acc
  },
  {},
)

const nonZeroSparkMinerRates = SparkMinerRates.filter(
  (record) => record.success_rate != 0,
)
const tidySparkMinerRates = SparkMinerRates.sort(
  (recordA, recordB) => recordB.success_rate - recordA.success_rate,
).map((record) => {
  const { ttfb_ms } = sparkMinerRetrievalTimingsMap[record.miner_id] ?? {}
  delete record.successful
  delete record.successful_http
  return {
    ...record,
    ttfb_ms,
    success_rate: `${(record.success_rate * 100).toFixed(2)}%`,
    success_rate_http: `${(record.success_rate_http * 100).toFixed(2)}%`,
    success_rate_http_head: `${(record.success_rate_http_head * 100).toFixed(2)}%`,
  }
})
const tidySparkClientRates = SparkClientRates.sort(
  (recordA, recordB) => recordB.success_rate - recordA.success_rate,
).map((record) => {
  delete record.successful
  delete record.successful_http
  return {
    ...record,
    success_rate: `${(record.success_rate * 100).toFixed(2)}%`,
    success_rate_http: `${(record.success_rate_http * 100).toFixed(2)}%`,
  }
})

Overall Spark RSR

This section shows the overall Spark Retrieval Success Rate Score of Filecoin. You can adjust the date range. Records start on the 7th April 2024.
const start = view(Inputs.date({ label: 'Start', value: getDateXDaysAgo(180) }))
const end = view(Inputs.date({ label: 'End', value: getDateXDaysAgo(1) }))
${ resize((width) => LineGraph(SparkRates, {width, title: "Retrieval Success Rate", start, end })) }
${ resize((width) => LineGraph(SparkNonZeroRates, {width, title: "Non-zero Miners: Retrieval Success Rate", start, end })) }

Spark Miner RSR Histograms

The following histograms use the Spark RSR values calculated in aggregate for each Filecoin Storage Provider over the past 30 days.
${ resize((width) => Histogram(SparkMinerRates, { width, title: "Retrieval Success Rate Buckets", thresholds: 10 })) }
${ resize((width) => Histogram(nonZeroSparkMinerRates.map((record) => ({success_rate: record.success_rate, success_rate_http: record.success_rate_http? record.success_rate_http: null, success_rate_http_head: record.success_rate_http_head? record.success_rate_http_head: null})), { width, title: "Non-zero Miners: Retrieval Success Rate Buckets", thresholds: 10 })) }

Spark Miner RSR buckets over time

const countAbove = (a, t) => a.filter((v) => v > t).length
const nonZeroMinersOverTime = Object.entries(SparkMinerRsrSummaries).flatMap(
  ([day, miners]) => [
    {
      day: new Date(day),
      count_succes_rate: countAbove(
        miners.map((m) => m.success_rate),
        0,
      ),
      type: 'HTTP or Graphsync',
    },
    {
      day: new Date(day),
      count_succes_rate_http: miners.some((m) => m.success_rate_http != null)
        ? countAbove(
            miners.map((m) => m.success_rate_http),
            0,
          )
        : null,
      type: 'HTTP only',
    },
    {
      day: new Date(day),
      count_succes_rate_http_head: miners.some(
        (m) => m.success_rate_http_head != null,
      )
        ? countAbove(
            miners.map((m) => m.success_rate_http_head),
            0,
          )
        : null,
      type: 'HTTP only w/ HEAD',
    },
  ],
)
const percentiles = Object.entries(SparkMinerRsrSummaries).flatMap(
  ([day, miners]) =>
    [0.8, 0.9, 0.95, 0.99, 0.995, 0.999].map((above) => ({
      day: new Date(day),
      label: `> ${above * 100}%`,
      count_succes_rate: countAbove(
        miners.map((m) => m.success_rate),
        above,
      ),
    })),
)
${Plot.plot({ title: '# of Filecoin SPs with a non-zero Spark Retrieval Success Rate', x: { label: null }, y: { grid: true, label: '# Non-Zero SPs' }, color: { legend: true }, marks: [ Plot.ruleY([0]), Plot.lineY(nonZeroMinersOverTime, { x: 'day', y: 'count_succes_rate', stroke: "type", curve: 'catmull-rom', tip: { format: { x: d => new Date(d).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }), y: v => `${v} SPs`, type: true } } }), Plot.lineY(nonZeroMinersOverTime, { x: 'day', y: 'count_succes_rate_http', stroke: "type", curve: 'catmull-rom', tip: { format: { x: d => new Date(d).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }), y: v => v ? `${v} SPs` : 'N/A', type: true } } }) ] })}
${Plot.plot({ title: '# of Filecoin SPs with Spark Retrieval Success Rate above x%', x: { label: null }, y: { grid: true, label: '# SPs above x%' }, color: { scheme: "Paired", legend: "swatches" }, marks: [ Plot.ruleY([0]), Plot.line(percentiles, { x: 'day', y: 'count_succes_rate', stroke: 'label', curve: 'catmull-rom', tip: { format: { x: d => new Date(d).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }), y: v => `${v} SPs`, label: true } } }) ] })}
// prettier-ignore
const mapping = {
  'HTTP 5xx': [
    /^HTTP_5/,
    /^ERROR_5/,
    'BAD_GATEWAY',
    'GATEWAY_TIMEOUT'
  ],
  'Graphsync timeout': [
    'LASSIE_504'
  ],
  'Graphsync error': [
    /^LASSIE_(?!504)/
  ],
  'IPNI no advertisement': [
    'IPNI_ERROR_404',
    'IPNI_NO_VALID_ADVERTISEMENT',
  ],
  'IPNI error': [
    /^IPNI_ERROR_/
  ],
  'Other': [
    'CANNOT_PARSE_CAR_FILE',
    'CAR_TOO_LARGE',
    'UNKNOWN_FETCH_ERROR',
    'HOSTNAME_DNS_ERROR',
    'CONNECTION_REFUSED',
    'UNSUPPORTED_MULTIADDR_FORMAT',
    /^ERROR_4/,
    'TIMEOUT',
  ],
}
const tidy = clone(SparkRetrievalResultCodes).flatMap(({ day, rates }) => {
  for (const [key, value] of Object.entries(rates)) {
    rates[key] = Number(value)
  }
  for (const [label, codes] of Object.entries(mapping)) {
    combine(rates, label, codes)
  }
  const sorted = {}
  move(rates, sorted, 'OK')
  move(rates, sorted, 'HTTP 5xx')
  move(rates, sorted, 'Graphsync error')
  move(rates, sorted, 'IPNI error')
  move(rates, sorted, 'IPNI no advertisement')
  for (const [key, value] of Object.entries(rates)) {
    if (key !== 'Other') {
      move(rates, sorted, key)
    }
  }
  move(rates, sorted, 'Other')

  return Object.entries(sorted).map(([code, rate]) => ({
    day: new Date(day),
    code,
    rate,
  }))
})

Spark Retrieval Result Codes

This section shows the Spark Retrieval Result Codes breakdown.
${Plot.plot({ x: {label: null, type: "band", ticks: "week" }, y: { percent: true }, color: { scheme: "Accent", legend: "swatches", label: "code" }, marks: [ Plot.rectY(tidy, { x: "day", y: "rate", fill: "code", offset: "normalize", sort: {color: null, x: "-y" }, interval: 'day', tip: { format: { x: d => new Date(d).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }), y: v => v.toFixed(2), code: true } } }) ] })}

Spark Time To First Byte (TTFB)

The section shows the median of all median TTFB values from all retrieval tasks.
${Plot.plot({ title: 'Time to First Byte (ms)', x: { type: 'utc', ticks: 'month' }, y: { grid: true, zero: true, label: 'ttfb (ms)' }, marks: [ Plot.lineY(SparkRetrievalTimes, { x: 'day', y: 'ttfb_ms', stroke: "#FFBD3F", tip: { format: { x: d => new Date(d).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }), y: v => v.toFixed(0) } } }) ] })}
Result code to label mapping
${JSON.stringify(
  mapping,
  (key, value) => {
    return value instanceof RegExp
      ? value.toString()
      : value
  },
  2
)}

Spark Miner Stats Table

The following table shows the Spark RSR and TTFB values calculated in aggregate for each Filecoin Storage Provider over the past 30 days. Click on a miner id to view stats about this storage provider.
const searchMinerStats = view(
  Inputs.search(tidySparkMinerRates, {
    placeholder: 'Search Storage Providers…',
  }),
)
${Inputs.table(searchMinerStats, {rows: 16, format: {miner_id: id => htl.html`${id}`}})}

Spark Client RSR Table

The following table shows the Spark RSR values calculated in aggregate for each Filecoin Storage Client over the past 30 days. Click on a client id to view stats about this storage client.
const searchClientStats = view(
  Inputs.search(tidySparkClientRates, {
    placeholder: 'Search Storage Clients...',
  }),
)
${Inputs.table(searchClientStats, {rows: 16, format: {client_id: id => htl.html`${id}`}})}
<style> .hero { display: flex; flex-direction: column; align-items: center; font-family: var(--sans-serif); margin: 4rem 0 8rem; text-wrap: balance; text-align: center; } .hero h1 { margin: 1rem 0; padding: 1rem 0; max-width: none; font-size: 14vw; font-weight: 900; line-height: 1; background: linear-gradient(30deg, var(--theme-foreground-focus), currentColor); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .hero h2 { margin: 0; max-width: 34em; font-size: 20px; font-style: initial; font-weight: 500; line-height: 1.5; color: var(--theme-foreground-muted); } .divider { margin: 50px; } @media (min-width: 640px) { .hero h1 { font-size: 90px; } } </style>