fix(mcp): подготовить доступ и отзыв разрешений для приёмки
This commit is contained in:
commit
ef7bb79d4e
311 files changed
+73119
No files matched your search
@@ -0,0 +1,73 @@
|
||||
// Нагрузочный smoke по запущенному образу/серверу (autocannon).
|
||||
// node scripts/perf/load.mjs http://127.0.0.1:8080 [container-name]
|
||||
// Бюджеты (env): LOAD_MAX_P99_MS (150), LOAD_MIN_RPS (500), LOAD_MAX_RSS_MB (200), LOAD_DURATION_S (15)
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import autocannon from 'autocannon'
|
||||
|
||||
const base = process.argv[2] ?? 'http://127.0.0.1:8080'
|
||||
const container = process.argv[3]
|
||||
const duration = Number(process.env.LOAD_DURATION_S ?? 15)
|
||||
const maxP99 = Number(process.env.LOAD_MAX_P99_MS ?? 150)
|
||||
const minRps = Number(process.env.LOAD_MIN_RPS ?? 500)
|
||||
const maxRss = Number(process.env.LOAD_MAX_RSS_MB ?? 200)
|
||||
const email = process.env.LOAD_EMAIL ?? 'admin@smoke.local'
|
||||
const password = process.env.LOAD_PASSWORD
|
||||
|
||||
async function token() {
|
||||
if (!password) return null
|
||||
const res = await fetch(`${base}/api/v1/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
})
|
||||
if (!res.ok) throw new Error(`login ${res.status}`)
|
||||
return (await res.json()).access_token
|
||||
}
|
||||
|
||||
function run(title, opts) {
|
||||
return new Promise((resolve, reject) => {
|
||||
autocannon({ url: base, connections: 50, duration, ...opts }, (err, r) =>
|
||||
err ? reject(err) : resolve({ title, r }),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function rssMb() {
|
||||
if (!container) return null
|
||||
const out = execFileSync('docker', ['stats', '--no-stream', '--format', '{{.MemUsage}}', container]).toString()
|
||||
const m = /^([\d.]+)(KiB|MiB|GiB)/.exec(out.trim())
|
||||
if (!m) return null
|
||||
const v = Number(m[1])
|
||||
return m[2] === 'GiB' ? v * 1024 : m[2] === 'KiB' ? v / 1024 : v
|
||||
}
|
||||
|
||||
const jwt = await token()
|
||||
const scenarios = [{ title: 'GET /health', opts: { url: `${base}/health` } }]
|
||||
if (jwt) {
|
||||
scenarios.push({
|
||||
title: 'GET /api/v1/auth/me',
|
||||
opts: { url: `${base}/api/v1/auth/me`, headers: { authorization: `Bearer ${jwt}` } },
|
||||
})
|
||||
}
|
||||
|
||||
const failures = []
|
||||
for (const s of scenarios) {
|
||||
const { r } = await run(s.title, s.opts)
|
||||
const non2xx = r.non2xx
|
||||
console.log(
|
||||
`${s.title}: ${Math.round(r.requests.average)} rps, p99 ${r.latency.p99} ms, non2xx ${non2xx}, errors ${r.errors}`,
|
||||
)
|
||||
if (r.latency.p99 > maxP99) failures.push(`${s.title}: p99 ${r.latency.p99} > ${maxP99} ms`)
|
||||
if (r.requests.average < minRps) failures.push(`${s.title}: ${Math.round(r.requests.average)} rps < ${minRps}`)
|
||||
if (non2xx > 0 || r.errors > 0) failures.push(`${s.title}: ${non2xx} non2xx, ${r.errors} errors`)
|
||||
}
|
||||
const rss = rssMb()
|
||||
if (rss != null) {
|
||||
console.log(`RSS после нагрузки: ${Math.round(rss)} MiB`)
|
||||
if (rss > maxRss) failures.push(`RSS ${Math.round(rss)} MiB > ${maxRss} MiB`)
|
||||
}
|
||||
if (failures.length) {
|
||||
console.error('LOAD BUDGET FAILED:\n - ' + failures.join('\n - '))
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('LOAD OK')
|
||||
Reference in new issue
Block a user