Skip to content

Metrics Collection

Giriş

Metrics Collection, uygulama performansını, sistem sağlığını ve business metrics'leri izlemek için kullanılan sistemdir. Mid-level geliştiriciler için metrics collection'ı anlamak, performance monitoring, alerting ve capacity planning için kritik öneme sahiptir. Bu dosya, custom metrics, performance counters, metrics aggregation ve monitoring konularını kapsar.

Custom Metrics Implementation

1. Metrics Service

Custom metrics collection implementasyonu.

public class MetricsService : IMetricsService
{
    private readonly ILogger<MetricsService> _logger;
    private readonly Dictionary<string, Counter> _counters;
    private readonly Dictionary<string, Gauge> _gauges;
    private readonly Dictionary<string, Histogram> _histograms;
    private readonly object _lock = new object();

    public MetricsService(ILogger<MetricsService> logger)
    {
        _logger = logger;
        _counters = new Dictionary<string, Counter>();
        _gauges = new Dictionary<string, Gauge>();
        _histograms = new Dictionary<string, Histogram>();
    }

    public void IncrementCounter(string name, Dictionary<string, object> labels = null)
    {
        lock (_lock)
        {
            if (!_counters.ContainsKey(name))
            {
                _counters[name] = new Counter(name);
            }

            _counters[name].Increment(labels);
            _logger.LogDebug("Incremented counter: {Name}", name);
        }
    }

    public void SetGauge(string name, double value, Dictionary<string, object> labels = null)
    {
        lock (_lock)
        {
            if (!_gauges.ContainsKey(name))
            {
                _gauges[name] = new Gauge(name);
            }

            _gauges[name].SetValue(value, labels);
            _logger.LogDebug("Set gauge: {Name} = {Value}", name, value);
        }
    }

    public void RecordHistogram(string name, double value, Dictionary<string, object> labels = null)
    {
        lock (_lock)
        {
            if (!_histograms.ContainsKey(name))
            {
                _histograms[name] = new Histogram(name);
            }

            _histograms[name].RecordValue(value, labels);
            _logger.LogDebug("Recorded histogram: {Name} = {Value}", name, value);
        }
    }

    public MetricsSnapshot GetSnapshot()
    {
        lock (_lock)
        {
            return new MetricsSnapshot
            {
                Counters = _counters.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.GetSnapshot()),
                Gauges = _gauges.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.GetSnapshot()),
                Histograms = _histograms.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.GetSnapshot()),
                Timestamp = DateTime.UtcNow
            };
        }
    }

    public void Reset()
    {
        lock (_lock)
        {
            _counters.Clear();
            _gauges.Clear();
            _histograms.Clear();
            _logger.LogInformation("All metrics reset");
        }
    }
}

public class Counter
{
    private readonly string _name;
    private readonly Dictionary<string, long> _values;

    public Counter(string name)
    {
        _name = name;
        _values = new Dictionary<string, long>();
    }

    public void Increment(Dictionary<string, object> labels = null)
    {
        var key = GetLabelKey(labels);
        if (!_values.ContainsKey(key))
        {
            _values[key] = 0;
        }

        _values[key]++;
    }

    public CounterSnapshot GetSnapshot()
    {
        return new CounterSnapshot
        {
            Name = _name,
            Values = new Dictionary<string, long>(_values)
        };
    }

    private string GetLabelKey(Dictionary<string, object> labels)
    {
        if (labels == null || !labels.Any())
            return "default";

        return string.Join("|", labels.OrderBy(kvp => kvp.Key).Select(kvp => $"{kvp.Key}={kvp.Value}"));
    }
}

public class Gauge
{
    private readonly string _name;
    private readonly Dictionary<string, double> _values;

    public Gauge(string name)
    {
        _name = name;
        _values = new Dictionary<string, double>();
    }

    public void SetValue(double value, Dictionary<string, object> labels = null)
    {
        var key = GetLabelKey(labels);
        _values[key] = value;
    }

    public GaugeSnapshot GetSnapshot()
    {
        return new GaugeSnapshot
        {
            Name = _name,
            Values = new Dictionary<string, double>(_values)
        };
    }

    private string GetLabelKey(Dictionary<string, object> labels)
    {
        if (labels == null || !labels.Any())
            return "default";

        return string.Join("|", labels.OrderBy(kvp => kvp.Key).Select(kvp => $"{kvp.Key}={kvp.Value}"));
    }
}

public class Histogram
{
    private readonly string _name;
    private readonly Dictionary<string, List<double>> _values;

    public Histogram(string name)
    {
        _name = name;
        _values = new Dictionary<string, List<double>>();
    }

    public void RecordValue(double value, Dictionary<string, object> labels = null)
    {
        var key = GetLabelKey(labels);
        if (!_values.ContainsKey(key))
        {
            _values[key] = new List<double>();
        }

        _values[key].Add(value);
    }

    public HistogramSnapshot GetSnapshot()
    {
        var snapshots = new Dictionary<string, HistogramStats>();

        foreach (var kvp in _values)
        {
            var values = kvp.Value;
            if (values.Any())
            {
                snapshots[kvp.Key] = new HistogramStats
                {
                    Count = values.Count,
                    Sum = values.Sum(),
                    Min = values.Min(),
                    Max = values.Max(),
                    Average = values.Average(),
                    Percentile95 = CalculatePercentile(values, 95),
                    Percentile99 = CalculatePercentile(values, 99)
                };
            }
        }

        return new HistogramSnapshot
        {
            Name = _name,
            Stats = snapshots
        };
    }

    private double CalculatePercentile(List<double> values, int percentile)
    {
        if (!values.Any()) return 0;

        var sorted = values.OrderBy(v => v).ToList();
        var index = (int)Math.Ceiling(percentile / 100.0 * sorted.Count) - 1;
        return sorted[Math.Max(0, index)];
    }

    private string GetLabelKey(Dictionary<string, object> labels)
    {
        if (labels == null || !labels.Any())
            return "default";

        return string.Join("|", labels.OrderBy(kvp => kvp.Key).Select(kvp => $"{kvp.Key}={kvp.Value}"));
    }
}

public interface IMetricsService
{
    void IncrementCounter(string name, Dictionary<string, object> labels = null);
    void SetGauge(string name, double value, Dictionary<string, object> labels = null);
    void RecordHistogram(string name, double value, Dictionary<string, object> labels = null);
    MetricsSnapshot GetSnapshot();
    void Reset();
}

public class MetricsSnapshot
{
    public Dictionary<string, CounterSnapshot> Counters { get; set; } = new();
    public Dictionary<string, GaugeSnapshot> Gauges { get; set; } = new();
    public Dictionary<string, HistogramSnapshot> Histograms { get; set; } = new();
    public DateTime Timestamp { get; set; }
}

public class CounterSnapshot
{
    public string Name { get; set; }
    public Dictionary<string, long> Values { get; set; } = new();
}

public class GaugeSnapshot
{
    public string Name { get; set; }
    public Dictionary<string, double> Values { get; set; } = new();
}

public class HistogramSnapshot
{
    public string Name { get; set; }
    public Dictionary<string, HistogramStats> Stats { get; set; } = new();
}

public class HistogramStats
{
    public int Count { get; set; }
    public double Sum { get; set; }
    public double Min { get; set; }
    public double Max { get; set; }
    public double Average { get; set; }
    public double Percentile95 { get; set; }
    public double Percentile99 { get; set; }
}

2. Performance Metrics Collector

Performance metrics'leri toplayan servis.

public class PerformanceMetricsCollector : BackgroundService
{
    private readonly ILogger<PerformanceMetricsCollector> _logger;
    private readonly IMetricsService _metricsService;
    private readonly IConfiguration _configuration;
    private readonly Timer _timer;

    public PerformanceMetricsCollector(ILogger<PerformanceMetricsCollector> logger, 
        IMetricsService metricsService, IConfiguration configuration)
    {
        _logger = logger;
        _metricsService = metricsService;
        _configuration = configuration;

        var interval = _configuration.GetValue<int>("Metrics:CollectionIntervalSeconds", 60);
        _timer = new Timer(CollectMetrics, null, TimeSpan.Zero, TimeSpan.FromSeconds(interval));
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        return Task.CompletedTask;
    }

    private async void CollectMetrics(object state)
    {
        try
        {
            await CollectSystemMetricsAsync();
            await CollectApplicationMetricsAsync();
            await CollectBusinessMetricsAsync();

            _logger.LogDebug("Performance metrics collected successfully");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error collecting performance metrics");
        }
    }

    private async Task CollectSystemMetricsAsync()
    {
        try
        {
            // CPU usage
            var cpuUsage = await GetCpuUsageAsync();
            _metricsService.SetGauge("system.cpu.usage_percent", cpuUsage);

            // Memory usage
            var memoryInfo = GC.GetTotalMemory(false);
            var workingSet = Environment.WorkingSet;
            var memoryUsagePercent = (double)workingSet / memoryInfo * 100;

            _metricsService.SetGauge("system.memory.total_bytes", memoryInfo);
            _metricsService.SetGauge("system.memory.working_set_bytes", workingSet);
            _metricsService.SetGauge("system.memory.usage_percent", memoryUsagePercent);

            // Process metrics
            var process = Process.GetCurrentProcess();
            _metricsService.SetGauge("system.process.cpu_time_ms", process.TotalProcessorTime.TotalMilliseconds);
            _metricsService.SetGauge("system.process.thread_count", process.Threads.Count);
            _metricsService.SetGauge("system.process.handle_count", process.HandleCount);

            await Task.CompletedTask;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error collecting system metrics");
        }
    }

    private async Task CollectApplicationMetricsAsync()
    {
        try
        {
            // GC metrics
            var gen0Collections = GC.CollectionCount(0);
            var gen1Collections = GC.CollectionCount(1);
            var gen2Collections = GC.CollectionCount(2);

            _metricsService.SetGauge("application.gc.gen0_collections", gen0Collections);
            _metricsService.SetGauge("application.gc.gen1_collections", gen1Collections);
            _metricsService.SetGauge("application.gc.gen2_collections", gen2Collections);

            // Thread pool metrics
            ThreadPool.GetAvailableThreads(out var workerThreads, out var completionPortThreads);
            ThreadPool.GetMaxThreads(out var maxWorkerThreads, out var maxCompletionPortThreads);

            _metricsService.SetGauge("application.threadpool.available_worker_threads", workerThreads);
            _metricsService.SetGauge("application.threadpool.available_completion_port_threads", completionPortThreads);
            _metricsService.SetGauge("application.threadpool.max_worker_threads", maxWorkerThreads);
            _metricsService.SetGauge("application.threadpool.max_completion_port_threads", maxCompletionPortThreads);

            await Task.CompletedTask;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error collecting application metrics");
        }
    }

    private async Task CollectBusinessMetricsAsync()
    {
        try
        {
            // Business-specific metrics can be added here
            // For example: active users, request rates, business transactions

            await Task.CompletedTask;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error collecting business metrics");
        }
    }

    private async Task<double> GetCpuUsageAsync()
    {
        try
        {
            var process = Process.GetCurrentProcess();
            var startTime = process.TotalProcessorTime;
            var startCpuTime = Environment.TickCount;

            await Task.Delay(100); // Wait 100ms to get CPU usage

            var endTime = process.TotalProcessorTime;
            var endCpuTime = Environment.TickCount;

            var cpuUsedMs = (endTime - startTime).TotalMilliseconds;
            var totalMsPassed = endCpuTime - startCpuTime;
            var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed);

            return cpuUsageTotal * 100;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting CPU usage");
            return 0;
        }
    }

    public override void Dispose()
    {
        _timer?.Dispose();
        base.Dispose();
    }
}

Mülakat Soruları

Temel Sorular

  1. Metrics collection nedir?
  2. Cevap: Uygulama ve sistem performansını izlemek için veri toplama.

  3. Counter, Gauge, Histogram farkları nelerdir?

  4. Cevap: Counter: artan değer, Gauge: anlık değer, Histogram: dağılım.

  5. Performance metrics nelerdir?

  6. Cevap: CPU, memory, response time, throughput, error rate.

  7. Business metrics nedir?

  8. Cevap: İş süreçlerini ölçen metrikler (user count, transaction rate).

  9. Metrics aggregation nasıl yapılır?

  10. Cevap: Sum, average, percentiles, time-based grouping.

Teknik Sorular

  1. Custom metrics nasıl implement edilir?
  2. Cevap: IMetricsService interface, Counter/Gauge/Histogram classes.

  3. Performance counters nasıl kullanılır?

  4. Cevap: System.Diagnostics.PerformanceCounter, custom counters.

  5. Metrics sampling nasıl yapılır?

  6. Cevap: Time-based sampling, rate limiting, adaptive sampling.

  7. Metrics storage nasıl yapılır?

  8. Cevap: Time-series databases, Prometheus, InfluxDB.

  9. Metrics visualization nasıl yapılır?

  10. Cevap: Grafana, custom dashboards, real-time monitoring.

Best Practices

  1. Metrics Design
  2. Meaningful names kullanın
  3. Consistent labeling implement edin
  4. Cardinality control edin
  5. Documentation sağlayın

  6. Performance Optimization

  7. Efficient collection implement edin
  8. Sampling strategies uygulayın
  9. Background collection kullanın
  10. Resource usage minimize edin

  11. Data Management

  12. Retention policies uygulayın
  13. Data aggregation yapın
  14. Storage optimization sağlayın
  15. Backup strategies implement edin

  16. Monitoring & Alerting

  17. Threshold-based alerting kurun
  18. Trend analysis implement edin
  19. Anomaly detection ekleyin
  20. Escalation procedures tanımlayın

  21. Integration

  22. Standard formats kullanın
  23. Multiple backends support edin
  24. Export capabilities ekleyin
  25. Testing strategies implement edin

Kaynaklar