XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

EARSystem

【ASP】耳间,网页端的情绪智能推荐系统

公开
关注 0 Fork 0 Star 0
UTF-8
// Local, synthetic demo accounts only. No credentials, locations or diary text are reported.
import { readFile, mkdir, writeFile } from 'node:fs/promises';
import { performance } from 'node:perf_hooks';

const origin = new URL(process.argv[2] || 'http://localhost:5188');
if (!['localhost', '127.0.0.1'].includes(origin.hostname) || !['http:', 'https:'].includes(origin.protocol))
    throw new Error('This benchmark only supports a local EARSystem development server.');
const root = new URL('../', import.meta.url);
const credentials = (await readFile(new URL('src/EARSystem.Web/App_Data/demo-credentials.txt', root), 'utf8'))
    .trim().split(/\r?\n/).map(line => line.split('\t')).filter(x => /^demo\d\d@ear\.local$/.test(x[0]));
if (credentials.length !== 20) throw new Error('Generate the 20 demo accounts first; see README.');

class Session {
    jar = new Map();
    token = '';
    async request(path, options = {}) {
        const headers = { ...options.headers, Cookie: [...this.jar].map(([k,v]) => k + '=' + v).join('; ') };
        const response = await fetch(new URL(path, origin), { ...options, headers, redirect: 'manual' });
        for (const cookie of response.headers.getSetCookie()) {
            const pair = cookie.split(';')[0], separator = pair.indexOf('=');
            this.jar.set(pair.slice(0, separator), pair.slice(separator + 1));
        }
        return response;
    }
    async csrf(path) {
        const response = await this.request(path);
        if (response.status !== 200) throw new Error('Cannot load local login form: HTTP ' + response.status);
        const token = (await response.text()).match(/name="__RequestVerificationToken"[^>]*value="([^"]+)"/)?.[1];
        if (!token) throw new Error('Antiforgery token missing.');
        this.token = token.replaceAll('&', '&');
    }
    async login(email, password) {
        await this.csrf('/Account/Login');
        const response = await this.request('/Account/Login', {
            method: 'POST', body: new URLSearchParams({ Email: email, Password: password, __RequestVerificationToken: this.token })
        });
        if (response.status !== 302) throw new Error('Demo login failed: HTTP ' + response.status);
        await this.csrf('/Nearby');
    }
    async recommend() {
        const start = performance.now();
        const response = await this.request('/api/recommendations', {
            method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': this.token },
            body: JSON.stringify({ longitude:120.155, latitude:30.274, coordinateSystem:'GCJ02', radiusMeters:3000, mode:'Demo' })
        });
        const data = await response.json();
        if (response.status !== 200 || data.items?.length !== 10 || data.mode !== 'Demo')
            throw new Error('Recommendation failed: HTTP ' + response.status);
        return { ms: performance.now() - start, candidates: data.candidateCount };
    }
}

const sessions = credentials.map(() => new Session());
await Promise.all(sessions.map((s,i) => s.login(...credentials[i])));
await Promise.all(sessions.map(s => s.recommend())); // one warm-up per user
const samples = [];
for (let round = 0; round < 5; round++)
    samples.push(...await Promise.all(sessions.map(s => s.recommend())));
const times = samples.map(x => x.ms).sort((a,b) => a-b);
const percentile = p => Math.round(times[Math.ceil(times.length * p)-1] * 100) / 100;
const report = {
    measuredAt: new Date().toISOString(), environment:'Local Kestrel + SQL Server LocalDB, synthetic data',
    users:20, concurrency:20, rounds:5, requests:times.length, failures:0,
    minMs:percentile(.01), p50Ms:percentile(.5), p95Ms:percentile(.95), maxMs:percentile(1),
    meanMs:Math.round(times.reduce((a,b)=>a+b,0)/times.length*100)/100,
    candidateCount:samples[0].candidates, targetP95Ms:1000, passed:percentile(.95)<1000
};
const output = new URL('artifacts/', root);
await mkdir(output, {recursive:true});
await writeFile(new URL('benchmark.json', output), JSON.stringify(report, null, 2) + '\n');
console.log(JSON.stringify(report, null, 2));
if (!report.passed) process.exitCode = 1;