@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.0] - 2026-06-14
11
+
12
+ ### Added
13
+
14
+ - Redis analyzer CLI with memory, hit-rate, persistence, replication, keyspace, connection, and slow-command commands
15
+ - URI, host/port, TLS, password, and database-number connection support
16
+ - `.analyzerrc.json` profiles, compare mode, HTML reports, and watch mode
17
+ - Interactive CLI with report generation and runtime settings
18
+ - Markdown, JSON, and HTML reports under `./reports/`
19
+ - Diff reporting between JSON snapshots
20
+ - Automated tests for option parsing, Redis INFO parsing, health scoring, and report generation
21
+ - GitHub Actions CI and publish workflows
22
+ - Dependabot configuration for npm and GitHub Actions
23
+ - MIT license and OSS metadata
24
+
25
+ ### Changed
26
+
27
+ - Full analysis generates Markdown, JSON, and optional HTML reports in one run
28
+
29
+ ### Fixed
30
+
31
+ - Explicit `--profile` selection overrides sourced environment defaults for the active run
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Denis Cuciuc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,244 @@
1
+ # Redis Analyzer
2
+
3
+ [![Node.js 20+](https://img.shields.io/badge/node-20%2B-339933?logo=node.js)](https://nodejs.org/)
4
+ [![npm version](https://img.shields.io/npm/v/@deniscuciuc/redis-analyzer?logo=npm&color=cb3837)](https://www.npmjs.com/package/@deniscuciuc/redis-analyzer)
5
+ [![npm downloads](https://img.shields.io/npm/dm/@deniscuciuc/redis-analyzer)](https://www.npmjs.com/package/@deniscuciuc/redis-analyzer)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
7
+ [![TypeScript](https://img.shields.io/badge/types-TypeScript-3178C6?logo=typescript)](https://www.typescriptlang.org/)
8
+ [![CI](https://github.com/deniscuciuc/db-analyzer-redis/actions/workflows/ci.yml/badge.svg)](https://github.com/deniscuciuc/db-analyzer-redis/actions/workflows/ci.yml)
9
+
10
+ A CLI tool that analyzes Redis health with a focus on memory pressure, cache hit rate, persistence safety, replication lag, connection pressure, and slow commands. It emits structured JSON for automation or writes Markdown, JSON, and optional HTML reports to `./reports/`.
11
+
12
+ ## Quick start
13
+
14
+ No installation required:
15
+
16
+ ```bash
17
+ npx @deniscuciuc/redis-analyzer -h localhost -p 6379 -c health
18
+ npx @deniscuciuc/redis-analyzer --uri redis://localhost:6379/0 -c full --json > report.json
19
+ ```
20
+
21
+ Or install globally:
22
+
23
+ ```bash
24
+ npm install -g @deniscuciuc/redis-analyzer
25
+ redis-analyzer -h your-host -p 6379 -c health
26
+ ```
27
+
28
+ > **Working with an AI agent?** See [.github/copilot-instructions.md](.github/copilot-instructions.md) for the integrated GitHub Copilot workflow and JSON contracts.
29
+
30
+ ---
31
+
32
+ ## Table of Contents
33
+
34
+ - [Features](#features)
35
+ - [Requirements](#requirements)
36
+ - [Development / local setup](#development--local-setup)
37
+ - [Configuration](#configuration)
38
+ - [Usage](#usage)
39
+ - [Commands](#commands)
40
+ - [CLI options](#cli-options)
41
+ - [Output formats](#output-formats)
42
+ - [Health score](#health-score)
43
+ - [Architecture](#architecture)
44
+ - [Contributing](#contributing)
45
+
46
+ ---
47
+
48
+ ## Features
49
+
50
+ - Memory usage analysis with fragmentation and RSS overhead checks
51
+ - Cache hit-rate analysis with eviction and expiry context
52
+ - Persistence safety checks for RDB and AOF
53
+ - Replication health checks for standalone, primary, and replica nodes
54
+ - Slow command analysis from `SLOWLOG`
55
+ - Keyspace summaries by logical database
56
+ - Interactive mode, compare mode, HTML reports, and watch mode for safe commands
57
+
58
+ ## Requirements
59
+
60
+ - Node.js >= 20
61
+ - pnpm >= 10
62
+ - Redis 6+
63
+
64
+ ## Development / local setup
65
+
66
+ ```bash
67
+ pnpm install
68
+ cp .env.example .env
69
+ # edit .env with your Redis connection details
70
+ pnpm build
71
+ node dist/index.js --help
72
+ ```
73
+
74
+ ## Configuration
75
+
76
+ ### Environment variables (`.env`)
77
+
78
+ ```bash
79
+ export REDIS_HOST=localhost
80
+ export REDIS_PORT=6379
81
+ export REDIS_DB=0
82
+ export REDIS_PASSWORD=secret
83
+ export REDIS_TLS=false
84
+
85
+ # Alternative: URI format
86
+ # export REDIS_URI=redis://:secret@redis.internal:6379/0
87
+ ```
88
+
89
+ ### Config file (`.analyzerrc.json`)
90
+
91
+ Place `.analyzerrc.json` in your project root (or `~/.config/db-analyzer/config.json` for global settings). Copy `analyzerrc.example.json` to get started:
92
+
93
+ ```bash
94
+ cp analyzerrc.example.json .analyzerrc.json
95
+ ```
96
+
97
+ Profiles let you switch between Redis instances:
98
+
99
+ ```bash
100
+ . ./.env && npx ts-node index.ts -c health --profile prod
101
+ pnpm analyze:health -- --profile local
102
+ ```
103
+
104
+ CLI flags always win. When you explicitly select `--profile`, that profile overrides the sourced environment defaults for that run.
105
+
106
+ ## Usage
107
+
108
+ ### Interactive mode
109
+
110
+ ```bash
111
+ pnpm start
112
+ ```
113
+
114
+ ### npm scripts
115
+
116
+ ```bash
117
+ pnpm analyze
118
+ pnpm analyze:help
119
+ pnpm analyze:health
120
+ pnpm analyze:memory
121
+ pnpm analyze:hit-rate
122
+ pnpm analyze:slow
123
+ pnpm analyze:keys
124
+ pnpm analyze:connections
125
+ pnpm analyze:persistence
126
+ pnpm analyze:replication
127
+ pnpm analyze:config
128
+ pnpm analyze:html
129
+ pnpm analyze:watch
130
+ pnpm server:info
131
+ pnpm build
132
+ pnpm lint
133
+ pnpm test
134
+ ```
135
+
136
+ ### Direct CLI
137
+
138
+ ```bash
139
+ . ./.env && npx ts-node index.ts -j -c health
140
+ . ./.env && npx ts-node index.ts -j -c slow-commands --slow-threshold 5000
141
+ npx ts-node index.ts --uri redis://localhost:6379/0 -c full --html
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Commands
147
+
148
+ | Command | Description |
149
+ | ------- | ----------- |
150
+ | `full` | Complete analysis (default) |
151
+ | `health` | Health score and key metrics |
152
+ | `server-info` | Redis version, mode, uptime, config file |
153
+ | `memory` | Memory usage, fragmentation, and overhead |
154
+ | `hit-rate` | Cache hit ratio, expirations, and evictions |
155
+ | `slow-commands` | `SLOWLOG` analysis with top command types |
156
+ | `keys` | Keyspace counts, expiries, and average TTL |
157
+ | `connections` | Connected, blocked, rejected, and buffer metrics |
158
+ | `persistence` | RDB and AOF health |
159
+ | `replication` | Primary / replica status and lag |
160
+ | `config` | Important Redis configuration values |
161
+
162
+ ## CLI options
163
+
164
+ | Option | Short | Description | Default |
165
+ | ------ | ----- | ----------- | ------- |
166
+ | `--host` | `-h` | Redis host | `localhost` |
167
+ | `--port` | `-p` | Redis port | `6379` |
168
+ | `--password` | `-a` | Redis password | - |
169
+ | `--db` | `-n` | Database number | `0` |
170
+ | `--tls` | | Enable TLS | `false` |
171
+ | `--uri` | | Redis URI (`redis://...`) | - |
172
+ | `--profile` | | Use named profile from `.analyzerrc.json` | - |
173
+ | `--config` | | Use a custom config file path | auto-search |
174
+ | `--slow-threshold` | | Slow command threshold in microseconds | `10000` |
175
+ | `--max-slow-commands` | | Max `SLOWLOG` rows to fetch | `25` |
176
+ | `--compare` | | Compare against a previous JSON report | - |
177
+ | `--watch` | | Poll interval in seconds | - |
178
+ | `--command` | `-c` | Run a specific analysis command | `full` |
179
+ | `--json` | `-j` | Output JSON | `false` |
180
+ | `--quiet` | `-q` | Suppress non-essential output | `false` |
181
+ | `--output` | `-o` | Reports directory | `./reports` |
182
+ | `--html` | | Also generate an HTML report | `false` |
183
+ | `--interactive` | `-i` | Interactive menu | `false` |
184
+
185
+ ---
186
+
187
+ ## Output formats
188
+
189
+ ### JSON
190
+
191
+ Use `-j` to emit machine-readable JSON to stdout.
192
+
193
+ ### Markdown / HTML reports
194
+
195
+ `full` analysis writes:
196
+
197
+ - Markdown report
198
+ - JSON report
199
+ - Optional HTML report when `--html` is set
200
+
201
+ ### Compare mode
202
+
203
+ Compare two snapshots:
204
+
205
+ ```bash
206
+ . ./.env && npx ts-node index.ts -c full --compare ./reports/redis-analysis-prev.json
207
+ ```
208
+
209
+ ## Health score
210
+
211
+ The analyzer starts at **100** and deducts points for:
212
+
213
+ - high memory usage or fragmentation
214
+ - low cache hit rate
215
+ - failed or stale persistence
216
+ - replication lag or broken links
217
+ - rejected client connections
218
+
219
+ Score guide:
220
+
221
+ | Score | Status |
222
+ | ----- | ------ |
223
+ | 90-100 | Excellent |
224
+ | 70-89 | Good |
225
+ | 50-69 | Warning |
226
+ | 0-49 | Critical |
227
+
228
+ ## Architecture
229
+
230
+ ```text
231
+ index.ts # CLI bootstrap and Redis connection setup
232
+ src/cli/{options,runner}.ts # CLI parsing and command execution
233
+ src/config/loader.ts # Config loading and profile resolution
234
+ src/collectors/stats-collector.ts # INFO / SLOWLOG / CONFIG collection
235
+ src/analyzers/*.ts # Memory, performance, persistence, replication analysis
236
+ src/reporters/*.ts # Markdown, HTML, and diff rendering
237
+ src/interactive/{index,display,menus}.ts
238
+ src/watch/runner.ts # Watch mode loop
239
+ tests/*.test.ts # Automated tests
240
+ ```
241
+
242
+ ## Contributing
243
+
244
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -0,0 +1,17 @@
1
+ {
2
+ "profiles": {
3
+ "local": {
4
+ "host": "localhost",
5
+ "port": 6379,
6
+ "db": 0
7
+ },
8
+ "prod": {
9
+ "uri": "redis://:password@prod-redis.internal:6379/0",
10
+ "tls": true
11
+ }
12
+ },
13
+ "defaultProfile": "local",
14
+ "slowCommandThreshold": 10000,
15
+ "maxSlowCommands": 25,
16
+ "output": "./reports"
17
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const ioredis_1 = __importDefault(require("ioredis"));
8
+ const options_1 = require("./src/cli/options");
9
+ const runner_1 = require("./src/cli/runner");
10
+ const loader_1 = require("./src/config/loader");
11
+ const constants_1 = require("./src/constants");
12
+ const interactive_1 = require("./src/interactive");
13
+ const runner_2 = require("./src/watch/runner");
14
+ function resolveValue(cliValue, envValue, profileValue, fallbackValue, preferProfile) {
15
+ if (cliValue !== undefined && cliValue !== fallbackValue) {
16
+ return cliValue;
17
+ }
18
+ if (preferProfile) {
19
+ return profileValue ?? envValue ?? cliValue ?? fallbackValue;
20
+ }
21
+ return envValue ?? profileValue ?? cliValue ?? fallbackValue;
22
+ }
23
+ function resolveOptionalValue(cliValue, envValue, profileValue, preferProfile) {
24
+ if (cliValue !== undefined) {
25
+ return cliValue;
26
+ }
27
+ if (preferProfile) {
28
+ return profileValue ?? envValue;
29
+ }
30
+ return envValue ?? profileValue;
31
+ }
32
+ function parseNumber(value) {
33
+ if (!value) {
34
+ return undefined;
35
+ }
36
+ const parsed = Number.parseInt(value, 10);
37
+ return Number.isFinite(parsed) ? parsed : undefined;
38
+ }
39
+ function parseBoolean(value) {
40
+ if (!value) {
41
+ return undefined;
42
+ }
43
+ return value === "true" || value === "1";
44
+ }
45
+ function resolveConnectionDetails(connection) {
46
+ if (!connection.uri) {
47
+ return connection;
48
+ }
49
+ try {
50
+ const parsed = new URL(connection.uri);
51
+ return {
52
+ ...connection,
53
+ host: parsed.hostname || connection.host,
54
+ port: parsed.port ? Number.parseInt(parsed.port, 10) : connection.port,
55
+ db: parsed.pathname
56
+ ? Number.parseInt(parsed.pathname.replace("/", ""), 10) || connection.db
57
+ : connection.db,
58
+ };
59
+ }
60
+ catch {
61
+ return connection;
62
+ }
63
+ }
64
+ async function closeClient(client) {
65
+ try {
66
+ await client.quit();
67
+ }
68
+ catch {
69
+ client.disconnect();
70
+ }
71
+ }
72
+ async function main() {
73
+ const options = (0, options_1.parseOptions)();
74
+ const config = (0, loader_1.loadConfig)(options.config);
75
+ const profile = (0, loader_1.resolveProfile)(config, options.profile);
76
+ const preferProfile = Boolean(options.profile);
77
+ if (options.watch !== undefined && options.json) {
78
+ throw new Error("--watch cannot be combined with --json.");
79
+ }
80
+ const connection = resolveConnectionDetails({
81
+ host: resolveValue(options.host, process.env.REDIS_HOST, profile.host, constants_1.DEFAULTS.host, preferProfile),
82
+ port: resolveValue(options.port, parseNumber(process.env.REDIS_PORT), profile.port, constants_1.DEFAULTS.port, preferProfile),
83
+ password: resolveOptionalValue(options.password, process.env.REDIS_PASSWORD, profile.password, preferProfile),
84
+ db: resolveValue(options.db, parseNumber(process.env.REDIS_DB), profile.db, constants_1.DEFAULTS.db, preferProfile),
85
+ tls: resolveValue(options.tls, parseBoolean(process.env.REDIS_TLS), profile.tls, false, preferProfile),
86
+ uri: resolveOptionalValue(options.uri, process.env.REDIS_URI, profile.uri, preferProfile),
87
+ });
88
+ const runtimeOptions = {
89
+ ...options,
90
+ outputDir: resolveValue(options.outputDir, undefined, config.output, constants_1.DEFAULTS.output, false),
91
+ slowCommandThreshold: resolveValue(options.slowCommandThreshold, undefined, config.slowCommandThreshold, constants_1.DEFAULTS.slowCommandThreshold, false),
92
+ maxSlowCommands: resolveValue(options.maxSlowCommands, undefined, config.maxSlowCommands, constants_1.DEFAULTS.maxSlowCommands, false),
93
+ };
94
+ const client = connection.uri
95
+ ? new ioredis_1.default(connection.uri, {
96
+ lazyConnect: true,
97
+ tls: connection.tls ? {} : undefined,
98
+ })
99
+ : new ioredis_1.default({
100
+ host: connection.host,
101
+ port: connection.port,
102
+ password: connection.password,
103
+ db: connection.db,
104
+ tls: connection.tls ? {} : undefined,
105
+ lazyConnect: true,
106
+ });
107
+ try {
108
+ await client.connect();
109
+ await client.ping();
110
+ if (runtimeOptions.interactive) {
111
+ const interactive = new interactive_1.InteractiveCLI(client, connection, runtimeOptions);
112
+ await interactive.start();
113
+ return;
114
+ }
115
+ if (runtimeOptions.watch !== undefined) {
116
+ await (0, runner_2.runWatchLoop)({
117
+ intervalSeconds: runtimeOptions.watch,
118
+ command: runtimeOptions.command,
119
+ runCommand: () => (0, runner_1.executeCommand)(client, runtimeOptions, connection),
120
+ });
121
+ return;
122
+ }
123
+ await (0, runner_1.executeCommand)(client, runtimeOptions, connection);
124
+ }
125
+ catch (error) {
126
+ const message = error instanceof Error ? error.message : String(error);
127
+ throw new Error(`Cannot connect to Redis: ${message}`);
128
+ }
129
+ finally {
130
+ await closeClient(client);
131
+ }
132
+ }
133
+ main().catch((error) => {
134
+ const message = error instanceof Error ? error.message : String(error);
135
+ console.error("Error during analysis:", message);
136
+ process.exit(1);
137
+ });
@@ -0,0 +1,5 @@
1
+ import type { MemoryAnalysis, RedisInfo } from "../types";
2
+ export declare class MemoryAnalyzer {
3
+ analyze(info: RedisInfo): MemoryAnalysis;
4
+ }
5
+ //# sourceMappingURL=memory-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/memory-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG1D,qBAAa,cAAc;IAC1B,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,cAAc;CA+DxC"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryAnalyzer = void 0;
4
+ const constants_1 = require("../constants");
5
+ const format_1 = require("../utils/format");
6
+ class MemoryAnalyzer {
7
+ analyze(info) {
8
+ const hasLimit = info.maxmemory > 0;
9
+ const usagePercent = hasLimit
10
+ ? (info.usedMemory / info.maxmemory) * 100
11
+ : 0;
12
+ const fragRatio = info.memFragmentationRatio;
13
+ const fragSeverity = fragRatio >= constants_1.THRESHOLDS.memory.fragCritical
14
+ ? "critical"
15
+ : fragRatio >= constants_1.THRESHOLDS.memory.fragWarning
16
+ ? "warning"
17
+ : "ok";
18
+ const recommendations = [];
19
+ if (hasLimit && usagePercent >= constants_1.THRESHOLDS.memory.usageCritical) {
20
+ recommendations.push(`Memory usage at ${usagePercent.toFixed(1)}% — increase maxmemory or reduce data size immediately.`);
21
+ }
22
+ else if (hasLimit && usagePercent >= constants_1.THRESHOLDS.memory.usageWarning) {
23
+ recommendations.push(`Memory at ${usagePercent.toFixed(1)}% of maxmemory — plan capacity increase.`);
24
+ }
25
+ if (!hasLimit) {
26
+ recommendations.push("maxmemory is not set — Redis will grow until an OOM kill unless the host limits it.");
27
+ }
28
+ if (fragSeverity === "critical") {
29
+ recommendations.push(`High fragmentation ratio ${fragRatio.toFixed(2)} (${(0, format_1.formatBytes)(info.memFragmentationBytes)} wasted). Run MEMORY PURGE or restart Redis during a low-traffic window.`);
30
+ }
31
+ else if (fragSeverity === "warning") {
32
+ recommendations.push(`Moderate fragmentation ratio ${fragRatio.toFixed(2)}. Monitor allocator pressure and peak RSS.`);
33
+ }
34
+ if (info.usedMemory > 0 && info.usedMemoryRss > info.usedMemory * 2) {
35
+ recommendations.push(`RSS (${(0, format_1.formatBytes)(info.usedMemoryRss)}) is more than 2x allocated memory (${info.usedMemoryHuman}). Consider MEMORY PURGE, jemalloc tuning, or a controlled restart.`);
36
+ }
37
+ return {
38
+ usedMemory: info.usedMemory,
39
+ usedMemoryHuman: info.usedMemoryHuman,
40
+ maxMemory: info.maxmemory,
41
+ maxMemoryHuman: info.maxmemoryHuman,
42
+ usagePercent,
43
+ fragRatio,
44
+ fragBytes: info.memFragmentationBytes,
45
+ fragSeverity,
46
+ rssOverhead: info.usedMemoryRss - info.usedMemory,
47
+ peakMemory: info.usedMemoryPeak,
48
+ dataMemory: info.usedMemoryDataset,
49
+ overheadMemory: info.usedMemoryOverhead,
50
+ recommendations,
51
+ };
52
+ }
53
+ }
54
+ exports.MemoryAnalyzer = MemoryAnalyzer;
@@ -0,0 +1,6 @@
1
+ import type { HitRateAnalysis, RedisInfo, SlowCommand, SlowCommandAnalysis } from "../types";
2
+ export declare class PerformanceAnalyzer {
3
+ analyzeHitRate(info: RedisInfo): HitRateAnalysis;
4
+ analyzeSlowCommands(commands: SlowCommand[], totalLogged: number, threshold: number): SlowCommandAnalysis;
5
+ }
6
+ //# sourceMappingURL=performance-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"performance-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/performance-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,eAAe,EACf,SAAS,EACT,WAAW,EACX,mBAAmB,EAEnB,MAAM,UAAU,CAAC;AAElB,qBAAa,mBAAmB;IAC/B,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe;IAwChD,mBAAmB,CAClB,QAAQ,EAAE,WAAW,EAAE,EACvB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GACf,mBAAmB;CAmDtB"}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PerformanceAnalyzer = void 0;
4
+ const constants_1 = require("../constants");
5
+ class PerformanceAnalyzer {
6
+ analyzeHitRate(info) {
7
+ const total = info.keyspaceHits + info.keyspaceMisses;
8
+ const hitRate = total === 0 ? 100 : (info.keyspaceHits / total) * 100;
9
+ const evictionSeverity = info.evictedKeys > 0 && info.maxmemoryPolicy === "noeviction"
10
+ ? "critical"
11
+ : info.evictedKeys > 1000
12
+ ? "warning"
13
+ : "ok";
14
+ const recommendations = [];
15
+ if (hitRate < constants_1.THRESHOLDS.hitRate.critical) {
16
+ recommendations.push(`Cache hit rate is critically low at ${hitRate.toFixed(1)}%. Review TTL strategy, memory sizing, and whether Redis is serving the right workload.`);
17
+ }
18
+ else if (hitRate < constants_1.THRESHOLDS.hitRate.warning) {
19
+ recommendations.push(`Cache hit rate ${hitRate.toFixed(1)}% is below the 90% target. Review hot keys, eviction pressure, and expiry policy.`);
20
+ }
21
+ if (info.evictedKeys > 0) {
22
+ recommendations.push(`${info.evictedKeys.toLocaleString()} keys have been evicted (policy: ${info.maxmemoryPolicy}). Increase headroom or adjust TTL and eviction policy.`);
23
+ }
24
+ return {
25
+ hits: info.keyspaceHits,
26
+ misses: info.keyspaceMisses,
27
+ hitRate,
28
+ evictedKeys: info.evictedKeys,
29
+ expiredKeys: info.expiredKeys,
30
+ evictionPolicy: info.maxmemoryPolicy,
31
+ evictionSeverity,
32
+ recommendations,
33
+ };
34
+ }
35
+ analyzeSlowCommands(commands, totalLogged, threshold) {
36
+ const byCommand = new Map();
37
+ for (const command of commands) {
38
+ const name = command.command[0]?.toUpperCase() ?? "UNKNOWN";
39
+ const current = byCommand.get(name) ?? { count: 0, totalMs: 0 };
40
+ byCommand.set(name, {
41
+ count: current.count + 1,
42
+ totalMs: current.totalMs + command.durationMs,
43
+ });
44
+ }
45
+ const topCommandTypes = [...byCommand.entries()]
46
+ .sort((left, right) => right[1].count - left[1].count)
47
+ .slice(0, 5)
48
+ .map(([command, summary]) => ({
49
+ command,
50
+ count: summary.count,
51
+ avgMs: summary.totalMs / summary.count,
52
+ }));
53
+ const recommendations = [];
54
+ if (totalLogged > 100) {
55
+ recommendations.push(`${totalLogged} slow commands were logged. Check for O(N) operations such as KEYS, large set scans, or long list traversals.`);
56
+ }
57
+ const dangerousCommands = new Set([
58
+ "KEYS",
59
+ "FLUSHDB",
60
+ "FLUSHALL",
61
+ "DEBUG",
62
+ "CONFIG",
63
+ ]);
64
+ for (const command of topCommandTypes) {
65
+ if (dangerousCommands.has(command.command)) {
66
+ recommendations.push(`${command.command} appears in the slow log. Replace KEYS with SCAN and avoid administrative commands on the hot path.`);
67
+ }
68
+ }
69
+ return {
70
+ totalLogged,
71
+ commands,
72
+ topCommandTypes,
73
+ threshold,
74
+ recommendations,
75
+ };
76
+ }
77
+ }
78
+ exports.PerformanceAnalyzer = PerformanceAnalyzer;
@@ -0,0 +1,5 @@
1
+ import type { PersistenceAnalysis, RedisInfo } from "../types";
2
+ export declare class PersistenceAnalyzer {
3
+ analyze(info: RedisInfo): PersistenceAnalysis;
4
+ }
5
+ //# sourceMappingURL=persistence-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/persistence-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE/D,qBAAa,mBAAmB;IAC/B,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,mBAAmB;CAsE7C"}