npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@onurgvnc/tx2

v1.0.1

Published

tx2

Readme

TX2

original project: https://github.com/pm2/tx2 | https://npmjs.com/package/tx2

📋 Table of Contents

Introduction

TX2 is a comprehensive monitoring library that bridges your Node.js applications with the PM2 process manager. It provides real-time metrics, event tracking, remote actions, and error reporting capabilities through PM2's IPC (Inter-Process Communication) mechanism.

What Does TX2 Do?

  • 📊 Metrics Collection: Collects and reports CPU, memory, request rate, and custom metrics
  • 📬 Event Notifications: Sends important application events to PM2 dashboard
  • ⚡ Action Handling: Receives and executes commands from PM2 dashboard
  • 🐛 Error Reporting: Captures and reports errors with full stack traces
  • 📈 Performance Monitoring: Tracks histograms, counters, meters, and custom measurements

Architecture Overview

TX2 operates through a bidirectional communication channel with PM2:

Your Application <--[IPC via process.send/on]--> PM2 Daemon <--[API]--> PM2 Web Dashboard
        |                                            |                         |
    TX2 Library                                 PM2 God Daemon          Monitoring Interface

The communication flow:

  1. Metrics: Automatically sent every 990ms via axm:monitor messages
  2. Events: Instantly transmitted via human:event messages
  3. Actions: Registered via axm:action and responded via axm:reply
  4. Errors: Immediately reported via process:exception messages

Installation

For TypeScript Projects

# Copy the tx2.ts file to your project
cp tx2.ts your-project/src/

# Install required dependencies
npm install fast-safe-stringify
npm install --save-dev @types/node

For JavaScript Projects

# Install the original tx2 package
npm install tx2

# Or use the TypeScript version and compile it
npx tsc tx2.ts --target ES2020 --module commonjs

Basic Setup

import tx2 from './tx2'

// Initialize monitoring when application starts
tx2.event('app:ready', {
  version: process.env.npm_package_version,
  node: process.version,
  pid: process.pid
})

// Graceful shutdown handling
process.on('SIGINT', () => {
  tx2.event('app:shutdown', { reason: 'SIGINT' })
  process.exit(0)
})

Core Concepts

Inter-Process Communication (IPC)

TX2 leverages Node.js's built-in IPC capabilities to communicate with PM2:

  • Outbound Messages: Uses process.send() to transmit data to PM2
  • Inbound Messages: Listens via process.on('message') for PM2 commands
  • Message Queue: No internal buffering - messages are sent immediately
  • Connection Detection: Automatically detects if running under PM2 via process.send availability

Message Protocol

TX2 implements PM2's monitoring protocol with 5 distinct message types:

| Type | Direction | Purpose | Frequency | | ------------------- | --------- | ------------------------ | ---------------------- | | axm:monitor | App → PM2 | Metric data transmission | Every 990ms | | human:event | App → PM2 | Event notifications | On-demand | | axm:action | App → PM2 | Action registration | Once per action | | axm:reply | App → PM2 | Action response | After action execution | | process:exception | App → PM2 | Error reporting | On error |

Data Serialization

TX2 handles complex data serialization challenges:

  • Circular References: Safely handled via fast-safe-stringify
  • Error Objects: Converted to plain objects preserving all properties
  • Large Payloads: No size limits, but large data may impact performance
  • Special Properties: Handles __name, _length, and other meta properties

Features

1. Events

Events allow you to track important occurrences in your application lifecycle. They appear in PM2 logs and can trigger alerts.

Basic Event Usage

// Simple event notification
tx2.event('server:started')

// Event with string data
tx2.event('cache:cleared', 'Redis cache has been flushed')

// Event with numeric data
tx2.event('payment:processed', 150.99)

// Event with boolean data
tx2.event('maintenance:mode', true)

Structured Event Data

// Event with object payload (recommended approach)
tx2.event('user:registered', {
  userId: 'usr_123456',
  email: '[email protected]',
  plan: 'premium',
  referralSource: 'google',
  timestamp: Date.now(),
  metadata: {
    ip: '192.168.1.1',
    userAgent: 'Mozilla/5.0...',
    country: 'US'
  }
})

// Complex nested event structure
tx2.event('order:completed', {
  orderId: 'ORD-2024-001',
  customer: {
    id: 789,
    name: 'John Doe',
    email: '[email protected]',
    loyaltyPoints: 250,
    tier: 'gold'
  },
  items: [
    {
      sku: 'PROD-1',
      name: 'Premium Widget',
      quantity: 2,
      price: 29.99,
      discount: 5.00
    },
    {
      sku: 'PROD-2',
      name: 'Standard Gadget',
      quantity: 1,
      price: 49.99,
      discount: 0
    }
  ],
  totals: {
    subtotal: 109.97,
    discount: 10.00,
    tax: 9.90,
    shipping: 5.00,
    total: 114.87
  },
  payment: {
    method: 'credit_card',
    last4: '1234',
    processor: 'stripe',
    transactionId: 'txn_abc123'
  },
  fulfillment: {
    method: 'standard',
    estimatedDelivery: '2024-03-15',
    warehouse: 'EAST-1'
  }
})

Event Naming Conventions

// ✅ Good - Clear namespace:action format
tx2.event('database:connected')
tx2.event('api:request:started')
tx2.event('cache:miss')
tx2.event('auth:login:success')
tx2.event('payment:subscription:renewed')

// ❌ Bad - Ambiguous or unclear names
tx2.event('event1')
tx2.event('something_happened')
tx2.event('data')
tx2.event('update')

Event Patterns for Different Scenarios

// Lifecycle Events
tx2.event('app:initializing', { stage: 'database' })
tx2.event('app:ready', { startupTime: Date.now() - startTime })
tx2.event('app:shutdown', { reason: 'SIGTERM', cleanup: true })

// Performance Events
tx2.event('performance:slow_query', {
  query: 'SELECT * FROM users...',
  duration: 5234,
  threshold: 1000
})

// Security Events
tx2.event('security:login:failed', {
  username: 'admin',
  ip: req.ip,
  attempts: failedAttempts,
  blocked: failedAttempts > 5
})

// Business Events
tx2.event('business:revenue:milestone', {
  type: 'daily',
  amount: 10000,
  currency: 'USD',
  transactions: 156
})

2. Actions

Actions enable remote control of your application through the PM2 dashboard. They're perfect for administrative tasks, debugging, and operational controls.

Simple Actions

// Basic action without parameters
tx2.action('clear_cache', (reply) => {
  cache.flush()
  reply({
    success: true,
    message: 'Cache cleared successfully',
    timestamp: new Date().toISOString()
  })
})

// Action with error handling
tx2.action('reload_config', (reply) => {
  try {
    const oldConfig = { ...config }
    config.reload()

    reply({
      success: true,
      changes: diffConfigs(oldConfig, config),
      version: config.version
    })
  } catch (error) {
    reply({
      success: false,
      error: error.message,
      stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
    })
  }
})

Parameterized Actions

// Action with typed parameters
interface DatabaseBackupOptions {
  format: 'sql' | 'json' | 'csv'
  compress: boolean
  tables?: string[]
}

tx2.action('backup_database',
  { format: 'sql', compress: true },
  async (options: DatabaseBackupOptions, reply) => {
    try {
      const backup = await db.backup({
        format: options.format,
        compress: options.compress,
        tables: options.tables || 'all'
      })

      reply({
        success: true,
        filename: backup.filename,
        size: backup.size,
        tables: backup.tables.length,
        compressed: options.compress,
        location: backup.path
      })
    } catch (error) {
      reply({
        success: false,
        error: error.message,
        code: error.code
      })
    }
  }
)

Asynchronous Actions

// Long-running async action
tx2.action('generate_report', async (reply) => {
  const reportId = uuid()

  // Immediate acknowledgment
  reply({
    success: true,
    reportId,
    status: 'processing',
    message: 'Report generation started'
  })

  // Continue processing in background
  generateReportAsync(reportId).catch(error => {
    tx2.issue({
      type: 'report_generation_failed',
      reportId,
      error: error.message
    })
  })
})

// Action with progress updates
tx2.action('migrate_data', async (reply) => {
  const stages = ['backup', 'transform', 'validate', 'apply', 'verify']
  const results = {}

  for (const stage of stages) {
    try {
      tx2.event('migration:stage', { stage, status: 'started' })
      results[stage] = await runMigrationStage(stage)
      tx2.event('migration:stage', { stage, status: 'completed' })
    } catch (error) {
      reply({
        success: false,
        failedStage: stage,
        completedStages: Object.keys(results),
        error: error.message
      })
      return
    }
  }

  reply({
    success: true,
    stages: results,
    duration: Date.now() - startTime
  })
})

Common Action Patterns

// 1. Toggle Pattern
let maintenanceMode = false

tx2.action('toggle_maintenance', (reply) => {
  maintenanceMode = !maintenanceMode

  if (maintenanceMode) {
    app.disable('x-powered-by')
    app.use((req, res) => res.status(503).send('Maintenance in progress'))
  } else {
    app.enable('x-powered-by')
  }

  reply({
    enabled: maintenanceMode,
    message: `Maintenance mode ${maintenanceMode ? 'enabled' : 'disabled'}`,
    affectedServices: ['api', 'websocket']
  })
})

// 2. Health Check Pattern
tx2.action('health_check', async (reply) => {
  const checks = await Promise.allSettled([
    db.ping(),
    redis.ping(),
    elasticsearch.ping(),
    messageQueue.health()
  ])

  reply({
    healthy: checks.every(c => c.status === 'fulfilled'),
    timestamp: new Date().toISOString(),
    services: {
      database: checks[0].status === 'fulfilled',
      redis: checks[1].status === 'fulfilled',
      elasticsearch: checks[2].status === 'fulfilled',
      queue: checks[3].status === 'fulfilled'
    },
    system: {
      memory: process.memoryUsage(),
      uptime: process.uptime(),
      cpu: process.cpuUsage()
    }
  })
})

// 3. Diagnostic Pattern
tx2.action('diagnose', (reply) => {
  const diagnostics = {
    environment: {
      node: process.version,
      platform: process.platform,
      arch: process.arch,
      pid: process.pid,
      ppid: process.ppid
    },
    memory: {
      ...process.memoryUsage(),
      limit: v8.getHeapStatistics().heap_size_limit
    },
    connections: {
      http: server.connections,
      websocket: wss.clients.size,
      database: db.pool.totalCount,
      redis: redis.client.connections
    },
    queues: {
      pending: queue.pending(),
      active: queue.active(),
      completed: queue.completed(),
      failed: queue.failed()
    }
  }

  reply(diagnostics)
})

// 4. Reset Pattern
tx2.action('reset_all_counters', (reply) => {
  const counters = [
    'requests',
    'errors',
    'cache_hits',
    'cache_misses'
  ]

  counters.forEach(name => {
    if (tx2.metricExists(name)) {
      tx2.counter(name).reset()
    }
  })

  reply({
    reset: true,
    counters,
    timestamp: Date.now()
  })
})

3. Metrics

Metrics provide continuous monitoring of your application's performance. TX2 supports multiple metric types, each optimized for different measurement scenarios.

3.1. Simple Metric

Tracks a single value or function result. Ideal for gauges and instantaneous measurements.

// Static value metric
const dbConnections = tx2.metric({
  name: 'db_connections',
  value: 5,
  unit: 'connections'
})

// Update the value
dbConnections.set(10)

// Dynamic value with function
tx2.metric({
  name: 'memory_usage',
  unit: 'MB',
  value: () => {
    const used = process.memoryUsage().heapUsed
    return Math.round(used / 1024 / 1024)
  }
})

// Complex computed metric
let cache = new Map()
tx2.metric({
  name: 'cache_efficiency',
  unit: '%',
  value: () => {
    if (cacheRequests === 0) return 0
    return Math.round((cacheHits / cacheRequests) * 100)
  }
})

// Boolean state metric
tx2.metric({
  name: 'database_connected',
  value: () => db.isConnected() ? 1 : 0
})

// String value metric
tx2.metric({
  name: 'app_version',
  value: () => `v${package.version}-${process.env.NODE_ENV}`
})

// Real-time calculation
const startTime = Date.now()
tx2.metric({
  name: 'uptime_hours',
  unit: 'hours',
  value: () => {
    const uptime = Date.now() - startTime
    return Math.floor(uptime / 1000 / 60 / 60)
  }
})

3.2. Counter

Accumulative metrics that can be incremented, decremented, and reset. Perfect for counting events and tracking totals.

// Basic counter
const requestCounter = tx2.counter('total_requests')

// Middleware integration
app.use((req, res, next) => {
  requestCounter.inc()

  res.on('finish', () => {
    if (res.statusCode >= 400) {
      errorCounter.inc()
    }
    if (res.statusCode >= 500) {
      criticalErrorCounter.inc()
    }
  })

  next()
})

// Counter with units
const dataTransferred = tx2.counter({
  name: 'data_transferred',
  unit: 'MB'
})

// Batch operations
stream.on('data', (chunk) => {
  dataTransferred.inc(chunk.length / 1024 / 1024)
})

// WebSocket connection tracking
const wsConnections = tx2.counter({
  name: 'websocket_connections',
  unit: 'connections'
})

io.on('connection', (socket) => {
  wsConnections.inc()

  socket.on('disconnect', () => {
    wsConnections.dec()
  })

  socket.on('error', () => {
    wsErrors.inc()
    wsConnections.dec()
  })
})

// Advanced counter usage
class SessionManager {
  private activeSessions = tx2.counter('active_sessions')
  private totalSessions = tx2.counter('total_sessions')

  createSession(userId: string): string {
    const sessionId = generateSessionId()
    this.activeSessions.inc()
    this.totalSessions.inc()
    return sessionId
  }

  destroySession(sessionId: string): void {
    this.activeSessions.dec()
  }

  resetDaily(): void {
    // Keep total, reset active
    this.activeSessions.reset()
  }
}

3.3. Histogram

Statistical analysis of values over time. Provides percentiles, averages, and distribution metrics.

// Response time tracking
const responseTime = tx2.histogram({
  name: 'response_time',
  measurement: 'mean',
  unit: 'ms'
})

app.use((req, res, next) => {
  const start = Date.now()

  // Monkey-patch res.end
  const originalEnd = res.end
  res.end = function(...args) {
    const duration = Date.now() - start
    responseTime.update(duration)

    // Log slow requests
    if (duration > 1000) {
      tx2.event('slow_request', {
        url: req.url,
        method: req.method,
        duration
      })
    }

    return originalEnd.apply(this, args)
  }

  next()
})
Available Measurements
// Minimum value tracking
const minLatency = tx2.histogram({
  name: 'min_latency',
  measurement: 'min',
  unit: 'ms'
})

// Maximum value tracking
const maxMemory = tx2.histogram({
  name: 'peak_memory',
  measurement: 'max',
  unit: 'MB'
})

// Average calculation
const avgProcessingTime = tx2.histogram({
  name: 'avg_processing',
  measurement: 'mean',
  unit: 'ms'
})

// Median (50th percentile)
const medianQueryTime = tx2.histogram({
  name: 'median_query',
  measurement: 'median',
  unit: 'ms'
})

// 95th percentile (95% of values are below this)
const p95ResponseTime = tx2.histogram({
  name: 'p95_response',
  measurement: 'p95',
  unit: 'ms'
})

// 99th percentile (99% of values are below this)
const p99ResponseTime = tx2.histogram({
  name: 'p99_response',
  measurement: 'p99',
  unit: 'ms'
})

// Variance (spread of data)
const loadVariance = tx2.histogram({
  name: 'load_variance',
  measurement: 'variance'
})

// Standard deviation
const jitter = tx2.histogram({
  name: 'network_jitter',
  measurement: 'stddev',
  unit: 'ms'
})

// Exponential Moving Average (trend indicator)
const trendingValue = tx2.histogram({
  name: 'trending_requests',
  measurement: 'ema'
})

// Sum of all values
const totalDataProcessed = tx2.histogram({
  name: 'total_data',
  measurement: 'sum',
  unit: 'bytes'
})

// Count of measurements
const apiCallCount = tx2.histogram({
  name: 'api_calls',
  measurement: 'count'
})
Advanced Histogram Usage
// Database query analyzer
const queryAnalyzer = tx2.histogram({
  name: 'db_query_time',
  measurement: 'p99',
  unit: 'ms'
})

// Hook into database events
db.on('query', (query) => {
  const start = Date.now()

  query.on('end', () => {
    const duration = Date.now() - start
    queryAnalyzer.update(duration)

    // Track slow queries separately
    if (duration > 100) {
      slowQueryHistogram.update(duration)
    }
  })
})

// Full statistics retrieval
const apiStats = tx2.histogram({
  name: 'api_latency',
  measurement: 'mean',
  unit: 'ms'
})

// Get comprehensive statistics
setInterval(() => {
  const stats = apiStats.fullResults()
  console.log('API Performance Stats:', {
    min: `${stats.min}ms`,
    max: `${stats.max}ms`,
    average: `${stats.mean}ms`,
    median: `${stats.median}ms`,
    p95: `${stats.p95}ms`,
    p99: `${stats.p99}ms`,
    stdDev: `${stats.stddev}ms`,
    total: stats.count
  })

  // Alert on degraded performance
  if (stats.p95 > 500) {
    tx2.event('performance:degraded', {
      p95: stats.p95,
      threshold: 500
    })
  }
}, 60000)

// Custom percentile analysis
const customPercentiles = apiStats.percentiles([0.1, 0.25, 0.5, 0.75, 0.9, 0.99])
// Returns: { 0.1: 10, 0.25: 25, 0.5: 50, 0.75: 75, 0.9: 90, 0.99: 99 }

// Memory-efficient histogram with periodic reset
let histogramUpdateCount = 0
const efficientHistogram = tx2.histogram({
  name: 'efficient_metric',
  measurement: 'mean'
})

function updateHistogram(value: number) {
  efficientHistogram.update(value)
  histogramUpdateCount++

  // Reset every 10000 measurements to prevent memory growth
  if (histogramUpdateCount >= 10000) {
    efficientHistogram.reset()
    histogramUpdateCount = 0
  }
}

3.4. Meter

Measures the rate of events over time using an Exponentially Weighted Moving Average (EWMA) algorithm.

// Simple request rate meter
const requestsPerSec = tx2.meter({
  name: 'req/sec'
})

app.use((req, res, next) => {
  requestsPerSec.mark()
  next()
})

// Bandwidth meter
const bytesPerSec = tx2.meter({
  name: 'bandwidth',
  unit: 'MB/sec'
})

stream.on('data', (chunk) => {
  // Mark with the number of bytes
  bytesPerSec.mark(chunk.length / 1024 / 1024)
})

// Customized meter with different time windows
const customMeter = tx2.meter({
  name: 'events/sec',
  seconds: 5,      // Calculate rate every 5 seconds
  timeframe: 60    // Use 60-second time window for averaging
})

// Message processing rate
messageQueue.on('message', (msg) => {
  customMeter.mark()

  // Handle batch messages
  if (msg.batch) {
    customMeter.mark(msg.batch.length)
  }
})

// Transaction rate tracking
const transactionRate = tx2.meter({
  name: 'transactions/min',
  seconds: 60,
  unit: 'tx/min'
})

async function processTransaction(tx) {
  transactionRate.mark()

  if (tx.amount > 10000) {
    largeTransactionRate.mark()
  }

  await db.saveTransaction(tx)
}
Meter vs Counter Comparison
// Counter: Cumulative total
const totalRequests = tx2.counter('total_requests')
totalRequests.inc()  // 1, 2, 3, 4, 5...

// Meter: Rate per second
const requestRate = tx2.meter({ name: 'req/sec' })
requestRate.mark()  // 10 req/s, 15 req/s, 8 req/s...

// Using both together for comprehensive monitoring
app.use((req, res, next) => {
  totalRequests.inc()     // Track total count
  requestRate.mark()      // Track current rate

  if (req.path.startsWith('/api')) {
    apiRequests.inc()
    apiRate.mark()
  }

  next()
})

3.5. Transpose

Exposes any dynamic value as a metric. Perfect for custom calculations and external service monitoring.

// Simple value transposition
tx2.transpose('random_value', () => {
  return Math.random() * 100
})

// Object notation
tx2.transpose({
  name: 'complex_calculation',
  data: () => {
    const result = performComplexCalculation()
    return Math.round(result * 100) / 100
  }
})

// External service monitoring
tx2.transpose('redis_memory', async () => {
  try {
    const info = await redis.info('memory')
    return parseInt(info.used_memory) / 1024 / 1024 // MB
  } catch {
    return -1 // Indicate error
  }
})

// Composite metrics
tx2.transpose('efficiency_score', () => {
  const hitRate = (cacheHits / (cacheHits + cacheMisses)) * 100
  const errorRate = (errors / requests) * 100
  const efficiency = hitRate * (1 - errorRate / 100)
  return Math.round(efficiency)
})

// System status aggregation
tx2.transpose('system_health', () => {
  const checks = {
    database: db.isConnected(),
    redis: redis.status === 'ready',
    queue: queue.isRunning(),
    disk: diskUsage < 90
  }

  const healthScore = Object.values(checks).filter(Boolean).length
  return healthScore / Object.keys(checks).length * 100
})

4. Issues

Comprehensive error tracking and reporting system that captures errors with full context.

// String error
tx2.issue('Critical: Database connection lost')

// Error object with stack trace
try {
  await riskyDatabaseOperation()
} catch (error) {
  tx2.issue(error)
}

// Enhanced error object
class CustomError extends Error {
  constructor(message, code, statusCode) {
    super(message)
    this.code = code
    this.statusCode = statusCode
    this.timestamp = new Date().toISOString()
  }
}

try {
  await apiCall()
} catch (error) {
  const enhancedError = new CustomError(
    error.message,
    'API_ERROR',
    500
  )
  enhancedError.endpoint = '/api/users'
  enhancedError.userId = req.user?.id

  tx2.issue(enhancedError)
}

// Structured error reporting
tx2.issue({
  type: 'payment_failure',
  message: 'Payment processing failed',
  code: 'PAYMENT_DECLINED',
  severity: 'critical',
  user: {
    id: user.id,
    email: user.email
  },
  payment: {
    amount: 99.99,
    currency: 'USD',
    method: 'credit_card',
    processor: 'stripe'
  },
  error: {
    code: stripeError.code,
    message: stripeError.message,
    decline_code: stripeError.decline_code
  },
  timestamp: new Date().toISOString(),
  context: {
    ip: req.ip,
    userAgent: req.get('user-agent'),
    sessionId: req.sessionID
  }
})

Global Error Handlers

// Uncaught exception handler
process.on('uncaughtException', (error) => {
  tx2.issue({
    type: 'uncaughtException',
    error: {
      message: error.message,
      stack: error.stack,
      name: error.name
    },
    fatal: true,
    pid: process.pid
  })

  // Graceful shutdown
  setTimeout(() => {
    process.exit(1)
  }, 1000)
})

// Unhandled promise rejection
process.on('unhandledRejection', (reason, promise) => {
  tx2.issue({
    type: 'unhandledRejection',
    reason: reason instanceof Error ? {
      message: reason.message,
      stack: reason.stack
    } : reason,
    promise: String(promise)
  })
})

// Express error middleware
app.use((err, req, res, next) => {
  const errorReport = {
    message: err.message,
    stack: err.stack,
    status: err.status || 500,
    request: {
      url: req.url,
      method: req.method,
      headers: req.headers,
      body: req.body,
      query: req.query,
      params: req.params
    },
    user: req.user ? {
      id: req.user.id,
      email: req.user.email
    } : null,
    session: req.sessionID,
    ip: req.ip,
    timestamp: new Date().toISOString()
  }

  tx2.issue(errorReport)

  res.status(errorReport.status).json({
    error: process.env.NODE_ENV === 'production'
      ? 'Internal Server Error'
      : errorReport.message
  })
})

API Reference

TX2 Class

class TX2 extends EventEmitter {
  // Singleton instance
  constructor()

  // Event Methods
  event(name: string, data?: any): void

  // Action Methods
  action(name: string, callback: (reply: Function) => void): void
  action<T>(name: string, opts: T, callback: (opts: T, reply: Function) => void): void

  // Issue Methods
  issue(err: string | Error | object): ErrorObject
  _interpretError(err: any): ErrorObject

  // Metric Methods
  metric<T>(name: string, cb: () => T): Metric<T>
  metric(name: string, cb: number): Metric<number>
  metric<T>(name: string, unit: string, cb: () => T): Metric<T>
  metric(name: string, unit: string, cb: number): Metric<number>
  metric<T>(options: MetricOptions<T>): Metric<T>

  counter(name: string): Counter
  counter(options: CounterOptions): Counter

  histogram(options: HistogramOptions): Histogram

  meter(options: MeterOptions): Meter

  transpose(name: string, reporter: () => any): void
  transpose(options: TransposeOptions): void

  // Utility Methods
  metricExists(name: string): boolean
  prepareData(): Record<string, MetricData>
  send(args: SendArgs): void

  // EventEmitter Methods
  on(event: 'data', listener: (msg: TX2Message) => void): this
  once(event: 'data', listener: (msg: TX2Message) => void): this
  emit(event: 'data', msg: TX2Message): boolean
}

Type Definitions

// Core Interfaces
interface MetricOptions<T = any> {
  name: string
  value?: T | (() => T)
  val?: T | (() => T)  // Alias for value
  unit?: string
}

interface CounterOptions {
  name: string
  unit?: string
}

interface HistogramOptions {
  name: string
  measurement?: 'min' | 'max' | 'sum' | 'count' | 'mean' |
                'variance' | 'stddev' | 'median' |
                'p75' | 'p95' | 'p99' | 'p999' | 'ema'
  unit?: string
  value?: any | (() => any)
  val?: any | (() => any)
}

interface MeterOptions {
  name: string
  seconds?: number      // Tick interval in seconds (default: 1)
  timeframe?: number    // Time window in seconds (default: 60)
  unit?: string
}

interface TransposeOptions {
  name: string
  data: () => any
}

interface ErrorObject {
  name?: string
  message?: string
  stack?: string
  stackframes?: Array<{
    file_name: string
    line_number: number
  }>
  [key: string]: any
}

// Metric Types
interface Metric<T = any> {
  val: () => T
  set: (data: T) => void
}

interface Counter {
  val: () => number
  inc: (amount?: number) => void
  dec: (amount?: number) => void
  reset: (count?: number) => void
}

interface Histogram {
  update: (value: number) => void
  percentiles: (percentiles: number[]) => { [key: number]: number | null }
  reset: () => void
  val: () => number
  getMin: () => number | null
  getMax: () => number | null
  getSum: () => number
  getCount: () => number
  getEma: () => number
  fullResults: () => HistogramResults
}

interface HistogramResults {
  min: number | null
  max: number | null
  sum: number
  variance: number | null
  mean: number
  stddev: number | null
  count: number
  median: number | null
  p75: number | null
  p95: number | null
  p99: number | null
  p999: number | null
  ema: number
}

interface Meter {
  mark: (n?: number) => void
  val: () => number
}

// Message Types
type TX2EventType =
  | 'axm:monitor'
  | 'human:event'
  | 'axm:action'
  | 'axm:reply'
  | 'process:exception'

interface TX2Message {
  type: TX2EventType
  data: any
}

interface SendArgs {
  type: TX2EventType
  data: any
}

Advanced Usage

Building a Complete Monitoring Dashboard

// Comprehensive monitoring setup
class ApplicationMonitor {
  private metrics = {
    // Counters
    requests: tx2.counter('http_requests_total'),
    errors: tx2.counter('errors_total'),
    cache_hits: tx2.counter('cache_hits'),
    cache_misses: tx2.counter('cache_misses'),

    // Histograms
    responseTime: tx2.histogram({
      name: 'http_response_time',
      measurement: 'p95',
      unit: 'ms'
    }),
    dbQueryTime: tx2.histogram({
      name: 'db_query_time',
      measurement: 'p99',
      unit: 'ms'
    }),

    // Meters
    throughput: tx2.meter({
      name: 'throughput',
      unit: 'req/sec'
    }),
    errorRate: tx2.meter({
      name: 'error_rate',
      unit: 'errors/sec'
    }),

    // Metrics
    connections: tx2.metric({
      name: 'active_connections',
      value: () => this.getActiveConnections()
    }),
    memoryUsage: tx2.metric({
      name: 'memory_usage',
      unit: 'MB',
      value: () => Math.round(process.memoryUsage().heapUsed / 1024 / 1024)
    })
  }

  private getActiveConnections(): number {
    return (
      httpServer.connections +
      wsServer.clients.size +
      db.pool.activeCount
    )
  }

  recordRequest(req: Request, res: Response, duration: number) {
    this.metrics.requests.inc()
    this.metrics.throughput.mark()
    this.metrics.responseTime.update(duration)

    if (res.statusCode >= 400) {
      this.metrics.errors.inc()
      this.metrics.errorRate.mark()

      if (res.statusCode >= 500) {
        tx2.event('http:error:5xx', {
          url: req.url,
          method: req.method,
          status: res.statusCode,
          duration
        })
      }
    }

    // Track slow requests
    if (duration > 1000) {
      tx2.event('http:slow_request', {
        url: req.url,
        duration,
        threshold: 1000
      })
    }
  }

  recordCacheOperation(hit: boolean) {
    if (hit) {
      this.metrics.cache_hits.inc()
    } else {
      this.metrics.cache_misses.inc()
    }
  }

  recordDatabaseQuery(query: string, duration: number) {
    this.metrics.dbQueryTime.update(duration)

    if (duration > 100) {
      tx2.event('db:slow_query', {
        query: query.substring(0, 200),
        duration
      })
    }
  }
}

const monitor = new ApplicationMonitor()

// Express middleware integration
app.use((req, res, next) => {
  const start = Date.now()

  res.on('finish', () => {
    monitor.recordRequest(req, res, Date.now() - start)
  })

  next()
})

// Database hook
db.on('query', (query) => {
  const start = Date.now()
  query.on('end', () => {
    monitor.recordDatabaseQuery(query.sql, Date.now() - start)
  })
})

// Cache wrapper
class CacheWrapper {
  async get(key: string) {
    const value = await cache.get(key)
    monitor.recordCacheOperation(value !== null)
    return value
  }
}

Circuit Breaker Implementation

class CircuitBreaker {
  private failures = 0
  private lastFailTime = 0
  private state: 'closed' | 'open' | 'half-open' = 'closed'

  // Metrics
  private failureCounter = tx2.counter(`circuit_breaker_${this.name}_failures`)
  private stateMetric = tx2.metric({
    name: `circuit_breaker_${this.name}_state`,
    value: () => this.state
  })
  private callsMetric = tx2.histogram({
    name: `circuit_breaker_${this.name}_calls`,
    measurement: 'count'
  })

  constructor(
    private name: string,
    private threshold = 5,
    private timeout = 60000,
    private resetTimeout = 30000
  ) {
    // Admin actions
    tx2.action(`reset_circuit_${name}`, (reply) => {
      this.reset()
      reply({
        circuit: this.name,
        previousState: this.state,
        newState: 'closed',
        failures: 0
      })
    })

    tx2.action(`circuit_${name}_status`, (reply) => {
      reply({
        name: this.name,
        state: this.state,
        failures: this.failures,
        lastFailure: this.lastFailTime
          ? new Date(this.lastFailTime).toISOString()
          : null,
        threshold: this.threshold,
        config: {
          timeout: this.timeout,
          resetTimeout: this.resetTimeout
        }
      })
    })
  }

  async call<T>(fn: () => Promise<T>): Promise<T> {
    // Check circuit state
    if (this.state === 'open') {
      const timeSinceFailure = Date.now() - this.lastFailTime

      if (timeSinceFailure > this.timeout) {
        this.state = 'half-open'
        tx2.event(`circuit:half_open`, { circuit: this.name })
      } else {
        throw new Error(`Circuit breaker is open for ${this.name}`)
      }
    }

    try {
      const result = await fn()
      this.onSuccess()
      return result
    } catch (error) {
      this.onFailure(error)
      throw error
    } finally {
      this.callsMetric.update(1)
    }
  }

  private onSuccess() {
    if (this.state === 'half-open') {
      this.reset()
      tx2.event('circuit:closed', {
        circuit: this.name,
        recoveredAfter: Date.now() - this.lastFailTime
      })
    }
  }

  private onFailure(error: Error) {
    this.failures++
    this.failureCounter.inc()
    this.lastFailTime = Date.now()

    if (this.failures >= this.threshold) {
      this.state = 'open'
      tx2.event('circuit:opened', {
        circuit: this.name,
        failures: this.failures,
        threshold: this.threshold,
        error: error.message
      })

      // Auto-recovery timer
      setTimeout(() => {
        if (this.state === 'open') {
          this.state = 'half-open'
        }
      }, this.resetTimeout)
    }
  }

  private reset() {
    this.failures = 0
    this.state = 'closed'
    this.failureCounter.reset()
  }
}

// Usage
const apiCircuit = new CircuitBreaker('external_api', 5, 60000)

async function callExternalAPI(data) {
  return apiCircuit.call(async () => {
    const response = await fetch('https://api.example.com', {
      method: 'POST',
      body: JSON.stringify(data)
    })

    if (!response.ok) {
      throw new Error(`API returned ${response.status}`)
    }

    return response.json()
  })
}

Memory Leak Detection

class MemoryLeakDetector {
  private samples: number[] = []
  private increasing = 0
  private threshold = 10

  private memoryHistogram = tx2.histogram({
    name: 'memory_heap_used',
    measurement: 'max',
    unit: 'MB'
  })

  private leakMetric = tx2.metric({
    name: 'memory_leak_probability',
    unit: '%',
    value: () => this.getLeakProbability()
  })

  constructor() {
    // Sample memory every 10 seconds
    setInterval(() => this.sample(), 10000)

    // Force GC action if available
    if (global.gc) {
      tx2.action('force_gc', (reply) => {
        const before = process.memoryUsage()
        global.gc()
        const after = process.memoryUsage()

        reply({
          freed: {
            heap: (before.heapUsed - after.heapUsed) / 1024 / 1024,
            external: (before.external - after.external) / 1024 / 1024,
            total: ((before.heapUsed - after.heapUsed) +
                   (before.external - after.external)) / 1024 / 1024
          },
          unit: 'MB',
          before: this.formatMemory(before),
          after: this.formatMemory(after)
        })
      })
    }

    // Memory snapshot action
    tx2.action('memory_snapshot', (reply) => {
      const mem = process.memoryUsage()
      const heap = v8.getHeapStatistics()

      reply({
        process: this.formatMemory(mem),
        heap: {
          total: heap.total_heap_size / 1024 / 1024,
          executable: heap.total_heap_size_executable / 1024 / 1024,
          physical: heap.total_physical_size / 1024 / 1024,
          available: heap.total_available_size / 1024 / 1024,
          used: heap.used_heap_size / 1024 / 1024,
          limit: heap.heap_size_limit / 1024 / 1024,
          malloced: heap.malloced_memory / 1024 / 1024,
          peak_malloced: heap.peak_malloced_memory / 1024 / 1024
        },
        samples: this.samples.slice(-20),
        trend: this.getTrend(),
        leakProbability: this.getLeakProbability()
      })
    })
  }

  private sample() {
    const mem = process.memoryUsage()
    const heapMB = mem.heapUsed / 1024 / 1024

    this.samples.push(heapMB)
    if (this.samples.length > 100) {
      this.samples.shift()
    }

    this.memoryHistogram.update(heapMB)

    // Detect consistent increases
    if (this.samples.length > 1) {
      const last = this.samples[this.samples.length - 1]
      const prev = this.samples[this.samples.length - 2]

      if (last > prev) {
        this.increasing++

        // Alert on potential leak
        if (this.increasing > this.threshold) {
          tx2.event('memory:potential_leak', {
            current: heapMB,
            samples: this.samples.slice(-10),
            trend: 'increasing',
            consecutiveIncreases: this.increasing
          })
        }
      } else {
        this.increasing = Math.max(0, this.increasing - 1)
      }
    }

    // Check absolute threshold
    if (heapMB > 500) {
      tx2.event('memory:high_usage', {
        current: heapMB,
        threshold: 500,
        unit: 'MB'
      })
    }
  }

  private getTrend(): 'increasing' | 'stable' | 'decreasing' {
    if (this.samples.length < 10) return 'stable'

    const recent = this.samples.slice(-10)
    const avg = recent.reduce((a, b) => a + b, 0) / recent.length
    const older = this.samples.slice(-20, -10)
    const oldAvg = older.reduce((a, b) => a + b, 0) / older.length

    const change = (avg - oldAvg) / oldAvg * 100

    if (change > 10) return 'increasing'
    if (change < -10) return 'decreasing'
    return 'stable'
  }

  private getLeakProbability(): number {
    if (this.samples.length < 20) return 0

    // Calculate linear regression slope
    const n = this.samples.length
    const sumX = (n * (n + 1)) / 2
    const sumY = this.samples.reduce((a, b) => a + b, 0)
    const sumXY = this.samples.reduce((sum, y, x) => sum + x * y, 0)
    const sumX2 = (n * (n + 1) * (2 * n + 1)) / 6

    const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX)

    // Positive slope indicates potential leak
    return Math.min(100, Math.max(0, slope * 100))
  }

  private formatMemory(mem: NodeJS.MemoryUsage) {
    return {
      rss: Math.round(mem.rss / 1024 / 1024),
      heapTotal: Math.round(mem.heapTotal / 1024 / 1024),
      heapUsed: Math.round(mem.heapUsed / 1024 / 1024),
      external: Math.round(mem.external / 1024 / 1024),
      arrayBuffers: Math.round(mem.arrayBuffers / 1024 / 1024)
    }
  }
}

// Initialize detector
const memoryDetector = new MemoryLeakDetector()

Database Connection Pool Monitor

class DatabasePoolMonitor {
  private queryHistogram = tx2.histogram({
    name: 'db_query_duration',
    measurement: 'p99',
    unit: 'ms'
  })

  private connectionHistogram = tx2.histogram({
    name: 'db_connection_wait',
    measurement: 'mean',
    unit: 'ms'
  })

  constructor(private pool: Pool) {
    // Pool metrics
    tx2.metric({
      name: 'db_pool_total',
      value: () => pool.totalCount
    })

    tx2.metric({
      name: 'db_pool_idle',
      value: () => pool.idleCount
    })

    tx2.metric({
      name: 'db_pool_active',
      value: () => pool.totalCount - pool.idleCount
    })

    tx2.metric({
      name: 'db_pool_waiting',
      value: () => pool.waitingCount
    })

    // Pool events
    pool.on('acquire', (client) => {
      const waitTime = Date.now() - client._poolAcquireTime
      this.connectionHistogram.update(waitTime)

      if (waitTime > 1000) {
        tx2.event('db:slow_acquire', {
          waitTime,
          poolSize: pool.totalCount,
          waiting: pool.waitingCount
        })
      }
    })

    pool.on('release', () => {
      // Track connection lifecycle
    })

    pool.on('error', (err, client) => {
      tx2.issue({
        type: 'database_pool_error',
        error: err.message,
        code: err.code,
        client: client._clientId,
        pool: {
          total: pool.totalCount,
          idle: pool.idleCount,
          waiting: pool.waitingCount
        }
      })
    })

    // Pool management actions
    tx2.action('db_pool_status', (reply) => {
      reply({
        config: {
          min: pool.options.min,
          max: pool.options.max,
          idleTimeout: pool.options.idleTimeoutMillis,
          connectionTimeout: pool.options.connectionTimeoutMillis
        },
        current: {
          total: pool.totalCount,
          idle: pool.idleCount,
          active: pool.totalCount - pool.idleCount,
          waiting: pool.waitingCount
        },
        statistics: {
          queryTime: this.queryHistogram.fullResults(),
          connectionWait: this.connectionHistogram.fullResults()
        }
      })
    })

    tx2.action('db_pool_resize',
      { min: 2, max: 10 },
      async (opts, reply) => {
        try {
          await pool.resize(opts.min, opts.max)
          reply({
            success: true,
            newSize: {
              min: opts.min,
              max: opts.max,
              current: pool.totalCount
            }
          })
        } catch (error) {
          reply({
            success: false,
            error: error.message
          })
        }
      }
    )
  }

  async query(text: string, params?: any[]): Promise<any> {
    const start = Date.now()
    const client = await this.pool.connect()

    try {
      const result = await client.query(text, params)
      const duration = Date.now() - start

      this.queryHistogram.update(duration)

      if (duration > 100) {
        tx2.event('db:slow_query', {
          query: text.substring(0, 100),
          duration,
          rows: result.rowCount
        })
      }

      return result
    } catch (error) {
      tx2.issue({
        type: 'query_error',
        query: text,
        params,
        error: error.message,
        code: error.code
      })
      throw error
    } finally {
      client.release()
    }
  }
}

// Usage
const dbMonitor = new DatabasePoolMonitor(pgPool)

// Use monitored query method
app.get('/users', async (req, res) => {
  try {
    const result = await dbMonitor.query(
      'SELECT * FROM users WHERE active = $1',
      [true]
    )
    res.json(result.rows)
  } catch (error) {
    res.status(500).json({ error: 'Database error' })
  }
})

Technical Details

Monitoring Interval Architecture

TX2 uses a fixed 990ms interval for metric transmission. This design choice:

  • Network Latency Compensation: The 10ms buffer ensures metrics arrive before the 1-second mark
  • Dashboard Synchronization: Aligns with PM2 dashboard's 1-second refresh rate
  • CPU Optimization: Prevents excessive CPU usage from too frequent updates
  • Batching Efficiency: All metrics are collected and sent in a single message
// Internal implementation
private _monitorInterval = setInterval(() => {
  this.send({
    type: 'axm:monitor',
    data: this.prepareData()  // Collects all metric values
  })
}, 990)

// The interval is "unref'd" to not keep the process alive
this._monitorInterval.unref()

Message Buffering Strategy

TX2 implements a no-buffer strategy for optimal real-time monitoring:

  • Events: Sent immediately when event() is called
  • Issues: Transmitted instantly upon issue() call
  • Actions: Replies sent immediately when callback executes
  • Metrics: Only metrics are batched and sent every 990ms

This ensures critical events and errors are never delayed or lost.

Memory Management Strategies

Histogram Memory Control

// Problem: Histograms store all values indefinitely
const histogram = tx2.histogram({
  name: 'api_calls',
  measurement: 'mean'
})

// Each update adds to internal array
histogram.update(100)  // stores [100]
histogram.update(200)  // stores [100, 200]
// After 1M calls: stores [100, 200, ..., 1000000 values]

// Solution 1: Periodic reset
class ManagedHistogram {
  private histogram = tx2.histogram(this.options)
  private updateCount = 0
  private maxSamples = 10000

  update(value: number) {
    this.histogram.update(value)
    this.updateCount++

    if (this.updateCount >= this.maxSamples) {
      // Store summary before reset
      const summary = this.histogram.fullResults()
      tx2.event('histogram:reset', {
        name: this.options.name,
        summary
      })

      this.histogram.reset()
      this.updateCount = 0
    }
  }
}

// Solution 2: Sliding window
class SlidingWindowHistogram {
  private values: number[] = []
  private maxSize = 1000

  update(value: number) {
    this.values.push(value)
    if (this.values.length > this.maxSize) {
      this.values.shift()  // Remove oldest
    }

    // Update actual histogram with windowed data
    this.histogram.reset()
    this.values.forEach(v => this.histogram.update(v))
  }
}

Action Listener Management

TX2's improved implementation prevents memory leaks from duplicate action registrations:

// Internal implementation uses a Map to store single listener per action
private _actionListeners: Map<string, Function> = new Map()

action(name: string, handler: Function) {
  // Replaces existing handler if present
  this._actionListeners.set(name, handler)

  // Single global listener handles all actions
  if (!this._globalListenerRegistered) {
    process.on('message', (data) => {
      for (const [actionName, handler] of this._actionListeners) {
        if (data === actionName || data.msg === actionName) {
          handler(data)
        }
      }
    })
    this._globalListenerRegistered = true
  }
}

// Safe to call multiple times
for (let i = 0; i < 100; i++) {
  tx2.action('test', handler)  // Only one listener remains
}

Error Serialization Deep Dive

TX2's error handling preserves all error properties:

// How TX2 serializes errors
function serializeError(err: any): object {
  if (typeof err === 'string') {
    return { message: err, stack: err }
  }

  if (err instanceof Error) {
    // Get all properties including non-enumerable
    const props = Object.getOwnPropertyNames(err)
    const serialized: any = {}

    props.forEach(prop => {
      serialized[prop] = err[prop]
    })

    // Handle special Error properties
    if (err.stack) serialized.stack = err.stack
    if (err.message) serialized.message = err.message
    if (err.name) serialized.name = err.name

    // Handle custom properties
    if (err.__error_callsites) {
      serialized.stackframes = err.__error_callsites.map(site => ({
        file_name: site.getFileName(),
        line_number: site.getLineNumber()
      }))
    }

    return serialized
  }

  return err  // Already an object
}

// Usage example
class CustomError extends Error {
  code: string
  statusCode: number
  details: any

  constructor(message: string, code: string, statusCode: number) {
    super(message)
    this.code = code
    this.statusCode = statusCode
    this.name = 'CustomError'
  }
}

const error = new CustomError('Not found', 'RESOURCE_NOT_FOUND', 404)
error.details = { resource: 'user', id: 123 }

tx2.issue(error)
// Sends: {
//   message: 'Not found',
//   stack: '...',
//   name: 'CustomError',
//   code: 'RESOURCE_NOT_FOUND',
//   statusCode: 404,
//   details: { resource: 'user', id: 123 }
// }

Circular Reference Handling

TX2 uses fast-safe-stringify to handle circular references:

// Example of circular reference
const user = { id: 1, name: 'John' }
const post = { id: 1, title: 'Hello', author: user }
user.posts = [post]  // Circular reference

// Safe to send
tx2.event('data:circular', user)
// Serializes to: {"id":1,"name":"John","posts":[{"id":1,"title":"Hello","author":"[Circular]"}]}

// Complex circular structures
const obj1 = { name: 'obj1' }
const obj2 = { name: 'obj2' }
const obj3 = { name: 'obj3' }
obj1.ref = obj2
obj2.ref = obj3
obj3.ref = obj1  // Three-way circular

tx2.event('complex:circular', obj1)
// Safely serialized without infinite recursion

Statistical Algorithms

Welford's Algorithm for Variance

TX2 uses Welford's online algorithm for numerically stable variance calculation:

// Welford's algorithm implementation
class WelfordVariance {
  private n = 0
  private mean = 0
  private M2 = 0

  update(value: number) {
    this.n++
    const delta = value - this.mean
    this.mean += delta / this.n
    const delta2 = value - this.mean
    this.M2 += delta * delta2
  }

  get variance() {
    return this.n < 2 ? 0 : this.M2 / (this.n - 1)
  }

  get stddev() {
    return Math.sqrt(this.variance)
  }
}

Exponentially Weighted Moving Average (EWMA)

Used in meters for rate calculation:

// EWMA implementation
class EWMA {
  private rate = 0
  private uncounted = 0
  private alpha: number

  constructor(
    private interval: number,  // Tick interval
    private timeframe: number  // Time window
  ) {
    this.alpha = 1 - Math.exp(-interval / timeframe)
  }

  update(n: number) {
    this.uncounted += n
  }

  tick() {
    const instantRate = this.uncounted / (this.interval / 1000)
    this.uncounted = 0

    // Exponential decay
    this.rate += this.alpha * (instantRate - this.rate)
  }

  getRate(): number {
    return this.rate
  }
}

// Usage in Meter
const ewma = new EWMA(1000, 60000)  // 1s tick, 1min window
setInterval(() => ewma.tick(), 1000)

// Mark events
ewma.update(1)  // Event occurred
ewma.getRate()  // Current rate

Performance Considerations

Metric Function Performance

// ❌ Bad - Heavy computation in metric function
tx2.metric({
  name: 'expensive_metric',
  value: () => {
    // This runs every 990ms!
    let sum = 0
    for (let i = 0; i < 1000000; i++) {
      sum += Math.sqrt(i)
    }
    return sum
  }
})

// ✅ Good - Cache expensive calculations
let cachedValue = 0
const updateInterval = setInterval(() => {
  let sum = 0
  for (let i = 0; i < 1000000; i++) {
    sum += Math.sqrt(i)
  }
  cachedValue = sum
}, 10000)  // Update every 10 seconds

tx2.metric({
  name: 'expensive_metric',
  value: () => cachedValue  // Fast retrieval
})

Event Throttling

// ❌ Bad - Unthrottled events
app.use((req, res, next) => {
  tx2.event('request', { url: req.url })  // Fires for every request!
  next()
})

// ✅ Good - Throttled events
const throttle = (fn: Function, delay: number) => {
  let last = 0
  return (...args: any[]) => {
    const now = Date.now()
    if (now - last >= delay) {
      last = now
      fn(...args)
    }
  }
}

const throttledEvent = throttle((data) => {
  tx2.event('request_batch', data)
}, 1000)

let requestBatch: any[] = []
app.use((req, res, next) => {
  requestBatch.push({ url: req.url, method: req.method })

  if (requestBatch.length >= 100) {
    throttledEvent({ count: requestBatch.length, requests: requestBatch })
    requestBatch = []
  }

  next()
})

Histogram Optimization

// Optimized histogram with automatic memory management
class OptimizedHistogram {
  private histogram: Histogram
  private sampleCount = 0
  private summaryStats = {
    total: 0,
    min: Infinity,
    max: -Infinity
  }

  constructor(
    private options: HistogramOptions,
    private maxSamples = 1000
  ) {
    this.histogram = tx2.histogram(options)
  }

  update(value: number) {
    // Always track summary stats
    this.summaryStats.total++
    this.summaryStats.min = Math.min(this.summaryStats.min, value)
    this.summaryStats.max = Math.max(this.summaryStats.max, value)

    // Sample for detailed stats
    if (this.sampleCount < this.maxSamples) {
      this.histogram.update(value)
      this.sampleCount++
    } else {
      // Reservoir sampling for even distribution
      const rand = Math.floor(Math.random() * this.summaryStats.total)
      if (rand < this.maxSamples) {
        this.histogram.reset()
        this.histogram.update(value)
        this.sampleCount = 1
      }
    }
  }
}

Troubleshooting

1. Metrics Not Appearing in PM2 Dashboard

// Check if running under PM2
if (!process.send) {
  console.warn('WARNING: Not running under PM2 - metrics will not be sent')
  console.warn('Start with: pm2 start app.js')
}

// Verify PM2 configuration
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'my-app',
    script: './dist/app.js',
    instances: 1,
    exec_mode: 'fork',  // Required for IPC
    max_memory_restart: '500M',

    // Enable monitoring
    pmx: true,
    instance_var: 'INSTANCE_ID',

    // Error handling
    error_file: './logs/err.log',
    out_file: './logs/out.log',
    merge_logs: true,
    time: true
  }]
}

// Debug TX2 messages
tx2.on('data', (msg) => {
  console.log('[TX2 Debug]', JSON.stringify(msg))
})

2. Actions Not Triggering

// Debug action registration
const originalAction = tx2.action.bind(tx2)
tx2.action = function(name: string, ...args: any[]) {
  console.log(`[Action Registered] ${name}`)
  return originalAction(name, ...args)
}

// Test action
tx2.action('test_action', (reply) => {
  console.log('[Action Triggered] test_action')
  reply({
    success: true,
    timestamp: Date.now(),
    pid: process.pid
  })
})

// Monitor incoming messages
process.on('message', (msg) => {
  console.log('[IPC Message]', msg)
})

3. Memory Leak Warnings

// Warning: Possible EventEmitter memory leak detected
(tx2 as any).setMaxListeners(50)

// Or globally
require('events').EventEmitter.defaultMaxListeners = 50

// Track listener count
console.log('TX2 Listeners:', tx2.listenerCount('data'))
console.log('Process Listeners:', process.listenerCount('message'))

// Clean up histograms periodically
class HistogramManager {
  private histograms: Map<string, Histogram> = new Map()

  create(options: HistogramOptions): Histogram {
    const histogram = tx2.histogram(options)
    this.histograms.set(options.name, histogram)

    // Auto-cleanup timer
    setInterval(() => {
      if (histogram.getCount() > 10000) {
        console.log(`Resetting histogram: ${options.name}`)
        histogram.reset()
      }
    }, 300000)  // Every 5 minutes

    return histogram
  }

  cleanup() {
    this.histograms.forEach(h => h.reset())
    this.histograms.clear()
  }
}

4. High CPU Usage

// Profile metric functions
const metricsProfile: Map<string, number> = new Map()

function profiledMetric(options: MetricOptions) {
  const name = options.name
  const originalValue = options.value

  if (typeof originalValue === 'function') {
    options.value = () => {
      const start = process.hrtime.bigint()
      const result = originalValue()
      const duration = Number(process.hrtime.bigint() - start) / 1000000  // ms

      metricsProfile.set(name, duration)

      if (duration > 10) {
        console.warn(`[Slow Metric] ${name} took ${duration}ms`)
      }

      return result
    }
  }

  return tx2.metric(options)
}

// Monitor metric performance
setInterval(() => {
  const slowMetrics = Array.from(metricsProfile.entries())
    .filter(([_, duration]) => duration > 5)
    .sort((a, b) => b[1] - a[1])

  if (slowMetrics.length > 0) {
    console.log('Slow Metrics:', slowMetrics)
  }
}, 60000)

5. TypeScript Configuration

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",

    // Strict settings
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,

    // Module resolution
    "moduleResolution": "node",
    "resolveJsonModule": true,

    // Source maps for debugging
    "sourceMap": true,
    "inlineSources": true,

    // Decorators if needed
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,

    // Type roots
    "types": ["node"],
    "typeRoots": ["./node_modules/@types", "./types"]
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "test"]
}

// Type declarations for TX2
// types/tx2.d.ts
declare module 'tx2' {
  // ... (include all interfaces from this document)
}

Best Practices Summary

✅ Do's

  1. Meaningful Naming: Use clear namespace:action format for events
  2. Specify Units: Always define units for metrics
  3. Error Handling: Wrap all actions in try-catch blocks
  4. Memory Management: Periodically reset histograms with high volume
  5. Event Throttling: Implement throttling for high-frequency events
  6. Cache Calculations: Cache expensive metric calculations
  7. Graceful Shutdown: Clean up metrics and send final events on shutdown
  8. Debug Mode: Add debug logging in development
  9. Monitor Performance: Track metric calculation times
  10. Document Actions: Provide clear action descriptions and expected parameters

❌ Don'ts

  1. Heavy Computations: Avoid expensive operations in metric value functions
  2. Excessive Events: Don't send events for every single operation
  3. Large Payloads: Avoid sending huge objects in events or action replies
  4. Synchronous I/O: Never use sync I/O in metric functions
  5. Global State Reliance: Don't depend on mutable global state in actions
  6. Unhandled Promises: Always handle promise rejections in actions
  7. Memory Leaks: Don't store unlimited data in memory
  8. Tight Loops: Avoid tight loops in metric calculations
  9. Blocking Operations: Never block the event loop in metrics
  10. Sensitive Data: Never log passwords or tokens in events

Complete Real-World Example

// Complete e-commerce monitoring setup
import tx2 from './tx2'
import express from 'express'
import { Pool } from 'pg'
import Redis from 'ioredis'

const app = express()
const db = new Pool({ /* config */ })
const redis = new Redis()
const cache = new Map()

// Initialize monitoring
class EcommerceMonitor {
  // Counters
  private orders = tx2.counter('orders_total')
  private revenue = tx2.counter({ name: 'revenue_total', unit: 'USD' })
  private refunds = tx2.counter({ name: 'refunds_total', unit: 'USD' })
  private cartAbandoned = tx2.counter('carts_abandoned')

  // Histograms
  private cartValue = tx2.histogram({
    name: 'cart_value',
    measurement: 'mean',
    unit: 'USD'
  })

  private checkoutDuration = tx2.histogram({
    name: 'checkout_duration',
    measurement: 'p95',
    unit: 'seconds'
  })

  private searchLatency = tx2.histogram({
    name: 'search_latency',
    measurement: 'p99',
    unit: 'ms'
  })

  // Meters
  private orderRate = tx2.meter({
    name: 'order_rate',
    unit: 'orders/min',
    seconds: 60
  })

  private searchRate = tx2.meter({
    name: 'search_rate',
    unit: 'searches/sec'
  })

  // Metrics
  private conversionRate = tx2.metric({
    name: 'conversion_rate',
    unit: '%',
    value: () => {
      const rate = (this.orders.val() / this.sessions.val()) * 100
      return Math.round(rate * 100) / 100
    }
  })

  private avgOrderValue = tx2.metric({
    name: 'average_order_value',
    unit: 'USD',
    value: () => {
      const orders = this.orders.val()
      if (orders === 0) return 0
      return Math.round(this.revenue.val() / orders * 100) / 100
    }
  })

  private inventoryLow = tx2.metric({
    name: 'inventory_low_items',
    value: async () => {
      const result = await db.query(
        'SELECT COUNT(*) FROM products WHERE stock < 10'
      )
      return result.rows[0].count
    }
  })

  // Sessions tracking
  private sessions = tx2.counter('sessions_total')
  private activeSessions = new Set<string>()

  // Actions
  constructor() {
    // Daily reset action
    tx2.action('reset_daily_metrics', (reply) => {
      const previousData = {
        orders: this.orders.val(),
        revenue: this.revenue.val(),
        sessions: this.sessions.val()
      }

      this.orders.reset()
      this.revenue.reset()
      this.sessions.reset()
      this.cartAbandoned.reset()

      reply({
        reset: true,
        previousData,
        timestamp: new Date().toISOString()
      })
    })

    // Cache management
    tx2.action('clear_cache', async (reply) => {
      try {
        await redis.flushdb()
        cache.clear()

        reply({
          success: true,
          cleared: {
            redis: true,
            memory: true
          }
        })
      } catch (error) {
        reply({
          success: false,
          error: error.message
        })
      }
    })

    // Inventory check
    tx2.action('inventory_report', async (reply) => {
      const [low, outOfStock, total] = await Promise.all([
        db.query('SELECT * FROM products WHERE stock < 10'),
        db.query('SELECT * FROM products WHERE stock = 0'),
        db.query('SELECT COUNT(*) FROM products')
      ])

      reply({
        total: total.rows[0].count,
        lowStock: low.rows.map(p => ({
          id: p.id,
          name: p.name,
          stock: p.stock
        })),
        outOfStock: outOfStock.rows.map(p => ({
          id: p.id,
          name: p.name
        })),
        critical: outOfStock.rows.length > 0
      })
    })

    // Performance snapshot
    tx2.action('performance_snapshot', (reply) => {
      reply({
        metrics: {
          orders: this.orders.val(),
          revenue: this.revenue.val(),
          avgOrderValue: this.avgOrderValue.val(),
          conversionRate: this.conversionRate.val()
        },
        performance: {
          checkoutTime: this.checkoutDuration.fullResults(),
          searchLatency: this.searchLatency.fullResults()
        },
        rates: {
          orders: this.orderRate.val(),
          searches: this.searchRate.val()
        },
        system: {
          memory: process.memoryUsage(),
          uptime: process.uptime(),
          connections: {
            database: db.totalCount,
            redis: redis.status === 'ready'
          }
        }
      })
    })
  }

  // Track order
  recordOrder(order: any) {
    this.orders.inc()
    this.revenue.inc(order.total)
    this.orderRate.mark()
    this.cartValue.update(order.total)

    tx2.event('order:completed', {
      orderId: order.id,
      total: order.total,
      items: order.items.length,
      customer: order.customerId,
      paymentMethod: order.paymentMethod
    })

    // Track high-value orders
    if (order.total > 1000) {
      tx2.event('order:high_value', {
        orderId: order.id,
        total: order.total,
        customer: order.customerId
      })
    }
  }

  // Track search
  recordSearch(query: string, results: number, duration: number) {
    this.searchRate.mark()
    this.searchLatency.update(duration)

    if (results === 0) {
      tx2.event('search:no_results', { query })
    }

    if (duration > 500) {
      tx2.event('search:slow', {
        query,
        duration,
        results
      })
    }
  }

  // Track checkout
  recordCheckout(sessionId: string, duration: number, success: boolean) {
    if (success) {
      this.checkoutDuration.update(duration / 1000)
    } else {
      this.cartAbandoned.inc()
      tx2.event('checkout:abandoned', {
        sessionId,
        duration,
        stage: 'payment'
      })
    }
  }

  // Session management
  startSession(sessionId: string) {
    this.sessions.inc()
    this.activeSessions.add(sessionId)
  }

  endSession(sessionId: string) {
    this.activeSessions.delete(sessionId)
  }
}

// Initialize monitor
const monitor = new EcommerceMonitor()

// Middleware
app.use((req, res, next) => {
  // Session tracking
  if (!req.session.started) {
    monitor.startSession(req.sessionID)
    req.session.started = true
  }

  next()
})

// Routes
app.post('/api/orders', async (req, res) => {
  const startTime = Date.now()

  try {
    const order = await processOrder(req.body)
    const duration = Date.now() - startTime

    monitor.recordOrder(order)
    monitor.recordCheckout(req.sessionID, duration, true)

    res.json({ success: true, order })
  } catch (error) {
    monitor.recordCheckout(req.sessionID, Date.now() - startTime, false)

    tx2.issue({
      type: 'order_processing_error',
      error: error.message,
      sessionId: req.sessionID,
      cart: req.body
    })

    res.status(500).json({ error: 'Order processing failed' })
  }
})

app.get('/api/search', async (req, res) => {
  const start = Date.now()

  try {
    const results = await searchProducts(req.query.q)
    const duration = Date.now() - start

    monitor.recordSearch(req.query.q, results.length, duration)

    res.json({ results, count: results.length })
  } catch (error) {
    tx2.issue({
      type: 'search_error',
      query: req.query.q,
      error: error.message
    })

    res.status(500).json({ error: 'Search failed' })
  }
})

// Startup
app.listen(3000, () => {
  tx2.event('app:started', {
    port: 3000,
    environment: process.env.NODE_ENV,
    version: process.env.npm_package_version,
    node: process.version
  })

  console.log('E-commerce API running on port 3000')
})

// Graceful shutdown
process.on('SIGTERM', async () => {
  tx2.event('app:shutdown:started'