mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-23 03:27:43 +09:00
docs: publish reproducible README measurements
Generate all four README tables from reviewed source and build snapshots, distinguish collection and asset sizes from runtime results, and remove unsupported footprint claims. Add a consistency check in CI and document how to reproduce profiling and measurements.
This commit is contained in:
1 parent
d67e5d07d0
commit
2de3a971c5
10 files changed
+1402
-44
No files matched your search
@@ -0,0 +1,42 @@
|
||||
name: Measurement tools and README tables
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev]
|
||||
paths:
|
||||
- 'README.md'
|
||||
- 'docs/README*.md'
|
||||
- 'docs/measurements/**'
|
||||
- 'scripts/measure-repository.py'
|
||||
- 'scripts/update-readme-measurements.py'
|
||||
- 'scripts/profile-api.py'
|
||||
- 'scripts/test/test_repository_measurements.py'
|
||||
- 'scripts/test/test_profile_api.py'
|
||||
- '.github/workflows/measurements-ci.yml'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'README.md'
|
||||
- 'docs/README*.md'
|
||||
- 'docs/measurements/**'
|
||||
- 'scripts/measure-repository.py'
|
||||
- 'scripts/update-readme-measurements.py'
|
||||
- 'scripts/profile-api.py'
|
||||
- 'scripts/test/test_repository_measurements.py'
|
||||
- 'scripts/test/test_profile_api.py'
|
||||
- '.github/workflows/measurements-ci.yml'
|
||||
|
||||
jobs:
|
||||
measurements:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Test measurement tools (stdlib only)
|
||||
run: |
|
||||
python -m unittest discover -s scripts/test -p test_repository_measurements.py
|
||||
python -m unittest discover -s scripts/test -p test_profile_api.py
|
||||
- name: Check translated README tables against reviewed snapshots
|
||||
run: python scripts/update-readme-measurements.py
|
||||
@@ -44,25 +44,26 @@ English | [Español](docs/README.es.md) | [中文版](docs/README.zh-CN.md) | [P
|
||||
|
||||
## 📊 By the Numbers
|
||||
|
||||
Everything below is measured from this repository, not estimated.
|
||||
<!-- BEGIN GENERATED MEASUREMENTS -->
|
||||
Snapshot: **2026-09-05**. [Definitions, raw measurements and reproduction commands](docs/METRICS.md).
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **1,350+** REST endpoints | across 109 blueprints — `/api/v1/*`, with OpenAPI + Swagger UI at `/api/v1/docs` |
|
||||
| **118** one-click app templates | bundled in the repo, no registry account needed |
|
||||
| **4,300+** backend tests | the whole suite runs on every change, with a CI-enforced floor on the collected count |
|
||||
| **1.75 MB** gzipped web UI | 60+ screens, assets served from your own box — no CDN |
|
||||
| **~180 MB** resident | the entire panel, single process — sits comfortably beside your apps on a 1 GB VPS |
|
||||
| **501 MB** container image | or install straight onto the host; Docker is optional for the panel itself |
|
||||
| **$0** | MIT-licensed. No tiers, no seat limits, no upsell — and nothing phones home. |
|
||||
| **1,212** core route declarations | in **104** blueprint declarations under `backend/app/api`; source inventory, excluding extensions |
|
||||
| **118** bundled app templates | root-level app YAML files; database extension templates counted separately |
|
||||
| **5,089** backend test cases collected | clean-checkout collection; this is not a claim that every case passed or ran |
|
||||
| **0.83 MB** gzip, HTML-linked JS/CSS | scripts, stylesheets and modulepreloads listed in the production HTML |
|
||||
| **3.31 MB** gzip, all built JS/CSS | includes lazy chunks, locale bundles and vendor shims; excludes fonts and images |
|
||||
| **$0** license cost | MIT-licensed, without subscription or seat fees |
|
||||
|
||||
Self-hosted and Docker-native, on hardware you already pay for.
|
||||
Gzip values sum separately compressed files at level 9; they are not measured page-load times. RAM usage and image size depend on the build, platform and workload; no universal footprint is claimed.
|
||||
<!-- END GENERATED MEASUREMENTS -->
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
> ⏱️ Up and running in under 2 minutes
|
||||
> Installation time depends on the server, network and required packages.
|
||||
|
||||
### Option 1: One-Line Install (Recommended)
|
||||
|
||||
@@ -114,7 +115,7 @@ See the [Installation Guide](docs/INSTALLATION.md) for step-by-step instructions
|
||||
| **Disk** | 10 GB | 20+ GB |
|
||||
| **Docker** | 24.0+ (optional for the panel itself) | Latest |
|
||||
|
||||
> The panel itself only needs ~180 MB of RAM and ~500 MB of disk — the rest is headroom for your apps. It runs happily on a 1 GB VPS, a spare laptop, or a Raspberry Pi (ARM64), so it's as much at home in a homelab as it is on production hardware.
|
||||
> These requirements are sizing guidance, not a capacity benchmark. Allow additional RAM and disk for managed apps, images, databases, logs and backups. Measure your own workload using the [measurement guide](docs/METRICS.md).
|
||||
|
||||
---
|
||||
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
# ServerKit measurements
|
||||
|
||||
The README tables are a dated snapshot, generated from the same JSON files for
|
||||
English, Spanish, Portuguese and Chinese. They separate source inventory from
|
||||
build sizes and runtime measurements. A count of collected tests is not a count
|
||||
of passing tests, and compressed asset bytes are not a page-load benchmark.
|
||||
|
||||
## Recorded snapshot
|
||||
|
||||
- [Source inventory](measurements/repository.json): measured on September 5,
|
||||
2026 with Python 3.11.7 on Windows. The clean collection count was also checked
|
||||
on Linux. The base source revision, whether measured source has uncommitted
|
||||
changes, and a hash of that measured source are recorded. The hash describes
|
||||
the working files, so a pre-commit snapshot is not attributed to an unchanged
|
||||
historical revision.
|
||||
- [Production build inventory](measurements/frontend-build.json): Node version,
|
||||
per-asset byte/gzip sizes and a content hash are recorded. Built from an
|
||||
isolated copy of tracked/new frontend source after the request/form cleanup,
|
||||
excluding ignored files and reusing the installed dependencies, with
|
||||
`npm run build`. Its code hash matched the ordinary working-checkout build.
|
||||
- Runtime memory and container image bytes are **unmeasured in this snapshot**.
|
||||
The previous ~180 MB RAM and 501 MB image figures had no reproducible
|
||||
environment or image digest, so they were removed instead of repeated.
|
||||
|
||||
### What each number includes
|
||||
|
||||
| Measurement | Definition |
|
||||
| --- | --- |
|
||||
| Core route declarations | Route decorators in Git-tracked Python under `backend/app/api/`. A function with two route decorators counts twice. This is source inventory, not the set of enabled runtime endpoints. |
|
||||
| Core blueprint declarations | `Blueprint(...)` declarations in the same files. Extension blueprints and conditionally loaded routes are outside this count. |
|
||||
| Explicit method/route pairs | Literal route methods, excluding automatic HEAD/OPTIONS. The JSON separately reports declarations whose methods cannot be resolved statically. |
|
||||
| App templates | Git-tracked YAML files directly inside `backend/templates/`; nested database-extension templates are excluded. |
|
||||
| Backend tests | Pytest collection from `backend/tests` using the existing clean-collection instrument; ignored installed extension copies do not inflate the count. Skipped/opt-in tests can still be collected. |
|
||||
| HTML-linked code | JS/MJS/CSS referenced by script, stylesheet or modulepreload tags in production `index.html`. Later runtime requests, fonts and images are excluded. |
|
||||
| All built code | Every JS/MJS/CSS file in the production output, including lazy chunks, translations, extension code and public vendor shims. HTML, fonts, images, source maps and other assets are excluded. |
|
||||
| Gzip bytes | Sum of each file compressed independently with gzip level 9. MB means 1,000,000 bytes. These are reproducible compression estimates; actual transfer depends on server compression and browser caching. |
|
||||
|
||||
The old “1.75 MB web UI” number had no precise asset scope. The new HTML-linked
|
||||
and all-code totals must not be interpreted as a measured speedup over that
|
||||
old figure. Non-English locale chunks are already loaded on demand; their
|
||||
build-size warnings alone do not show that every visitor downloads them.
|
||||
|
||||
## Refresh the source and build measurements
|
||||
|
||||
Use the backend Python environment with its dependencies installed. From the
|
||||
repository root:
|
||||
|
||||
```bash
|
||||
python scripts/measure-repository.py --collect-tests --output docs/measurements/repository.json
|
||||
cd frontend
|
||||
npm run build
|
||||
npm run measure:build -- --output ../docs/measurements/frontend-build.json
|
||||
cd ..
|
||||
python scripts/update-readme-measurements.py --write
|
||||
python scripts/update-readme-measurements.py
|
||||
```
|
||||
|
||||
On Windows, `backend\venv\Scripts\python.exe` can replace `python`. Review the
|
||||
snapshot and README diffs together. The final command checks agreement between
|
||||
all translated tables and the snapshots; it does not pretend an old build
|
||||
snapshot is a fresh measurement. Rebuild when publishing new size claims.
|
||||
|
||||
## Measure API latency and database work
|
||||
|
||||
The panel already records API timings through its analytics middleware. For a
|
||||
bounded investigation, enable `SERVERKIT_PROFILE_REQUESTS=true` on an authorized
|
||||
local or staging panel and restart it. This adds a `Server-Timing` response
|
||||
header containing application elapsed time, SQL elapsed time and statement
|
||||
count. It records neither SQL text nor parameters and creates no new database
|
||||
table. The profiler is off by default and installs no SQL listeners when off.
|
||||
|
||||
Use the real HTTP sampler against the running panel:
|
||||
|
||||
```bash
|
||||
python scripts/profile-api.py --base-url http://127.0.0.1:47927 --path /api/v1/system/health --samples 20 --warmup 2 --output .reviews/api-profile.json
|
||||
```
|
||||
|
||||
For protected endpoints, supply a short-lived JWT through
|
||||
`SERVERKIT_PROFILE_TOKEN`, or `SERVERKIT_PROFILE_API_KEY` only for routes that
|
||||
already support API keys. Do not supply both. The sampler makes GET requests,
|
||||
refuses redirects, and does not include bodies, credentials or query values in
|
||||
its report. HTTP/transport failures are recorded and make the command fail.
|
||||
Missing profiler headers produce `null` fields, never invented zero costs.
|
||||
|
||||
Reports contain sample count, response sizes/statuses, min/p50/p95/max latency,
|
||||
and optional application/SQL distributions. Warmup is excluded from the latency
|
||||
summary, but its requests and failures remain visible. Turn profiling off when
|
||||
the investigation is finished.
|
||||
|
||||
Record panel revision, OS/architecture, Python version, database engine,
|
||||
server/resource counts, cache state, concurrency and whether the panel is idle
|
||||
or loaded. Compare the same dataset and environment before and after a change.
|
||||
The sampler is sequential and does not establish throughput under concurrency.
|
||||
Profiler SQL counts cover statements on the HTTP request thread through response
|
||||
construction; background jobs and streamed body iteration are not included.
|
||||
|
||||
## Measure memory, images and browser loading separately
|
||||
|
||||
- **Memory:** record the exact process/container, workload, uptime and sampling
|
||||
interval. Process RSS and Docker memory accounting are different measurements.
|
||||
Include workers and enabled services; do not describe one idle sample as a
|
||||
universal minimum or capacity guarantee.
|
||||
- **Image size:** record image digest, architecture, build arguments and whether
|
||||
bytes describe the local uncompressed image or registry transfer. A cached
|
||||
image from a different commit is not a measurement of the current source.
|
||||
- **Browser loading:** record cold/warm cache, device, connection, route, locale
|
||||
and extension set. Use a production build and inspect actual requests and
|
||||
navigation timings; source-file counts and total dist size cannot substitute
|
||||
for this. Large assets should be optimized when that trace shows they affect
|
||||
a relevant user flow.
|
||||
|
||||
No production latency, memory, capacity or percentage-speedup claim is made by
|
||||
the source/build snapshot. Existing fleet tests enforce one metrics query per
|
||||
reader at multiple fleet sizes; the opt-in profiler makes that kind of query
|
||||
budget observable on a running panel too.
|
||||
+12
-11
@@ -44,25 +44,26 @@ ni el coste de las plataformas gestionadas.
|
||||
|
||||
## 📊 En Números
|
||||
|
||||
Todo lo siguiente está medido sobre este repositorio, no estimado.
|
||||
<!-- BEGIN GENERATED MEASUREMENTS -->
|
||||
Instantánea: **2026-09-05**. [Definiciones, medidas y comandos para reproducirlas](METRICS.md).
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **1.519** endpoints REST | repartidos en 107 blueprints — `/api/v1/*`, con OpenAPI y Swagger UI en `/api/v1/docs` |
|
||||
| **106** plantillas de apps | incluidas en el repositorio, sin necesidad de cuenta en ningún registro |
|
||||
| **2.633** pruebas de backend | la suite completa se ejecuta ante cada cambio |
|
||||
| **1,75 MB** de interfaz web (gzip) | 65 pantallas, servidas desde tu propio servidor — sin CDN |
|
||||
| **~180 MB** en memoria | el panel entero, un solo proceso — convive sin apreturas con tus apps en un VPS de 1 GB |
|
||||
| **501 MB** de imagen de contenedor | o instálalo directamente en el host; Docker es opcional para el panel |
|
||||
| **$0** | Licencia MIT. Sin planes, sin límite de usuarios, sin ventas adicionales — y nada se comunica hacia afuera. |
|
||||
| **1.212** declaraciones de rutas del núcleo | en **104** declaraciones de blueprints de `backend/app/api`; inventario del código, sin extensiones |
|
||||
| **118** plantillas de apps incluidas | archivos YAML de apps en el directorio raíz; las plantillas de extensiones de bases de datos se cuentan aparte |
|
||||
| **5.089** casos de prueba de backend recopilados | recopilación de un checkout limpio; no significa que todos se hayan ejecutado o aprobado |
|
||||
| **0,83 MB** gzip de JS/CSS enlazado por el HTML | scripts, estilos y precargas de módulos del HTML de producción |
|
||||
| **3,31 MB** gzip de todo el JS/CSS generado | incluye módulos diferidos, idiomas y adaptadores de dependencias; excluye fuentes e imágenes |
|
||||
| **$0** de licencia | Licencia MIT, sin suscripciones ni tarifas por usuario |
|
||||
|
||||
Autoalojado y nativo en Docker, sobre hardware que ya estás pagando.
|
||||
Los valores gzip suman archivos comprimidos por separado a nivel 9; no son tiempos de carga medidos. La RAM y el tamaño de imagen dependen de la compilación, plataforma y carga de trabajo.
|
||||
<!-- END GENERATED MEASUREMENTS -->
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Inicio Rápido
|
||||
|
||||
> ⏱️ En funcionamiento en menos de 2 minutos
|
||||
> El tiempo de instalación depende del servidor, la red y los paquetes necesarios.
|
||||
|
||||
### Opción 1: Instalación en Una Línea (Recomendada)
|
||||
|
||||
@@ -115,7 +116,7 @@ Consulta la [Guía de Instalación](INSTALLATION.md) para instrucciones paso a p
|
||||
| **Disco** | 10 GB | 20+ GB |
|
||||
| **Docker** | 24.0+ (opcional para el panel) | Última versión |
|
||||
|
||||
> El panel en sí solo usa ~180 MB de RAM y ~500 MB de disco — el resto es margen para tus apps. Funciona bien en un VPS de 1 GB, un portátil viejo o una Raspberry Pi (ARM64): pensado tanto para homelabs como para producción.
|
||||
> Estos requisitos orientan el dimensionamiento; no son una prueba de capacidad. Reserva RAM y disco adicionales para apps, imágenes, bases de datos, registros y copias de seguridad. Consulta la [guía de medición](METRICS.md) para medir tu carga de trabajo.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+12
-11
@@ -44,25 +44,26 @@ ou o custo de plataformas gerenciadas.
|
||||
|
||||
## 📊 Em Números
|
||||
|
||||
Tudo abaixo é medido a partir deste repositório, não estimado.
|
||||
<!-- BEGIN GENERATED MEASUREMENTS -->
|
||||
Registro: **2026-09-05**. [Definições, medições e comandos de reprodução](METRICS.md).
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **1.519** endpoints REST | distribuídos em 107 blueprints — `/api/v1/*`, com OpenAPI e Swagger UI em `/api/v1/docs` |
|
||||
| **106** templates de aplicações | incluídos no repositório, sem precisar de conta em nenhum registro |
|
||||
| **2.633** testes de backend | a suíte completa roda a cada alteração |
|
||||
| **1,75 MB** de interface web (gzip) | 65 telas, servidas do seu próprio servidor — sem CDN |
|
||||
| **~180 MB** residentes | o painel inteiro, um único processo — convive folgadamente com suas apps num VPS de 1 GB |
|
||||
| **501 MB** de imagem de contêiner | ou instale direto no host; Docker é opcional para o painel |
|
||||
| **$0** | Licença MIT. Sem planos, sem limite de usuários, sem upsell — e nada é enviado para fora. |
|
||||
| **1.212** declarações de rotas do núcleo | em **104** declarações de blueprints em `backend/app/api`; inventário do código, sem extensões |
|
||||
| **118** templates de apps incluídos | arquivos YAML de apps no diretório raiz; templates de extensões de banco são contados separadamente |
|
||||
| **5.089** casos de teste de backend coletados | coleta de um checkout limpo; não significa que todos executaram ou passaram |
|
||||
| **0,83 MB** gzip de JS/CSS vinculado pelo HTML | scripts, estilos e pré-carregamentos de módulos no HTML de produção |
|
||||
| **3,31 MB** gzip de todo o JS/CSS gerado | inclui módulos sob demanda, idiomas e adaptadores de dependências; exclui fontes e imagens |
|
||||
| **$0** de licença | Licença MIT, sem assinaturas ou taxas por usuário |
|
||||
|
||||
Auto-hospedado e nativo em Docker, em hardware que você já paga.
|
||||
Os valores gzip somam arquivos comprimidos separadamente no nível 9; não são tempos de carregamento medidos. A RAM e o tamanho da imagem dependem da compilação, plataforma e carga de trabalho.
|
||||
<!-- END GENERATED MEASUREMENTS -->
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Início Rápido
|
||||
|
||||
> ⏱️ Pronto para usar em menos de 2 minutos
|
||||
> O tempo de instalação depende do servidor, da rede e dos pacotes necessários.
|
||||
|
||||
### Opção 1: Instalação em Uma Linha (Recomendado)
|
||||
|
||||
@@ -115,7 +116,7 @@ Consulte o [Guia de Instalação](INSTALLATION.md) para instruções passo a pas
|
||||
| **Disco** | 10 GB | 20+ GB |
|
||||
| **Docker** | 24.0+ (opcional para o painel) | Mais recente |
|
||||
|
||||
> O painel em si usa apenas ~180 MB de RAM e ~500 MB de disco — o resto é margem para os seus apps. Funciona bem num VPS de 1 GB, num notebook antigo ou num Raspberry Pi (ARM64): feito tanto para homelabs quanto para produção.
|
||||
> Estes requisitos orientam o dimensionamento; não são um teste de capacidade. Reserve RAM e disco adicionais para apps, imagens, bancos, logs e backups. Consulte o [guia de medição](METRICS.md) para medir sua carga de trabalho.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+12
-11
@@ -44,25 +44,26 @@ Docker 容器和安全策略——无需 Kubernetes 的复杂性,
|
||||
|
||||
## 📊 数据一览
|
||||
|
||||
以下数据均实测自本仓库,并非估算。
|
||||
<!-- BEGIN GENERATED MEASUREMENTS -->
|
||||
测量快照:**2026-09-05**。[定义、原始测量结果和复现命令](METRICS.md)。
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **1,519** 个 REST 接口 | 分布在 107 个蓝图中 — `/api/v1/*`,并在 `/api/v1/docs` 提供 OpenAPI 与 Swagger UI |
|
||||
| **106** 个一键应用模板 | 随仓库内置,无需注册任何应用市场账号 |
|
||||
| **2,633** 项后端测试 | 每次变更都会跑完整套件 |
|
||||
| **1.75 MB** 前端界面(gzip 后) | 65 个页面,静态资源全部由你自己的服务器提供 — 不依赖 CDN |
|
||||
| **~180 MB** 常驻内存 | 整个面板仅一个进程 — 在 1 GB 内存的 VPS 上与你的应用从容共存 |
|
||||
| **501 MB** 容器镜像 | 也可直接装在宿主机上;面板本身并不强制使用 Docker |
|
||||
| **$0** | MIT 许可证。没有套餐分级、没有席位限制、没有增值推销 — 也不会向外回传任何数据。 |
|
||||
| **1,212** 个核心路由声明 | 来自 `backend/app/api` 中 **104** 个蓝图声明;这是源码清单,不包括扩展 |
|
||||
| **118** 个内置应用模板 | 模板目录根层级的应用 YAML 文件;数据库扩展模板另计 |
|
||||
| **5,089** 个已收集的后端测试用例 | 基于干净检出的测试收集结果;不代表所有用例均已运行或通过 |
|
||||
| **0.83 MB** gzip 的 HTML 直接引用 JS/CSS | 生产 HTML 中的脚本、样式表和模块预加载资源 |
|
||||
| **3.31 MB** gzip 的全部构建 JS/CSS | 包括按需模块、语言包和依赖适配文件;不含字体和图片 |
|
||||
| **$0** 许可费用 | MIT 许可证,无订阅或席位费用 |
|
||||
|
||||
自托管、Docker 原生,跑在你已经付过钱的机器上。
|
||||
gzip 数值为各文件分别按级别 9 压缩后的总和,并非实测页面加载时间。内存占用与镜像大小取决于构建、平台和工作负载,不作通用占用保证。
|
||||
<!-- END GENERATED MEASUREMENTS -->
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
> ⏱️ 不到 2 分钟即可启动运行
|
||||
> 安装时间取决于服务器、网络和所需的软件包。
|
||||
|
||||
### 方式一:一键安装(推荐)
|
||||
|
||||
@@ -113,7 +114,7 @@ docker compose up -d # 访问 http://localhost:5000
|
||||
| **磁盘** | 10 GB | 20+ GB |
|
||||
| **Docker** | 24.0+(面板本身可选) | 最新版 |
|
||||
|
||||
> 面板本身仅占用约 180 MB 内存和 500 MB 磁盘——其余都是为你的应用预留的空间。在 1 GB VPS、闲置笔记本或树莓派(ARM64)上都能流畅运行:既适合家庭实验室,也适合生产环境。
|
||||
> 这些要求是资源规划建议,并非容量基准测试。请为托管应用、镜像、数据库、日志和备份预留额外内存与磁盘空间。可参考[测量指南](METRICS.md)评估自己的工作负载。
|
||||
|
||||
---
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"measured_at_utc": "2026-09-05T18:41:48.234988+00:00",
|
||||
"source_revision": "d67e5d07d0c8027d7611048ac74b371db43cd7d1",
|
||||
"measured_source_has_uncommitted_changes": false,
|
||||
"measured_source_sha256": "4e6556b3e881b20b53caf0e0c9d23956da514d95e0e6ec9a1bf43ffe0546a8b8",
|
||||
"scope": "Git-tracked core API Python declarations and root-level bundled app YAML templates; excludes extensions, dependencies and generated/install copies.",
|
||||
"python_version": "3.11.7",
|
||||
"platform": "Windows",
|
||||
"core_route_declarations": 1212,
|
||||
"core_blueprint_declarations": 104,
|
||||
"explicit_method_route_pairs_excluding_head_options": 1216,
|
||||
"route_declarations_with_dynamic_methods": 0,
|
||||
"core_api_source_files": 111,
|
||||
"bundled_app_templates": 118,
|
||||
"backend_tests_clean_collected": 5089,
|
||||
"collection_scope": "backend/tests, SERVERKIT_CLEAN_COLLECT=1; collected cases, not passed tests",
|
||||
"runtime_memory_bytes": null,
|
||||
"container_image_bytes": null
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Source counters and README snapshots must use explicit, consistent scopes."""
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
def load(name, filename):
|
||||
spec = importlib.util.spec_from_file_location(name, Path(__file__).parents[1] / filename)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
measure = load('measure_repository', 'measure-repository.py')
|
||||
readmes = load('readme_measurements', 'update-readme-measurements.py')
|
||||
|
||||
|
||||
class MeasurementTests(unittest.TestCase):
|
||||
def test_counts_declarations_separately_from_methods(self):
|
||||
result = measure.source_counts(['''
|
||||
bp = Blueprint('test', __name__)
|
||||
@bp.route('/a')
|
||||
@bp.route('/b', methods=['GET', 'POST', 'HEAD', 'OPTIONS'])
|
||||
def handler(): pass
|
||||
@bp.delete('/c')
|
||||
def remove(): pass
|
||||
@bp.route('/d', methods=METHODS)
|
||||
def dynamic(): pass
|
||||
'''])
|
||||
self.assertEqual(result['core_route_declarations'], 4)
|
||||
self.assertEqual(result['core_blueprint_declarations'], 1)
|
||||
self.assertEqual(result['explicit_method_route_pairs_excluding_head_options'], 4)
|
||||
self.assertEqual(result['route_declarations_with_dynamic_methods'], 1)
|
||||
|
||||
def test_all_languages_render_the_same_snapshot_without_footprint_claims(self):
|
||||
source = {'measured_at_utc': '2026-09-05T12:00:00Z', 'core_route_declarations': 1234,
|
||||
'core_blueprint_declarations': 99, 'bundled_app_templates': 118,
|
||||
'backend_tests_clean_collected': 5678}
|
||||
build = {'html_linked_code': {'gzip_bytes': 800_000},
|
||||
'all_code': {'gzip_bytes': 3_200_000}}
|
||||
blocks = readmes.blocks(source, build)
|
||||
self.assertEqual(len(blocks), 4)
|
||||
for block in blocks.values():
|
||||
self.assertIn('2026-09-05', block)
|
||||
self.assertIn('**118**', block)
|
||||
self.assertIn('**99**', block)
|
||||
self.assertNotIn('180 MB', block)
|
||||
self.assertNotIn('501 MB', block)
|
||||
self.assertIn('**5,678**', blocks['README.md'])
|
||||
self.assertIn('**5.678**', blocks['docs/README.es.md'])
|
||||
self.assertIn('**0,80 MB**', blocks['docs/README.pt.md'])
|
||||
self.assertIn('**3.20 MB**', blocks['docs/README.zh-CN.md'])
|
||||
|
||||
def test_unknown_test_count_cannot_be_published_as_zero(self):
|
||||
with self.assertRaisesRegex(ValueError, 'collect-tests'):
|
||||
readmes.blocks({'measured_at_utc': '2026-09-05', 'core_route_declarations': 1,
|
||||
'core_blueprint_declarations': 1, 'bundled_app_templates': 1,
|
||||
'backend_tests_clean_collected': None}, {})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render all README measurement tables from the same reviewed JSON snapshots.
|
||||
|
||||
Run with --write after refreshing docs/measurements/*.json; without it, check
|
||||
that all four language versions match. This validates documentation agreement,
|
||||
not whether a stored build was rebuilt from the current checkout.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
BEGIN = '<!-- BEGIN GENERATED MEASUREMENTS -->'
|
||||
END = '<!-- END GENERATED MEASUREMENTS -->'
|
||||
|
||||
|
||||
def blocks(source, build):
|
||||
date = source['measured_at_utc'][:10]
|
||||
routes = source['core_route_declarations']
|
||||
blueprints = source['core_blueprint_declarations']
|
||||
templates = source['bundled_app_templates']
|
||||
tests = source['backend_tests_clean_collected']
|
||||
if tests is None:
|
||||
raise ValueError('Refresh repository.json with --collect-tests before publishing counts')
|
||||
linked = build['html_linked_code']['gzip_bytes'] / 1_000_000
|
||||
total = build['all_code']['gzip_bytes'] / 1_000_000
|
||||
en = [
|
||||
f'Snapshot: **{date}**. [Definitions, raw measurements and reproduction commands](docs/METRICS.md).',
|
||||
f'| **{routes:,}** core route declarations | in **{blueprints}** blueprint declarations under `backend/app/api`; source inventory, excluding extensions |',
|
||||
f'| **{templates}** bundled app templates | root-level app YAML files; database extension templates counted separately |',
|
||||
f'| **{tests:,}** backend test cases collected | clean-checkout collection; this is not a claim that every case passed or ran |',
|
||||
f'| **{linked:.2f} MB** gzip, HTML-linked JS/CSS | scripts, stylesheets and modulepreloads listed in the production HTML |',
|
||||
f'| **{total:.2f} MB** gzip, all built JS/CSS | includes lazy chunks, locale bundles and vendor shims; excludes fonts and images |',
|
||||
'| **$0** license cost | MIT-licensed, without subscription or seat fees |',
|
||||
'Gzip values sum separately compressed files at level 9; they are not measured page-load times. RAM usage and image size depend on the build, platform and workload; no universal footprint is claimed.',
|
||||
]
|
||||
es = [
|
||||
f'Instantánea: **{date}**. [Definiciones, medidas y comandos para reproducirlas](METRICS.md).',
|
||||
f'| **{routes:,}** declaraciones de rutas del núcleo | en **{blueprints}** declaraciones de blueprints de `backend/app/api`; inventario del código, sin extensiones |',
|
||||
f'| **{templates}** plantillas de apps incluidas | archivos YAML de apps en el directorio raíz; las plantillas de extensiones de bases de datos se cuentan aparte |',
|
||||
f'| **{tests:,}** casos de prueba de backend recopilados | recopilación de un checkout limpio; no significa que todos se hayan ejecutado o aprobado |',
|
||||
f'| **{linked:.2f} MB** gzip de JS/CSS enlazado por el HTML | scripts, estilos y precargas de módulos del HTML de producción |',
|
||||
f'| **{total:.2f} MB** gzip de todo el JS/CSS generado | incluye módulos diferidos, idiomas y adaptadores de dependencias; excluye fuentes e imágenes |',
|
||||
'| **$0** de licencia | Licencia MIT, sin suscripciones ni tarifas por usuario |',
|
||||
'Los valores gzip suman archivos comprimidos por separado a nivel 9; no son tiempos de carga medidos. La RAM y el tamaño de imagen dependen de la compilación, plataforma y carga de trabajo.',
|
||||
]
|
||||
pt = [
|
||||
f'Registro: **{date}**. [Definições, medições e comandos de reprodução](METRICS.md).',
|
||||
f'| **{routes:,}** declarações de rotas do núcleo | em **{blueprints}** declarações de blueprints em `backend/app/api`; inventário do código, sem extensões |',
|
||||
f'| **{templates}** templates de apps incluídos | arquivos YAML de apps no diretório raiz; templates de extensões de banco são contados separadamente |',
|
||||
f'| **{tests:,}** casos de teste de backend coletados | coleta de um checkout limpo; não significa que todos executaram ou passaram |',
|
||||
f'| **{linked:.2f} MB** gzip de JS/CSS vinculado pelo HTML | scripts, estilos e pré-carregamentos de módulos no HTML de produção |',
|
||||
f'| **{total:.2f} MB** gzip de todo o JS/CSS gerado | inclui módulos sob demanda, idiomas e adaptadores de dependências; exclui fontes e imagens |',
|
||||
'| **$0** de licença | Licença MIT, sem assinaturas ou taxas por usuário |',
|
||||
'Os valores gzip somam arquivos comprimidos separadamente no nível 9; não são tempos de carregamento medidos. A RAM e o tamanho da imagem dependem da compilação, plataforma e carga de trabalho.',
|
||||
]
|
||||
zh = [
|
||||
f'测量快照:**{date}**。[定义、原始测量结果和复现命令](METRICS.md)。',
|
||||
f'| **{routes:,}** 个核心路由声明 | 来自 `backend/app/api` 中 **{blueprints}** 个蓝图声明;这是源码清单,不包括扩展 |',
|
||||
f'| **{templates}** 个内置应用模板 | 模板目录根层级的应用 YAML 文件;数据库扩展模板另计 |',
|
||||
f'| **{tests:,}** 个已收集的后端测试用例 | 基于干净检出的测试收集结果;不代表所有用例均已运行或通过 |',
|
||||
f'| **{linked:.2f} MB** gzip 的 HTML 直接引用 JS/CSS | 生产 HTML 中的脚本、样式表和模块预加载资源 |',
|
||||
f'| **{total:.2f} MB** gzip 的全部构建 JS/CSS | 包括按需模块、语言包和依赖适配文件;不含字体和图片 |',
|
||||
'| **$0** 许可费用 | MIT 许可证,无订阅或席位费用 |',
|
||||
'gzip 数值为各文件分别按级别 9 压缩后的总和,并非实测页面加载时间。内存占用与镜像大小取决于构建、平台和工作负载,不作通用占用保证。',
|
||||
]
|
||||
result = {}
|
||||
for path, rows in [('README.md', en), ('docs/README.es.md', es), ('docs/README.pt.md', pt), ('docs/README.zh-CN.md', zh)]:
|
||||
if path.endswith(('.es.md', '.pt.md')):
|
||||
for old, new in [(f'{routes:,}', f'{routes:,}'.replace(',', '.')),
|
||||
(f'{tests:,}', f'{tests:,}'.replace(',', '.')),
|
||||
(f'{linked:.2f} MB', f'{linked:.2f} MB'.replace('.', ',')),
|
||||
(f'{total:.2f} MB', f'{total:.2f} MB'.replace('.', ','))]:
|
||||
rows = [row.replace(old, new) for row in rows]
|
||||
result[path] = '\n'.join([BEGIN, rows[0], '', '| | |', '|---|---|', *rows[1:-1], '', rows[-1], END])
|
||||
return result
|
||||
|
||||
|
||||
def update(write=False):
|
||||
source = json.loads((ROOT / 'docs/measurements/repository.json').read_text(encoding='utf-8'))
|
||||
build = json.loads((ROOT / 'docs/measurements/frontend-build.json').read_text(encoding='utf-8'))
|
||||
stale = []
|
||||
for path, block in blocks(source, build).items():
|
||||
file = ROOT / path
|
||||
original = file.read_text(encoding='utf-8')
|
||||
if BEGIN in original:
|
||||
result = re.sub(re.escape(BEGIN) + r'.*?' + re.escape(END), lambda _match: block, original, flags=re.S)
|
||||
else:
|
||||
result, count = re.subn(r'(^## 📊[^\n]*\n\n).*?(\n---\n)', lambda match: match[1] + block + '\n' + match[2], original, count=1, flags=re.S | re.M)
|
||||
if count != 1:
|
||||
raise ValueError(f'{path}: cannot find the measurements section')
|
||||
if result != original:
|
||||
stale.append(path)
|
||||
if write:
|
||||
file.write_text(result, encoding='utf-8')
|
||||
if stale and not write:
|
||||
raise SystemExit('README measurements are stale: ' + ', '.join(stale))
|
||||
print(('Updated' if write else 'Checked') + ' all four README measurement tables.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--write', action='store_true')
|
||||
update(parser.parse_args().write)
|
||||
Reference in new issue
Block a user