💎 📊 Advanced Metrics with TuskLang Ruby
📊 Advanced Metrics with TuskLang Ruby
"We don't bow to any king" - Ruby Edition
Build sophisticated metrics and monitoring systems with TuskLang's advanced metrics features. From custom metrics collection to real-time dashboards, TuskLang provides the flexibility and power you need to monitor your Ruby applications effectively.
🚀 Quick Start
Basic Metrics Setup
require 'tusklang'
require 'tusklang/metrics'Initialize metrics system
metrics_system = TuskLang::Metrics::System.newConfigure metrics
metrics_system.configure do |config|
config.default_backend = 'prometheus'
config.collection_interval = 15.seconds
config.retention_period = 30.days
config.aggregation_enabled = true
endRegister metrics strategies
metrics_system.register_strategy(:prometheus, TuskLang::Metrics::Strategies::PrometheusStrategy.new)
metrics_system.register_strategy(:statsd, TuskLang::Metrics::Strategies::StatsDStrategy.new)
metrics_system.register_strategy(:custom, TuskLang::Metrics::Strategies::CustomMetricsStrategy.new)
TuskLang Configuration
config/metrics.tsk
[metrics]
enabled: true
default_backend: "prometheus"
collection_interval: "15s"
retention_period: "30d"
aggregation_enabled: true[metrics.backends]
prometheus: {
enabled: true,
port: 9090,
path: "/metrics",
labels: ["app", "version", "environment"]
}
statsd: {
enabled: false,
host: "localhost",
port: 8125,
prefix: "myapp"
}
influxdb: {
enabled: false,
url: "http://localhost:8086",
database: "metrics",
retention_policy: "30d"
}
[metrics.collectors]
system: {
enabled: true,
interval: "60s",
metrics: ["cpu", "memory", "disk", "network"]
}
application: {
enabled: true,
interval: "30s",
metrics: ["requests", "errors", "response_time", "throughput"]
}
business: {
enabled: true,
interval: "5m",
metrics: ["users", "orders", "revenue", "conversions"]
}
[metrics.alerts]
cpu_high: {
condition: "cpu_usage > 80",
duration: "5m",
severity: "warning"
}
memory_critical: {
condition: "memory_usage > 95",
duration: "2m",
severity: "critical"
}
error_rate_high: {
condition: "error_rate > 5",
duration: "10m",
severity: "warning"
}
🎯 Core Features
1. Prometheus Metrics Strategy
require 'tusklang/metrics'
require 'prometheus/client'class PrometheusStrategy
include TuskLang::Metrics::Strategy
def initialize
@config = TuskLang.parse_file('config/metrics.tsk')
@registry = Prometheus::Client.registry
@metrics = {}
setup_metrics
end
def increment(metric_name, value = 1, labels = {})
metric = get_or_create_counter(metric_name)
metric.increment(labels: labels, by: value)
end
def gauge(metric_name, value, labels = {})
metric = get_or_create_gauge(metric_name)
metric.set(value, labels: labels)
end
def histogram(metric_name, value, labels = {})
metric = get_or_create_histogram(metric_name)
metric.observe(value, labels: labels)
end
def summary(metric_name, value, labels = {})
metric = get_or_create_summary(metric_name)
metric.observe(value, labels: labels)
end
def get_metrics
@registry.metrics
end
def export_metrics
@registry.metrics.map do |metric|
{
name: metric.name,
type: metric.type,
help: metric.help,
samples: metric.values.map do |labels, value|
{
labels: labels,
value: value
}
end
}
end
end
private
def setup_metrics
# Setup default metrics
setup_system_metrics
setup_application_metrics
setup_business_metrics
end
def setup_system_metrics
if @config['metrics']['collectors']['system']['enabled']
@metrics['cpu_usage'] = @registry.gauge(:cpu_usage, docstring: 'CPU usage percentage')
@metrics['memory_usage'] = @registry.gauge(:memory_usage, docstring: 'Memory usage percentage')
@metrics['disk_usage'] = @registry.gauge(:disk_usage, docstring: 'Disk usage percentage')
@metrics['network_bytes'] = @registry.counter(:network_bytes_total, docstring: 'Total network bytes')
end
end
def setup_application_metrics
if @config['metrics']['collectors']['application']['enabled']
@metrics['http_requests_total'] = @registry.counter(:http_requests_total, docstring: 'Total HTTP requests')
@metrics['http_request_duration'] = @registry.histogram(:http_request_duration_seconds, docstring: 'HTTP request duration')
@metrics['http_errors_total'] = @registry.counter(:http_errors_total, docstring: 'Total HTTP errors')
@metrics['active_connections'] = @registry.gauge(:active_connections, docstring: 'Active connections')
end
end
def setup_business_metrics
if @config['metrics']['collectors']['business']['enabled']
@metrics['users_total'] = @registry.counter(:users_total, docstring: 'Total users')
@metrics['orders_total'] = @registry.counter(:orders_total, docstring: 'Total orders')
@metrics['revenue_total'] = @registry.counter(:revenue_total, docstring: 'Total revenue')
@metrics['conversion_rate'] = @registry.gauge(:conversion_rate, docstring: 'Conversion rate')
end
end
def get_or_create_counter(name)
@metrics[name] ||= @registry.counter(name.to_sym, docstring: "Counter for #{name}")
end
def get_or_create_gauge(name)
@metrics[name] ||= @registry.gauge(name.to_sym, docstring: "Gauge for #{name}")
end
def get_or_create_histogram(name)
@metrics[name] ||= @registry.histogram(name.to_sym, docstring: "Histogram for #{name}")
end
def get_or_create_summary(name)
@metrics[name] ||= @registry.summary(name.to_sym, docstring: "Summary for #{name}")
end
end
2. StatsD Metrics Strategy
require 'tusklang/metrics'
require 'statsd'class StatsDStrategy
include TuskLang::Metrics::Strategy
def initialize
@config = TuskLang.parse_file('config/metrics.tsk')
setup_statsd_client
end
def increment(metric_name, value = 1, labels = {})
metric = build_metric_name(metric_name, labels)
@statsd.increment(metric, value)
end
def gauge(metric_name, value, labels = {})
metric = build_metric_name(metric_name, labels)
@statsd.gauge(metric, value)
end
def histogram(metric_name, value, labels = {})
metric = build_metric_name(metric_name, labels)
@statsd.histogram(metric, value)
end
def timing(metric_name, value, labels = {})
metric = build_metric_name(metric_name, labels)
@statsd.timing(metric, value)
end
def set(metric_name, value, labels = {})
metric = build_metric_name(metric_name, labels)
@statsd.set(metric, value)
end
private
def setup_statsd_client
statsd_config = @config['metrics']['backends']['statsd']
@statsd = Statsd.new(
statsd_config['host'],
statsd_config['port'],
prefix: statsd_config['prefix']
)
end
def build_metric_name(name, labels)
metric_name = name.to_s
unless labels.empty?
label_strings = labels.map { |k, v| "#{k}=#{v}" }
metric_name += ".#{label_strings.join('.')}"
end
metric_name
end
end
3. Custom Metrics Strategy
require 'tusklang/metrics'class CustomMetricsStrategy
include TuskLang::Metrics::Strategy
def initialize
@config = TuskLang.parse_file('config/metrics.tsk')
@metrics = {}
@aggregations = {}
setup_aggregations
end
def increment(metric_name, value = 1, labels = {})
key = build_metric_key(metric_name, labels)
@metrics[key] ||= { count: 0, sum: 0, min: Float::INFINITY, max: -Float::INFINITY, values: [] }
metric = @metrics[key]
metric[:count] += value
metric[:sum] += value
metric[:min] = [metric[:min], value].min
metric[:max] = [metric[:max], value].max
metric[:values] << { value: value, timestamp: Time.now }
# Keep only recent values
cutoff_time = Time.now - 1.hour
metric[:values].reject! { |v| v[:timestamp] < cutoff_time }
end
def gauge(metric_name, value, labels = {})
key = build_metric_key(metric_name, labels)
@metrics[key] = { value: value, timestamp: Time.now }
end
def histogram(metric_name, value, labels = {})
key = build_metric_key(metric_name, labels)
@metrics[key] ||= { buckets: {}, count: 0, sum: 0 }
metric = @metrics[key]
metric[:count] += 1
metric[:sum] += value
# Update histogram buckets
bucket = calculate_bucket(value)
metric[:buckets][bucket] ||= 0
metric[:buckets][bucket] += 1
end
def get_metric(metric_name, labels = {})
key = build_metric_key(metric_name, labels)
@metrics[key]
end
def get_aggregated_metrics
aggregated = {}
@metrics.each do |key, metric|
metric_name, label_string = parse_metric_key(key)
if @aggregations[metric_name]
aggregated[metric_name] ||= {}
aggregated[metric_name][label_string] = calculate_aggregation(metric, @aggregations[metric_name])
end
end
aggregated
end
def export_metrics
{
metrics: @metrics,
aggregated: get_aggregated_metrics,
timestamp: Time.now.iso8601
}
end
private
def setup_aggregations
@aggregations = {
'http_request_duration' => ['avg', 'p95', 'p99'],
'response_time' => ['avg', 'p95', 'p99'],
'error_rate' => ['avg', 'sum'],
'throughput' => ['avg', 'sum']
}
end
def build_metric_key(name, labels)
if labels.empty?
name.to_s
else
label_string = labels.map { |k, v| "#{k}=#{v}" }.join(',')
"#{name}{#{label_string}}"
end
end
def parse_metric_key(key)
if key.include?('{')
name, labels = key.split('{', 2)
[name, labels.chomp('}')]
else
[key, '']
end
end
def calculate_bucket(value)
case value
when 0..10
'0-10'
when 11..50
'11-50'
when 51..100
'51-100'
when 101..500
'101-500'
else
'500+'
end
end
def calculate_aggregation(metric, aggregations)
result = {}
aggregations.each do |agg|
case agg
when 'avg'
result[:avg] = metric[:sum] / metric[:count] if metric[:count] > 0
when 'sum'
result[:sum] = metric[:sum]
when 'min'
result[:min] = metric[:min]
when 'max'
result[:max] = metric[:max]
when 'p95'
result[:p95] = calculate_percentile(metric[:values], 95)
when 'p99'
result[:p99] = calculate_percentile(metric[:values], 99)
end
end
result
end
def calculate_percentile(values, percentile)
return nil if values.empty?
sorted_values = values.map { |v| v[:value] }.sort
index = (percentile / 100.0 * (sorted_values.length - 1)).round
sorted_values[index]
end
end
4. Metrics Collection System
require 'tusklang/metrics'class MetricsCollector
def initialize
@config = TuskLang.parse_file('config/metrics.tsk')
@metrics_system = TuskLang::Metrics::System.new
@collectors = {}
setup_collectors
end
def start_collection
@collectors.each do |name, collector|
start_collector(name, collector)
end
end
def stop_collection
@collectors.each do |name, collector|
stop_collector(name, collector)
end
end
def collect_system_metrics
return unless @config['metrics']['collectors']['system']['enabled']
# CPU usage
cpu_usage = get_cpu_usage
@metrics_system.gauge('cpu_usage', cpu_usage, { type: 'system' })
# Memory usage
memory_usage = get_memory_usage
@metrics_system.gauge('memory_usage', memory_usage, { type: 'system' })
# Disk usage
disk_usage = get_disk_usage
@metrics_system.gauge('disk_usage', disk_usage, { type: 'system' })
# Network usage
network_bytes = get_network_bytes
@metrics_system.increment('network_bytes_total', network_bytes, { type: 'system' })
end
def collect_application_metrics
return unless @config['metrics']['collectors']['application']['enabled']
# Request metrics
request_count = get_request_count
@metrics_system.increment('http_requests_total', request_count, { type: 'application' })
# Error metrics
error_count = get_error_count
@metrics_system.increment('http_errors_total', error_count, { type: 'application' })
# Response time metrics
response_time = get_average_response_time
@metrics_system.histogram('http_request_duration_seconds', response_time, { type: 'application' })
# Connection metrics
active_connections = get_active_connections
@metrics_system.gauge('active_connections', active_connections, { type: 'application' })
end
def collect_business_metrics
return unless @config['metrics']['collectors']['business']['enabled']
# User metrics
user_count = get_user_count
@metrics_system.gauge('users_total', user_count, { type: 'business' })
# Order metrics
order_count = get_order_count
@metrics_system.increment('orders_total', order_count, { type: 'business' })
# Revenue metrics
revenue = get_revenue
@metrics_system.increment('revenue_total', revenue, { type: 'business' })
# Conversion metrics
conversion_rate = get_conversion_rate
@metrics_system.gauge('conversion_rate', conversion_rate, { type: 'business' })
end
private
def setup_collectors
@collectors = {
system: {
interval: parse_duration(@config['metrics']['collectors']['system']['interval']),
enabled: @config['metrics']['collectors']['system']['enabled']
},
application: {
interval: parse_duration(@config['metrics']['collectors']['application']['interval']),
enabled: @config['metrics']['collectors']['application']['enabled']
},
business: {
interval: parse_duration(@config['metrics']['collectors']['business']['interval']),
enabled: @config['metrics']['collectors']['business']['enabled']
}
}
end
def start_collector(name, collector)
return unless collector[:enabled]
Thread.new do
loop do
case name
when :system
collect_system_metrics
when :application
collect_application_metrics
when :business
collect_business_metrics
end
sleep collector[:interval]
end
end
end
def stop_collector(name, collector)
# Implementation to stop collector thread
end
def get_cpu_usage
# Implementation to get CPU usage
# This could use /proc/stat on Linux or other system calls
0.0
end
def get_memory_usage
# Implementation to get memory usage
if defined?(GC)
stats = GC.stat
(stats[:heap_allocated_pages] * stats[:heap_page_size] / 1024.0 / 1024.0).round(2)
else
0.0
end
end
def get_disk_usage
# Implementation to get disk usage
0.0
end
def get_network_bytes
# Implementation to get network bytes
0
end
def get_request_count
# Implementation to get request count
0
end
def get_error_count
# Implementation to get error count
0
end
def get_average_response_time
# Implementation to get average response time
0.0
end
def get_active_connections
# Implementation to get active connections
0
end
def get_user_count
# Implementation to get user count
User.count
end
def get_order_count
# Implementation to get order count
Order.count
end
def get_revenue
# Implementation to get revenue
0.0
end
def get_conversion_rate
# Implementation to get conversion rate
0.0
end
def parse_duration(duration_string)
case duration_string
when /(\d+)s/
$1.to_i
when /(\d+)m/
$1.to_i * 60
when /(\d+)h/
$1.to_i * 3600
else
60 # Default 1 minute
end
end
end
5. Metrics Middleware
require 'tusklang/metrics'class MetricsMiddleware
def initialize(app)
@app = app
@metrics_system = TuskLang::Metrics::System.new
@config = TuskLang.parse_file('config/metrics.tsk')
end
def call(env)
request = Rack::Request.new(env)
start_time = Time.now
# Record request start
@metrics_system.increment('http_requests_total', 1, {
method: request.method,
path: request.path,
status: 'started'
})
# Process request
status, headers, response = @app.call(env)
# Calculate duration
duration = Time.now - start_time
# Record request completion
@metrics_system.increment('http_requests_total', 1, {
method: request.method,
path: request.path,
status: status.to_s
})
# Record response time
@metrics_system.histogram('http_request_duration_seconds', duration, {
method: request.method,
path: request.path
})
# Record errors
if status >= 400
@metrics_system.increment('http_errors_total', 1, {
method: request.method,
path: request.path,
status: status.to_s
})
end
[status, headers, response]
rescue => e
# Record error
@metrics_system.increment('http_errors_total', 1, {
method: request.method,
path: request.path,
error: e.class.name
})
raise e
end
end
6. Metrics Decorators
require 'tusklang/metrics'module MetricsDecorators
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def track_metrics(methods, *options)
methods.each do |method|
define_method("#{method}_with_metrics") do |*args, &block|
metrics_system = TuskLang::Metrics::System.new
start_time = Time.now
begin
result = send("#{method}_without_metrics", *args, &block)
# Record success
duration = Time.now - start_time
metrics_system.increment("#{self.class.name.downcase}_#{method}_total", 1, { status: 'success' })
metrics_system.histogram("#{self.class.name.downcase}_#{method}_duration", duration, { status: 'success' })
result
rescue => e
# Record error
duration = Time.now - start_time
metrics_system.increment("#{self.class.name.downcase}_#{method}_total", 1, { status: 'error' })
metrics_system.histogram("#{self.class.name.downcase}_#{method}_duration", duration, { status: 'error' })
metrics_system.increment("#{self.class.name.downcase}_#{method}_errors", 1, { error: e.class.name })
raise e
end
end
alias_method "#{method}_without_metrics", method
alias_method method, "#{method}_with_metrics"
end
end
end
end
Usage in models
class User < ApplicationRecord
include MetricsDecorators
track_metrics :create, :update, :destroy
def self.find_by_email_with_metrics(email)
metrics_system = TuskLang::Metrics::System.new
start_time = Time.now
begin
result = find_by_email_without_metrics(email)
duration = Time.now - start_time
metrics_system.increment('user_find_by_email_total', 1, { found: result ? 'yes' : 'no' })
metrics_system.histogram('user_find_by_email_duration', duration, { found: result ? 'yes' : 'no' })
result
rescue => e
duration = Time.now - start_time
metrics_system.increment('user_find_by_email_errors', 1, { error: e.class.name })
metrics_system.histogram('user_find_by_email_duration', duration, { error: e.class.name })
raise e
end
end
alias_method :find_by_email_without_metrics, :find_by_email
alias_method :find_by_email, :find_by_email_with_metrics
end
🔧 Advanced Configuration
Metrics Aggregation
require 'tusklang/metrics'class MetricsAggregator
def initialize
@config = TuskLang.parse_file('config/metrics.tsk')
@aggregations = {}
setup_aggregations
end
def aggregate_metrics(metrics, interval = '1m')
aggregated = {}
metrics.each do |metric_name, values|
if @aggregations[metric_name]
aggregated[metric_name] = calculate_aggregation(values, @aggregations[metric_name], interval)
end
end
aggregated
end
def calculate_aggregation(values, aggregation_rules, interval)
result = {}
aggregation_rules.each do |rule|
case rule
when 'avg'
result[:avg] = values.sum / values.length if values.any?
when 'sum'
result[:sum] = values.sum
when 'min'
result[:min] = values.min
when 'max'
result[:max] = values.max
when 'count'
result[:count] = values.length
when 'p95'
result[:p95] = calculate_percentile(values, 95)
when 'p99'
result[:p99] = calculate_percentile(values, 99)
end
end
result
end
private
def setup_aggregations
@aggregations = {
'http_request_duration' => ['avg', 'p95', 'p99'],
'response_time' => ['avg', 'p95', 'p99'],
'error_rate' => ['avg', 'sum'],
'throughput' => ['avg', 'sum']
}
end
def calculate_percentile(values, percentile)
return nil if values.empty?
sorted_values = values.sort
index = (percentile / 100.0 * (sorted_values.length - 1)).round
sorted_values[index]
end
end
🚀 Performance Optimization
Metrics Caching
require 'tusklang/metrics'class MetricsCache
def initialize
@cache = TuskLang::Cache::RedisCache.new
@config = TuskLang.parse_file('config/metrics.tsk')
end
def cache_metric(key, value, ttl = 300)
@cache.set(key, value, ttl)
end
def get_cached_metric(key)
@cache.get(key)
end
def cache_aggregation(metric_name, aggregation, ttl = 3600)
key = "metrics_agg:#{metric_name}:#{aggregation}"
@cache.set(key, aggregation, ttl)
end
def get_cached_aggregation(metric_name, aggregation)
key = "metrics_agg:#{metric_name}:#{aggregation}"
@cache.get(key)
end
end
📊 Monitoring and Analytics
Metrics Analytics
require 'tusklang/metrics'class MetricsAnalytics
def initialize
@metrics = TuskLang::Metrics::Collector.new
end
def track_metric(metric_name, value, context = {})
@metrics.increment("metrics.tracked.total")
@metrics.increment("metrics.tracked.#{metric_name}")
if context[:type]
@metrics.increment("metrics.tracked.type.#{context[:type]}")
end
end
def track_aggregation(metric_name, aggregation_type, value)
@metrics.increment("metrics.aggregations.total")
@metrics.increment("metrics.aggregations.#{aggregation_type}")
@metrics.histogram("metrics.aggregation_values.#{aggregation_type}", value)
end
def get_metrics_stats
{
total_metrics: @metrics.get("metrics.tracked.total"),
metrics_by_type: @metrics.get_top("metrics.tracked.type", 5),
popular_metrics: @metrics.get_top("metrics.tracked", 10),
aggregation_count: @metrics.get("metrics.aggregations.total")
}
end
end
This comprehensive metrics system provides enterprise-grade monitoring features while maintaining the flexibility and power of TuskLang. The combination of multiple metrics backends, custom aggregations, and performance optimizations creates a robust foundation for monitoring Ruby applications.