|
import Kernel from "@onkernel/sdk"; |
|
import { createInterface } from "node:readline"; |
|
import { createWriteStream } from "node:fs"; |
|
|
|
const rl = createInterface({ |
|
input: process.stdin, |
|
output: process.stderr, |
|
}); |
|
|
|
function ask(question: string): Promise<string> { |
|
return new Promise((resolve) => rl.question(question, resolve)); |
|
} |
|
|
|
function parseDate(input: string): Date { |
|
const d = new Date(input); |
|
if (isNaN(d.getTime())) { |
|
console.error(`Invalid date: ${input}`); |
|
process.exit(1); |
|
} |
|
return d; |
|
} |
|
|
|
function getBrowserType(headless: boolean, gpu?: boolean): string { |
|
if (headless) return "headless"; |
|
if (gpu) return "gpu"; |
|
return "headful"; |
|
} |
|
|
|
function formatMs(ms: number): string { |
|
const totalSeconds = Math.floor(ms / 1000); |
|
const hours = Math.floor(totalSeconds / 3600); |
|
const minutes = Math.floor((totalSeconds % 3600) / 60); |
|
const seconds = totalSeconds % 60; |
|
return `${hours}h ${minutes}m ${seconds}s`; |
|
} |
|
|
|
function escapeCSV(value: string): string { |
|
if (value.includes(",") || value.includes('"') || value.includes("\n")) { |
|
return `"${value.replace(/"/g, '""')}"`; |
|
} |
|
return value; |
|
} |
|
|
|
async function main() { |
|
let apiKey = process.env.KERNEL_API_KEY; |
|
if (!apiKey) { |
|
apiKey = await ask("Enter your Kernel API key: "); |
|
if (!apiKey.trim()) { |
|
console.error("API key is required."); |
|
process.exit(1); |
|
} |
|
} |
|
|
|
const startInput = await ask("Start date (YYYY-MM-DD): "); |
|
const startDate = parseDate(startInput); |
|
|
|
const endInput = await ask("End date (YYYY-MM-DD): "); |
|
const endDate = parseDate(endInput); |
|
endDate.setHours(23, 59, 59, 999); |
|
|
|
if (endDate < startDate) { |
|
console.error("End date must be after start date."); |
|
process.exit(1); |
|
} |
|
|
|
rl.close(); |
|
|
|
const client = new Kernel({ apiKey }); |
|
|
|
const outputFile = `sessions_${startInput}_${endInput}.csv`; |
|
const ws = createWriteStream(outputFile); |
|
const columns = [ |
|
"session_id", |
|
"created_at", |
|
"deleted_at", |
|
"usage_time", |
|
"usage_ms", |
|
"pool_id", |
|
"pool_name", |
|
"browser_type", |
|
]; |
|
ws.write(columns.join(",") + "\n"); |
|
|
|
console.error(`Fetching sessions from ${startInput} to ${endInput}...`); |
|
|
|
let count = 0; |
|
let scanned = 0; |
|
|
|
for await (const session of client.browsers.list({ |
|
status: "all", |
|
limit: 100, |
|
})) { |
|
scanned++; |
|
const createdAt = new Date(session.created_at); |
|
|
|
// Sessions are returned newest-first. Once we pass the start date, stop. |
|
if (createdAt < startDate) break; |
|
if (createdAt > endDate) continue; |
|
|
|
const uptimeMs = session.usage?.uptime_ms ?? 0; |
|
|
|
const row = [ |
|
escapeCSV(session.session_id), |
|
escapeCSV(session.created_at), |
|
escapeCSV(session.deleted_at ?? ""), |
|
escapeCSV(uptimeMs > 0 ? formatMs(uptimeMs) : "active"), |
|
String(uptimeMs), |
|
escapeCSV(session.pool?.id ?? ""), |
|
escapeCSV(session.pool?.name ?? ""), |
|
escapeCSV(getBrowserType(session.headless, session.gpu)), |
|
]; |
|
ws.write(row.join(",") + "\n"); |
|
count++; |
|
|
|
if (scanned % 500 === 0) { |
|
console.error(` scanned ${scanned} sessions, matched ${count}...`); |
|
} |
|
} |
|
|
|
ws.end(); |
|
console.error( |
|
`\nDone. ${count} sessions written to ${outputFile} (scanned ${scanned}).` |
|
); |
|
} |
|
|
|
main().catch((err) => { |
|
console.error("Error:", err.message ?? err); |
|
process.exit(1); |
|
}); |