added typeaheead and introspetion

This commit is contained in:
Mark Gregory
2026-10-18 17:27:44 +01:00
parent c0ac9d57df
commit 543b639ffc
2 changed files with 108 additions and 0 deletions
+93
View File
@@ -2851,6 +2851,70 @@ struct WorkbenchAdministrationView: View {
}
}
// Phase 69: advisory diagnostics from the already loaded server snapshot.
// No additional queries, writes, or persistent SQL text are required.
private var diagnosticFindings: [String] {
func cell(_ row: [String], _ name: String) -> String {
guard let index = result.columns.firstIndex(where: { $0.caseInsensitiveCompare(name) == .orderedSame }),
index < row.count else { return "" }
return row[index]
}
func number(_ row: [String], _ name: String) -> Double {
Double(cell(row, name)) ?? 0
}
switch page {
case .slowQueries:
return result.rows.prefix(100).enumerated().compactMap { position, row in
let calls = number(row, "COUNT_STAR")
let noIndex = number(row, "SUM_NO_INDEX_USED")
let examined = number(row, "SUM_ROWS_EXAMINED")
let sent = number(row, "SUM_ROWS_SENT")
let digest = cell(row, "DIGEST_TEXT")
let label = digest.isEmpty ? "Digest #\(position + 1)" : String(digest.prefix(110))
if calls > 0 && noIndex > 0 {
return "\(label): \(Int(noIndex)) of \(Int(calls)) executions reported no index usage. Inspect EXPLAIN and predicates."
}
if calls > 0 && examined > 10000 && sent < examined / 100 {
return "\(label): high examined-to-returned row ratio. Check filters and index selectivity."
}
return nil
}
case .tableIO:
return result.rows.prefix(20).compactMap { row in
let reads = number(row, "COUNT_READ")
let writes = number(row, "COUNT_WRITE")
guard reads + writes > 10000 else { return nil }
let table = "\(cell(row, "OBJECT_SCHEMA")).\(cell(row, "OBJECT_NAME"))"
return "\(table): \(Int(reads)) reads, \(Int(writes)) writes since counters were reset. Review workload before tuning."
}
case .innodbLocks:
guard !result.rows.isEmpty else { return [] }
return ["\(result.rows.count) lock-wait relationship(s) visible. Inspect active transactions before taking action."]
case .connectionStatistics:
let values = Dictionary(uniqueKeysWithValues: result.rows.compactMap { row -> (String, Double)? in
guard row.count >= 2, let value = Double(row[1]) else { return nil }
return (row[0], value)
})
var findings: [String] = []
if (values["Aborted_connects"] ?? 0) > 0 {
findings.append("Aborted connections recorded: \(Int(values["Aborted_connects"] ?? 0)). Check credentials, network failures and connection limits.")
}
if (values["Created_tmp_disk_tables"] ?? 0) > 0 {
findings.append("Disk temporary tables recorded: \(Int(values["Created_tmp_disk_tables"] ?? 0)). Review heavy grouping and sorting queries.")
}
return findings
default:
return []
}
}
private var diagnosticReportText: String {
(["DBSyn Database Diagnostics", page.rawValue, "Snapshot: \(Date().formatted())", "",
"Advisory findings (cumulative counters; not proof of a defect):"] +
(diagnosticFindings.isEmpty ? ["No threshold-based findings for this snapshot."] : diagnosticFindings))
.joined(separator: "\n")
}
private var filteredRows: [[String]] {
guard !searchText.isEmpty else { return result.rows }
return result.rows.filter { row in
@@ -2907,6 +2971,35 @@ struct WorkbenchAdministrationView: View {
.padding(.vertical, 8)
}
if !diagnosticFindings.isEmpty && errorMessage == nil {
VStack(alignment: .leading, spacing: 6) {
HStack {
Label("Diagnostic Findings", systemImage: "stethoscope")
.font(.headline)
Spacer()
Button("Copy Report") {
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(diagnosticReportText, forType: .string)
}
}
ForEach(Array(diagnosticFindings.prefix(5).enumerated()), id: \.offset) { _, finding in
Text("• " + finding)
.font(.caption)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
}
if diagnosticFindings.count > 5 {
Text("\(diagnosticFindings.count - 5) additional findings available in Copy Report")
.font(.caption2).foregroundStyle(.secondary)
}
}
.padding(12)
.frame(maxWidth: .infinity, alignment: .leading)
.background(.quaternary.opacity(0.35), in: RoundedRectangle(cornerRadius: 8))
.padding(.horizontal, 16)
.padding(.vertical, 6)
}
if supportsTrends && !diagnosticSamples.isEmpty && page != .dashboard && page != .serverStatus {
HStack(spacing: 12) {
Label("\(diagnosticSamples.count) samples", systemImage: "chart.xyaxis.line")
+15
View File
@@ -0,0 +1,15 @@
# Phase 69 — Database Diagnostics
Adds advisory diagnostics to the existing Server Administration monitoring pages.
- Slow Query Analysis: flags statement digests with reported non-index usage and high examined/returned row ratios.
- Table I/O Hotspots: highlights heavily accessed tables based on cumulative Performance Schema counters.
- InnoDB Lock Waits: displays a warning when blocking relationships are present.
- Connection Statistics: flags aborted connections and disk-based temporary tables.
- Copy Report exports the complete diagnostic findings for the current snapshot.
All findings are read-only, computed from existing server query results, and should be interpreted as investigation prompts rather than definitive tuning instructions. Thresholds are intentionally conservative. No automatic schema modifications, server setting changes or query execution are performed by the diagnostic layer.
The existing performance dashboards, introspection, SQL autocomplete, data synchronisation previews and appearance settings remain unchanged.
Validation: Swift source was statically inspected; macOS compilation and live MySQL runtime verification are outstanding.