# Tempo Performance

Tempo Performance is the public dashboard of end-to-end benchmark results for the Tempo network.

> [!NOTE]
> The reference layout on this page composes library components with `style`. It is not a shipped component, so `@tempoxyz/ds` exports no `MetricTile` or `BenchmarkDashboard`. Copy the composition into your app and adapt it. The library has no chart component, so the layout marks where a chart goes.

## Overview

Tempo Performance runs at [perf.tempo.xyz](https://perf.tempo.xyz). It shows one benchmark run at a time: settled TPS, block time, and gas throughput, followed by charts for block validation, payload building, the transaction pool, persistence, and server resources.

A Methodology page describes the benchmark host, the workload, and how each metric is derived.

People who use it:

* **Protocol and node engineers** who compare nightly runs to catch regressions.
* **Anyone evaluating Tempo**, since the results are public.

## Surfaces

Tempo Performance is a responsive web app, built with `@tempoxyz/ds`. On a phone, the run selects stack at full width, the settled TPS tile spans the column, and the other metric tiles fall into two columns. No native app is documented.

## Key Screens

### Dashboard

```tsx
import { Button, ChoiceGroup, Field, Grid, Popover, Select, style, Surface, Table } from '@tempoxyz/ds'
import { InfoCircle } from '@tempoxyz/ds/icons'
import { useState } from 'react'

export const fill = true
const nightlies = [
  'main-2026-10-19-296be2ab',
  'main-2026-10-17-2c25f7cd',
  'main-2026-10-16-dee08cf9',
]

const scenarios = { 'public-mix': 'Public mix · multi-region' }

const metrics = [
  {
    definition: 'Number of blocks produced during the benchmark run.',
    label: 'Blocks',
    value: '142',
  },
  {
    definition: 'Average wall-clock time between consecutive blocks.',
    label: 'Block time',
    value: '633 ms',
  },
  {
    definition:
      'Average gas per second across the entire run: total gas used ÷ total run duration.',
    label: 'Avg gas/s',
    value: '1.38 Ggas/s',
  },
  {
    definition:
      'Highest gas per second achieved by any single block, from its gas usage and block time.',
    label: 'Peak gas/s',
    value: '2.00 Ggas/s',
  },
  {
    definition:
      'Total configured account set for this run, including txgen senders and pre-state recipients.',
    label: 'Accounts',
    value: '1,000',
  },
  {
    definition: 'Number of TIP-20 tokens configured for this benchmark run.',
    label: 'TIP-20 tokens',
    value: '4',
  },
]

const quantiles = [
  ['p50', '219.1 ms'],
  ['p95', '264.4 ms'],
  ['p99', '452.3 ms'],
]

function Definition(props: Definition.Props) {
  return (
    <Popover.Root>
      <Popover.Trigger
        aria-label={`About ${props.label}`}
        render={<Button shape="icon" size="small" variant="text" />}
      >
        <InfoCircle />
      </Popover.Trigger>
      <Popover.Popup>
        <Popover.Title>{props.label}</Popover.Title>
        <Popover.Description>{props.children}</Popover.Description>
      </Popover.Popup>
    </Popover.Root>
  )
}

declare namespace Definition {
  type Props = { children: string; label: string }
}

export function Demo() {
  const [nightly, setNightly] = useState<string | null>(nightlies[0] ?? null)
  const [scenario, setScenario] = useState<string | null>('public-mix')
  const commit = (nightly ?? '').slice(-8, -1)

  return (
    <div {...styles.page()}>
      <header {...styles.bar()}>
        <span {...styles.brand()}>Tempo Performance</span>
        <nav aria-label="Performance" {...styles.nav()}>
          <Button.Link aria-current="page" href="#dashboard" size="small" variant="secondary">
            Dashboard
          </Button.Link>
          <Button.Link href="#methodology" size="small" variant="text">
            Methodology
          </Button.Link>
        </nav>
      </header>

      <div {...styles.run()}>
        <div>
          <h1 {...styles.title()}>Benchmark run</h1>
          <p {...styles.meta()}>
            Oct 20, 2026, 3:01 AM ·{' '}
            <a href="#commit" {...styles.commit()}>
              {commit}
            </a>{' '}
            · 2m 35s ·{' '}
            <a href="#workflow" {...styles.link()}>
              GitHub workflow
            </a>
          </p>
        </div>
        <div {...styles.filters()}>
          <Field.Root {...styles.filter()}>
            <Field.Label>Nightly</Field.Label>
            <Select.Root onValueChange={setNightly} value={nightly}>
              <Select.Trigger size="small">
                <Select.Value>
                  {(value: string) => <span {...styles.mono()}>{value}</span>}
                </Select.Value>
                <Select.Icon />
              </Select.Trigger>
              <Select.Popup size="small">
                {nightlies.map((value) => (
                  <Select.Item key={value} value={value}>
                    <Select.ItemText>
                      <span {...styles.mono()}>{value}</span>
                    </Select.ItemText>
                  </Select.Item>
                ))}
              </Select.Popup>
            </Select.Root>
          </Field.Root>
          <Field.Root {...styles.filter()}>
            <Field.Label>Scenario</Field.Label>
            <Select.Root items={scenarios} onValueChange={setScenario} value={scenario}>
              <Select.Trigger size="small">
                <Select.Value />
                <Select.Icon />
              </Select.Trigger>
              <Select.Popup size="small">
                {Object.entries(scenarios).map(([value, label]) => (
                  <Select.Item key={value} label={label} value={value}>
                    <Select.ItemText>{label}</Select.ItemText>
                  </Select.Item>
                ))}
              </Select.Popup>
            </Select.Root>
          </Field.Root>
        </div>
      </div>

      <Surface size="medium">
        <div {...styles.label()}>
          Settled TPS
          <Definition label="Settled TPS">
            Median transactions per second that landed in blocks for this benchmark run.
          </Definition>
        </div>
        <p {...styles.hero()}>
          14,960 <span {...styles.unit()}>TPS</span>
        </p>
        <p {...styles.caption()}>
          Median settled throughput across 142 measured blocks. Run duration: 2m 35s.
        </p>
      </Surface>

      <Grid fit="small" gap="4">
        {metrics.map((metric) => (
          <Surface key={metric.label} size="medium">
            <div {...styles.label()}>
              {metric.label}
              <Definition label={metric.label}>{metric.definition}</Definition>
            </div>
            <p {...styles.value()}>{metric.value}</p>
          </Surface>
        ))}
      </Grid>

      <Surface>
        <Surface.Header {...styles.section()}>
          <Surface.Title>Block validation</Surface.Title>
          <ChoiceGroup.Root aria-label="Metric scope" defaultValue="network" size="small">
            <ChoiceGroup.Item value="network">Network</ChoiceGroup.Item>
            <ChoiceGroup.Item value="rpc">RPC node</ChoiceGroup.Item>
            <ChoiceGroup.Item value="validators">Validators</ChoiceGroup.Item>
          </ChoiceGroup.Root>
        </Surface.Header>
        <div {...styles.label()}>
          <h3 {...styles.heading()}>New payload validation latency</h3>
          <Definition label="New payload validation latency">
            Latency percentiles reported by the consensus engine while validating newPayload
            requests.
          </Definition>
        </div>
        <div {...styles.chart()}>Time-series chart. The library has no chart component yet.</div>
        <Table.Root size="small">
          <Table.Caption>Average latency by quantile for this run</Table.Caption>
          <Table.Head>
            <Table.Row>
              <Table.HeaderCell>Quantile</Table.HeaderCell>
              <Table.HeaderCell>Average</Table.HeaderCell>
            </Table.Row>
          </Table.Head>
          <Table.Body>
            {quantiles.map(([quantile, average]) => (
              <Table.Row key={quantile}>
                <Table.Cell mono>{quantile}</Table.Cell>
                <Table.Cell numeric>{average}</Table.Cell>
              </Table.Row>
            ))}
          </Table.Body>
        </Table.Root>
      </Surface>
    </div>
  )
}

namespace styles {
  export const bar = style({
    alignItems: 'center',
    display: 'flex',
    flexWrap: 'wrap',
    gap: '3',
    justifyContent: 'space-between',
  })

  export const brand = style({ typography: 'label.16.strong' })

  export const caption = style({ color: 'gray.900', margin: '0', typography: 'copy.13' })

  export const chart = style({
    alignItems: 'center',
    borderColor: 'grayAlpha.200',
    borderRadius: 'xl',
    borderStyle: 'dashed',
    borderWidth: '1px !custom',
    color: 'gray.900',
    display: 'flex',
    justifyContent: 'center',
    marginBlock: '4',
    minHeight: '48',
    padding: '4',
    textAlign: 'center',
    typography: 'copy.13',
  })

  export const commit = style({ color: 'foreground', typography: 'label.13.mono' })

  export const filter = style({ flex: '1 1 14rem !custom', minWidth: '0' })

  export const filters = style({ display: 'flex', flexWrap: 'wrap', gap: '3' })

  export const heading = style({ margin: '0', typography: 'label.14.strong' })

  export const hero = style({
    alignItems: 'baseline',
    columnGap: '2',
    display: 'flex',
    fontFamily: 'mono',
    fontSize: '4xl',
    fontVariantNumeric: 'tabular-nums !custom',
    margin: '0',
    marginBlock: '2',
  })

  export const label = style({
    alignItems: 'center',
    color: 'gray.900',
    columnGap: '1',
    display: 'flex',
    typography: 'label.13',
  })

  export const link = style({ color: 'foreground' })

  export const meta = style({ color: 'gray.900', margin: '0', typography: 'copy.13' })

  export const mono = style({ typography: 'label.13.mono' })

  export const nav = style({ columnGap: '1', display: 'flex' })

  export const page = style({
    backgroundColor: 'background.primary',
    display: 'flex',
    flexDirection: 'column',
    padding: '4',
    rowGap: '4',
  })

  export const run = style({
    alignItems: 'flex-end',
    display: 'flex',
    flexWrap: 'wrap',
    gap: '4',
    justifyContent: 'space-between',
  })

  export const section = style({ flexWrap: 'wrap', rowGap: '3' })

  export const title = style({ margin: '0', typography: 'heading.20' })

  export const unit = style({ color: 'gray.900', fontSize: 'sm' })

  export const value = style({
    fontFamily: 'mono',
    fontSize: '2xl',
    fontVariantNumeric: 'tabular-nums !custom',
    margin: '0',
    marginBlockStart: '1',
  })
}
```

[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Products/PerformanceDashboard.tsx)

The dashboard names the run, lets you pick another one, and then reads from the headline metric down to detailed sections.

* The header links to Dashboard and Methodology with `Button.Link`, and marks the current page with `aria-current="page"`. Use links rather than `Tabs`, since each is its own page.
* The run header shows the date, the short commit as a monospace link, the duration, and a link to the GitHub workflow.
* `Field` with `Select` picks the Nightly run and the Scenario. Run IDs, such as `main-2026-10-19-296be2ab`, render in monospace.
* A `Surface` holds the settled TPS hero value, with a caption that states how it's computed.
* `Grid` with `fit="small"` arranges one `Surface` per metric.
* An icon `Button` opens a `Popover` with each metric's definition.
* A section `Surface` holds a `ChoiceGroup` for the metric scope (Network, RPC node, or Validators), a chart region, and a `Table` of quantiles with `numeric` cells.

This screen follows these patterns:

* [Dashboards](/docs/patterns/dashboards) for the hero metric, tile grid, filters, and sections.
* [Tables and Lists](/docs/patterns/tables-and-lists) for the quantile table.
* [Blockchain Values](/docs/patterns/blockchain-values) for commit hashes, gas units, and block counts.

## Content

### Lead With One Hero Metric

Settled TPS is the run's headline. Give it a caption that states the aggregate and the sample, such as Median settled throughput across 142 measured blocks.

### Define Every Metric

Give every tile and chart a one-sentence definition that names what is measured and how it is aggregated. Start with the noun, as in Average wall-clock time between consecutive blocks.

Open definitions in a `Popover` from an icon button. `Tooltip` stays closed on touch input, so a phone would never show the definition.

**Do:** Open a metric's definition in a `Popover`, with the metric name as its `Popover.Title`.

**Don't:** Put a definition in a `Tooltip`, which stays closed on touch screens.

### Put the Unit With the Value

Write the full unit beside the number, such as 1.38 Ggas/s, rather than splitting it between the label and the value. Put a space between a number and its unit, as in 633 ms. Keep one format per metric across tiles, legends, and tables.

**Do:** Label the tile Avg gas/s and show 1.38 Ggas/s.

**Don't:** Show 633ms in a tile and 219.1 ms in a legend for the same kind of value.

### Say Submitted or Settled

Submitted TPS is the load generator's input. Settled TPS counts transactions included in blocks. Name which one a value is in every label, legend, and caption.

### Write Labels in Sentence Case

Write metric labels as Block time and Peak gas/s, like other Tempo UI labels. Keep acronyms and protocol names as written, such as TPS, RPC, and TIP-20.

### Identify Runs Exactly

Show run IDs in full in the Nightly select, since two runs can share a date. Use the seven-character commit in the run header and link it to the commit on GitHub.

### Name Quantiles the Same Way Everywhere

Write quantiles as p50, p95, and p99 in selectors, legends, and tables. Say whether a chart shows one quantile, as in Persistence (p50), or several.

## Open Gaps

The library has no part for these dashboard needs yet. Propose one as an issue in the Design System project before adding it.

* **Charts.** There is no time-series, stacked, or bar chart, and no axis, legend, or hover readout. The dashboard's legends also toggle series with Show only this series.
* **Metric tile.** Each tile is composed from `Surface`, a label, a definition button, and a value. All three Tempo products repeat this shape.
* **Monospace display type.** Mono typography tokens stop at `label.14.mono`. The hero and tile values set `fontFamily: 'mono'` with a `fontSize` token instead.
* **Info button.** The icon button and `Popover` pairing for definitions is composed each time.

## Related

* [Dashboards](/docs/patterns/dashboards) for metric layouts
* [Tables and Lists](/docs/patterns/tables-and-lists) for quantile and timing tables
* [Surface](/docs/components/surface) for tiles and sections
* [Grid](/docs/components/grid) for the tile layout
* [Popover](/docs/components/popover) for metric definitions
* [Select](/docs/components/select) for run and scenario filters
* [ChoiceGroup](/docs/components/choice-group) for the metric scope
