feat(bench): regenerar weights deja de ser una promesa y pasa a ser un comando
Durante meses la cabecera de los cinco weights.rs prometia que regenerar "es mecanico". No lo era: no habia comando, ni plantilla, ni forma de correrlo. La promesa costo lo que cuestan las promesas sin codigo — al medir por primera vez, los 19 extrinsics estaban SUBDECLARADOS, los 19. Tres piezas: · scripts/bench/regen-weights.sh — construye el WASM con runtime-benchmarks y mide los 5 pallets. SKIP_BUILD=1 + WASM=<ruta> mide un blob YA construido: es el modo del banco de referencia, donde el Pi 5 no compila Substrate sino que recibe el blob que se despliega, de modo que el binario medido y el desplegado son el MISMO. · scripts/bench/origin-weights-template.hbs — emite la forma de la casa (trait + SubstrateWeight<T> + impl para ()) y, dentro, un guard GENERADO a partir de los benchmarks reales que reprueba cualquier proof_size = 0 o ref_time = 0. Generado, no escrito: no puede quedarse corto cuando se anada un extrinsic. · scripts/bench/HEADER.txt — la cabecera que va dentro de cada fichero generado, diciendo que un weight es una latencia y que sin su hardware y su perfil de potencia no se reproduce. El script documenta ademas dos cosas medidas hoy que cuestan horas si no estan escritas: (1) hay que usar frame-omni-bencher 0.15.0, porque la 0.23 trae sp-storage 25 contra el sp-storage 22 de este runtime y TrackedStorageKey no decodifica al cruzar el FFI — falla con "unexpected first byte decoding Option", que no se parece a "version equivocada"; (2) el governor ondemand arranca a 1600 MHz y sube a 2400, asi que las primeras muestras salen ~50% lentas y el weight sale de la rampa. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b7010db328
commit
b72650974d
3 files changed
+220
No files matched your search
@@ -0,0 +1,9 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Origin Chain — Pilar 4 · Charon
|
||||||
|
//
|
||||||
|
// ATENCION AL LEER ESTOS NUMEROS: un weight es una LATENCIA, y una latencia sin
|
||||||
|
// el hardware y el perfil de potencia que la produjeron no se puede reproducir.
|
||||||
|
// La cabecera dice HOSTNAME y CPU: creele a eso y a nada mas. Un weight medido
|
||||||
|
// en un portatil y cobrado en un Pi 5 es mispricing estructural, solo que
|
||||||
|
// invisible. Los numeros CANONICOS de mainnet salen del banco de referencia
|
||||||
|
// (Pi 5, governor y aislamiento declarados), no de la maquina de quien compila.
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
{{header}}
|
||||||
|
//! Weights AUTOGENERADOS para `{{pallet}}` — NO editar a mano.
|
||||||
|
//!
|
||||||
|
//! Regenerar: `scripts/bench/regen-weights.sh` (ver ese script para el
|
||||||
|
//! procedimiento completo y el hardware al que hay que creerle).
|
||||||
|
//!
|
||||||
|
//! GENERADO POR SUBSTRATE BENCHMARK CLI VERSION {{version}}
|
||||||
|
//! FECHA: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: `{{cmd.repeat}}`
|
||||||
|
//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}`
|
||||||
|
//! WASM-EXECUTION: `{{cmd.wasm_execution}}`, DB CACHE: {{cmd.db_cache}}
|
||||||
|
|
||||||
|
// Comando ejecutado:
|
||||||
|
{{#each args as |arg|}}
|
||||||
|
// {{arg}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
#![allow(unused_parens)]
|
||||||
|
#![allow(unused_imports)]
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
use frame_support::{traits::Get, weights::constants::RocksDbWeight, weights::Weight};
|
||||||
|
|
||||||
|
/// Trait de weights para los extrinsics de `{{pallet}}`.
|
||||||
|
pub trait WeightInfo {
|
||||||
|
{{#each benchmarks as |benchmark|}}
|
||||||
|
/// Peso de `{{benchmark.name}}` — MEDIDO sobre el WASM, no estimado a mano.
|
||||||
|
fn {{benchmark.name~}}
|
||||||
|
({{~#each benchmark.components as |c|}}{{c.name}}: u32, {{/each~}}) -> Weight;
|
||||||
|
{{/each}}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Weights parametrizados por el `DbWeight` real del runtime.
|
||||||
|
pub struct SubstrateWeight<T>(PhantomData<T>);
|
||||||
|
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||||
|
{{#each benchmarks as |benchmark|}}
|
||||||
|
{{#each benchmark.comments as |comment|}}
|
||||||
|
/// {{comment}}
|
||||||
|
{{/each}}
|
||||||
|
{{#each benchmark.component_ranges as |range|}}
|
||||||
|
/// El rango del componente `{{range.name}}` es `[{{range.min}}, {{range.max}}]`.
|
||||||
|
{{/each}}
|
||||||
|
fn {{benchmark.name~}}
|
||||||
|
(
|
||||||
|
{{~#each benchmark.components as |c| ~}}
|
||||||
|
{{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}}
|
||||||
|
) -> Weight {
|
||||||
|
// Proof size en bytes:
|
||||||
|
// Medido: `{{benchmark.base_recorded_proof_size}}`
|
||||||
|
// Estimado: `{{benchmark.base_calculated_proof_size}}`
|
||||||
|
// Tiempo minimo de ejecucion: {{underscore benchmark.min_execution_time}}_000 picosegundos.
|
||||||
|
Weight::from_parts({{underscore benchmark.base_weight}}, 0)
|
||||||
|
.saturating_add(Weight::from_parts(0, {{benchmark.base_calculated_proof_size}}))
|
||||||
|
{{#each benchmark.component_weight as |cw|}}
|
||||||
|
// Error estandar: {{underscore cw.error}}
|
||||||
|
.saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into()))
|
||||||
|
{{/each}}
|
||||||
|
{{#if (ne benchmark.base_reads "0")}}
|
||||||
|
.saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}}_u64))
|
||||||
|
{{/if}}
|
||||||
|
{{#if (ne benchmark.base_writes "0")}}
|
||||||
|
.saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}}_u64))
|
||||||
|
{{/if}}
|
||||||
|
}
|
||||||
|
{{/each}}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Impl para mocks y tests — `RocksDbWeight` directo, sin necesitar `T`.
|
||||||
|
impl WeightInfo for () {
|
||||||
|
{{#each benchmarks as |benchmark|}}
|
||||||
|
fn {{benchmark.name~}}
|
||||||
|
(
|
||||||
|
{{~#each benchmark.components as |c| ~}}
|
||||||
|
{{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}}
|
||||||
|
) -> Weight {
|
||||||
|
Weight::from_parts({{underscore benchmark.base_weight}}, 0)
|
||||||
|
.saturating_add(Weight::from_parts(0, {{benchmark.base_calculated_proof_size}}))
|
||||||
|
{{#each benchmark.component_weight as |cw|}}
|
||||||
|
.saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into()))
|
||||||
|
{{/each}}
|
||||||
|
{{#if (ne benchmark.base_reads "0")}}
|
||||||
|
.saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}}_u64))
|
||||||
|
{{/if}}
|
||||||
|
{{#if (ne benchmark.base_writes "0")}}
|
||||||
|
.saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}}_u64))
|
||||||
|
{{/if}}
|
||||||
|
}
|
||||||
|
{{/each}}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod guard {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// NINGUN EXTRINSIC PUEDE DECLARAR `proof_size = 0`.
|
||||||
|
///
|
||||||
|
/// El `proof_size` es lo que el block-builder usa para no pasarse del limite
|
||||||
|
/// de PoV. Un cero no dice «este extrinsic no toca storage»: dice «miento
|
||||||
|
/// sobre lo que va a ocupar la prueba», y con eso entra en un bloque mucho
|
||||||
|
/// mas de lo que cabe.
|
||||||
|
///
|
||||||
|
/// El test nace de un error propio y de la peor clase: la cabecera de los
|
||||||
|
/// weights interinos afirmaba que `T::DbWeight` «puebla tanto ref_time como
|
||||||
|
/// proof_size … cerrando el hueco». Es falso y se comprueba en la fuente —
|
||||||
|
/// `sp_weights::RuntimeDbWeight` solo tiene los campos `read` y `write`, y
|
||||||
|
/// `reads(r)` devuelve `Weight::from_parts(read * r, 0)`. Los interinos
|
||||||
|
/// arreglaron reads/writes y dejaron el proof_size donde estaba: en 0. La
|
||||||
|
/// afirmacion de haberlo cerrado sobrevivio mas de un mes sin que ninguna
|
||||||
|
/// lectura la desmintiera; la desmintio una medicion.
|
||||||
|
///
|
||||||
|
/// Esta lista la GENERA la plantilla a partir de los benchmarks reales, asi
|
||||||
|
/// que no puede quedarse corta cuando se anada un extrinsic nuevo.
|
||||||
|
#[test]
|
||||||
|
fn ningun_weight_declara_proof_size_cero() {
|
||||||
|
let casos: &[(&str, Weight)] = &[
|
||||||
|
{{#each benchmarks as |benchmark|}}
|
||||||
|
("{{benchmark.name}}", <() as WeightInfo>::{{benchmark.name}}({{#each benchmark.components as |c|}}1, {{/each}})),
|
||||||
|
{{/each}}
|
||||||
|
];
|
||||||
|
for (nombre, w) in casos {
|
||||||
|
assert!(
|
||||||
|
w.proof_size() > 0,
|
||||||
|
"`{nombre}` declara proof_size = 0: miente al block-builder sobre el peso PoV"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
w.ref_time() > 0,
|
||||||
|
"`{nombre}` declara ref_time = 0: el extrinsic saldria gratis"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+79
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# regen-weights.sh — REGENERAR LOS WEIGHTS DE LOS 5 PALLETS CUSTOM
|
||||||
|
#
|
||||||
|
# POR QUE EXISTE
|
||||||
|
# ──────────────
|
||||||
|
# Durante meses los weights fueron "INTERIM hand-derived" y el fichero prometia
|
||||||
|
# que regenerarlos "es mecanico". No lo era: no habia comando. Cuando por fin se
|
||||||
|
# midio (2026-09-09) los 19 extrinsics estaban SUBDECLARADOS — los 19 — y el
|
||||||
|
# proof_size valia 0 en todos, que es el mismo agujero que los interinos decian
|
||||||
|
# haber cerrado. La promesa costo lo que cuestan las promesas sin codigo.
|
||||||
|
#
|
||||||
|
# QUE HACE
|
||||||
|
# ────────
|
||||||
|
# 1. Construye el runtime WASM con `runtime-benchmarks`.
|
||||||
|
# 2. Corre `frame-omni-bencher` sobre ESE blob (el mismo que se despliega).
|
||||||
|
# 3. Reescribe los 5 `weights.rs` con la plantilla de la casa, que emite
|
||||||
|
# trait + SubstrateWeight<T> + () + un guard generado que reprueba cualquier
|
||||||
|
# proof_size = 0.
|
||||||
|
#
|
||||||
|
# LA VERSION DEL BENCHER NO ES OPCIONAL
|
||||||
|
# ─────────────────────────────────────
|
||||||
|
# `frame-omni-bencher 0.15.0`. La ultima (0.23) trae `sp-storage 25` contra el
|
||||||
|
# `sp-storage 22` de este runtime, y `TrackedStorageKey` no decodifica al cruzar
|
||||||
|
# la frontera FFI. Falla con "unexpected first byte decoding Option", que no se
|
||||||
|
# parece en nada a "version equivocada".
|
||||||
|
# cargo install frame-omni-bencher --version 0.15.0 --locked
|
||||||
|
#
|
||||||
|
# DONDE CORRERLO
|
||||||
|
# ──────────────
|
||||||
|
# Para NUMEROS CANONICOS: en el banco de referencia (Pi 5), con el governor
|
||||||
|
# fijado a `performance` y el core aislado. `ondemand` arranca a 1600 MHz y sube
|
||||||
|
# a 2400: las primeras muestras salen ~50% lentas y el weight sale de la rampa,
|
||||||
|
# no del silicio. Sin `isolcpus` el aislamiento es una moneda al aire (medido).
|
||||||
|
# En cualquier otra maquina el resultado es un SMOKE del pipeline, util para ver
|
||||||
|
# la forma (reads/writes/proof_size, que NO dependen del hardware) e inservible
|
||||||
|
# como precio.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/../.."
|
||||||
|
ROOT="$PWD"
|
||||||
|
STEPS="${STEPS:-50}"
|
||||||
|
REPEAT="${REPEAT:-20}"
|
||||||
|
BENCHER="${BENCHER:-frame-omni-bencher}"
|
||||||
|
WASM="${WASM:-$ROOT/target/release/wbuild/origin-chain-runtime/origin_chain_runtime.compact.compressed.wasm}"
|
||||||
|
# SKIP_BUILD=1 mide un blob YA construido. Es el modo del banco de referencia:
|
||||||
|
# el Pi 5 no compila Substrate, se le lleva el blob que se va a desplegar — asi
|
||||||
|
# el binario medido y el desplegado son el MISMO, que es la condicion de Laurha.
|
||||||
|
|
||||||
|
command -v "$BENCHER" >/dev/null || { echo "falta $BENCHER — ver cabecera"; exit 1; }
|
||||||
|
"$BENCHER" --version | grep -q "0\.15\.0" || echo "⚠ el bencher NO es 0.15.0; si falla al decodificar TrackedStorageKey, es esto"
|
||||||
|
|
||||||
|
if [ "${SKIP_BUILD:-0}" = "1" ]; then
|
||||||
|
echo "═══ 1/2 SALTADO — midiendo blob ya construido ═══"
|
||||||
|
else
|
||||||
|
echo "═══ 1/2 construyendo el WASM con runtime-benchmarks ═══"
|
||||||
|
WASM_BUILD_STD=0 cargo build --release -p origin-chain-runtime --features wasm-build,runtime-benchmarks
|
||||||
|
fi
|
||||||
|
echo "blob: $( (shasum -a 256 "$WASM" 2>/dev/null || sha256sum "$WASM") | cut -d' ' -f1)"
|
||||||
|
|
||||||
|
echo "═══ 2/2 midiendo (steps=$STEPS repeat=$REPEAT) ═══"
|
||||||
|
declare -A DEST=(
|
||||||
|
[origin_chain_provenance]=crates/origin-chain-provenance/src/weights.rs
|
||||||
|
[origin_chain_agent_identity]=crates/origin-chain-agent-identity/src/weights.rs
|
||||||
|
[origin_chain_fees]=crates/origin-chain-fees/src/weights.rs
|
||||||
|
[origin_chain_emission]=crates/origin-chain-emission/src/weights.rs
|
||||||
|
[origin_chain_governance]=crates/origin-chain-governance/src/weights.rs
|
||||||
|
)
|
||||||
|
for p in "${!DEST[@]}"; do
|
||||||
|
echo "── $p"
|
||||||
|
"$BENCHER" v1 benchmark pallet \
|
||||||
|
--runtime "$WASM" --genesis-builder-preset development \
|
||||||
|
--pallet "$p" --extrinsic "" \
|
||||||
|
--steps "$STEPS" --repeat "$REPEAT" \
|
||||||
|
--template "$ROOT/scripts/bench/origin-weights-template.hbs" \
|
||||||
|
--header "$ROOT/scripts/bench/HEADER.txt" \
|
||||||
|
--output "$ROOT/${DEST[$p]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "═══ listo — ahora: cargo test --workspace ═══"
|
||||||
Reference in new issue
Block a user