/
Servers.AvailabilityGroup.cshtml
119 lines (117 loc) · 7.1 KB
/
Servers.AvailabilityGroup.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
@using StackExchange.Opserver.Data.SQL
@model SQLNode.AGInfo
@{
var ag = Model;
if (!ag.HasDatabases)
{
// don't render empty AGs
return;
}
}
@helper RenderDBIssues(SQLNode.AGReplica r) {
if (r.SynchronizationHealth != SynchronizationHealths.Healthy)
{
var unhealthy = r.Databases.Where(db => db.SynchronizationHealth.HasValue && db.SynchronizationHealth != SynchronizationHealths.Healthy).ToList();
var replica = Model.Node?.Cluster?.GetNode(r.ReplicaServerName);
var remoteAgData = replica?.AvailabilityGroups.SafeData(true).First(g => g.Name == Model.Name);
var remoteDBs = remoteAgData?.LocalReplica?.Databases?.ToDictionary(db => db.DatabaseName) ?? new Dictionary<string, SQLNode.AGDatabaseReplica>();
if (r.Databases.Count > unhealthy.Count)
{
foreach (var db in unhealthy.OrderByDescending(u => remoteDBs.TryGetValue(u.DatabaseName, out var rdb) ? rdb.SynchronizationState : u.SynchronizationState).ThenBy(u => u.DatabaseName))
{
var remoteDB = remoteDBs.TryGetValue(db.DatabaseName, out var rdb) ? rdb : db;
<div class="text-warning" title="@db.DatabaseName
Sync (@Model.Node.Name): @(db.SynchronizationState.HasValue ? db.SynchronizationState.Value.AsString(EnumFormat.Description) : "Unknown")
Health (@Model.Node.Name): @(db.SynchronizationHealth.HasValue ? db.SynchronizationHealth.Value.AsString(EnumFormat.Description) : "Unknown")
@if (replica != null && remoteDB != db) {<text>
Sync (@replica.Name): @(remoteDB.SynchronizationState.HasValue ? remoteDB.SynchronizationState.Value.AsString(EnumFormat.Description) : "Unknown")
Health (@replica.Name): @(remoteDB.SynchronizationHealth.HasValue ? remoteDB.SynchronizationHealth.Value.AsString(EnumFormat.Description) : "Unknown")
</text>}
@if (db.SuspendReason.HasValue) {<text>
Suspension Reason: @db.SuspendReason.Value.AsString(EnumFormat.Description)</text>}">@db.IconSpan() @db.MonitorStatus.Span(db.DatabaseName)
(@(remoteDB.SynchronizationState.HasValue ? remoteDB.SynchronizationState.Value.AsString(EnumFormat.Description) : "Unknown"))
</div>
}
}
else
{
<div class="text-danger">Sync: @(r.SynchronizationHealth.HasValue ? r.SynchronizationHealth.Value.AsString(EnumFormat.Description) : "Unknwon")</div>
}
}
}
<div class="col-md-3">
<div class="panel panel-default">
<div class="panel-heading">
<span class="cluster-name">@ag.IconSpan() @ag.ClusterName:</span> <a href="#/cluster/@ag.ClusterName.UrlEncode()/@ag.Name.UrlEncode()">@ag.Name</a>
</div>
<div class="panel-body small list-group">
<div class="well well-sm well-condensed">
<div class="pull-right">
<i class="fa fa-database @(ag.LocalReplica.Databases.GetWorstStatus().TextClass(showGood: true))" aria-hidden="true"></i> @SQLHelpers.HealthDescriptionAGs(ag.LocalReplica.Databases, minimal: true)
</div>
<div>
@ag.LocalReplica.IconSpan() <a href="#/cluster/@ag.ClusterName.UrlEncode()/@ag.Name.UrlEncode()/@ag.Node.Name.UrlEncode()">@ag.Node.Name</a> <span class="text-success">(Master)</span>
</div>
@RenderDBIssues(ag.LocalReplica)
<div>
@Icon.Upload @(((long)ag.RemoteReplicas.Sum(r => r.BytesSentPerSecond)).ToSize(zero: "0"))ps
@Icon.Download @(((long)ag.RemoteReplicas.Sum(r => r.BytesReceivedPerSecond)).ToSize(zero: "0"))ps
</div>
<div>
<span class="pull-right">@ag.Node.LastFetch.ToPollSpan(mini: true, lastSuccess: true)</span>
<span class="text-muted">Log Size:</span> @ag.LocalReplica.Databases.Sum(db => db.LogKBytesUsed).ToComma() <span class="text-muted">KB</span>
</div>
@if (ag.GroupSynchronizationHealth != SynchronizationHealths.Healthy && ag.LocalReplica != null)
{
var volumeIds = ag.Node.Databases.SafeData(true).Where(db => ag.LocalReplica.Databases.Select(rdb => rdb.DatabaseId).Contains(db.Id)).Select(db => db.LogVolumeId);
var volumes = ag.Node.Volumes.SafeData(true).Where(v => volumeIds.Contains(v.VolumeId));
foreach (var v in volumes)
{
<div class="ag-log-volume">
<span class="text-muted">Space Free:</span> @v.VolumeMountPoint
<span class="volume-space">@((v.AvailableBytes / 1024).ToComma()) <span class="text-muted">KB</span></span>
</div>
}
}
</div>
@foreach (var r in ag.RemoteReplicas)
{
<div class="col-md-11 col-md-offset-1 well-condensed">
<div class="well well-sm well-condensed @(r.SynchronizationHealth == SynchronizationHealths.Healthy ? "" : " ag-problem")" title="Group: @r.AvailabilityGroupName
Node: @r.ReplicaServerName
ReplicaId: @r.ReplicaId.ToString()
Sync Health: @r.SynchronizationHealth
Connection State: @r.ConnectedState
Mode: @r.AvailabilityMode">
<div class="pull-right">
<i class="fa fa-database @(r.Databases.GetWorstStatus().TextClass(showGood: true))" aria-hidden="true"></i> @SQLHelpers.HealthDescriptionAGs(r.Databases, minimal: true)
</div>
<div>
<span>
@r.IconSpan() <a href="#/cluster/@ag.ClusterName.UrlEncode()/@ag.Name.UrlEncode()/@r.ReplicaServerName.UrlEncode()">@r.ReplicaServerName</a>
@switch (r.AvailabilityMode)
{
case AvailabilityModes.AsyncrhonousCommit: <span class="text-muted">(async)</span>
break;
case AvailabilityModes.SynchronousCommit: <span class="text-primary">(sync)</span>
break;
default: <span class="text-warning">(unknown)</span>
break;
}
</span>
</div>
@RenderDBIssues(r)
<div>
@Icon.Upload @(((long)r.BytesReceivedPerSecond).ToSize(zero: "0 B"))ps
@Icon.Download @(((long)r.BytesSentPerSecond).ToSize(zero: "0 B"))ps
</div>
<div>
<span class="pull-right">@ag.Node.AvailabilityGroups.ToPollSpan(mini: true, lastSuccess: true)</span>
<span class="text-muted">Queue:</span> @r.Databases.Sum(db => db.LogSendQueueSize).ToComma() <span class="text-muted">KB</span>
</div>
</div>
</div>
}
</div>
</div>
</div>