< Summary

Information
Class: OzonOrdersWeb.WebServices.JobLogs.ServerDiagnosticsService
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/JobLogs/ServerDiagnosticsService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 97
Coverable lines: 97
Total lines: 139
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 30
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetSnapshotAsync()0%7280%
ReadDatabaseMetricsAsync()0%4260%
GetApplicationDrive()0%620%
ExecuteScalarAsync()0%2040%
ReadStatusAsync()0%7280%
ToInt64(...)0%620%
get_Version()100%210%
get_WriteBytes()100%210%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/JobLogs/ServerDiagnosticsService.cs

#LineLine coverage
 1using System.Data;
 2using System.Diagnostics;
 3using Microsoft.EntityFrameworkCore;
 4using OzonRepositories.Context;
 5
 6namespace OzonOrdersWeb.WebServices.JobLogs;
 7
 8public sealed class ServerDiagnosticsService : IServerDiagnosticsService
 9{
 10    private readonly ApplicationDbContext _context;
 11    private readonly DatabaseIoRateTracker _ioRateTracker;
 012    private readonly DateTime _processStartedAtUtc = Process.GetCurrentProcess().StartTime.ToUniversalTime();
 13
 014    public ServerDiagnosticsService(ApplicationDbContext context, DatabaseIoRateTracker ioRateTracker)
 15    {
 016        _context = context;
 017        _ioRateTracker = ioRateTracker;
 018    }
 19
 20    public async Task<ServerDiagnosticsSnapshot> GetSnapshotAsync(CancellationToken cancellationToken = default)
 21    {
 022        var capturedAtUtc = DateTime.UtcNow;
 023        var process = Process.GetCurrentProcess();
 024        var elapsed = Math.Max(1, (capturedAtUtc - _processStartedAtUtc).TotalSeconds);
 025        var cpuPercent = Math.Min(100, process.TotalProcessorTime.TotalSeconds / elapsed / Environment.ProcessorCount * 
 026        var drive = GetApplicationDrive();
 27        try
 28        {
 029            var database = await ReadDatabaseMetricsAsync(cancellationToken);
 030            var failedJobs = await _context.BackgroundJobExecutions.AsNoTracking()
 031                .CountAsync(x => x.Status == "Failed" && x.StartedAt >= capturedAtUtc.AddHours(-24), cancellationToken);
 032            var rates = _ioRateTracker.Update(capturedAtUtc, database.ReadBytes, database.WriteBytes);
 033            return new ServerDiagnosticsSnapshot
 034            {
 035                CapturedAtUtc = capturedAtUtc,
 036                ServerStatus = "Работает",
 037                ProcessorCount = Environment.ProcessorCount,
 038                ProcessCpuPercent = cpuPercent,
 039                ProcessMemoryBytes = process.WorkingSet64,
 040                AvailableMemoryBytes = GC.GetGCMemoryInfo().TotalAvailableMemoryBytes,
 041                DiskTotalBytes = drive?.TotalSize ?? 0,
 042                DiskFreeBytes = drive?.AvailableFreeSpace ?? 0,
 043                DatabaseStatus = "Доступна",
 044                DatabaseVersion = database.Version,
 045                DatabaseSizeBytes = database.SizeBytes,
 046                DatabaseReadLatencyMs = database.ReadLatencyMs,
 047                DatabaseReadBytesPerSecond = rates.ReadBytesPerSecond,
 048                DatabaseWriteBytesPerSecond = rates.WriteBytesPerSecond,
 049                DatabaseReadBytesTotal = database.ReadBytes,
 050                DatabaseWriteBytesTotal = database.WriteBytes,
 051                DatabaseConnections = database.Connections,
 052                DatabaseRunningThreads = database.RunningThreads,
 053                DatabaseSlowQueries = database.SlowQueries,
 054                FailedJobsLast24Hours = failedJobs
 055            };
 56        }
 057        catch (Exception exception)
 58        {
 059            return new ServerDiagnosticsSnapshot
 060            {
 061                CapturedAtUtc = capturedAtUtc,
 062                ServerStatus = "Работает",
 063                ProcessorCount = Environment.ProcessorCount,
 064                ProcessCpuPercent = cpuPercent,
 065                ProcessMemoryBytes = process.WorkingSet64,
 066                AvailableMemoryBytes = GC.GetGCMemoryInfo().TotalAvailableMemoryBytes,
 067                DiskTotalBytes = drive?.TotalSize ?? 0,
 068                DiskFreeBytes = drive?.AvailableFreeSpace ?? 0,
 069                DatabaseError = exception.Message,
 070                FailedJobsLast24Hours = 0
 071            };
 72        }
 073    }
 74
 75    private async Task<DatabaseMetrics> ReadDatabaseMetricsAsync(CancellationToken cancellationToken)
 76    {
 077        var connection = _context.Database.GetDbConnection();
 078        var shouldClose = connection.State != ConnectionState.Open;
 079        if (shouldClose)
 080            await connection.OpenAsync(cancellationToken);
 81
 82        try
 83        {
 084            var startedAt = Stopwatch.GetTimestamp();
 085            var version = await ExecuteScalarAsync(connection, "SELECT VERSION()", cancellationToken) ?? "Неизвестная ве
 086            var readLatencyMs = Stopwatch.GetElapsedTime(startedAt).Milliseconds;
 087            var sizeBytes = ToInt64(await ExecuteScalarAsync(connection, """
 088                SELECT COALESCE(SUM(data_length + index_length), 0)
 089                FROM information_schema.tables
 090                WHERE table_schema = DATABASE()
 091                """, cancellationToken));
 92
 093            var status = await ReadStatusAsync(connection, cancellationToken);
 094            return new DatabaseMetrics(
 095                version,
 096                sizeBytes,
 097                readLatencyMs,
 098                status.GetValueOrDefault("Innodb_data_read"),
 099                status.GetValueOrDefault("Innodb_data_written"),
 0100                status.GetValueOrDefault("Threads_connected"),
 0101                status.GetValueOrDefault("Threads_running"),
 0102                status.GetValueOrDefault("Slow_queries"));
 103        }
 104        finally
 105        {
 0106            if (shouldClose)
 0107                await connection.CloseAsync();
 108        }
 0109    }
 110
 0111    private static DriveInfo? GetApplicationDrive() => DriveInfo.GetDrives()
 0112        .Where(x => x.IsReady && AppContext.BaseDirectory.StartsWith(x.RootDirectory.FullName, StringComparison.OrdinalI
 0113        .OrderByDescending(x => x.RootDirectory.FullName.Length)
 0114        .FirstOrDefault();
 115
 116    private static async Task<string?> ExecuteScalarAsync(System.Data.Common.DbConnection connection, string commandText
 117    {
 0118        await using var command = connection.CreateCommand();
 0119        command.CommandText = commandText;
 0120        var value = await command.ExecuteScalarAsync(cancellationToken);
 0121        return value?.ToString();
 0122    }
 123
 124    private static async Task<Dictionary<string, long>> ReadStatusAsync(System.Data.Common.DbConnection connection, Canc
 125    {
 0126        await using var command = connection.CreateCommand();
 0127        command.CommandText = "SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected', 'Threads_running', 'Slow_
 0128        await using var reader = await command.ExecuteReaderAsync(cancellationToken);
 0129        var result = new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase);
 0130        while (await reader.ReadAsync(cancellationToken))
 0131            result[reader.GetString(0)] = ToInt64(reader.GetValue(1)?.ToString());
 0132        return result;
 0133    }
 134
 0135    private static long ToInt64(string? value) => long.TryParse(value, out var result) ? result : 0;
 136
 0137    private sealed record DatabaseMetrics(string Version, long SizeBytes, long ReadLatencyMs, long ReadBytes,
 0138        long WriteBytes, long Connections, long RunningThreads, long SlowQueries);
 139}