an atproto pds written in F# (.NET 9) 馃
pds fsharp giraffe dotnet atproto
at main 4.6 kB view raw
1namespace PDSharp.Core 2 3open System 4open System.IO 5open System.Runtime.InteropServices 6 7/// Health monitoring module for guardrails and uptime checks 8module Health = 9 10 /// Health status response 11 type HealthStatus = { 12 /// Version of the PDS 13 Version : string 14 /// Uptime in seconds 15 UptimeSeconds : int64 16 /// Server start time in ISO8601 17 StartTime : string 18 /// Database status 19 DatabaseStatus : DatabaseStatus 20 /// Disk usage information 21 DiskUsage : DiskUsage option 22 /// Backup status 23 BackupStatus : BackupStatus option 24 } 25 26 /// Database connectivity status 27 and DatabaseStatus = { 28 /// Whether the database is reachable 29 IsHealthy : bool 30 /// Optional error message 31 Message : string option 32 } 33 34 /// Disk usage metrics 35 and DiskUsage = { 36 /// Total disk space in bytes 37 TotalBytes : int64 38 /// Free disk space in bytes 39 FreeBytes : int64 40 /// Used disk space in bytes 41 UsedBytes : int64 42 /// Percentage of disk used 43 UsedPercent : float 44 /// Whether disk pressure is critical (>90%) 45 IsCritical : bool 46 } 47 48 /// Backup status tracking 49 and BackupStatus = { 50 /// Timestamp of last successful backup 51 LastBackupTime : DateTimeOffset option 52 /// Age of last backup in hours 53 BackupAgeHours : float option 54 /// Whether backup is too old (>24 hours) 55 IsStale : bool 56 } 57 58 /// Get disk usage for a given path 59 let getDiskUsage (path : string) : DiskUsage option = 60 try 61 let driveInfo = 62 if RuntimeInformation.IsOSPlatform OSPlatform.Windows then 63 let driveLetter = Path.GetPathRoot path 64 DriveInfo driveLetter 65 else 66 DriveInfo(if Directory.Exists path then path else "/") 67 68 if driveInfo.IsReady then 69 let total = driveInfo.TotalSize 70 let free = driveInfo.TotalFreeSpace 71 let used = total - free 72 let usedPercent = float used / float total * 100.0 73 74 Some { 75 TotalBytes = total 76 FreeBytes = free 77 UsedBytes = used 78 UsedPercent = Math.Round(usedPercent, 2) 79 IsCritical = usedPercent >= 90.0 80 } 81 else 82 None 83 with _ -> 84 None 85 86 87 88 /// Check if a SQLite database file is accessible 89 let checkDatabaseHealth (connectionString : string) : DatabaseStatus = 90 try 91 let dbPath = 92 connectionString.Split ';' 93 |> Array.tryFind (fun s -> s.Trim().StartsWith("Data Source=", StringComparison.OrdinalIgnoreCase)) 94 |> Option.map (fun s -> s.Split('=').[1].Trim()) 95 96 match dbPath with 97 | Some path when File.Exists path -> { IsHealthy = true; Message = None } 98 | Some path -> { 99 IsHealthy = false 100 Message = Some $"Database file not found: {path}" 101 } 102 | None -> { 103 IsHealthy = false 104 Message = Some "Could not parse connection string" 105 } 106 with ex -> { IsHealthy = false; Message = Some ex.Message } 107 108 /// Calculate backup status from last backup time 109 let getBackupStatus (lastBackupTime : DateTimeOffset option) : BackupStatus = 110 match lastBackupTime with 111 | Some time -> 112 let age = DateTimeOffset.UtcNow - time 113 let ageHours = age.TotalHours 114 115 { 116 LastBackupTime = Some time 117 BackupAgeHours = Some(Math.Round(ageHours, 2)) 118 IsStale = ageHours > 24.0 119 } 120 | None -> { 121 LastBackupTime = None 122 BackupAgeHours = None 123 IsStale = true 124 } 125 126 /// Mutable state for tracking server state 127 type HealthState() = 128 let mutable startTime = DateTimeOffset.UtcNow 129 let mutable lastBackupTime : DateTimeOffset option = None 130 131 member _.StartTime = startTime 132 member _.SetStartTime(time : DateTimeOffset) = startTime <- time 133 member _.LastBackupTime = lastBackupTime 134 135 member _.RecordBackup() = 136 lastBackupTime <- Some DateTimeOffset.UtcNow 137 138 member _.RecordBackup(time : DateTimeOffset) = lastBackupTime <- Some time 139 140 member _.GetUptime() : int64 = 141 int64 (DateTimeOffset.UtcNow - startTime).TotalSeconds 142 143 /// Build a complete health status 144 let buildHealthStatus 145 (version : string) 146 (healthState : HealthState) 147 (connectionString : string) 148 (dataPath : string) 149 : HealthStatus = 150 { 151 Version = version 152 UptimeSeconds = healthState.GetUptime() 153 StartTime = healthState.StartTime.ToString("o") 154 DatabaseStatus = checkDatabaseHealth connectionString 155 DiskUsage = getDiskUsage dataPath 156 BackupStatus = Some(getBackupStatus healthState.LastBackupTime) 157 }