@ably/cli
Version:
Ably CLI for Pub/Sub, Chat and Spaces
177 lines (176 loc) • 7.07 kB
JavaScript
import { Flags } from "@oclif/core";
import chalk from "chalk";
import { ControlBaseCommand } from "../../../control-base-command.js";
import { StatsDisplay } from "../../../services/stats-display.js";
export default class AccountsStatsCommand extends ControlBaseCommand {
static description = "Get account stats with optional live updates";
static examples = [
"$ ably accounts stats",
"$ ably accounts stats --unit hour",
"$ ably accounts stats --start 1618005600000 --end 1618091999999",
"$ ably accounts stats --limit 10",
"$ ably accounts stats --json",
"$ ably accounts stats --pretty-json",
"$ ably accounts stats --live",
"$ ably accounts stats --live --interval 15",
];
static flags = {
...ControlBaseCommand.globalFlags,
debug: Flags.boolean({
default: false,
description: "Show debug information for live stats polling",
}),
end: Flags.integer({
description: "End time in milliseconds since epoch",
}),
interval: Flags.integer({
default: 6,
description: "Polling interval in seconds (only used with --live)",
}),
limit: Flags.integer({
default: 10,
description: "Maximum number of stats records to return",
}),
live: Flags.boolean({
default: false,
description: "Subscribe to live stats updates (uses minute interval)",
}),
start: Flags.integer({
description: "Start time in milliseconds since epoch",
}),
unit: Flags.string({
default: "minute",
description: "Time unit for stats",
options: ["minute", "hour", "day", "month"],
}),
};
isPolling = false;
pollInterval = undefined;
statsDisplay = null; // Track when we're already fetching stats
async run() {
const { flags } = await this.parse(AccountsStatsCommand);
// For live stats, enforce minute interval
if (flags.live && flags.unit !== "minute") {
this.warn("Live stats only support minute intervals. Using minute interval.");
flags.unit = "minute";
}
// Display authentication information
this.showAuthInfoIfNeeded(flags);
const controlApi = this.createControlApi(flags);
// Create stats display
this.statsDisplay = new StatsDisplay({
intervalSeconds: flags.interval,
isAccountStats: true,
json: this.shouldOutputJson(flags),
live: flags.live,
startTime: flags.live ? new Date() : undefined,
unit: flags.unit,
});
await (flags.live
? this.runLiveStats(flags, controlApi)
: this.runOneTimeStats(flags, controlApi));
}
async fetchAndDisplayStats(flags, controlApi) {
try {
const now = new Date();
const start = new Date(now.getTime() - 24 * 60 * 60 * 1000); // Last 24 hours
const stats = await controlApi.getAccountStats({
end: now.getTime(),
limit: 1, // Only get the most recent stats for live updates
start: start.getTime(),
unit: flags.unit,
});
if (stats.length > 0) {
this.statsDisplay.display(stats[0]);
}
}
catch (error) {
this.error(`Error fetching stats: ${error instanceof Error ? error.message : String(error)}`);
}
}
async pollStats(flags, controlApi) {
try {
this.isPolling = true;
if (flags.debug) {
console.log(chalk.dim(`\n[${new Date().toISOString()}] Polling for new stats...`));
}
await this.fetchAndDisplayStats(flags, controlApi);
}
catch (error) {
if (flags.debug) {
console.error(chalk.red(`Error during stats polling: ${error instanceof Error ? error.message : String(error)}`));
}
}
finally {
this.isPolling = false;
}
}
async runLiveStats(flags, controlApi) {
try {
// Get account info to display the name
const { account } = await controlApi.getMe();
this.log(`Subscribing to live stats for account ${account.name} (${account.id})...`);
// Setup graceful shutdown
const cleanup = () => {
if (this.pollInterval) {
clearInterval(this.pollInterval);
this.pollInterval = undefined;
}
this.log("\nUnsubscribed from live stats");
};
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);
// Show stats immediately before starting polling
await this.fetchAndDisplayStats(flags, controlApi);
// Poll for stats at the specified interval
this.pollInterval = setInterval(() => {
// Use non-blocking polling - don't wait for previous poll to complete
if (!this.isPolling) {
this.pollStats(flags, controlApi);
}
else if (flags.debug) {
// Only show this message if debug flag is enabled
console.log(chalk.yellow("Skipping poll - previous request still in progress"));
}
}, flags.interval * 1000);
// Keep the process running
await new Promise(() => {
// This promise is intentionally never resolved
// The process will exit via the SIGINT/SIGTERM handlers
});
}
catch (error) {
this.error(`Error setting up live stats: ${error instanceof Error ? error.message : String(error)}`);
if (this.pollInterval) {
clearInterval(this.pollInterval);
}
}
}
async runOneTimeStats(flags, controlApi) {
try {
// If no start/end time provided, use the last 24 hours
if (!flags.start && !flags.end) {
const now = new Date();
flags.end = now.getTime();
flags.start = now.getTime() - 24 * 60 * 60 * 1000; // 24 hours ago
}
const stats = await controlApi.getAccountStats({
end: flags.end,
limit: flags.limit,
start: flags.start,
unit: flags.unit,
});
if (stats.length === 0) {
this.log("No stats found for the specified period");
return;
}
// Display each stat interval
for (const stat of stats) {
this.statsDisplay.display(stat);
}
}
catch (error) {
this.error(`Error fetching account stats: ${error instanceof Error ? error.message : String(error)}`);
}
}
}