服务延迟报警 [no ci]

This commit is contained in:
naiba 2022-09-17 10:30:32 +08:00
parent eb07b9468b
commit c60ebd1bae
8 changed files with 90 additions and 26 deletions

View File

@ -393,6 +393,9 @@ type monitorForm struct {
NotificationTag string NotificationTag string
SkipServersRaw string SkipServersRaw string
Duration uint64 Duration uint64
MinLatency float32
MaxLatency float32
LatencyNotify string
} }
func (ma *memberAPI) addOrEditMonitor(c *gin.Context) { func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
@ -409,6 +412,9 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
m.Notify = mf.Notify == "on" m.Notify = mf.Notify == "on"
m.NotificationTag = mf.NotificationTag m.NotificationTag = mf.NotificationTag
m.Duration = mf.Duration m.Duration = mf.Duration
m.LatencyNotify = mf.LatencyNotify == "on"
m.MinLatency = mf.MinLatency
m.MaxLatency = mf.MaxLatency
err = m.InitSkipServers() err = m.InitSkipServers()
} }
if err == nil { if err == nil {

View File

@ -48,6 +48,10 @@ type Monitor struct {
NotificationTag string // 当前服务监控所属的通知组 NotificationTag string // 当前服务监控所属的通知组
Cover uint8 Cover uint8
MinLatency float32
MaxLatency float32
LatencyNotify bool
SkipServers map[uint64]bool `gorm:"-" json:"-"` SkipServers map[uint64]bool `gorm:"-" json:"-"`
CronJobID cron.EntryID `gorm:"-" json:"-"` CronJobID cron.EntryID `gorm:"-" json:"-"`
} }

View File

@ -130,6 +130,21 @@ other = "秒"
[EnableFailureNotification] [EnableFailureNotification]
other = "启用故障通知" other = "启用故障通知"
[FailureNotification]
other = "故障通知"
[MaxLatency]
other = "最大延迟(ms)"
[MinLatency]
other = "最小延迟(ms)"
[EnableLatencyNotification]
other = "启用延迟通知"
[LatencyNotification]
other = "延迟通知"
[IntroductionOfMonitor] [IntroductionOfMonitor]
other = """ other = """
<b>HTTP-GET</b> URL( http/https, HTTPSSSL)<br> <b>HTTP-GET</b> URL( http/https, HTTPSSSL)<br>

View File

@ -77,6 +77,8 @@ function showFormModal(modelSelector, formID, URL, getData) {
item.name === "Duration" item.name === "Duration"
) { ) {
obj[item.name] = parseInt(item.value); obj[item.name] = parseInt(item.value);
} else if (item.name.endsWith("Latency")) {
obj[item.name] = parseFloat(item.value);
} else { } else {
obj[item.name] = item.value; obj[item.name] = item.value;
} }
@ -94,9 +96,9 @@ function showFormModal(modelSelector, formID, URL, getData) {
if (item.name.endsWith("TasksRaw")) { if (item.name.endsWith("TasksRaw")) {
if (item.value.length > 2) { if (item.value.length > 2) {
obj[item.name] = JSON.stringify( obj[item.name] = JSON.stringify(
[...item.value.matchAll(/\d+/gm)].map((k) => [...item.value.matchAll(/\d+/gm)].map((k) =>
parseInt(k[0]) parseInt(k[0])
) )
); );
} }
} }
@ -163,29 +165,29 @@ function addOrEditAlertRule(rule) {
const node2 = modal.find("i.dropdown.icon.2"); const node2 = modal.find("i.dropdown.icon.2");
for (let i = 0; i < failTriggerTasksList.length; i++) { for (let i = 0; i < failTriggerTasksList.length; i++) {
node1.after( node1.after(
'<a class="ui label transition visible" data-value="' + '<a class="ui label transition visible" data-value="' +
failTriggerTasksList[i] + failTriggerTasksList[i] +
'" style="display: inline-block !important;">ID:' + '" style="display: inline-block !important;">ID:' +
failTriggerTasksList[i] + failTriggerTasksList[i] +
'<i class="delete icon"></i></a>' '<i class="delete icon"></i></a>'
); );
} }
for (let i = 0; i < recoverTriggerTasksList.length; i++) { for (let i = 0; i < recoverTriggerTasksList.length; i++) {
node2.after( node2.after(
'<a class="ui label transition visible" data-value="' + '<a class="ui label transition visible" data-value="' +
recoverTriggerTasksList[i] + recoverTriggerTasksList[i] +
'" style="display: inline-block !important;">ID:' + '" style="display: inline-block !important;">ID:' +
recoverTriggerTasksList[i] + recoverTriggerTasksList[i] +
'<i class="delete icon"></i></a>' '<i class="delete icon"></i></a>'
); );
} }
} }
modal modal
.find("input[name=FailTriggerTasksRaw]") .find("input[name=FailTriggerTasksRaw]")
.val(rule ? "[]," + failTriggerTasks.substr(1, failTriggerTasks.length - 2) : "[]"); .val(rule ? "[]," + failTriggerTasks.substr(1, failTriggerTasks.length - 2) : "[]");
modal modal
.find("input[name=RecoverTriggerTasksRaw]") .find("input[name=RecoverTriggerTasksRaw]")
.val(rule ? "[]," + recoverTriggerTasks.substr(1, recoverTriggerTasks.length - 2) : "[]"); .val(rule ? "[]," + recoverTriggerTasks.substr(1, recoverTriggerTasks.length - 2) : "[]");
showFormModal(".rule.modal", "#ruleForm", "/api/alert-rule"); showFormModal(".rule.modal", "#ruleForm", "/api/alert-rule");
} }
@ -257,10 +259,10 @@ function issueNewApiToken(apiToken) {
const modal = $(".api.modal"); const modal = $(".api.modal");
modal.children(".header").text((apiToken ? LANG.Edit : LANG.Add) + ' ' + "API Token"); modal.children(".header").text((apiToken ? LANG.Edit : LANG.Add) + ' ' + "API Token");
modal modal
.find(".nezha-primary-btn.button") .find(".nezha-primary-btn.button")
.html( .html(
apiToken ? LANG.Edit + '<i class="edit icon"></i>' : LANG.Add + '<i class="add icon"></i>' apiToken ? LANG.Edit + '<i class="edit icon"></i>' : LANG.Add + '<i class="add icon"></i>'
); );
modal.find("textarea[name=Note]").val(apiToken ? apiToken.Note : null); modal.find("textarea[name=Note]").val(apiToken ? apiToken.Note : null);
showFormModal(".api.modal", "#apiForm", "/api/token"); showFormModal(".api.modal", "#apiForm", "/api/token");
} }
@ -313,6 +315,13 @@ function addOrEditMonitor(monitor) {
} else { } else {
modal.find(".ui.nb-notify.checkbox").checkbox("set unchecked"); modal.find(".ui.nb-notify.checkbox").checkbox("set unchecked");
} }
modal.find("input[name=MaxLatency]").val(monitor ? monitor.MaxLatency : null);
modal.find("input[name=MinLatency]").val(monitor ? monitor.MinLatency : null);
if (monitor && monitor.LatencyNotify) {
modal.find(".ui.nb-lt-notify.checkbox").checkbox("set checked");
} else {
modal.find(".ui.nb-lt-notify.checkbox").checkbox("set unchecked");
}
modal.find("a.ui.label.visible").each((i, el) => { modal.find("a.ui.label.visible").each((i, el) => {
el.remove(); el.remove();
}); });

View File

@ -10,7 +10,7 @@
<script src="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.js"></script> <script src="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.js"></script>
<script src="/static/semantic-ui-alerts.min.js"></script> <script src="/static/semantic-ui-alerts.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script> <script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
<script src="/static/main.js?v20220915"></script> <script src="/static/main.js?v20220917"></script>
<script> <script>
(function () { (function () {
updateLang({{.LANG }}); updateLang({{.LANG }});

View File

@ -54,6 +54,20 @@
<label>{{tr "EnableFailureNotification"}}</label> <label>{{tr "EnableFailureNotification"}}</label>
</div> </div>
</div> </div>
<div class="field">
<label>{{tr "MaxLatency"}}</label>
<input type="number" name="MaxLatency" placeholder="100.88" />
</div>
<div class="field">
<label>{{tr "MinLatency"}}</label>
<input type="number" name="MinLatency" placeholder="100.88" />
</div>
<div class="field">
<div class="ui nb-lt-notify checkbox">
<input name="LatencyNotify" type="checkbox" tabindex="0" class="hidden" />
<label>{{tr "EnableLatencyNotification"}}</label>
</div>
</div>
</form> </form>
<div class="ui warning message"> <div class="ui warning message">
<p> <p>

View File

@ -20,7 +20,8 @@
<th>{{tr "Type"}}</th> <th>{{tr "Type"}}</th>
<th>{{tr "Duration"}}</th> <th>{{tr "Duration"}}</th>
<th>{{tr "NotificationMethodGroup"}}</th> <th>{{tr "NotificationMethodGroup"}}</th>
<th>{{tr "Notification"}}</th> <th>{{tr "FailureNotification"}}</th>
<th>{{tr "LatencyNotification"}}</th>
<th>{{tr "Administration"}}</th> <th>{{tr "Administration"}}</th>
</tr> </tr>
</thead> </thead>
@ -39,6 +40,7 @@
<td>{{$monitor.Duration}} {{tr "Seconds"}}</td> <td>{{$monitor.Duration}} {{tr "Seconds"}}</td>
<td>{{$monitor.NotificationTag}}</td> <td>{{$monitor.NotificationTag}}</td>
<td>{{$monitor.Notify}}</td> <td>{{$monitor.Notify}}</td>
<td>{{$monitor.LatencyNotify}}</td>
<td> <td>
<div class="ui mini icon buttons"> <div class="ui mini icon buttons">
<button class="ui button" onclick="addOrEditMonitor({{$monitor}})"> <button class="ui button" onclick="addOrEditMonitor({{$monitor}})">

View File

@ -82,10 +82,10 @@ func NewServiceSentinel(serviceSentinelDispatchBus chan<- model.Monitor) {
} }
/* /*
使用缓存 channel处理上报的 Service 请求结果然后判断是否需要报警 使用缓存 channel处理上报的 Service 请求结果然后判断是否需要报警
需要记录上一次的状态信息 需要记录上一次的状态信息
加锁顺序serviceResponseDataStoreLock > monthlyStatusLock > monitorsLock 加锁顺序serviceResponseDataStoreLock > monthlyStatusLock > monitorsLock
*/ */
type ServiceSentinel struct { type ServiceSentinel struct {
// 服务监控任务上报通道 // 服务监控任务上报通道
@ -364,6 +364,20 @@ func (ss *ServiceSentinel) worker() {
log.Println("NEZHA>> 服务监控数据持久化失败:", err) log.Println("NEZHA>> 服务监控数据持久化失败:", err)
} }
} }
// 延迟报警
if mh.Delay > 0 {
ss.monitorsLock.RLock()
if ss.monitors[mh.MonitorID].LatencyNotify {
if mh.Delay > ss.monitors[mh.MonitorID].MaxLatency {
go SendNotification(ss.monitors[mh.MonitorID].NotificationTag, fmt.Sprintf("[Latency] %s %2f > %2f", ss.monitors[mh.MonitorID].Name, mh.Delay, ss.monitors[mh.MonitorID].MaxLatency), true)
}
if mh.Delay < ss.monitors[mh.MonitorID].MinLatency {
go SendNotification(ss.monitors[mh.MonitorID].NotificationTag, fmt.Sprintf("[Latency] %s %2f < %2f", ss.monitors[mh.MonitorID].Name, mh.Delay, ss.monitors[mh.MonitorID].MinLatency), true)
}
}
ss.monitorsLock.RUnlock()
}
// 故障报警
if stateCode == StatusDown || stateCode != ss.lastStatus[mh.MonitorID] { if stateCode == StatusDown || stateCode != ss.lastStatus[mh.MonitorID] {
ss.monitorsLock.RLock() ss.monitorsLock.RLock()
isNeedSendNotification := (ss.lastStatus[mh.MonitorID] != 0 || stateCode == StatusDown) && ss.monitors[mh.MonitorID].Notify isNeedSendNotification := (ss.lastStatus[mh.MonitorID] != 0 || stateCode == StatusDown) && ss.monitors[mh.MonitorID].Notify