@deniscuciuc/redis-analyzer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +244 -0
  4. package/analyzerrc.example.json +17 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +137 -0
  8. package/dist/src/analyzers/memory-analyzer.d.ts +5 -0
  9. package/dist/src/analyzers/memory-analyzer.d.ts.map +1 -0
  10. package/dist/src/analyzers/memory-analyzer.js +54 -0
  11. package/dist/src/analyzers/performance-analyzer.d.ts +6 -0
  12. package/dist/src/analyzers/performance-analyzer.d.ts.map +1 -0
  13. package/dist/src/analyzers/performance-analyzer.js +78 -0
  14. package/dist/src/analyzers/persistence-analyzer.d.ts +5 -0
  15. package/dist/src/analyzers/persistence-analyzer.d.ts.map +1 -0
  16. package/dist/src/analyzers/persistence-analyzer.js +59 -0
  17. package/dist/src/analyzers/replication-analyzer.d.ts +5 -0
  18. package/dist/src/analyzers/replication-analyzer.d.ts.map +1 -0
  19. package/dist/src/analyzers/replication-analyzer.js +52 -0
  20. package/dist/src/cli/options.d.ts +24 -0
  21. package/dist/src/cli/options.d.ts.map +1 -0
  22. package/dist/src/cli/options.js +155 -0
  23. package/dist/src/cli/runner.d.ts +13 -0
  24. package/dist/src/cli/runner.d.ts.map +1 -0
  25. package/dist/src/cli/runner.js +214 -0
  26. package/dist/src/collectors/stats-collector.d.ts +15 -0
  27. package/dist/src/collectors/stats-collector.d.ts.map +1 -0
  28. package/dist/src/collectors/stats-collector.js +151 -0
  29. package/dist/src/config/loader.d.ts +13 -0
  30. package/dist/src/config/loader.d.ts.map +1 -0
  31. package/dist/src/config/loader.js +63 -0
  32. package/dist/src/constants.d.ts +52 -0
  33. package/dist/src/constants.d.ts.map +1 -0
  34. package/dist/src/constants.js +93 -0
  35. package/dist/src/health.d.ts +10 -0
  36. package/dist/src/health.d.ts.map +1 -0
  37. package/dist/src/health.js +100 -0
  38. package/dist/src/interactive/display.d.ts +13 -0
  39. package/dist/src/interactive/display.d.ts.map +1 -0
  40. package/dist/src/interactive/display.js +130 -0
  41. package/dist/src/interactive/index.d.ts +23 -0
  42. package/dist/src/interactive/index.d.ts.map +1 -0
  43. package/dist/src/interactive/index.js +236 -0
  44. package/dist/src/interactive/menus.d.ts +25 -0
  45. package/dist/src/interactive/menus.d.ts.map +1 -0
  46. package/dist/src/interactive/menus.js +49 -0
  47. package/dist/src/reporters/diff-reporter.d.ts +21 -0
  48. package/dist/src/reporters/diff-reporter.d.ts.map +1 -0
  49. package/dist/src/reporters/diff-reporter.js +96 -0
  50. package/dist/src/reporters/html-reporter.d.ts +9 -0
  51. package/dist/src/reporters/html-reporter.d.ts.map +1 -0
  52. package/dist/src/reporters/html-reporter.js +140 -0
  53. package/dist/src/reporters/report-generator.d.ts +23 -0
  54. package/dist/src/reporters/report-generator.d.ts.map +1 -0
  55. package/dist/src/reporters/report-generator.js +239 -0
  56. package/dist/src/types.d.ts +184 -0
  57. package/dist/src/types.d.ts.map +1 -0
  58. package/dist/src/types.js +2 -0
  59. package/dist/src/utils/format.d.ts +6 -0
  60. package/dist/src/utils/format.d.ts.map +1 -0
  61. package/dist/src/utils/format.js +32 -0
  62. package/dist/src/utils/print.d.ts +8 -0
  63. package/dist/src/utils/print.d.ts.map +1 -0
  64. package/dist/src/utils/print.js +42 -0
  65. package/dist/src/watch/runner.d.ts +8 -0
  66. package/dist/src/watch/runner.d.ts.map +1 -0
  67. package/dist/src/watch/runner.js +49 -0
  68. package/dist/tests/analysis-and-reports.test.d.ts +2 -0
  69. package/dist/tests/analysis-and-reports.test.d.ts.map +1 -0
  70. package/dist/tests/analysis-and-reports.test.js +172 -0
  71. package/dist/tests/collector-and-options.test.d.ts +2 -0
  72. package/dist/tests/collector-and-options.test.d.ts.map +1 -0
  73. package/dist/tests/collector-and-options.test.js +110 -0
  74. package/package.json +82 -0
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PersistenceAnalyzer = void 0;
4
+ const constants_1 = require("../constants");
5
+ class PersistenceAnalyzer {
6
+ analyze(info) {
7
+ const now = Math.floor(Date.now() / 1000);
8
+ const secondsSinceRdb = info.rdbLastSaveTime > 0 ? now - info.rdbLastSaveTime : 0;
9
+ const hoursSinceRdb = secondsSinceRdb / 3600;
10
+ const recommendations = [];
11
+ let severity = "ok";
12
+ if (info.rdbLastBgsaveStatus === "err") {
13
+ severity = "critical";
14
+ recommendations.push("Last RDB save failed. Check Redis logs and available disk space immediately.");
15
+ }
16
+ else if (info.rdbLastSaveTime > 0 &&
17
+ hoursSinceRdb > constants_1.THRESHOLDS.persistence.rdbStaleHours) {
18
+ severity = "warning";
19
+ recommendations.push(`No successful RDB save in ${hoursSinceRdb.toFixed(1)} hours. Verify save settings or trigger a manual BGSAVE.`);
20
+ }
21
+ if (info.aofEnabled && info.aofLastBgrewriteStatus === "err") {
22
+ severity = "critical";
23
+ recommendations.push("Last AOF rewrite failed. Check disk pressure and Redis background rewrite activity.");
24
+ }
25
+ if (!info.aofEnabled && info.rdbChangesSinceLastSave > 10_000) {
26
+ if (severity === "ok") {
27
+ severity = "warning";
28
+ }
29
+ recommendations.push(`${info.rdbChangesSinceLastSave.toLocaleString()} unsaved changes detected since the last RDB snapshot. Consider enabling AOF or reducing snapshot intervals.`);
30
+ }
31
+ if (!info.aofEnabled && info.rdbLastSaveTime === 0) {
32
+ if (severity === "ok") {
33
+ severity = "warning";
34
+ }
35
+ recommendations.push("No persistence configured (no AOF and no successful RDB snapshot). Data will be lost on restart.");
36
+ }
37
+ return {
38
+ rdb: {
39
+ enabled: info.rdbLastSaveTime > 0 ||
40
+ info.rdbChangesSinceLastSave > 0 ||
41
+ info.rdbBgsaveInProgress,
42
+ lastSaveTime: info.rdbLastSaveTime,
43
+ secondsSinceLastSave: secondsSinceRdb,
44
+ lastStatus: info.rdbLastBgsaveStatus,
45
+ changesSinceLastSave: info.rdbChangesSinceLastSave,
46
+ saveInProgress: info.rdbBgsaveInProgress,
47
+ },
48
+ aof: {
49
+ enabled: info.aofEnabled,
50
+ lastStatus: info.aofLastBgrewriteStatus,
51
+ rewriteInProgress: info.aofRewriteInProgress,
52
+ currentSizeBytes: info.aofCurrentSize,
53
+ },
54
+ severity,
55
+ recommendations,
56
+ };
57
+ }
58
+ }
59
+ exports.PersistenceAnalyzer = PersistenceAnalyzer;
@@ -0,0 +1,5 @@
1
+ import type { RedisInfo, ReplicationAnalysis } from "../types";
2
+ export declare class ReplicationAnalyzer {
3
+ analyze(info: RedisInfo): ReplicationAnalysis;
4
+ }
5
+ //# sourceMappingURL=replication-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replication-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/replication-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAE/D,qBAAa,mBAAmB;IAC/B,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,mBAAmB;CA0D7C"}
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReplicationAnalyzer = void 0;
4
+ const constants_1 = require("../constants");
5
+ class ReplicationAnalyzer {
6
+ analyze(info) {
7
+ if (info.role === "master" && info.connectedSlaves === 0) {
8
+ return {
9
+ role: "standalone",
10
+ connectedSlaves: 0,
11
+ severity: "ok",
12
+ recommendations: [],
13
+ };
14
+ }
15
+ const recommendations = [];
16
+ let severity = "ok";
17
+ let lagSeconds;
18
+ if (info.role === "slave") {
19
+ if (info.masterLinkStatus === "down") {
20
+ severity = "critical";
21
+ recommendations.push("Replication link to the primary is down. Check network reachability and primary health.");
22
+ }
23
+ else if (info.masterLastIoSecondsAgo !== undefined) {
24
+ lagSeconds = info.masterLastIoSecondsAgo;
25
+ if (lagSeconds >= constants_1.THRESHOLDS.replication.lagCritical) {
26
+ severity = "critical";
27
+ recommendations.push(`Replication lag is ${lagSeconds}s. Check network throughput, replica CPU pressure, and write load on the primary.`);
28
+ }
29
+ else if (lagSeconds >= constants_1.THRESHOLDS.replication.lagWarning) {
30
+ severity = "warning";
31
+ recommendations.push(`Replication lag of ${lagSeconds}s detected. Monitor catch-up speed and backlog size.`);
32
+ }
33
+ }
34
+ if (info.masterSyncInProgress) {
35
+ recommendations.push("A full resync is in progress. Expect reduced replica freshness until synchronization completes.");
36
+ }
37
+ }
38
+ if (info.role === "master" && info.connectedSlaves > 0) {
39
+ recommendations.push(`Primary has ${info.connectedSlaves} replica(s). Validate lag from replica INFO output.`);
40
+ }
41
+ return {
42
+ role: info.role === "master" ? "master" : "slave",
43
+ connectedSlaves: info.connectedSlaves,
44
+ linkStatus: info.masterLinkStatus,
45
+ lagSeconds,
46
+ syncInProgress: info.masterSyncInProgress,
47
+ severity,
48
+ recommendations,
49
+ };
50
+ }
51
+ }
52
+ exports.ReplicationAnalyzer = ReplicationAnalyzer;
@@ -0,0 +1,24 @@
1
+ import type { AnalyzerOptions } from "../types";
2
+ export interface ParsedOptions extends AnalyzerOptions {
3
+ host: string;
4
+ port: number;
5
+ password?: string;
6
+ db: number;
7
+ tls: boolean;
8
+ uri?: string;
9
+ profile?: string;
10
+ config?: string;
11
+ compare?: string;
12
+ html: boolean;
13
+ watch?: number;
14
+ command: string;
15
+ json: boolean;
16
+ quiet: boolean;
17
+ outputDir: string;
18
+ slowCommandThreshold: number;
19
+ maxSlowCommands: number;
20
+ interactive: boolean;
21
+ }
22
+ export declare function parseOptions(argv?: string[]): ParsedOptions;
23
+ export declare function toAnalyzerOptions(options: ParsedOptions): AnalyzerOptions;
24
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/cli/options.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD,MAAM,WAAW,aAAc,SAAQ,eAAe;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,IAAI,WAAwB,GAAG,aAAa,CA8GxE;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,aAAa,GAAG,eAAe,CAMzE"}
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseOptions = parseOptions;
4
+ exports.toAnalyzerOptions = toAnalyzerOptions;
5
+ const constants_1 = require("../constants");
6
+ function parseOptions(argv = process.argv.slice(2)) {
7
+ const options = {
8
+ host: constants_1.DEFAULTS.host,
9
+ port: constants_1.DEFAULTS.port,
10
+ db: constants_1.DEFAULTS.db,
11
+ tls: false,
12
+ command: "full",
13
+ json: false,
14
+ quiet: false,
15
+ html: false,
16
+ outputDir: constants_1.DEFAULTS.output,
17
+ slowCommandThreshold: constants_1.DEFAULTS.slowCommandThreshold,
18
+ maxSlowCommands: constants_1.DEFAULTS.maxSlowCommands,
19
+ interactive: false,
20
+ };
21
+ for (let index = 0; index < argv.length; index++) {
22
+ switch (argv[index]) {
23
+ case "--host":
24
+ case "-h":
25
+ options.host = argv[++index];
26
+ break;
27
+ case "--port":
28
+ case "-p":
29
+ options.port = Number.parseInt(argv[++index], 10);
30
+ break;
31
+ case "--password":
32
+ case "-a":
33
+ options.password = argv[++index];
34
+ break;
35
+ case "--db":
36
+ case "-n":
37
+ options.db = Number.parseInt(argv[++index], 10);
38
+ break;
39
+ case "--tls":
40
+ options.tls = true;
41
+ break;
42
+ case "--uri":
43
+ options.uri = argv[++index];
44
+ break;
45
+ case "--output":
46
+ case "-o":
47
+ options.outputDir = argv[++index];
48
+ break;
49
+ case "--profile":
50
+ options.profile = argv[++index];
51
+ break;
52
+ case "--config":
53
+ options.config = argv[++index];
54
+ break;
55
+ case "--compare":
56
+ options.compare = argv[++index];
57
+ break;
58
+ case "--html":
59
+ options.html = true;
60
+ break;
61
+ case "--watch": {
62
+ const nextValue = argv[index + 1];
63
+ if (nextValue && !nextValue.startsWith("-")) {
64
+ options.watch = Number.parseInt(nextValue, 10);
65
+ index++;
66
+ }
67
+ else {
68
+ options.watch = constants_1.DEFAULTS.watchInterval;
69
+ }
70
+ break;
71
+ }
72
+ case "--slow-threshold":
73
+ options.slowCommandThreshold = Number.parseInt(argv[++index], 10);
74
+ break;
75
+ case "--max-slow-commands":
76
+ options.maxSlowCommands = Number.parseInt(argv[++index], 10);
77
+ break;
78
+ case "--help":
79
+ printHelp();
80
+ process.exit(0);
81
+ return options;
82
+ case "--json":
83
+ case "-j":
84
+ options.json = true;
85
+ break;
86
+ case "--quiet":
87
+ case "-q":
88
+ options.quiet = true;
89
+ break;
90
+ case "--command":
91
+ case "-c":
92
+ options.command = argv[++index];
93
+ break;
94
+ case "--interactive":
95
+ case "-i":
96
+ case "start":
97
+ options.interactive = true;
98
+ break;
99
+ }
100
+ }
101
+ if (options.watch !== undefined &&
102
+ (!Number.isFinite(options.watch) || options.watch <= 0)) {
103
+ throw new Error(`Invalid watch interval: ${options.watch}`);
104
+ }
105
+ if (!constants_1.COMMANDS.includes(options.command)) {
106
+ throw new Error(`Unknown command "${options.command}". Use --help to see available commands.`);
107
+ }
108
+ return options;
109
+ }
110
+ function toAnalyzerOptions(options) {
111
+ return {
112
+ slowCommandThreshold: options.slowCommandThreshold,
113
+ maxSlowCommands: options.maxSlowCommands,
114
+ outputDir: options.outputDir,
115
+ };
116
+ }
117
+ function printHelp() {
118
+ console.log(`
119
+ Redis Analyzer
120
+ ==============
121
+
122
+ Usage:
123
+ npx ts-node index.ts [options]
124
+
125
+ Connection options:
126
+ -h, --host <host> Redis host (env: REDIS_HOST)
127
+ -p, --port <port> Redis port (env: REDIS_PORT)
128
+ -a, --password <password> Redis password (env: REDIS_PASSWORD)
129
+ -n, --db <number> Redis database number (env: REDIS_DB)
130
+ --tls Enable TLS (env: REDIS_TLS=true)
131
+ --uri <uri> Redis URI (env: REDIS_URI)
132
+ --profile <name> Use named profile from .analyzerrc.json
133
+ --config <path> Use a custom config file path
134
+
135
+ Analysis options:
136
+ --slow-threshold <μs> Slow command threshold in microseconds (default: ${constants_1.DEFAULTS.slowCommandThreshold})
137
+ --max-slow-commands <n> Max slow log rows to fetch (default: ${constants_1.DEFAULTS.maxSlowCommands})
138
+ --compare <path> Compare against a previous JSON report
139
+ --watch [seconds] Watch mode (default interval: ${constants_1.DEFAULTS.watchInterval}s)
140
+
141
+ Output options:
142
+ -o, --output <dir> Output directory for reports (default: ${constants_1.DEFAULTS.output})
143
+ -j, --json Output JSON to stdout
144
+ --html Also generate an HTML report
145
+ -q, --quiet Suppress non-essential output
146
+ -i, --interactive Interactive mode with menu
147
+ start Alias for --interactive
148
+
149
+ Commands:
150
+ -c, --command <cmd> Run a specific analysis command
151
+
152
+ Available commands:
153
+ ${constants_1.COMMANDS.join("\n ")}
154
+ `);
155
+ }
@@ -0,0 +1,13 @@
1
+ import type { Redis } from "ioredis";
2
+ import type { FullRedisReport, FullReport, RedisConnection, RedisInfo } from "../types";
3
+ import type { ParsedOptions } from "./options";
4
+ export declare function summarizeKeyspaces(info: RedisInfo): {
5
+ totalKeys: number;
6
+ totalExpiringKeys: number;
7
+ expiringRatio: number;
8
+ keyspaces: RedisInfo["keyspaces"];
9
+ };
10
+ export declare function buildFullReport(client: Redis, options: ParsedOptions, connection: RedisConnection): Promise<FullRedisReport>;
11
+ export declare function executeCommand(client: Redis, options: ParsedOptions, connection: RedisConnection): Promise<void>;
12
+ export declare function loadPreviousReport(reportPath: string): FullReport;
13
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/cli/runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAUrC,OAAO,KAAK,EACX,eAAe,EACf,UAAU,EACV,eAAe,EACf,SAAS,EAET,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AA+C/C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,GAAG;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;CAClC,CAeA;AAED,wBAAsB,eAAe,CACpC,MAAM,EAAE,KAAK,EACb,OAAO,EAAE,aAAa,EACtB,UAAU,EAAE,eAAe,GACzB,OAAO,CAAC,eAAe,CAAC,CAmD1B;AAED,wBAAsB,cAAc,CACnC,MAAM,EAAE,KAAK,EACb,OAAO,EAAE,aAAa,EACtB,UAAU,EAAE,eAAe,GACzB,OAAO,CAAC,IAAI,CAAC,CA2Df;AAwFD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CASjE"}
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.summarizeKeyspaces = summarizeKeyspaces;
4
+ exports.buildFullReport = buildFullReport;
5
+ exports.executeCommand = executeCommand;
6
+ exports.loadPreviousReport = loadPreviousReport;
7
+ const node_fs_1 = require("node:fs");
8
+ const memory_analyzer_1 = require("../analyzers/memory-analyzer");
9
+ const performance_analyzer_1 = require("../analyzers/performance-analyzer");
10
+ const persistence_analyzer_1 = require("../analyzers/persistence-analyzer");
11
+ const replication_analyzer_1 = require("../analyzers/replication-analyzer");
12
+ const stats_collector_1 = require("../collectors/stats-collector");
13
+ const constants_1 = require("../constants");
14
+ const health_1 = require("../health");
15
+ const diff_reporter_1 = require("../reporters/diff-reporter");
16
+ const report_generator_1 = require("../reporters/report-generator");
17
+ const options_1 = require("./options");
18
+ function createServices(client, options) {
19
+ const analyzerOptions = (0, options_1.toAnalyzerOptions)(options);
20
+ return {
21
+ client,
22
+ collector: new stats_collector_1.StatsCollector(client),
23
+ memory: new memory_analyzer_1.MemoryAnalyzer(),
24
+ performance: new performance_analyzer_1.PerformanceAnalyzer(),
25
+ persistence: new persistence_analyzer_1.PersistenceAnalyzer(),
26
+ replication: new replication_analyzer_1.ReplicationAnalyzer(),
27
+ reporter: new report_generator_1.ReportGenerator(options.outputDir, analyzerOptions),
28
+ };
29
+ }
30
+ function buildMetrics(info) {
31
+ return {
32
+ version: info.redisVersion,
33
+ mode: info.redisMode,
34
+ uptimeDays: info.uptimeInDays,
35
+ connectedClients: info.connectedClients,
36
+ blockedClients: info.blockedClients,
37
+ maxClients: info.maxClients,
38
+ opsPerSec: info.instantaneousOpsPerSec,
39
+ totalKeyCount: info.keyspaces.reduce((sum, keyspace) => sum + keyspace.keys, 0),
40
+ keyspacesCount: info.keyspaces.length,
41
+ rejectedConnections: info.rejectedConnections,
42
+ };
43
+ }
44
+ function summarizeKeyspaces(info) {
45
+ const totalKeys = info.keyspaces.reduce((sum, keyspace) => sum + keyspace.keys, 0);
46
+ const totalExpiringKeys = info.keyspaces.reduce((sum, keyspace) => sum + keyspace.expires, 0);
47
+ return {
48
+ totalKeys,
49
+ totalExpiringKeys,
50
+ expiringRatio: totalKeys === 0 ? 0 : (totalExpiringKeys / totalKeys) * 100,
51
+ keyspaces: info.keyspaces,
52
+ };
53
+ }
54
+ async function buildFullReport(client, options, connection) {
55
+ const services = createServices(client, options);
56
+ const [info, totalSlowLogged, config] = await Promise.all([
57
+ services.collector.getInfo(),
58
+ services.collector.getSlowLogLength(),
59
+ services.collector.getConfigValues(constants_1.IMPORTANT_CONFIG_KEYS),
60
+ ]);
61
+ const slowCommands = await services.collector.getSlowLog(options.maxSlowCommands);
62
+ const memory = services.memory.analyze(info);
63
+ const hitRate = services.performance.analyzeHitRate(info);
64
+ const persistence = services.persistence.analyze(info);
65
+ const replication = services.replication.analyze(info);
66
+ const slowCommandAnalysis = services.performance.analyzeSlowCommands(slowCommands, totalSlowLogged, options.slowCommandThreshold);
67
+ const metrics = buildMetrics(info);
68
+ const reportBase = {
69
+ generatedAt: new Date(),
70
+ host: connection.host,
71
+ port: connection.port,
72
+ db: connection.db,
73
+ metrics,
74
+ memory,
75
+ hitRate,
76
+ persistence,
77
+ replication,
78
+ slowCommands: slowCommandAnalysis,
79
+ keyspaces: info.keyspaces,
80
+ config,
81
+ };
82
+ const recommendations = (0, health_1.buildRecommendations)({
83
+ memory,
84
+ hitRate,
85
+ persistence,
86
+ replication,
87
+ metrics,
88
+ });
89
+ const healthScore = (0, health_1.computeHealthScore)(reportBase);
90
+ return {
91
+ ...reportBase,
92
+ healthScore,
93
+ recommendations,
94
+ };
95
+ }
96
+ async function executeCommand(client, options, connection) {
97
+ const log = options.quiet || options.json ? () => { } : console.log;
98
+ const services = createServices(client, options);
99
+ if (options.command !== "full") {
100
+ const result = await runCommand(services, options, connection);
101
+ console.log(JSON.stringify(result, null, 2));
102
+ return;
103
+ }
104
+ const report = await buildFullReport(client, options, connection);
105
+ if (options.compare) {
106
+ const previous = loadPreviousReport(options.compare);
107
+ diff_reporter_1.DiffReporter.print(diff_reporter_1.DiffReporter.diff(report, previous), options.json ? console.error : console.log);
108
+ }
109
+ if (options.json) {
110
+ console.log(JSON.stringify({
111
+ success: true,
112
+ report,
113
+ summary: {
114
+ healthScore: report.healthScore,
115
+ usedMemory: report.memory.usedMemoryHuman,
116
+ memoryUsagePercent: Number(report.memory.usagePercent.toFixed(2)),
117
+ hitRate: Number(report.hitRate.hitRate.toFixed(2)),
118
+ totalKeys: report.metrics.totalKeyCount,
119
+ slowCommandsLogged: report.slowCommands.totalLogged,
120
+ rejectedConnections: report.metrics.rejectedConnections,
121
+ },
122
+ recommendations: report.recommendations,
123
+ }, null, 2));
124
+ return;
125
+ }
126
+ services.reporter.printSummary(report);
127
+ log("\nGenerating reports...");
128
+ const [markdown, json, html] = await Promise.all([
129
+ services.reporter.generateFullReport(report),
130
+ services.reporter.generateJsonReport(report),
131
+ options.html ? services.reporter.generateHtmlReport(report) : undefined,
132
+ ]);
133
+ log("\nReports generated:");
134
+ log(` - Markdown: ${markdown}`);
135
+ log(` - JSON: ${json}`);
136
+ if (html) {
137
+ log(` - HTML: ${html}`);
138
+ }
139
+ }
140
+ async function runCommand(services, options, connection) {
141
+ switch (options.command) {
142
+ case "health": {
143
+ const report = await buildFullReport(services.client, options, connection);
144
+ return {
145
+ healthScore: report.healthScore,
146
+ metrics: report.metrics,
147
+ issues: report.recommendations.map((recommendation) => recommendation.message),
148
+ };
149
+ }
150
+ case "server-info": {
151
+ const info = await services.collector.getInfo();
152
+ return {
153
+ version: info.redisVersion,
154
+ mode: info.redisMode,
155
+ os: info.os,
156
+ uptimeDays: info.uptimeInDays,
157
+ tcpPort: info.tcpPort,
158
+ configFile: info.configFile,
159
+ };
160
+ }
161
+ case "memory": {
162
+ const info = await services.collector.getInfo();
163
+ return services.memory.analyze(info);
164
+ }
165
+ case "hit-rate": {
166
+ const info = await services.collector.getInfo();
167
+ return services.performance.analyzeHitRate(info);
168
+ }
169
+ case "slow-commands": {
170
+ const [commands, totalLogged] = await Promise.all([
171
+ services.collector.getSlowLog(options.maxSlowCommands),
172
+ services.collector.getSlowLogLength(),
173
+ ]);
174
+ return services.performance.analyzeSlowCommands(commands, totalLogged, options.slowCommandThreshold);
175
+ }
176
+ case "keys": {
177
+ const info = await services.collector.getInfo();
178
+ return summarizeKeyspaces(info);
179
+ }
180
+ case "connections": {
181
+ const info = await services.collector.getInfo();
182
+ return {
183
+ connectedClients: info.connectedClients,
184
+ blockedClients: info.blockedClients,
185
+ maxClients: info.maxClients,
186
+ clientUtilization: info.maxClients === 0
187
+ ? 0
188
+ : Number(((info.connectedClients / info.maxClients) * 100).toFixed(2)),
189
+ rejectedConnections: info.rejectedConnections,
190
+ recentMaxInputBufferBytes: info.clientRecentMaxInputBuffer,
191
+ recentMaxOutputBufferBytes: info.clientRecentMaxOutputBuffer,
192
+ };
193
+ }
194
+ case "persistence": {
195
+ const info = await services.collector.getInfo();
196
+ return services.persistence.analyze(info);
197
+ }
198
+ case "replication": {
199
+ const info = await services.collector.getInfo();
200
+ return services.replication.analyze(info);
201
+ }
202
+ case "config":
203
+ return services.collector.getConfigValues(constants_1.IMPORTANT_CONFIG_KEYS);
204
+ default:
205
+ throw new Error(`Unsupported command: ${options.command}`);
206
+ }
207
+ }
208
+ function loadPreviousReport(reportPath) {
209
+ if (!(0, node_fs_1.existsSync)(reportPath)) {
210
+ throw new Error(`Previous report not found: ${reportPath}`);
211
+ }
212
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(reportPath, "utf-8"));
213
+ return "report" in parsed ? parsed.report : parsed;
214
+ }
@@ -0,0 +1,15 @@
1
+ import type { Redis } from "ioredis";
2
+ import type { RedisInfo, SlowCommand } from "../types";
3
+ type RawSlowLogEntry = [number, number, number, string[], string?, string?];
4
+ export declare function parseSlowLogEntries(entries: RawSlowLogEntry[]): SlowCommand[];
5
+ export declare function parseRedisInfo(raw: string): RedisInfo;
6
+ export declare class StatsCollector {
7
+ private readonly client;
8
+ constructor(client: Redis);
9
+ getInfo(): Promise<RedisInfo>;
10
+ getSlowLog(count: number): Promise<SlowCommand[]>;
11
+ getSlowLogLength(): Promise<number>;
12
+ getConfigValues(keys: readonly string[]): Promise<Record<string, string>>;
13
+ }
14
+ export {};
15
+ //# sourceMappingURL=stats-collector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats-collector.d.ts","sourceRoot":"","sources":["../../../src/collectors/stats-collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAgB,SAAS,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAErE,KAAK,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAE5E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,WAAW,EAAE,CAa7E;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CA8GrD;AAED,qBAAa,cAAc;IACd,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,KAAK;IAEpC,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC;IAI7B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAKjD,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAKnC,eAAe,CACpB,IAAI,EAAE,SAAS,MAAM,EAAE,GACrB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAelC"}