brainy/src/utils/statisticsCollector.ts
David Snelling 9c87982a7d 🧠 Brainy 2.0.0 - Zero-Configuration AI Database with Triple Intelligence™
MAJOR RELEASE: Complete evolution of Brainy with groundbreaking features and performance.

🎯 KEY FEATURES:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Triple Intelligence™ Engine
  - Unified Vector + Metadata + Graph search
  - O(log n) performance on all operations
  - 3ms average search latency at any scale

 API Consolidation
  - 15+ search methods → 2 clean APIs
  - search() for vector similarity
  - find() for natural language queries

 Natural Language Processing
  - 220+ pre-computed NLP patterns
  - Instant context understanding
  - "Show me recent React components with tests"

 Zero Configuration
  - Works instantly, no setup required
  - Built-in embedding models (no API keys)
  - Smart defaults for everything
  - Automatic optimization

 Enterprise Features (Free for Everyone)
  - Scales to 10M+ items
  - Write-Ahead Logging (WAL) for durability
  - Distributed architecture with sharding
  - Read/write separation
  - Connection pooling & request deduplication
  - Built-in monitoring & health checks

 Universal Compatibility
  - Node.js, Browser, Edge Workers
  - 4 Storage Adapters (Memory, FileSystem, OPFS, S3)
  - TypeScript with full type safety
  - Worker-based embeddings

📦 WHAT'S INCLUDED:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• Core AI Database with HNSW indexing
• 19 Production-ready augmentations
• Universal Memory Manager
• Complete CLI with all commands
• Brain Cloud integration (soulcraft.com)
• Comprehensive documentation
• 52 test files with 400+ tests
• Migration guide from 1.x

📊 PERFORMANCE:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• Initialize: 450ms (24MB memory)
• Search: 3ms average (up to 10M items)
• Metadata Filter: 0.8ms (O(log n))
• Bulk Import: 2.3s per 1000 items
• Production Scale: 5.8ms at 10M items

🔧 TECHNICAL IMPROVEMENTS:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• TypeScript compilation: 153 errors → 0
• Memory usage: 200MB → 24MB baseline
• Circular dependencies resolved
• Worker thread communication fixed
• Storage adapter consistency
• Request coalescing for 3x performance

🛠️ CLI FEATURES:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• brainy add - Smart data ingestion
• brainy find - Natural language search
• brainy search - Vector similarity
• brainy chat - AI conversation mode
• brainy cloud - Brain Cloud integration
• brainy augment - Manage extensions
• 100% API compatibility

📚 DOCUMENTATION:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• Professional README with examples
• Quick Start guide (5 minutes)
• Enterprise Features guide
• Migration guide from 1.x
• API reference
• Architecture documentation

🌟 USE CASES:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• AI memory layer for chatbots
• Semantic document search
• Code intelligence platforms
• Knowledge management systems
• Real-time recommendation engines
• Customer support automation

MIT License - Enterprise features included free for everyone.
No premium tiers, no paywalls, no limits.

Built with ❤️ by the Brainy community.
Visit https://soulcraft.com for Brain Cloud integration.
2025-08-26 12:32:21 -07:00

452 lines
No EOL
15 KiB
TypeScript

/**
* Lightweight statistics collector for Brainy
* Designed to have minimal performance impact even with millions of entries
*/
import { StatisticsData } from '../coreTypes.js'
interface TimeSeriesData {
timestamp: number
count: number
}
export class StatisticsCollector {
// Content type tracking (lightweight counters)
private contentTypes: Map<string, number> = new Map()
// Data freshness tracking (only track timestamps, not full data)
private oldestTimestamp: number = Date.now()
private newestTimestamp: number = Date.now()
private updateTimestamps: TimeSeriesData[] = []
// Search performance tracking (rolling window)
private searchMetrics = {
totalSearches: 0,
totalSearchTimeMs: 0,
searchTimestamps: [] as TimeSeriesData[],
topSearchTerms: new Map<string, number>()
}
// Verb type tracking
private verbTypes: Map<string, number> = new Map()
// Storage size estimates (updated periodically, not on every operation)
private storageSizeCache = {
lastUpdated: 0,
sizes: {
nouns: 0,
verbs: 0,
metadata: 0,
index: 0
}
}
// Throttling metrics
private throttlingMetrics = {
currentlyThrottled: false,
lastThrottleTime: 0,
consecutiveThrottleEvents: 0,
currentBackoffMs: 1000,
totalThrottleEvents: 0,
throttleEventsByHour: new Array(24).fill(0),
throttleReasons: new Map<string, number>(),
delayedOperations: 0,
retriedOperations: 0,
failedDueToThrottling: 0,
totalDelayMs: 0,
serviceThrottling: new Map<string, {
throttleCount: number
lastThrottle: number
status: 'normal' | 'throttled' | 'recovering'
}>()
}
private readonly MAX_TIMESTAMPS = 1000 // Keep last 1000 timestamps
private readonly MAX_SEARCH_TERMS = 100 // Track top 100 search terms
private readonly SIZE_UPDATE_INTERVAL = 60000 // Update sizes every minute
/**
* Track content type (very lightweight)
*/
trackContentType(type: string): void {
this.contentTypes.set(type, (this.contentTypes.get(type) || 0) + 1)
}
/**
* Track data update timestamp (lightweight)
*/
trackUpdate(timestamp?: number): void {
const ts = timestamp || Date.now()
// Update oldest/newest
if (ts < this.oldestTimestamp) this.oldestTimestamp = ts
if (ts > this.newestTimestamp) this.newestTimestamp = ts
// Add to rolling window
this.updateTimestamps.push({ timestamp: ts, count: 1 })
// Keep window size limited
if (this.updateTimestamps.length > this.MAX_TIMESTAMPS) {
this.updateTimestamps.shift()
}
}
/**
* Track search performance (lightweight)
*/
trackSearch(searchTerm: string, durationMs: number): void {
this.searchMetrics.totalSearches++
this.searchMetrics.totalSearchTimeMs += durationMs
// Add to rolling window
this.searchMetrics.searchTimestamps.push({
timestamp: Date.now(),
count: 1
})
// Keep window size limited
if (this.searchMetrics.searchTimestamps.length > this.MAX_TIMESTAMPS) {
this.searchMetrics.searchTimestamps.shift()
}
// Track search term (limit to top N)
const termCount = (this.searchMetrics.topSearchTerms.get(searchTerm) || 0) + 1
this.searchMetrics.topSearchTerms.set(searchTerm, termCount)
// Prune if too many terms
if (this.searchMetrics.topSearchTerms.size > this.MAX_SEARCH_TERMS * 2) {
this.pruneSearchTerms()
}
}
/**
* Track verb type (lightweight)
*/
trackVerbType(type: string): void {
this.verbTypes.set(type, (this.verbTypes.get(type) || 0) + 1)
}
/**
* Update storage size estimates (called periodically, not on every operation)
*/
updateStorageSizes(sizes: {
nouns: number
verbs: number
metadata: number
index: number
}): void {
this.storageSizeCache = {
lastUpdated: Date.now(),
sizes
}
}
/**
* Track a throttling event
*/
trackThrottlingEvent(reason: string, service?: string): void {
this.throttlingMetrics.currentlyThrottled = true
this.throttlingMetrics.consecutiveThrottleEvents++
this.throttlingMetrics.lastThrottleTime = Date.now()
this.throttlingMetrics.totalThrottleEvents++
// Track by hour
const hourIndex = new Date().getHours()
this.throttlingMetrics.throttleEventsByHour[hourIndex]++
// Track reason
const reasonCount = this.throttlingMetrics.throttleReasons.get(reason) || 0
this.throttlingMetrics.throttleReasons.set(reason, reasonCount + 1)
// Track service-level throttling
if (service) {
const serviceInfo = this.throttlingMetrics.serviceThrottling.get(service) || {
throttleCount: 0,
lastThrottle: 0,
status: 'normal' as const
}
serviceInfo.throttleCount++
serviceInfo.lastThrottle = Date.now()
serviceInfo.status = 'throttled'
this.throttlingMetrics.serviceThrottling.set(service, serviceInfo)
}
// Exponential backoff
this.throttlingMetrics.currentBackoffMs = Math.min(
this.throttlingMetrics.currentBackoffMs * 2,
30000 // Max 30 seconds
)
}
/**
* Clear throttling state after successful operations
*/
clearThrottlingState(): void {
if (this.throttlingMetrics.consecutiveThrottleEvents > 0) {
this.throttlingMetrics.consecutiveThrottleEvents = 0
this.throttlingMetrics.currentBackoffMs = 1000 // Reset to initial backoff
this.throttlingMetrics.currentlyThrottled = false
// Update service statuses
for (const [, info] of this.throttlingMetrics.serviceThrottling) {
if (info.status === 'throttled') {
info.status = 'recovering'
} else if (info.status === 'recovering') {
const timeSinceThrottle = Date.now() - info.lastThrottle
if (timeSinceThrottle > 60000) { // 1 minute recovery period
info.status = 'normal'
}
}
}
}
}
/**
* Track delayed operation
*/
trackDelayedOperation(delayMs: number): void {
this.throttlingMetrics.delayedOperations++
this.throttlingMetrics.totalDelayMs += delayMs
}
/**
* Track retried operation
*/
trackRetriedOperation(): void {
this.throttlingMetrics.retriedOperations++
}
/**
* Track operation failed due to throttling
*/
trackFailedDueToThrottling(): void {
this.throttlingMetrics.failedDueToThrottling++
}
/**
* Update throttling metrics from storage adapter
*/
updateThrottlingMetrics(metrics: {
currentlyThrottled: boolean
lastThrottleTime: number
consecutiveThrottleEvents: number
currentBackoffMs: number
totalThrottleEvents: number
throttleEventsByHour: number[]
throttleReasons: Record<string, number>
delayedOperations: number
retriedOperations: number
failedDueToThrottling: number
totalDelayMs: number
}): void {
this.throttlingMetrics.currentlyThrottled = metrics.currentlyThrottled
this.throttlingMetrics.lastThrottleTime = metrics.lastThrottleTime
this.throttlingMetrics.consecutiveThrottleEvents = metrics.consecutiveThrottleEvents
this.throttlingMetrics.currentBackoffMs = metrics.currentBackoffMs
this.throttlingMetrics.totalThrottleEvents = metrics.totalThrottleEvents
this.throttlingMetrics.throttleEventsByHour = [...metrics.throttleEventsByHour]
// Update throttle reasons map
this.throttlingMetrics.throttleReasons.clear()
for (const [reason, count] of Object.entries(metrics.throttleReasons)) {
this.throttlingMetrics.throttleReasons.set(reason, count)
}
this.throttlingMetrics.delayedOperations = metrics.delayedOperations
this.throttlingMetrics.retriedOperations = metrics.retriedOperations
this.throttlingMetrics.failedDueToThrottling = metrics.failedDueToThrottling
this.throttlingMetrics.totalDelayMs = metrics.totalDelayMs
}
/**
* Get comprehensive statistics
*/
getStatistics(): Partial<StatisticsData> {
const now = Date.now()
const hourAgo = now - 3600000
const dayAgo = now - 86400000
const weekAgo = now - 604800000
const monthAgo = now - 2592000000
// Calculate data freshness
const updatesLastHour = this.updateTimestamps.filter(t => t.timestamp > hourAgo).length
const updatesLastDay = this.updateTimestamps.filter(t => t.timestamp > dayAgo).length
// Calculate age distribution
const ageDistribution = {
last24h: 0,
last7d: 0,
last30d: 0,
older: 0
}
// Estimate based on update patterns (not scanning all data)
const totalUpdates = this.updateTimestamps.length
if (totalUpdates > 0) {
const recentUpdates = this.updateTimestamps.filter(t => t.timestamp > dayAgo).length
const weekUpdates = this.updateTimestamps.filter(t => t.timestamp > weekAgo).length
const monthUpdates = this.updateTimestamps.filter(t => t.timestamp > monthAgo).length
ageDistribution.last24h = Math.round((recentUpdates / totalUpdates) * 100)
ageDistribution.last7d = Math.round(((weekUpdates - recentUpdates) / totalUpdates) * 100)
ageDistribution.last30d = Math.round(((monthUpdates - weekUpdates) / totalUpdates) * 100)
ageDistribution.older = 100 - ageDistribution.last24h - ageDistribution.last7d - ageDistribution.last30d
}
// Calculate search metrics
const searchesLastHour = this.searchMetrics.searchTimestamps.filter(t => t.timestamp > hourAgo).length
const searchesLastDay = this.searchMetrics.searchTimestamps.filter(t => t.timestamp > dayAgo).length
const avgSearchTime = this.searchMetrics.totalSearches > 0
? this.searchMetrics.totalSearchTimeMs / this.searchMetrics.totalSearches
: 0
// Get top search terms
const topSearchTerms = Array.from(this.searchMetrics.topSearchTerms.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([term]) => term)
// Calculate storage metrics
const totalSize = Object.values(this.storageSizeCache.sizes).reduce((a, b) => a + b, 0)
// Calculate average delay for throttling
const averageDelayMs = this.throttlingMetrics.delayedOperations > 0
? this.throttlingMetrics.totalDelayMs / this.throttlingMetrics.delayedOperations
: 0
// Convert service throttling map to record
const serviceThrottlingRecord: Record<string, {
throttleCount: number
lastThrottle: string
status: 'normal' | 'throttled' | 'recovering'
}> = {}
for (const [service, info] of this.throttlingMetrics.serviceThrottling) {
serviceThrottlingRecord[service] = {
throttleCount: info.throttleCount,
lastThrottle: new Date(info.lastThrottle).toISOString(),
status: info.status
}
}
return {
contentTypes: Object.fromEntries(this.contentTypes),
dataFreshness: {
oldestEntry: new Date(this.oldestTimestamp).toISOString(),
newestEntry: new Date(this.newestTimestamp).toISOString(),
updatesLastHour,
updatesLastDay,
ageDistribution
},
storageMetrics: {
totalSizeBytes: totalSize,
nounsSizeBytes: this.storageSizeCache.sizes.nouns,
verbsSizeBytes: this.storageSizeCache.sizes.verbs,
metadataSizeBytes: this.storageSizeCache.sizes.metadata,
indexSizeBytes: this.storageSizeCache.sizes.index
},
searchMetrics: {
totalSearches: this.searchMetrics.totalSearches,
averageSearchTimeMs: avgSearchTime,
searchesLastHour,
searchesLastDay,
topSearchTerms
},
verbStatistics: {
totalVerbs: Array.from(this.verbTypes.values()).reduce((a, b) => a + b, 0),
verbTypes: Object.fromEntries(this.verbTypes),
averageConnectionsPerVerb: 2 // Verbs connect 2 nouns
},
throttlingMetrics: {
storage: {
currentlyThrottled: this.throttlingMetrics.currentlyThrottled,
lastThrottleTime: this.throttlingMetrics.lastThrottleTime > 0
? new Date(this.throttlingMetrics.lastThrottleTime).toISOString()
: undefined,
consecutiveThrottleEvents: this.throttlingMetrics.consecutiveThrottleEvents,
currentBackoffMs: this.throttlingMetrics.currentBackoffMs,
totalThrottleEvents: this.throttlingMetrics.totalThrottleEvents,
throttleEventsByHour: [...this.throttlingMetrics.throttleEventsByHour],
throttleReasons: Object.fromEntries(this.throttlingMetrics.throttleReasons)
},
operationImpact: {
delayedOperations: this.throttlingMetrics.delayedOperations,
retriedOperations: this.throttlingMetrics.retriedOperations,
failedDueToThrottling: this.throttlingMetrics.failedDueToThrottling,
averageDelayMs,
totalDelayMs: this.throttlingMetrics.totalDelayMs
},
serviceThrottling: Object.keys(serviceThrottlingRecord).length > 0
? serviceThrottlingRecord
: undefined
}
}
}
/**
* Merge statistics from storage (for distributed systems)
*/
mergeFromStorage(stored: Partial<StatisticsData>): void {
// Merge content types
if (stored.contentTypes) {
for (const [type, count] of Object.entries(stored.contentTypes)) {
this.contentTypes.set(type, count)
}
}
// Merge verb types
if (stored.verbStatistics?.verbTypes) {
for (const [type, count] of Object.entries(stored.verbStatistics.verbTypes)) {
this.verbTypes.set(type, count)
}
}
// Merge search metrics
if (stored.searchMetrics) {
this.searchMetrics.totalSearches = stored.searchMetrics.totalSearches || 0
this.searchMetrics.totalSearchTimeMs = (stored.searchMetrics.averageSearchTimeMs || 0) * this.searchMetrics.totalSearches
}
// Merge data freshness
if (stored.dataFreshness) {
this.oldestTimestamp = new Date(stored.dataFreshness.oldestEntry).getTime()
this.newestTimestamp = new Date(stored.dataFreshness.newestEntry).getTime()
}
}
/**
* Reset statistics (for testing)
*/
reset(): void {
this.contentTypes.clear()
this.verbTypes.clear()
this.updateTimestamps = []
this.searchMetrics = {
totalSearches: 0,
totalSearchTimeMs: 0,
searchTimestamps: [],
topSearchTerms: new Map()
}
this.oldestTimestamp = Date.now()
this.newestTimestamp = Date.now()
}
private pruneSearchTerms(): void {
// Keep only top N search terms
const sorted = Array.from(this.searchMetrics.topSearchTerms.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, this.MAX_SEARCH_TERMS)
this.searchMetrics.topSearchTerms.clear()
for (const [term, count] of sorted) {
this.searchMetrics.topSearchTerms.set(term, count)
}
}
}