mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
633 lines • 26.4 kB
JavaScript
/**
* Caching Strategy Analyzer
* Analyzes caching implementations and identifies optimization opportunities
*/
import fs from 'fs-extra';
import * as path from 'path';
import { glob } from 'glob';
export class CachingAnalyzer {
projectRoot;
constructor(projectRoot) {
this.projectRoot = projectRoot;
}
async analyze(issues) {
const implementedCaches = await this.detectImplementedCaches();
const missedOpportunities = await this.identifyMissedCachingOpportunities();
const cachingStrategy = await this.analyzeCachingStrategyType();
const cacheEfficiency = this.calculateCacheEfficiency(implementedCaches);
const recommendations = this.generateCachingRecommendations(implementedCaches, missedOpportunities, cacheEfficiency);
// Calculate overall caching score
const overallCachingScore = this.calculateCachingScore(implementedCaches, missedOpportunities, cacheEfficiency);
// Determine risk level
let riskLevel;
if (overallCachingScore >= 85)
riskLevel = 'optimal';
else if (overallCachingScore >= 70)
riskLevel = 'good';
else if (overallCachingScore >= 50)
riskLevel = 'needs_improvement';
else
riskLevel = 'poor';
// Add caching-related performance issues
this.addCachingIssues(issues, implementedCaches, missedOpportunities, cacheEfficiency);
return {
cachingStrategy,
implementedCaches,
missedOpportunities,
cacheEfficiency,
recommendations,
overallCachingScore,
riskLevel
};
}
async detectImplementedCaches() {
const caches = [];
try {
const files = await glob('**/*.{ts,js,jsx,tsx,py}', {
cwd: this.projectRoot,
ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**']
});
for (const file of files) {
const filePath = path.join(this.projectRoot, file);
const content = await fs.readFile(filePath, 'utf-8');
// Detect various caching patterns
await this.detectMemoryCache(content, file, caches);
await this.detectFileSystemCache(content, file, caches);
await this.detectRedisCache(content, file, caches);
await this.detectDatabaseCache(content, file, caches);
await this.detectHttpCache(content, file, caches);
await this.detectComputationCache(content, file, caches);
}
// Check for package.json caching dependencies
await this.detectCachingLibraries(caches);
}
catch (error) {
// Continue with what we have
}
return caches;
}
async detectMemoryCache(content, file, caches) {
const patterns = [
{ pattern: /new\s+Map\s*\(/, name: 'JavaScript Map cache' },
{ pattern: /new\s+WeakMap\s*\(/, name: 'JavaScript WeakMap cache' },
{ pattern: /\.cache\s*=/, name: 'Property-based cache' },
{ pattern: /memoize\s*\(/, name: 'Memoization cache' },
{ pattern: /lru-cache|node-cache/, name: 'LRU Cache library' },
{ pattern: /memory-cache/, name: 'Memory cache library' },
{ pattern: /Map\(\)\s*\/\/.*cache/i, name: 'Documented Map cache' }
];
for (const { pattern, name } of patterns) {
if (pattern.test(content)) {
caches.push({
name,
type: 'memory',
location: file,
estimatedSize: this.estimateCacheSize(content, pattern),
usage: this.analyzeCacheUsage(content, pattern),
effectiveness: this.estimateCacheEffectiveness(content, pattern)
});
}
}
}
async detectFileSystemCache(content, file, caches) {
const patterns = [
{ pattern: /\.cache\//i, name: 'Cache directory' },
{ pattern: /temp.*cache|cache.*temp/i, name: 'Temporary file cache' },
{ pattern: /fs\.writeFile.*cache|fs\.readFile.*cache/i, name: 'File-based cache' },
{ pattern: /\.json.*cache|cache.*\.json/i, name: 'JSON cache files' },
{ pattern: /disk.*cache|cache.*disk/i, name: 'Disk cache' }
];
for (const { pattern, name } of patterns) {
if (pattern.test(content)) {
caches.push({
name,
type: 'disk',
location: file,
estimatedSize: this.estimateCacheSize(content, pattern),
usage: this.analyzeCacheUsage(content, pattern),
effectiveness: this.estimateCacheEffectiveness(content, pattern)
});
}
}
}
async detectRedisCache(content, file, caches) {
const patterns = [
{ pattern: /redis\.get|redis\.set/, name: 'Redis cache operations' },
{ pattern: /createClient.*redis/i, name: 'Redis client cache' },
{ pattern: /ioredis/, name: 'IORedis cache' },
{ pattern: /node_redis/, name: 'Node Redis cache' }
];
for (const { pattern, name } of patterns) {
if (pattern.test(content)) {
caches.push({
name,
type: 'network',
location: file,
estimatedSize: 0, // External
usage: this.analyzeCacheUsage(content, pattern),
effectiveness: this.estimateCacheEffectiveness(content, pattern)
});
}
}
}
async detectDatabaseCache(content, file, caches) {
const patterns = [
{ pattern: /query.*cache|cache.*query/i, name: 'Database query cache' },
{ pattern: /result.*cache|cache.*result/i, name: 'Database result cache' },
{ pattern: /connection.*pool/i, name: 'Database connection pool' },
{ pattern: /prepared.*statement/i, name: 'Prepared statement cache' }
];
for (const { pattern, name } of patterns) {
if (pattern.test(content)) {
caches.push({
name,
type: 'memory',
location: file,
estimatedSize: this.estimateCacheSize(content, pattern),
usage: this.analyzeCacheUsage(content, pattern),
effectiveness: this.estimateCacheEffectiveness(content, pattern)
});
}
}
}
async detectHttpCache(content, file, caches) {
const patterns = [
{ pattern: /cache-control|etag|last-modified/i, name: 'HTTP cache headers' },
{ pattern: /axios.*cache|fetch.*cache/i, name: 'HTTP request cache' },
{ pattern: /service.*worker/i, name: 'Service Worker cache' },
{ pattern: /sw\.cache|cache\.add/i, name: 'Cache API' }
];
for (const { pattern, name } of patterns) {
if (pattern.test(content)) {
caches.push({
name,
type: 'network',
location: file,
estimatedSize: 0, // External
usage: this.analyzeCacheUsage(content, pattern),
effectiveness: this.estimateCacheEffectiveness(content, pattern)
});
}
}
}
async detectComputationCache(content, file, caches) {
const patterns = [
{ pattern: /useMemo|useCallback/i, name: 'React memoization' },
{ pattern: /@memoize|@cache/i, name: 'Decorator-based cache' },
{ pattern: /computed.*cache|cache.*computed/i, name: 'Computed value cache' },
{ pattern: /expensive.*cache|cache.*expensive/i, name: 'Expensive operation cache' }
];
for (const { pattern, name } of patterns) {
if (pattern.test(content)) {
caches.push({
name,
type: 'computed',
location: file,
estimatedSize: this.estimateCacheSize(content, pattern),
usage: this.analyzeCacheUsage(content, pattern),
effectiveness: this.estimateCacheEffectiveness(content, pattern)
});
}
}
}
async detectCachingLibraries(caches) {
const packageJsonPath = path.join(this.projectRoot, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
try {
const packageJson = await fs.readJson(packageJsonPath);
const dependencies = {
...packageJson.dependencies,
...packageJson.devDependencies
};
const cachingLibraries = [
{ name: 'lru-cache', type: 'memory' },
{ name: 'node-cache', type: 'memory' },
{ name: 'memory-cache', type: 'memory' },
{ name: 'redis', type: 'network' },
{ name: 'ioredis', type: 'network' },
{ name: 'memcached', type: 'network' },
{ name: 'memoizee', type: 'computed' },
{ name: 'lodash.memoize', type: 'computed' }
];
for (const lib of cachingLibraries) {
if (dependencies[lib.name]) {
caches.push({
name: `${lib.name} library`,
type: lib.type,
location: 'package.json',
estimatedSize: 0,
usage: 'unknown',
effectiveness: 'unknown'
});
}
}
}
catch (error) {
// Skip if can't read package.json
}
}
}
async identifyMissedCachingOpportunities() {
const opportunities = [];
try {
const files = await glob('**/*.{ts,js,jsx,tsx}', {
cwd: this.projectRoot,
ignore: ['node_modules/**', '.git/**', 'dist/**', '**/*.test.*', '**/*.spec.*']
});
for (const file of files) {
const filePath = path.join(this.projectRoot, file);
const content = await fs.readFile(filePath, 'utf-8');
// Check for expensive computations
this.detectExpensiveComputations(content, file, opportunities);
// Check for repeated database queries
this.detectRepeatedDatabaseQueries(content, file, opportunities);
// Check for repeated API calls
this.detectRepeatedApiCalls(content, file, opportunities);
// Check for repeated file I/O
this.detectRepeatedFileIO(content, file, opportunities);
// Check for repeated calculations
this.detectRepeatedCalculations(content, file, opportunities);
}
}
catch (error) {
// Continue with what we have
}
return opportunities;
}
detectExpensiveComputations(content, file, opportunities) {
const patterns = [
{
pattern: /for\s*\([^)]*\)\s*{[^}]*for\s*\([^)]*\)\s*{/,
type: 'expensive_computation',
message: 'Nested loops detected - consider caching results'
},
{
pattern: /\.sort\s*\([^)]*\).*\.filter\s*\([^)]*\).*\.map\s*\(/,
type: 'expensive_computation',
message: 'Complex array operations chain - consider memoization'
},
{
pattern: /JSON\.parse\s*\([^)]*JSON\.stringify/,
type: 'expensive_computation',
message: 'JSON parse/stringify operations - cache parsed results'
}
];
patterns.forEach(({ pattern, type, message }) => {
if (pattern.test(content)) {
opportunities.push({
type,
location: file,
estimatedCost: 100, // milliseconds
frequency: 'high',
cachability: 'excellent',
recommendation: message,
potentialSavings: 80
});
}
});
}
detectRepeatedDatabaseQueries(content, file, opportunities) {
const queryPatterns = [
/\.query\s*\(/g,
/\.find\s*\(/g,
/\.findOne\s*\(/g,
/\.aggregate\s*\(/g
];
queryPatterns.forEach(pattern => {
const matches = content.match(pattern);
if (matches && matches.length > 3) {
opportunities.push({
type: 'database_query',
location: file,
estimatedCost: 50,
frequency: 'very_high',
cachability: 'good',
recommendation: 'Multiple database queries detected - implement query result caching',
potentialSavings: 70
});
}
});
}
detectRepeatedApiCalls(content, file, opportunities) {
const apiPatterns = [
/fetch\s*\(/g,
/axios\./g,
/\.get\s*\(/g,
/\.post\s*\(/g
];
apiPatterns.forEach(pattern => {
const matches = content.match(pattern);
if (matches && matches.length > 2) {
opportunities.push({
type: 'api_call',
location: file,
estimatedCost: 200,
frequency: 'medium',
cachability: 'moderate',
recommendation: 'Multiple API calls detected - consider response caching',
potentialSavings: 60
});
}
});
}
detectRepeatedFileIO(content, file, opportunities) {
const filePatterns = [
/fs\.readFile/g,
/fs\.readFileSync/g,
/fs\.read/g
];
filePatterns.forEach(pattern => {
const matches = content.match(pattern);
if (matches && matches.length > 2) {
opportunities.push({
type: 'file_io',
location: file,
estimatedCost: 30,
frequency: 'high',
cachability: 'excellent',
recommendation: 'Multiple file reads detected - cache file contents',
potentialSavings: 90
});
}
});
}
detectRepeatedCalculations(content, file, opportunities) {
// Look for functions that might be called repeatedly
const functionCalls = content.match(/(\w+)\s*\(/g) || [];
const callCounts = new Map();
functionCalls.forEach(call => {
const funcName = call.replace(/\s*\(/, '');
callCounts.set(funcName, (callCounts.get(funcName) || 0) + 1);
});
// Find functions called many times
for (const [funcName, count] of callCounts) {
if (count > 5 && !['if', 'for', 'while', 'console', 'log'].includes(funcName)) {
opportunities.push({
type: 'expensive_computation',
location: `${file} - ${funcName}()`,
estimatedCost: 20,
frequency: 'very_high',
cachability: 'good',
recommendation: `Function ${funcName}() called ${count} times - consider memoization`,
potentialSavings: 50
});
}
}
}
async analyzeCachingStrategyType() {
const implementedCaches = await this.detectImplementedCaches();
// Determine cache types in use
const cacheTypes = new Set(implementedCaches.map(c => c.type));
const hasMemoryCache = cacheTypes.has('memory');
const hasNetworkCache = cacheTypes.has('network');
const hasDiskCache = cacheTypes.has('disk');
// Determine strategy type
let type = 'none';
if (hasMemoryCache && hasNetworkCache) {
type = 'hybrid';
}
else if (hasNetworkCache && implementedCaches.some(c => c.name.includes('Redis'))) {
type = 'redis';
}
else if (hasDiskCache) {
type = 'filesystem';
}
else if (hasMemoryCache) {
type = 'in-memory';
}
// Analyze cache layers
const layers = [];
if (hasMemoryCache)
layers.push('memory');
if (hasDiskCache)
layers.push('disk');
if (hasNetworkCache)
layers.push('network');
// Determine TTL strategy
let ttlStrategy = 'none';
const hasTTL = implementedCaches.some(c => c.ttl !== undefined);
if (hasTTL)
ttlStrategy = 'fixed';
// Determine invalidation strategy
let invalidationStrategy = 'none';
if (implementedCaches.some(c => c.name.includes('event'))) {
invalidationStrategy = 'event-based';
}
else if (hasTTL) {
invalidationStrategy = 'time-based';
}
return {
type,
layers,
ttlStrategy,
invalidationStrategy,
consistency: layers.length > 1 ? 'eventual' : 'strong'
};
}
calculateCacheEfficiency(caches) {
const totalCaches = caches.length;
const activeCaches = caches.filter(c => c.usage !== 'rare').length;
const redundantCaches = this.findRedundantCaches(caches);
const expiredCaches = caches.filter(c => c.effectiveness === 'low').length;
// Calculate average hit ratio (simulated)
const averageHitRatio = caches
.filter(c => c.hitRatio !== undefined)
.reduce((sum, c) => sum + (c.hitRatio || 0), 0) / totalCaches || 0.7;
// Calculate memory utilization
const totalMemory = caches.reduce((sum, c) => sum + c.estimatedSize, 0);
const effectiveMemory = caches
.filter(c => c.effectiveness === 'high')
.reduce((sum, c) => sum + c.estimatedSize, 0);
const memoryUtilization = totalMemory > 0 ? effectiveMemory / totalMemory : 0;
// Calculate optimization potential
const optimizationPotential = redundantCaches + expiredCaches;
return {
totalCaches,
activeCaches,
averageHitRatio,
memoryUtilization,
redundantCaches,
expiredCaches,
optimizationPotential
};
}
findRedundantCaches(caches) {
const cacheGroups = new Map();
// Group caches by type and location
caches.forEach(cache => {
const key = `${cache.type}-${cache.location}`;
if (!cacheGroups.has(key)) {
cacheGroups.set(key, []);
}
cacheGroups.get(key).push(cache);
});
// Count groups with multiple caches
let redundant = 0;
for (const group of cacheGroups.values()) {
if (group.length > 1) {
redundant += group.length - 1;
}
}
return redundant;
}
generateCachingRecommendations(caches, opportunities, efficiency) {
const recommendations = [];
// Check for no caching
if (caches.length === 0) {
recommendations.push({
priority: 'critical',
category: 'implementation',
title: 'Implement basic caching strategy',
description: 'No caching detected. Start with in-memory caching for frequently accessed data.',
expectedImpact: 'major',
effort: 'medium',
implementation: [
'Install an LRU cache library (e.g., lru-cache)',
'Identify hot paths in your application',
'Implement caching for expensive operations',
'Add cache metrics and monitoring'
]
});
}
// Check for missed opportunities
if (opportunities.length > 5) {
recommendations.push({
priority: 'high',
category: 'optimization',
title: 'Cache expensive operations',
description: `Found ${opportunities.length} caching opportunities that could improve performance.`,
expectedImpact: 'major',
effort: 'low',
implementation: opportunities.slice(0, 3).map(o => o.recommendation)
});
}
// Check for inefficient caching
if (efficiency.averageHitRatio < 0.5) {
recommendations.push({
priority: 'high',
category: 'optimization',
title: 'Improve cache hit ratio',
description: 'Low cache hit ratio detected. Review cache keys and TTL values.',
expectedImpact: 'moderate',
effort: 'low',
implementation: [
'Analyze cache key patterns',
'Increase TTL for stable data',
'Implement cache warming strategies',
'Consider predictive caching'
]
});
}
// Check for redundant caches
if (efficiency.redundantCaches > 0) {
recommendations.push({
priority: 'medium',
category: 'cleanup',
title: 'Remove redundant caches',
description: `Found ${efficiency.redundantCaches} redundant cache implementations.`,
expectedImpact: 'minor',
effort: 'low',
implementation: [
'Consolidate similar caches',
'Create a centralized cache manager',
'Remove duplicate cache logic'
]
});
}
// Architecture recommendations
if (caches.length > 10 && !caches.some(c => c.name.includes('manager'))) {
recommendations.push({
priority: 'medium',
category: 'architecture',
title: 'Implement cache management layer',
description: 'Multiple caches detected without central management.',
expectedImpact: 'moderate',
effort: 'high',
implementation: [
'Create a cache manager service',
'Implement cache statistics collection',
'Add cache invalidation strategies',
'Create cache configuration system'
]
});
}
return recommendations;
}
calculateCachingScore(caches, opportunities, efficiency) {
let score = 50; // Base score
// Points for having caching
if (caches.length > 0)
score += 20;
// Points for cache efficiency
score += efficiency.averageHitRatio * 20;
// Points for active caches
const activeRatio = efficiency.totalCaches > 0 ?
efficiency.activeCaches / efficiency.totalCaches : 0;
score += activeRatio * 10;
// Deduct for missed opportunities
score -= Math.min(20, opportunities.length * 2);
// Deduct for redundant caches
score -= efficiency.redundantCaches * 3;
// Deduct for expired caches
score -= efficiency.expiredCaches * 2;
return Math.max(0, Math.min(100, Math.round(score)));
}
addCachingIssues(issues, caches, opportunities, efficiency) {
// Add issue for no caching
if (caches.length === 0) {
issues.push({
file: 'project',
type: 'no_caching',
message: 'No caching strategy detected',
impact: 'high',
recommendation: 'Implement caching for frequently accessed data'
});
}
// Add issue for many missed opportunities
if (opportunities.length > 10) {
issues.push({
file: 'project',
type: 'missed_caching_opportunities',
message: `${opportunities.length} caching opportunities identified`,
impact: 'medium',
recommendation: 'Review and implement caching for expensive operations'
});
}
// Add issue for poor cache efficiency
if (efficiency.averageHitRatio < 0.3) {
issues.push({
file: 'cache-system',
type: 'poor_cache_efficiency',
message: `Cache hit ratio is very low (${(efficiency.averageHitRatio * 100).toFixed(0)}%)`,
impact: 'high',
recommendation: 'Review cache invalidation and key strategies'
});
}
}
estimateCacheSize(content, pattern) {
// Simple heuristic based on code patterns
const matches = content.match(pattern) || [];
return matches.length * 1024; // 1KB per match as estimate
}
analyzeCacheUsage(content, pattern) {
const matches = content.match(pattern) || [];
if (matches.length > 10)
return 'frequent';
if (matches.length > 5)
return 'moderate';
if (matches.length > 0)
return 'rare';
return 'unknown';
}
estimateCacheEffectiveness(content, pattern) {
// Look for cache hit/miss patterns
if (content.includes('cache.hit') || content.includes('cacheHit')) {
return 'high';
}
if (content.includes('cache.get') || content.includes('cache.set')) {
return 'medium';
}
return 'unknown';
}
}
//# sourceMappingURL=CachingAnalyzer.js.map