Skip to content

Commit

Permalink
feat(debug): add congestion control debug page (#11442)
Browse files Browse the repository at this point in the history
Adding a new debug page for congestion control at
`/debug/pages/congestion_control`.
It shows the list of most recent blocks and chunks. There is information
about various parameters that can tell the level of congestion across
shards.

No data is shown if the congestion control feature is not enabled.
  • Loading branch information
Trisfald authored Jun 3, 2024
1 parent 42e65a6 commit f902679
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 0 deletions.
3 changes: 3 additions & 0 deletions chain/client-primitives/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//! without backwards compatibility of JSON encoding.
use crate::types::StatusError;
use near_async::time::Utc;
use near_primitives::congestion_info::CongestionInfo;
use near_primitives::types::EpochId;
use near_primitives::views::{
CatchupStatusView, ChainProcessingInfo, EpochValidatorInfo, RequestedStatePartsView,
Expand Down Expand Up @@ -42,6 +43,8 @@ pub struct DebugChunkStatus {
pub gas_used: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub processing_time_ms: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub congestion_info: Option<CongestionInfo>,
}

#[derive(serde::Serialize, serde::Deserialize, Debug)]
Expand Down
1 change: 1 addition & 0 deletions chain/client/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ impl ClientActorInner {
chunk.chunk_hash().0,
)
.map(|s| s.whole_milliseconds() as u64),
congestion_info: chunk.congestion_info(),
})
.collect(),
None => vec![],
Expand Down
43 changes: 43 additions & 0 deletions chain/jsonrpc/res/congestion_control.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.explanation {
color: black;
}

.error {
color: red;
white-space: pre;
}

table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}

table,
th,
td {
border: 1px solid black;
}

td {
text-align: left;
padding: 8px;
vertical-align: middle;
}

th {
text-align: center;
vertical-align: middle;
padding: 8px;
background-color: lightgrey;
}

.block_height {
background-color: lightgray;
font-weight: bold;
}

.not_available {
font-style: italic;
color: lightgray;
}
16 changes: 16 additions & 0 deletions chain/jsonrpc/res/congestion_control.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<html>

<head>
<link rel="stylesheet" href="congestion_control.css">
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
</head>

<body>
<div id="react-container"></div>

<script type="text/jsx" src="congestion_control.js"></script>
</body>

</html>
126 changes: 126 additions & 0 deletions chain/jsonrpc/res/congestion_control.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
function sortBlocks(blocks) {
function sortingKey(row) {
// Note: using the expression `row.block_timestamp / 1e12 % 1` reduces the timestamp
// to a number between 0 and 1, making block_timestamp strictly weaker than block_height
// in this comparison.
return row.block_height + (row.block_timestamp / 1e12 % 1);
}
blocks.sort((a, b) => sortingKey(b) - sortingKey(a));
return blocks;
}

function toTgas(gas) {
return (gas / (1024 * 1024 * 1024 * 1024)).toFixed(2)
}

function toMiB(bytes) {
return (bytes / (1024 * 1024)).toFixed(2)
}

function BlocksTable({ rows }) {
let numShards = 0;
for (let row of rows) {
for (let chunk of row.chunks) {
numShards = Math.max(numShards, chunk.shard_id + 1);
}
}
const header = <tr>
<th>Height</th>
{[...Array(numShards).keys()].map(i =>
<th key={i} colSpan="4">Shard {i} (delayed(Tgas)/buffered(Tgas)/receipt(MiB)/allowed(shard))</th>)}
</tr>;

// One 'tr' element per row.
const tableRows = [];
for (let i = 0; i < rows.length; i++) {
const row = rows[i];

const chunkCells = [];
row.chunks.forEach((chunk, shardId) => {
if (chunk.congestion_info) {
const info = chunk.congestion_info.V1;
chunkCells.push(<React.Fragment key={shardId}>
<td>{toTgas(info.delayed_receipts_gas)}</td>
<td>{toTgas(info.buffered_receipts_gas)}</td>
<td>{toMiB(info.receipt_bytes)}</td>
<td>{info.allowed_shard}</td>
</React.Fragment>);
} else {
chunkCells.push(<React.Fragment key={shardId}>
<td className="not_available">N/A</td>
<td className="not_available">N/A</td>
<td className="not_available">N/A</td>
<td className="not_available">N/A</td>
</React.Fragment>);
}
});

tableRows.push(
<tr key={row.block_hash}>
<td className="block_height">
<span>{row.block_height}</span>
</td>
{chunkCells}
</tr>);
}
return <div>
<table>
<tbody>
{header}
{tableRows}
</tbody>
</table>
</div>
}

function Page() {
const [rows, setRows] = React.useState([]);
const [error, setError] = React.useState(null);
let blockStatusApiPath = '../api/block_status';
const url = new URL(window.location.toString());
let title = 'Congestion control';
if (url.searchParams.has('height')) {
blockStatusApiPath += '/' + url.searchParams.get('height');
title = 'Blocks from ' + url.searchParams.get('height');
}
// useEffect with empty dependency list means to run this once at beginning.
React.useEffect(() => {
(async () => {
try {
let resp = await fetch('../api/status');
if (resp.status == 405) {
throw new Error('Debug not allowed - did you set enable_debug_rpc: true in your config?');
} else if (!resp.ok) {
throw new Error('Debug API call failed: ' + resp.statusText);
}

resp = await fetch(blockStatusApiPath);
if (!resp.ok) {
throw new Error('Could not fetch block debug status: ' + resp.statusText);
}
const { status_response: { BlockStatus: data } } = await resp.json();
setRows(sortBlocks(data.blocks));
} catch (error) {
setError(error);
}
})();
}, []);

return <div>
<h1>{title}</h1>
<div className="explanation">
<b>delayed</b>: sum of gas in currently delayed receipts<br />
<b>buffered</b>: sum of gas in currently buffered receipts<br />
<b>receipt</b>: size of borsh serialized receipts stored in state because they were delayed, buffered, postponed, or yielded<br />
<b>allowed</b>: if fully congested, only this shard can forward receipts<br />
</div>
{error && <div className="error">{error.stack}</div>}
<h2>Blocks</h2>
<BlocksTable
rows={rows} />
</div>;
}

ReactDOM
.createRoot(document.getElementById('react-container'))
.render(<Page />);
1 change: 1 addition & 0 deletions chain/jsonrpc/res/debug.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ <h1><a href="debug/pages/sync">Sync info</a></h1>
<h1><a href="debug/pages/validator">Validator info</a></h1>
<h1><a href="debug/client_config">Client Config</a></h1>
<h1><a href="debug/pages/split_store">Split Store</a></h1>
<h1><a href="debug/pages/congestion_control">Congestion control</a></h1>
</body>

</html>
3 changes: 3 additions & 0 deletions chain/jsonrpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1482,6 +1482,9 @@ async fn display_debug_html(
"validator" => Some(debug_page_string!("validator.html", handler)),
"validator.css" => Some(debug_page_string!("validator.css", handler)),
"split_store" => Some(debug_page_string!("split_store.html", handler)),
"congestion_control" => Some(debug_page_string!("congestion_control.html", handler)),
"congestion_control.css" => Some(debug_page_string!("congestion_control.css", handler)),
"congestion_control.js" => Some(debug_page_string!("congestion_control.js", handler)),
_ => None,
};

Expand Down

0 comments on commit f902679

Please sign in to comment.