mirror of https://github.com/openspug/spug
upgrade monitor module
parent
b4bcb2606a
commit
3553036862
|
@ -4,20 +4,18 @@
|
||||||
from apscheduler.schedulers.background import BackgroundScheduler
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
from apscheduler.executors.pool import ThreadPoolExecutor
|
from apscheduler.executors.pool import ThreadPoolExecutor
|
||||||
from apscheduler.triggers.interval import IntervalTrigger
|
from apscheduler.triggers.interval import IntervalTrigger
|
||||||
from apscheduler.events import EVENT_SCHEDULER_SHUTDOWN, EVENT_JOB_MAX_INSTANCES, EVENT_JOB_ERROR, EVENT_JOB_EXECUTED
|
from apscheduler.events import EVENT_SCHEDULER_SHUTDOWN, EVENT_JOB_MAX_INSTANCES, EVENT_JOB_ERROR
|
||||||
from django_redis import get_redis_connection
|
from django_redis import get_redis_connection
|
||||||
from django.utils.functional import SimpleLazyObject
|
from django.utils.functional import SimpleLazyObject
|
||||||
from django.db import close_old_connections
|
from django.db import close_old_connections
|
||||||
from apps.monitor.models import Detection
|
from apps.monitor.models import Detection
|
||||||
from apps.alarm.models import Alarm
|
|
||||||
from apps.monitor.utils import seconds_to_human
|
|
||||||
from apps.notify.models import Notify
|
from apps.notify.models import Notify
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from libs import spug, AttrDict, human_datetime, human_diff_time
|
from libs import AttrDict, human_datetime
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
|
from random import randint
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import time
|
|
||||||
|
|
||||||
MONITOR_WORKER_KEY = settings.MONITOR_WORKER_KEY
|
MONITOR_WORKER_KEY = settings.MONITOR_WORKER_KEY
|
||||||
|
|
||||||
|
@ -32,46 +30,6 @@ class Scheduler:
|
||||||
EVENT_SCHEDULER_SHUTDOWN | EVENT_JOB_ERROR | EVENT_JOB_MAX_INSTANCES
|
EVENT_SCHEDULER_SHUTDOWN | EVENT_JOB_ERROR | EVENT_JOB_MAX_INSTANCES
|
||||||
)
|
)
|
||||||
|
|
||||||
def _record_alarm(self, obj, status):
|
|
||||||
duration = seconds_to_human(time.time() - obj.latest_fault_time)
|
|
||||||
Alarm.objects.create(
|
|
||||||
name=obj.name,
|
|
||||||
type=obj.get_type_display(),
|
|
||||||
status=status,
|
|
||||||
duration=duration,
|
|
||||||
notify_grp=obj.notify_grp,
|
|
||||||
notify_mode=obj.notify_mode)
|
|
||||||
|
|
||||||
def _do_notify(self, event, obj, out):
|
|
||||||
obj.out = out
|
|
||||||
obj.grp = json.loads(obj.notify_grp)
|
|
||||||
if event == '2':
|
|
||||||
obj.duration = human_diff_time(datetime.now(), datetime.fromtimestamp(obj.latest_fault_time))
|
|
||||||
for mode in json.loads(obj.notify_mode):
|
|
||||||
if mode == '1':
|
|
||||||
spug.notify_by_wx(event, obj)
|
|
||||||
elif mode == '3':
|
|
||||||
spug.notify_by_dd(event, obj)
|
|
||||||
elif mode == '4':
|
|
||||||
spug.notify_by_email(event, obj)
|
|
||||||
elif mode == '5':
|
|
||||||
spug.notify_by_qy_wx(event, obj)
|
|
||||||
|
|
||||||
def _handle_notify(self, obj, is_notified, out):
|
|
||||||
if obj.latest_status == 0:
|
|
||||||
if is_notified:
|
|
||||||
self._record_alarm(obj, '2')
|
|
||||||
logging.warning(f'{human_datetime()} recover job_id: {obj.id}, job_name: {obj.name}')
|
|
||||||
self._do_notify('2', obj, out)
|
|
||||||
else:
|
|
||||||
if obj.fault_times >= obj.threshold:
|
|
||||||
if time.time() - obj.latest_notify_time >= obj.quiet * 60:
|
|
||||||
obj.latest_notify_time = int(time.time())
|
|
||||||
obj.save()
|
|
||||||
self._record_alarm(obj, '1')
|
|
||||||
logging.warning(f'{human_datetime()} notify job_id: {obj.id}, job_name: {obj.name}')
|
|
||||||
self._do_notify('1', obj, out)
|
|
||||||
|
|
||||||
def _handle_event(self, event):
|
def _handle_event(self, event):
|
||||||
close_old_connections()
|
close_old_connections()
|
||||||
obj = SimpleLazyObject(lambda: Detection.objects.filter(pk=event.job_id).first())
|
obj = SimpleLazyObject(lambda: Detection.objects.filter(pk=event.job_id).first())
|
||||||
|
@ -84,20 +42,6 @@ class Scheduler:
|
||||||
elif event.code == EVENT_JOB_ERROR:
|
elif event.code == EVENT_JOB_ERROR:
|
||||||
logging.warning(f'EVENT_JOB_ERROR: job_id {event.job_id} exception: {event.exception}')
|
logging.warning(f'EVENT_JOB_ERROR: job_id {event.job_id} exception: {event.exception}')
|
||||||
Notify.make_notify('monitor', '1', f'{obj.name} - 执行异常', f'{event.exception}')
|
Notify.make_notify('monitor', '1', f'{obj.name} - 执行异常', f'{event.exception}')
|
||||||
elif event.code == EVENT_JOB_EXECUTED:
|
|
||||||
is_ok, out = event.retval
|
|
||||||
is_notified = True if obj.latest_notify_time else False
|
|
||||||
if obj.latest_status in [0, None] and is_ok is False:
|
|
||||||
obj.latest_fault_time = int(time.time())
|
|
||||||
if is_ok:
|
|
||||||
obj.latest_notify_time = 0
|
|
||||||
obj.fault_times = 0
|
|
||||||
else:
|
|
||||||
obj.fault_times += 1
|
|
||||||
obj.latest_status = 0 if is_ok else 1
|
|
||||||
obj.latest_run_time = human_datetime(event.scheduled_run_time)
|
|
||||||
obj.save()
|
|
||||||
self._handle_notify(obj, is_notified, out)
|
|
||||||
|
|
||||||
def _dispatch(self, task_id, tp, targets, extra, threshold, quiet):
|
def _dispatch(self, task_id, tp, targets, extra, threshold, quiet):
|
||||||
close_old_connections()
|
close_old_connections()
|
||||||
|
@ -109,12 +53,14 @@ class Scheduler:
|
||||||
def _init(self):
|
def _init(self):
|
||||||
self.scheduler.start()
|
self.scheduler.start()
|
||||||
for item in Detection.objects.filter(is_active=True):
|
for item in Detection.objects.filter(is_active=True):
|
||||||
|
now = datetime.now()
|
||||||
trigger = IntervalTrigger(minutes=int(item.rate), timezone=self.timezone)
|
trigger = IntervalTrigger(minutes=int(item.rate), timezone=self.timezone)
|
||||||
self.scheduler.add_job(
|
self.scheduler.add_job(
|
||||||
self._dispatch,
|
self._dispatch,
|
||||||
trigger,
|
trigger,
|
||||||
id=str(item.id),
|
id=str(item.id),
|
||||||
args=(item.id, item.type, item.targets, item.extra, item.threshold, item.quiet),
|
args=(item.id, item.type, item.targets, item.extra, item.threshold, item.quiet),
|
||||||
|
next_run_time=now + timedelta(seconds=randint(0, 60))
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
|
@ -27,12 +27,12 @@ export default observer(function () {
|
||||||
function handleTest() {
|
function handleTest() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const formData = lds.pick(store.record, ['type', 'targets', 'extra'])
|
const formData = lds.pick(store.record, ['type', 'targets', 'extra'])
|
||||||
http.post('/api/monitor/test/', formData, { timeout: 120000 })
|
http.post('/api/monitor/test/', formData, {timeout: 120000})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.is_success) {
|
if (res.is_success) {
|
||||||
Modal.success({ content: res.message })
|
Modal.success({content: res.message})
|
||||||
} else {
|
} else {
|
||||||
Modal.warning({ content: res.message })
|
Modal.warning({content: res.message})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false))
|
.finally(() => setLoading(false))
|
||||||
|
@ -46,13 +46,13 @@ export default observer(function () {
|
||||||
|
|
||||||
function handleAddGroup() {
|
function handleAddGroup() {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
icon: <ExclamationCircleOutlined />,
|
icon: <ExclamationCircleOutlined/>,
|
||||||
title: '添加监控分组',
|
title: '添加监控分组',
|
||||||
content: (
|
content: (
|
||||||
<Form layout="vertical" style={{ marginTop: 24 }}>
|
<Form layout="vertical" style={{marginTop: 24}}>
|
||||||
<Form.Item required label="监控分组">
|
<Form.Item required label="监控分组">
|
||||||
<Input onChange={e => store.record.group = e.target.value} />
|
<Input onChange={e => store.record.group = e.target.value}/>
|
||||||
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
),
|
),
|
||||||
|
@ -65,7 +65,7 @@ export default observer(function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function canNext() {
|
function canNext() {
|
||||||
const { type, targets, extra, group } = store.record;
|
const {type, targets, extra, group} = store.record;
|
||||||
const is_verify = name && group && targets.length;
|
const is_verify = name && group && targets.length;
|
||||||
if (['2', '3', '4'].includes(type)) {
|
if (['2', '3', '4'].includes(type)) {
|
||||||
return is_verify && extra
|
return is_verify && extra
|
||||||
|
@ -84,21 +84,21 @@ export default observer(function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStyle(t) {
|
function getStyle(t) {
|
||||||
return t.includes(store.record.type) ? { display: 'flex' } : { display: 'none' }
|
return t.includes(store.record.type) ? {display: 'flex'} : {display: 'none'}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, desc, type, targets, extra, group } = store.record;
|
const {name, desc, type, targets, extra, group} = store.record;
|
||||||
return (
|
return (
|
||||||
<Form labelCol={{ span: 6 }} wrapperCol={{ span: 14 }}>
|
<Form labelCol={{span: 6}} wrapperCol={{span: 14}}>
|
||||||
<Form.Item required label="监控分组" style={{ marginBottom: 0 }}>
|
<Form.Item required label="监控分组" style={{marginBottom: 0}}>
|
||||||
<Form.Item style={{ display: 'inline-block', width: 'calc(75%)', marginRight: 8 }}>
|
<Form.Item style={{display: 'inline-block', width: 'calc(75%)', marginRight: 8}}>
|
||||||
<Select value={group} placeholder="请选择监控分组" onChange={v => store.record.group = v}>
|
<Select value={group} placeholder="请选择监控分组" onChange={v => store.record.group = v}>
|
||||||
{store.groups.map(item => (
|
{store.groups.map(item => (
|
||||||
<Select.Option value={item} key={item}>{item}</Select.Option>
|
<Select.Option value={item} key={item}>{item}</Select.Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item style={{ display: 'inline-block', width: 'calc(25%-8px)' }}>
|
<Form.Item style={{display: 'inline-block', width: 'calc(25%-8px)'}}>
|
||||||
<Button type="link" onClick={handleAddGroup}>添加分组</Button>
|
<Button type="link" onClick={handleAddGroup}>添加分组</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
@ -112,7 +112,7 @@ export default observer(function () {
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="监控名称">
|
<Form.Item required label="监控名称">
|
||||||
<Input value={name} onChange={e => store.record.name = e.target.value} placeholder="请输入监控名称" />
|
<Input value={name} onChange={e => store.record.name = e.target.value} placeholder="请输入监控名称"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="监控地址" style={getStyle(['1'])}>
|
<Form.Item required label="监控地址" style={getStyle(['1'])}>
|
||||||
<Select
|
<Select
|
||||||
|
@ -120,7 +120,7 @@ export default observer(function () {
|
||||||
value={targets}
|
value={targets}
|
||||||
onChange={v => store.record.targets = v}
|
onChange={v => store.record.targets = v}
|
||||||
placeholder="http(s)://开头,支持多个地址,每输入完成一个后按回车确认"
|
placeholder="http(s)://开头,支持多个地址,每输入完成一个后按回车确认"
|
||||||
notFoundContent={null} />
|
notFoundContent={null}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="监控地址" style={getStyle(['2', '5'])}>
|
<Form.Item required label="监控地址" style={getStyle(['2', '5'])}>
|
||||||
<Select
|
<Select
|
||||||
|
@ -128,20 +128,21 @@ export default observer(function () {
|
||||||
value={targets}
|
value={targets}
|
||||||
onChange={v => store.record.targets = v}
|
onChange={v => store.record.targets = v}
|
||||||
placeholder="IP或域名,支持多个地址,每输入完成一个后按回车确认"
|
placeholder="IP或域名,支持多个地址,每输入完成一个后按回车确认"
|
||||||
notFoundContent={null} />
|
notFoundContent={null}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="监控主机" style={getStyle(['3', '4'])}>
|
<Form.Item required label="监控主机" style={getStyle(['3', '4'])}>
|
||||||
{store.record.targets?.length > 0 && `已选择 ${store.record.targets.length} 台`}
|
{store.record.targets?.length > 0 && `已选择 ${store.record.targets.length} 台`}
|
||||||
<Button type="link" onClick={() => setShowSelector(true)}>选择主机</Button>
|
<Button type="link" onClick={() => setShowSelector(true)}>选择主机</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="响应时间" style={getStyle(['1'])}>
|
<Form.Item label="响应时间" style={getStyle(['1'])}>
|
||||||
<Input suffix="ms" placeholder="最长响应时间(毫秒),不设置则默认10秒超时" onChange={e => store.record.extra = e.target.value}/>
|
<Input suffix="ms" value={extra} placeholder="最长响应时间(毫秒),不设置则默认10秒超时"
|
||||||
|
onChange={e => store.record.extra = e.target.value}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="检测端口" style={getStyle(['2'])}>
|
<Form.Item required label="检测端口" style={getStyle(['2'])}>
|
||||||
<Input value={extra} placeholder="请输入端口号" onChange={e => store.record.extra = e.target.value} />
|
<Input value={extra} placeholder="请输入端口号" onChange={e => store.record.extra = e.target.value}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="进程名称" help="执行 ps -ef 看到的进程名称。" style={getStyle(['3'])}>
|
<Form.Item required label="进程名称" help="执行 ps -ef 看到的进程名称。" style={getStyle(['3'])}>
|
||||||
<Input value={extra} placeholder="请输入进程名称" onChange={e => store.record.extra = e.target.value} />
|
<Input value={extra} placeholder="请输入进程名称" onChange={e => store.record.extra = e.target.value}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
required
|
required
|
||||||
|
@ -153,18 +154,18 @@ export default observer(function () {
|
||||||
value={extra || ''}
|
value={extra || ''}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="200px"
|
height="200px"
|
||||||
onChange={e => store.record.extra = cleanCommand(e)} />
|
onChange={e => store.record.extra = cleanCommand(e)}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="备注信息">
|
<Form.Item label="备注信息">
|
||||||
<Input.TextArea value={desc} onChange={e => store.record.desc = e.target.value} placeholder="请输入备注信息" />
|
<Input.TextArea value={desc} onChange={e => store.record.desc = e.target.value} placeholder="请输入备注信息"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item wrapperCol={{ span: 14, offset: 6 }} style={{ marginTop: 12 }}>
|
<Form.Item wrapperCol={{span: 14, offset: 6}} style={{marginTop: 12}}>
|
||||||
<Button disabled={!canNext()} type="primary" onClick={toNext}>下一步</Button>
|
<Button disabled={!canNext()} type="primary" onClick={toNext}>下一步</Button>
|
||||||
<Button disabled={!canNext()} type="link" loading={loading} onClick={handleTest}>执行测试</Button>
|
<Button disabled={!canNext()} type="link" loading={loading} onClick={handleTest}>执行测试</Button>
|
||||||
<span style={{color: '#888', fontSize: 12}}>Tips: 仅测试第一个监控地址</span>
|
<span style={{color: '#888', fontSize: 12}}>Tips: 仅测试第一个监控地址</span>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{showTmp && <TemplateSelector onOk={v => store.record.extra += v} onCancel={() => setShowTmp(false)} />}
|
{showTmp && <TemplateSelector onOk={v => store.record.extra += v} onCancel={() => setShowTmp(false)}/>}
|
||||||
<Selector
|
<Selector
|
||||||
visible={showSelector}
|
visible={showSelector}
|
||||||
selectedRowKeys={[...store.record.targets]}
|
selectedRowKeys={[...store.record.targets]}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import { http, hasPermission } from 'libs';
|
||||||
import groupStore from '../alarm/group/store';
|
import groupStore from '../alarm/group/store';
|
||||||
import hostStore from '../host/store';
|
import hostStore from '../host/store';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import lds from 'lodash';
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class ComTable extends React.Component {
|
class ComTable extends React.Component {
|
||||||
|
@ -102,9 +101,9 @@ class ComTable extends React.Component {
|
||||||
<Table.Column title="频率" dataIndex="rate" render={value => `${value}分钟`}/>
|
<Table.Column title="频率" dataIndex="rate" render={value => `${value}分钟`}/>
|
||||||
<Table.Column title="状态" render={info => {
|
<Table.Column title="状态" render={info => {
|
||||||
if (info.is_active) {
|
if (info.is_active) {
|
||||||
return <Tag color="green">监控中</Tag>
|
return <Tag color="blue">已启用</Tag>
|
||||||
} else {
|
} else {
|
||||||
return <Tag color="red">未启用</Tag>
|
return <Tag color="red">已禁用</Tag>
|
||||||
}
|
}
|
||||||
}}/>
|
}}/>
|
||||||
<Table.Column title="更新于" dataIndex="latest_run_time_alias"
|
<Table.Column title="更新于" dataIndex="latest_run_time_alias"
|
||||||
|
|
|
@ -34,13 +34,6 @@ export default observer(function () {
|
||||||
{store.types.map(item => <Select.Option key={item} value={item}>{item}</Select.Option>)}
|
{store.types.map(item => <Select.Option key={item} value={item}>{item}</Select.Option>)}
|
||||||
</Select>
|
</Select>
|
||||||
</SearchForm.Item>
|
</SearchForm.Item>
|
||||||
<SearchForm.Item span={7} title="任务状态">
|
|
||||||
<Select allowClear value={store.f_status} onChange={v => store.f_status = v} placeholder="请选择">
|
|
||||||
<Select.Option value={-1}>待检测</Select.Option>
|
|
||||||
<Select.Option value={0}>正常</Select.Option>
|
|
||||||
<Select.Option value={1}>异常</Select.Option>
|
|
||||||
</Select>
|
|
||||||
</SearchForm.Item>
|
|
||||||
</SearchForm>
|
</SearchForm>
|
||||||
<ComTable/>
|
<ComTable/>
|
||||||
{store.formVisible && <ComForm/>}
|
{store.formVisible && <ComForm/>}
|
||||||
|
|
|
@ -19,7 +19,6 @@ class Store {
|
||||||
|
|
||||||
@observable f_name;
|
@observable f_name;
|
||||||
@observable f_type;
|
@observable f_type;
|
||||||
@observable f_status;
|
|
||||||
@observable f_active = '';
|
@observable f_active = '';
|
||||||
@observable f_group;
|
@observable f_group;
|
||||||
|
|
||||||
|
@ -29,13 +28,6 @@ class Store {
|
||||||
if (this.f_name) records = records.filter(x => x.name.toLowerCase().includes(this.f_name.toLowerCase()));
|
if (this.f_name) records = records.filter(x => x.name.toLowerCase().includes(this.f_name.toLowerCase()));
|
||||||
if (this.f_type) records = records.filter(x => x.type_alias === this.f_type);
|
if (this.f_type) records = records.filter(x => x.type_alias === this.f_type);
|
||||||
if (this.f_group) records = records.filter(x => x.group === this.f_group);
|
if (this.f_group) records = records.filter(x => x.group === this.f_group);
|
||||||
if (this.f_status !== undefined) {
|
|
||||||
if (this.f_status === -1) {
|
|
||||||
records = records.filter(x => x.is_active && !x.latest_status_alias);
|
|
||||||
} else {
|
|
||||||
records = records.filter(x => x.latest_status === this.f_status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return records
|
return records
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue