From f9026798171048a42d893df04416ec76297b4346 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 3 Jun 2024 13:54:31 +0200 Subject: [PATCH] feat(debug): add congestion control debug page (#11442) 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. --- chain/client-primitives/src/debug.rs | 3 + chain/client/src/debug.rs | 1 + chain/jsonrpc/res/congestion_control.css | 43 ++++++++ chain/jsonrpc/res/congestion_control.html | 16 +++ chain/jsonrpc/res/congestion_control.js | 126 ++++++++++++++++++++++ chain/jsonrpc/res/debug.html | 1 + chain/jsonrpc/src/lib.rs | 3 + 7 files changed, 193 insertions(+) create mode 100644 chain/jsonrpc/res/congestion_control.css create mode 100644 chain/jsonrpc/res/congestion_control.html create mode 100644 chain/jsonrpc/res/congestion_control.js diff --git a/chain/client-primitives/src/debug.rs b/chain/client-primitives/src/debug.rs index 0926db64b9b..fb95bf8e86a 100644 --- a/chain/client-primitives/src/debug.rs +++ b/chain/client-primitives/src/debug.rs @@ -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, @@ -42,6 +43,8 @@ pub struct DebugChunkStatus { pub gas_used: u64, #[serde(skip_serializing_if = "Option::is_none")] pub processing_time_ms: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub congestion_info: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug)] diff --git a/chain/client/src/debug.rs b/chain/client/src/debug.rs index ec111062008..2bc5b5aea06 100644 --- a/chain/client/src/debug.rs +++ b/chain/client/src/debug.rs @@ -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![], diff --git a/chain/jsonrpc/res/congestion_control.css b/chain/jsonrpc/res/congestion_control.css new file mode 100644 index 00000000000..d9eed4dd1f5 --- /dev/null +++ b/chain/jsonrpc/res/congestion_control.css @@ -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; +} \ No newline at end of file diff --git a/chain/jsonrpc/res/congestion_control.html b/chain/jsonrpc/res/congestion_control.html new file mode 100644 index 00000000000..9de689bd809 --- /dev/null +++ b/chain/jsonrpc/res/congestion_control.html @@ -0,0 +1,16 @@ + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/chain/jsonrpc/res/congestion_control.js b/chain/jsonrpc/res/congestion_control.js new file mode 100644 index 00000000000..cd199c66e69 --- /dev/null +++ b/chain/jsonrpc/res/congestion_control.js @@ -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 = + Height + {[...Array(numShards).keys()].map(i => + Shard {i} (delayed(Tgas)/buffered(Tgas)/receipt(MiB)/allowed(shard)))} + ; + + // 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( + {toTgas(info.delayed_receipts_gas)} + {toTgas(info.buffered_receipts_gas)} + {toMiB(info.receipt_bytes)} + {info.allowed_shard} + ); + } else { + chunkCells.push( + N/A + N/A + N/A + N/A + ); + } + }); + + tableRows.push( + + + {row.block_height} + + {chunkCells} + ); + } + return
+ + + {header} + {tableRows} + +
+
+} + +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
+

{title}

+
+ delayed: sum of gas in currently delayed receipts
+ buffered: sum of gas in currently buffered receipts
+ receipt: size of borsh serialized receipts stored in state because they were delayed, buffered, postponed, or yielded
+ allowed: if fully congested, only this shard can forward receipts
+
+ {error &&
{error.stack}
} +

Blocks

+ +
; +} + +ReactDOM + .createRoot(document.getElementById('react-container')) + .render(); diff --git a/chain/jsonrpc/res/debug.html b/chain/jsonrpc/res/debug.html index 532c792606e..328b3332419 100644 --- a/chain/jsonrpc/res/debug.html +++ b/chain/jsonrpc/res/debug.html @@ -68,6 +68,7 @@

Sync info

Validator info

Client Config

Split Store

+

Congestion control

diff --git a/chain/jsonrpc/src/lib.rs b/chain/jsonrpc/src/lib.rs index 5504e22ab53..d8ecb3bb4e1 100644 --- a/chain/jsonrpc/src/lib.rs +++ b/chain/jsonrpc/src/lib.rs @@ -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, };