Files
openclaw-market/scripts/simulate-live-activity.ts
2026-03-16 08:52:44 +08:00

237 lines
6.2 KiB
TypeScript

/**
* 持续模拟心跳脚本
* 随机选择在线的虾发送心跳和任务,模拟实时活动
*
* 使用方法: npx tsx scripts/simulate-live-activity.ts
*
* 按 Ctrl+C 停止
*/
import { db } from "@/lib/db";
import { claws, heartbeats, tasks, tokenUsage } from "@/lib/db/schema";
import { sql, eq } from "drizzle-orm";
import { redis } from "@/lib/redis";
import { publishEvent } from "@/lib/redis";
const ACTIVE_CLAWS_KEY = "active:claws";
const STATS_GLOBAL_KEY = "stats:global";
const STATS_REGION_KEY = "stats:region";
const HOURLY_ACTIVITY_KEY = "stats:hourly";
// 任务摘要模板
const TASK_SUMMARIES = [
"Refactored authentication module",
"Implemented new API endpoint",
"Fixed memory leak in worker",
"Added unit tests",
"Optimized database queries",
"Integrated OAuth provider",
"Migrated code to TypeScript",
"Built real-time notification",
"Created dashboard components",
"Implemented caching layer",
"Fixed browser compatibility",
"Added i18n support",
"Refactored state management",
"Implemented file upload",
"Built deployment pipeline",
"Created API documentation",
"Implemented rate limiting",
"Fixed race condition",
"Added logging infrastructure",
"Optimized bundle size",
];
const TOOLS_USED = [
["Read", "Edit", "Bash"],
["Grep", "Read", "Write"],
["Bash", "Glob", "Grep"],
["WebSearch", "Read", "Edit"],
["Agent", "Read", "Grep"],
];
// 全局变量存储所有 claws
let allClaws: { id: string; name: string; model: string; city: string | null; country: string | null }[] = [];
async function loadClaws() {
const result = await db
.select({
id: claws.id,
name: claws.name,
model: claws.model,
city: claws.city,
country: claws.country,
})
.from(claws);
allClaws = result;
console.log(`📋 已加载 ${allClaws.length} 只虾\n`);
}
function randomChoice<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)];
}
function randomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
async function sendHeartbeat(claw: typeof allClaws[0]) {
const now = new Date();
// 更新数据库
await db
.update(claws)
.set({ lastHeartbeat: now, updatedAt: now })
.where(eq(claws.id, claw.id));
// 插入心跳记录
await db.insert(heartbeats).values({
clawId: claw.id,
ip: "127.0.0.1",
timestamp: now,
});
// 更新 Redis
await redis.set(`claw:online:${claw.id}`, "simulated", "EX", 300);
await redis.zadd(ACTIVE_CLAWS_KEY, Date.now(), claw.id);
// 发布事件
await publishEvent({
type: "heartbeat",
clawId: claw.id,
clawName: claw.name,
city: claw.city,
country: claw.country,
});
console.log(`💓 [${now.toLocaleTimeString()}] ${claw.name} 发送心跳`);
}
async function sendTask(claw: typeof allClaws[0]) {
const now = new Date();
const summary = randomChoice(TASK_SUMMARIES);
const durationMs = randomInt(10_000, 1_800_000); // 10秒 - 30分钟
const toolsUsed = randomChoice(TOOLS_USED);
// 插入任务
await db.insert(tasks).values({
clawId: claw.id,
summary,
durationMs,
model: claw.model,
toolsUsed,
timestamp: now,
});
// 更新任务计数
await db
.update(claws)
.set({
totalTasks: sql`${claws.totalTasks} + 1`,
updatedAt: now,
})
.where(eq(claws.id, claw.id));
// 更新 Redis 统计
await redis.hincrby(STATS_GLOBAL_KEY, "total_tasks", 1);
await redis.hincrby(STATS_GLOBAL_KEY, "tasks_today", 1);
// 更新每小时活动
const hourKey = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")}T${String(now.getUTCHours()).padStart(2, "0")}`;
await redis.hincrby(HOURLY_ACTIVITY_KEY, hourKey, 1);
// 发布事件
await publishEvent({
type: "task",
clawId: claw.id,
clawName: claw.name,
city: claw.city,
country: claw.country,
summary,
durationMs,
});
console.log(`📋 [${now.toLocaleTimeString()}] ${claw.name} 完成任务: ${summary}`);
}
async function reportTokens(claw: typeof allClaws[0]) {
const now = new Date();
const date = now.toISOString().split("T")[0];
// 随机 token 数量
const inputTokens = randomInt(1_000, 100_000);
const outputTokens = randomInt(500, 50_000);
// Upsert token 使用
await db.execute(sql`
INSERT INTO token_usage (claw_id, date, input_tokens, output_tokens)
VALUES (${claw.id}, ${date}, ${inputTokens}, ${outputTokens})
ON DUPLICATE KEY UPDATE
input_tokens = input_tokens + ${inputTokens},
output_tokens = output_tokens + ${outputTokens},
updated_at = NOW()
`);
// 更新 Redis 总计
await redis.hincrby(STATS_GLOBAL_KEY, "total_input_tokens", inputTokens);
await redis.hincrby(STATS_GLOBAL_KEY, "total_output_tokens", outputTokens);
console.log(`🔢 [${now.toLocaleTimeString()}] ${claw.name} 报告 Token: +${inputTokens} input, +${outputTokens} output`);
}
async function runSimulation() {
console.log("🚀 开始模拟实时活动...\n");
console.log("按 Ctrl+C 停止\n");
console.log("=".repeat(50) + "\n");
await loadClaws();
if (allClaws.length === 0) {
console.log("❌ 没有找到任何虾,请先运行数据生成脚本");
process.exit(1);
}
// 主循环
let iteration = 0;
while (true) {
iteration++;
console.log(`\n--- 第 ${iteration} 轮 ---`);
// 随机选择 1-5 只虾发送心跳
const heartbeatCount = randomInt(1, 5);
for (let i = 0; i < heartbeatCount; i++) {
const claw = randomChoice(allClaws);
await sendHeartbeat(claw);
}
// 30% 概率有虾完成任务
if (Math.random() < 0.3) {
const claw = randomChoice(allClaws);
await sendTask(claw);
}
// 20% 概率有虾报告 token
if (Math.random() < 0.2) {
const claw = randomChoice(allClaws);
await reportTokens(claw);
}
// 等待 2-5 秒
const delay = randomInt(2000, 5000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
// 处理退出
process.on("SIGINT", () => {
console.log("\n\n👋 停止模拟...");
process.exit(0);
});
// 运行
runSimulation().catch((err) => {
console.error("❌ 错误:", err);
process.exit(1);
});