diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..1228fcb3
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,29 @@
+# The Dockerfile for build localhost source, not git repo
+FROM debian:buster as builder
+
+MAINTAINER cppla https://cpp.la
+
+RUN apt-get update -y && apt-get -y install gcc g++ make libcurl4-openssl-dev
+
+COPY . .
+
+WORKDIR /server
+
+RUN make -j
+RUN pwd && ls -a
+
+# glibc env run
+FROM nginx:latest
+
+RUN mkdir -p /ServerStatus/server/ && ln -sf /dev/null /var/log/nginx/access.log && ln -sf /dev/null /var/log/nginx/error.log
+
+COPY --from=builder server /ServerStatus/server/
+COPY --from=builder web /usr/share/nginx/html/
+
+# china time
+ENV TZ=Asia/Shanghai
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+EXPOSE 80 35601
+HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD curl --fail http://localhost:80 || bash -c 'kill -s 15 -1 && (sleep 10; kill -s 9 -1)'
+CMD nohup sh -c '/etc/init.d/nginx start && /ServerStatus/server/sergate --config=/ServerStatus/server/config.json --web-dir=/usr/share/nginx/html'
diff --git a/README.md b/README.md
index 5c9f97a7..d697ac83 100644
--- a/README.md
+++ b/README.md
@@ -3,139 +3,205 @@
* ServerStatus中文版是一个酷炫高逼格的云探针、云监控、服务器云监控、多服务器探针~。
* 在线演示:https://tz.cloudcpp.com
-[![Build Status](https://img.shields.io/travis/otale/tale.svg?style=flat-square)](https://github.com/cppla/ServerStatus)
-[![Python Support](https://img.shields.io/badge/python-2.7%2B%20-blue.svg)](https://github.com/cppla/ServerStatus)
+[![Python Support](https://img.shields.io/badge/python-3.6%2B%20-blue.svg)](https://github.com/cppla/ServerStatus)
[![C++ Compiler](http://img.shields.io/badge/C++-GNU-blue.svg?style=flat&logo=cplusplus)](https://github.com/cppla/ServerStatus)
[![License](https://img.shields.io/badge/license-MIT-4EB1BA.svg?style=flat-square)](https://github.com/cppla/ServerStatus)
+[![Version](https://img.shields.io/badge/Version-Build%201.1.5-red)](https://github.com/cppla/ServerStatus)
-![Latest Version](http://dl.cpp.la/Archive/serverstatus.png)
+![Latest Host Version](https://dl.cpp.la/Archive/serverstatus_1.1.5.png)
-# 目录介绍:
+`Watchdog触发式告警,interval只是为了防止频繁收到报警信息造成的骚扰,并不是探测间隔。值得注意的是,Exprtk库默认使用窄字符类型,中文等Unicode字符无法解析计算,等待修复。 `
-* autodeploy 自动部署.
-* clients 客户端文件
-* server 服务端文件
-* web 网站文件
+# 目录:
-# 更新说明:
+* clients 客户端文件
+* server 服务端文件
+* web 网站文件
-* 20200629, 优化IPv6,优化前端。注意docker默认是不支持IPv6的, 如使用docker需要手动开启ipv6
-* 20200407, 网速计算不严谨,fixed
-* 20190129, 降低CPU占用
-* 20181221, 增加实时到三网的延迟
-* 20181126, add tupd(tcp, udp, process ,thread) count for view ddcc attack
-* 20180829, 网络情况:主机到三网(CU,CT,CM)每小时丢包率的检测
-* 20180726, 一切皆容器额,查看自动部署或autodeploy/readme
-* 20180312, 加入失联(被照顾)检测【正常:MH361, 屏蔽:MH370】,校准虚拟化流量统计异常
-* 20170807, 更新平均1,5,15负载, 增加服务器总流量监控
+* server/config.json 探针配置文件
+* web/json 探针月流量
-# 自动部署:
+# 部署:
【服务端】:
```bash
-wget https://raw.githubusercontent.com/cppla/ServerStatus/master/autodeploy/config.json
-docker run -d --restart=always --name=serverstatus -v {$path}/config.json:/ServerStatus/server/config.json -p {$port}:80 -p {$port}:35601 cppla/serverstatus
-eg:
-docker run -d --restart=always --name=serverstatus -v ~/config.json:/ServerStatus/server/config.json -p 80:80 -p 35601:35601 cppla/serverstatus
+`Docker`:
+
+wget --no-check-certificate -qO ~/serverstatus-config.json https://raw.githubusercontent.com/cppla/ServerStatus/master/server/config.json && mkdir ~/serverstatus-monthtraffic
+docker run -d --restart=always --name=serverstatus -v ~/serverstatus-config.json:/ServerStatus/server/config.json -v ~/serverstatus-monthtraffic:/usr/share/nginx/html/json -p 80:80 -p 35601:35601 cppla/serverstatus:latest
+
+`Docker-compose(推荐)`: docker-compose up -d
```
【客户端】:
```bash
-wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python client-linux.py SERVER={$SERVER} USER={$USER} PASSWORD={$PASSWORD} >/dev/null 2>&1 &
+wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python3 client-linux.py SERVER={$SERVER} USER={$USER} PASSWORD={$PASSWORD} >/dev/null 2>&1 &
eg:
-wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python client-linux.py SERVER=45.79.67.132 USER=s04 >/dev/null 2>&1 &
+wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python3 client-linux.py SERVER=45.79.67.132 USER=s04 >/dev/null 2>&1 &
```
+# 主题:
+
+* layui:https://github.com/zeyudada/StatusServerLayui ,预览:https://sslt.8zyw.cn
+
+
+* light:https://github.com/orilights/ServerStatus-Theme-Light ,预览:https://tz.cloudcpp.com/index3.html
+
+
+
# 手动安装教程:
-【克隆代码】:
-```
-git clone https://github.com/cppla/ServerStatus.git
-```
-
-【服务端配置】(服务端程序在ServerStatus/web下):
+**【服务端配置】**
-一、生成服务端程序
+#### 一、生成服务端程序
```
-cd ServerStatus/server
-make
+`Debian/Ubuntu`: apt-get -y install gcc g++ make libcurl4-openssl-dev
+`Centos/Redhat`: yum -y install gcc gcc-c++ make libcurl-devel
+
+cd ServerStatus/server && make
./sergate
```
如果没错误提示,OK,ctrl+c关闭;如果有错误提示,检查35601端口是否被占用
-二、修改配置文件
-修改config.json文件,注意username, password的值需要和客户端对应一致
+#### 二、修改配置文件
+```diff
+! watchdog rule 可以为任何已知字段的表达式。注意Exprtk库默认使用窄字符类型,中文等Unicode字符无法解析计算,等待修复
+! watchdog interval 最小通知间隔
+! watchdog callback 可自定义为Post方法的URL,告警内容将拼接其后并发起回调
+
+! Telegram: https://api.telegram.org/bot你自己的密钥/sendMessage?parse_mode=HTML&disable_web_page_preview=true&chat_id=你自己的标识&text=
+! Server酱: https://sctapi.ftqq.com/你自己的密钥.send?title=ServerStatus&desp=
+! PushDeer: https://api2.pushdeer.com/message/push?pushkey=你自己的密钥&text=
+! HttpBasicAuth: https://用户名:密码@你自己的域名/api/push?message=
```
-{"servers":
+
+```
+{
+ "servers":
[
{
"username": "s01",
- "name": "Mainserver 1",
- "type": "Dedicated Server",
- "host": "GenericServerHost123",
- "location": "Austria",
- "password": "some-hard-to-guess-copy-paste-password"
+ "name": "vps-1",
+ "type": "kvm",
+ "host": "chengdu",
+ "location": "🇨🇳",
+ "password": "USER_DEFAULT_PASSWORD",
+ "monthstart": 1
+ }
+ ],
+ "monitors": [
+ {
+ "name": "监测网站,默认为一天在线率",
+ "host": "https://www.baidu.com",
+ "interval": 300,
+ "type": "https"
+ },
+ {
+ "name": "监测tcp服务端口",
+ "host": "114.114.114.114:53",
+ "interval": 300,
+ "type": "tcp"
+ }
+ ],
+ "watchdog":
+ [
+ {
+ "name": "服务器负载高监控,排除内存大于32G物理机,同时排除node1机器",
+ "rule": "cpu>90&load_1>4&memory_total<33554432&name!='node1'",
+ "interval": 600,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "服务器内存使用率过高监控,排除小于1G的机器",
+ "rule": "(memory_used/memory_total)*100>90&memory_total>1048576",
+ "interval": 600,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "服务器宕机告警",
+ "rule": "online4=0&online6=0",
+ "interval": 600,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "DDOS和CC攻击监控,限制甲骨文机器",
+ "rule": "tcp_count>600&type='Oracle'",
+ "interval": 300,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "服务器月出口流量999GB告警",
+ "rule": "(network_out-last_network_out)/1024/1024/1024>999",
+ "interval": 3600,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "阿里云服务器流量18GB告警,限制username为乌兰察布",
+ "rule": "(network_out-last_network_out)/1024/1024/1024>18&(username='wlcb1'|username='wlcb2'|username='wlcb3'|username='wlcb4')",
+ "interval": 3600,
+ "callback": "https://yourSMSurl"
},
+ {
+ "name": "重要线路丢包率过高检查",
+ "rule": "(ping_10010>10|ping_189>10|ping_10086>10)&(host='sgp'|host='qqhk'|host='hk-21-x'|host='hk-31-x')",
+ "interval": 600,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "你可以组合任何已知字段的表达式",
+ "rule": "(hdd_used/hdd_total)*100>95",
+ "interval": 1800,
+ "callback": "https://yourSMSurl"
+ }
]
}
```
-三、拷贝ServerStatus/status到你的网站目录
+#### 三、拷贝ServerStatus/status到你的网站目录
例如:
```
sudo cp -r ServerStatus/web/* /home/wwwroot/default
```
-四、运行服务端:
+#### 四、运行服务端:
web-dir参数为上一步设置的网站根目录,务必修改成自己网站的路径
```
./sergate --config=config.json --web-dir=/home/wwwroot/default
```
-【客户端配置】(客户端程序在ServerStatus/clients下):
+**【客户端配置】**
+
客户端有两个版本,client-linux为普通linux,client-psutil为跨平台版,普通版不成功,换成跨平台版即可。
-一、client-linux版配置:
+#### 一、client-linux版配置:
1、vim client-linux.py, 修改SERVER地址,username帐号, password密码
-2、python client-linux.py 运行即可。
+2、python3 client-linux.py 运行即可。
-二、client-psutil版配置:
-1、安装psutil跨平台依赖库
-2、vim client-psutil.py, 修改SERVER地址,username帐号, password密码
-3、python client-psutil.py 运行即可。
+#### 二、client-psutil版配置:
+1、安装psutil跨平台依赖库
```
-### for Centos:
-sudo yum -y install epel-release
-sudo yum -y install python-pip
-sudo yum clean all
-sudo yum -y install gcc
-sudo yum -y install python-devel
-sudo pip install psutil
-### for Ubuntu/Debian:
-sudo root
-apt-get -y install python-setuptools python-dev build-essential
-apt-get -y install python-pip
-pip install psutil
-### for Windows:
-打开网址:https://pypi.python.org/pypi?:action=display&name=psutil#downloads
-下载psutil for windows程序包
-安装即可
+`Debian/Ubuntu`: apt -y install python3-pip && pip3 install psutil
+`Centos/Redhat`: yum -y install python3-pip gcc python3-devel && pip3 install psutil
+`Windows`: https://pypi.org/project/psutil/
```
+2、vim client-psutil.py, 修改SERVER地址,username帐号, password密码
+3、python3 client-psutil.py 运行即可。
-打开云探针页面,就可以正常的监控。接下来把服务器和客户端脚本自行加入开机启动,或者进程守护,或以后台方式运行即可!例如: nohup python client-linux.py &
+服务器和客户端自行加入开机启动,或进程守护,或后台方式运行。 例如: nohup python3 client-linux.py &
-# 为什么会有ServerStatus中文版:
+`extra scene (run web/ssview.py)`
+![Shell View](https://dl.cpp.la/Archive/serverstatus-shell.png?version=2023)
-* 有些功能确实没用
-* 原版本部署,英文说明复杂
-* 不符合中文版的习惯
-* 没有一次又一次的轮子,哪来如此优秀的云探针
-# 相关开源项目,感谢:
+# Make Better
-* ServerStatus:https://github.com/BotoX/ServerStatus
+* BotoX:https://github.com/BotoX/ServerStatus
* mojeda: https://github.com/mojeda
* mojeda's ServerStatus: https://github.com/mojeda/ServerStatus
* BlueVM's project: http://www.lowendtalk.com/discussion/comment/169690#Comment_169690
+
+# Jetbrains
+
+
diff --git a/autodeploy/Dockerfile b/autodeploy/Dockerfile
deleted file mode 100755
index edd9d80d..00000000
--- a/autodeploy/Dockerfile
+++ /dev/null
@@ -1,24 +0,0 @@
-FROM debian:latest as builder
-
-MAINTAINER cppla https://cpp.la
-
-RUN apt-get update
-RUN apt-get -y install gcc g++ make git
-RUN git clone https://github.com/cppla/ServerStatus
-
-WORKDIR /ServerStatus/server
-
-RUN make
-RUN pwd && ls -a
-
-# glibc env run
-FROM nginx:latest
-
-RUN mkdir -p /ServerStatus/server/
-
-COPY --from=builder /ServerStatus/server /ServerStatus/server/
-COPY --from=builder /ServerStatus/web /usr/share/nginx/html/
-
-EXPOSE 80 35601
-
-CMD nohup sh -c '/etc/init.d/nginx start && /ServerStatus/server/sergate --config=/ServerStatus/server/config.json --web-dir=/usr/share/nginx/html'
diff --git a/autodeploy/config.json b/autodeploy/config.json
deleted file mode 100755
index 5e82392c..00000000
--- a/autodeploy/config.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{"servers":
- [
- {
- "username": "s01",
- "name": "node1",
- "type": "xen",
- "host": "host1",
- "location": "cn",
- "password": "USER_DEFAULT_PASSWORD"
- },
- {
- "username": "s02",
- "name": "node2",
- "type": "vmware",
- "host": "host2",
- "location": "jp",
- "password": "USER_DEFAULT_PASSWORD"
- },
- {
- "disabled": true,
- "username": "s03",
- "name": "node3",
- "type": "Nothing",
- "host": "host3",
- "location": "fr",
- "password": "USER_DEFAULT_PASSWORD"
- },
- {
- "username": "s04",
- "name": "ssss",
- "type": "ssss",
- "host": "ssss",
- "location": "ssss",
- "password": "USER_DEFAULT_PASSWORD"
- }
- ]
-}
diff --git a/autodeploy/readme b/autodeploy/readme
deleted file mode 100755
index 3e773d06..00000000
--- a/autodeploy/readme
+++ /dev/null
@@ -1,15 +0,0 @@
-服务端:
-
-docker build -f Dockerfile -t sss .
-docker run -d --restart=always --name=sss -v {$path}/config.json:/ServerStatus/server/config.json -p {$port}:80 -p {$port}:35601 sss
-
-客户端:
-
-wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python client-linux.py SERVER={$SERVER} USER={$USER} PASSWORD={$PASSWORD} >/dev/null 2>&1 &
-
-
-
-
-
-附: docker安装
-curl -sSL https://get.docker.com/ | sh
diff --git a/clients/client-linux.py b/clients/client-linux.py
index 8fa8bb20..a9d5dc2a 100755
--- a/clients/client-linux.py
+++ b/clients/client-linux.py
@@ -1,33 +1,41 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# coding: utf-8
-# Update by : https://github.com/cppla/ServerStatus
-# 支持Python版本:2.7 to 3.7
+# Update by : https://github.com/cppla/ServerStatus, Update date: 20220530
+# 版本:1.0.3, 支持Python版本:2.7 to 3.10
# 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
-# 时间: 20200407
+# ONLINE_PACKET_HISTORY_LEN, 探测间隔120s,记录24小时在线率(720);探测时间300s,记录24小时(288);探测间隔60s,记录7天(10080)
# 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义,例如:CU = "www.facebook.com"。
SERVER = "127.0.0.1"
USER = "s01"
-
-PORT = 35601
PASSWORD = "USER_DEFAULT_PASSWORD"
-INTERVAL = 1
-PORBEPORT = 80
+PORT = 35601
CU = "cu.tz.cloudcpp.com"
CT = "ct.tz.cloudcpp.com"
CM = "cm.tz.cloudcpp.com"
+PROBEPORT = 80
+PROBE_PROTOCOL_PREFER = "ipv4" # ipv4, ipv6
+PING_PACKET_HISTORY_LEN = 100
+ONLINE_PACKET_HISTORY_LEN = 288
+INTERVAL = 1
import socket
+import ssl
import time
import timeit
import re
import os
import sys
import json
+import errno
import subprocess
import threading
+if sys.version_info.major == 3:
+ from queue import Queue
+elif sys.version_info.major == 2:
+ from Queue import Queue
def get_uptime():
with open('/proc/uptime', 'r') as f:
@@ -112,18 +120,6 @@ def tupd():
d = int(s[:-1])-2
return t,u,p,d
-def ip_status():
- ip_check = 0
- for i in [CU, CT, CM]:
- try:
- socket.create_connection((i, PORBEPORT), timeout=1).close()
- except:
- ip_check += 1
- if ip_check >= 2:
- return False
- else:
- return True
-
def get_network(ip_version):
if(ip_version == 4):
HOST = "ipv4.google.com"
@@ -153,30 +149,47 @@ def get_network(ip_version):
'avgrx': 0,
'avgtx': 0
}
+diskIO = {
+ 'read': 0,
+ 'write': 0
+}
+monitorServer = {}
def _ping_thread(host, mark, port):
lostPacket = 0
- allPacket = 0
- startTime = time.time()
+ packet_queue = Queue(maxsize=PING_PACKET_HISTORY_LEN)
while True:
+ # flush dns , every time.
+ IP = host
+ if host.count(':') < 1: # if not plain ipv6 address, means ipv4 address or hostname
+ try:
+ if PROBE_PROTOCOL_PREFER == 'ipv4':
+ IP = socket.getaddrinfo(host, None, socket.AF_INET)[0][4][0]
+ else:
+ IP = socket.getaddrinfo(host, None, socket.AF_INET6)[0][4][0]
+ except Exception:
+ pass
+
+ if packet_queue.full():
+ if packet_queue.get() == 0:
+ lostPacket -= 1
try:
b = timeit.default_timer()
- socket.create_connection((host, port), timeout=1).close()
- pingTime[mark] = int((timeit.default_timer()-b)*1000)
- except:
- lostPacket += 1
- finally:
- allPacket += 1
-
- if allPacket > 100:
- lostRate[mark] = float(lostPacket) / allPacket
+ socket.create_connection((IP, port), timeout=1).close()
+ pingTime[mark] = int((timeit.default_timer() - b) * 1000)
+ packet_queue.put(1)
+ except socket.error as error:
+ if error.errno == errno.ECONNREFUSED:
+ pingTime[mark] = int((timeit.default_timer() - b) * 1000)
+ packet_queue.put(1)
+ #elif error.errno == errno.ETIMEDOUT:
+ else:
+ lostPacket += 1
+ packet_queue.put(0)
- endTime = time.time()
- if endTime - startTime > 3600:
- lostPacket = 0
- allPacket = 0
- startTime = endTime
+ if packet_queue.qsize() > 30:
+ lostRate[mark] = float(lostPacket) / packet_queue.qsize()
time.sleep(INTERVAL)
@@ -205,13 +218,78 @@ def _net_speed():
netSpeed["avgtx"] = avgtx
time.sleep(INTERVAL)
-def get_realtime_date():
+def _disk_io():
+ '''
+ good luck for opensource! by: cpp.la
+ 磁盘IO:因为IOPS原因,SSD和HDD、包括RAID卡,ZFS等阵列技术。IO对性能的影响还需要结合自身服务器情况来判断。
+ 比如我这里是机械硬盘,大量做随机小文件读写,那么很低的读写也就能造成硬盘长时间的等待。
+ 如果这里做连续性IO,那么普通机械硬盘写入到100Mb/s,那么也能造成硬盘长时间的等待。
+ 磁盘读写有误差:4k,8k ,https://stackoverflow.com/questions/34413926/psutil-vs-dd-monitoring-disk-i-o
+ :return:
+ '''
+ while True:
+ # pre pid snapshot
+ snapshot_first = {}
+ # next pid snapshot
+ snapshot_second = {}
+ # read count snapshot
+ snapshot_read = 0
+ # write count snapshot
+ snapshot_write = 0
+ # process snapshot
+ pid_snapshot = [str(i) for i in os.listdir("/proc") if i.isdigit() is True]
+ for pid in pid_snapshot:
+ try:
+ with open("/proc/{}/io".format(pid)) as f:
+ pid_io = {}
+ for line in f.readlines():
+ if "read_bytes" in line:
+ pid_io["read"] = int(line.split("read_bytes:")[-1].strip())
+ elif "write_bytes" in line and "cancelled_write_bytes" not in line:
+ pid_io["write"] = int(line.split("write_bytes:")[-1].strip())
+ pid_io["name"] = open("/proc/{}/comm".format(pid), "r").read().strip()
+ snapshot_first[pid] = pid_io
+ except:
+ if pid in snapshot_first:
+ snapshot_first.pop(pid)
+
+ time.sleep(INTERVAL)
+
+ for pid in pid_snapshot:
+ try:
+ with open("/proc/{}/io".format(pid)) as f:
+ pid_io = {}
+ for line in f.readlines():
+ if "read_bytes" in line:
+ pid_io["read"] = int(line.split("read_bytes:")[-1].strip())
+ elif "write_bytes" in line and "cancelled_write_bytes" not in line:
+ pid_io["write"] = int(line.split("write_bytes:")[-1].strip())
+ pid_io["name"] = open("/proc/{}/comm".format(pid), "r").read().strip()
+ snapshot_second[pid] = pid_io
+ except:
+ if pid in snapshot_first:
+ snapshot_first.pop(pid)
+ if pid in snapshot_second:
+ snapshot_second.pop(pid)
+
+ for k, v in snapshot_first.items():
+ if snapshot_first[k]["name"] == snapshot_second[k]["name"] and snapshot_first[k]["name"] != "bash":
+ snapshot_read += (snapshot_second[k]["read"] - snapshot_first[k]["read"])
+ snapshot_write += (snapshot_second[k]["write"] - snapshot_first[k]["write"])
+ diskIO["read"] = snapshot_read
+ diskIO["write"] = snapshot_write
+
+def get_realtime_data():
+ '''
+ real time get system data
+ :return:
+ '''
t1 = threading.Thread(
target=_ping_thread,
kwargs={
'host': CU,
'mark': '10010',
- 'port': PORBEPORT
+ 'port': PROBEPORT
}
)
t2 = threading.Thread(
@@ -219,7 +297,7 @@ def get_realtime_date():
kwargs={
'host': CT,
'mark': '189',
- 'port': PORBEPORT
+ 'port': PROBEPORT
}
)
t3 = threading.Thread(
@@ -227,20 +305,103 @@ def get_realtime_date():
kwargs={
'host': CM,
'mark': '10086',
- 'port': PORBEPORT
+ 'port': PROBEPORT
}
)
t4 = threading.Thread(
target=_net_speed,
)
- t1.setDaemon(True)
- t2.setDaemon(True)
- t3.setDaemon(True)
- t4.setDaemon(True)
- t1.start()
- t2.start()
- t3.start()
- t4.start()
+ t5 = threading.Thread(
+ target=_disk_io,
+ )
+ for ti in [t1, t2, t3, t4, t5]:
+ ti.daemon = True
+ ti.start()
+
+
+def _monitor_thread(name, host, interval, type):
+ lostPacket = 0
+ packet_queue = Queue(maxsize=ONLINE_PACKET_HISTORY_LEN)
+ while True:
+ if name not in monitorServer.keys():
+ break
+ if packet_queue.full():
+ if packet_queue.get() == 0:
+ lostPacket -= 1
+ try:
+ if type == "http":
+ address = host.replace("http://", "")
+ m = timeit.default_timer()
+ if PROBE_PROTOCOL_PREFER == 'ipv4':
+ IP = socket.getaddrinfo(address, None, socket.AF_INET)[0][4][0]
+ else:
+ IP = socket.getaddrinfo(address, None, socket.AF_INET6)[0][4][0]
+ monitorServer[name]["dns_time"] = int((timeit.default_timer() - m) * 1000)
+ m = timeit.default_timer()
+ k = socket.create_connection((IP, 80), timeout=6)
+ monitorServer[name]["connect_time"] = int((timeit.default_timer() - m) * 1000)
+ m = timeit.default_timer()
+ k.sendall("GET / HTTP/1.2\r\nHost:{}\r\nUser-Agent:ServerStatus/cppla\r\nConnection:close\r\n\r\n".format(address).encode('utf-8'))
+ response = b""
+ while True:
+ data = k.recv(4096)
+ if not data:
+ break
+ response += data
+ http_code = response.decode('utf-8').split('\r\n')[0].split()[1]
+ monitorServer[name]["download_time"] = int((timeit.default_timer() - m) * 1000)
+ k.close()
+ if http_code not in ['200', '204', '301', '302', '401']:
+ raise Exception("http code not in 200, 204, 301, 302, 401")
+ elif type == "https":
+ context = ssl._create_unverified_context()
+ address = host.replace("https://", "")
+ m = timeit.default_timer()
+ if PROBE_PROTOCOL_PREFER == 'ipv4':
+ IP = socket.getaddrinfo(address, None, socket.AF_INET)[0][4][0]
+ else:
+ IP = socket.getaddrinfo(address, None, socket.AF_INET6)[0][4][0]
+ monitorServer[name]["dns_time"] = int((timeit.default_timer() - m) * 1000)
+ m = timeit.default_timer()
+ k = socket.create_connection((IP, 443), timeout=6)
+ monitorServer[name]["connect_time"] = int((timeit.default_timer() - m) * 1000)
+ m = timeit.default_timer()
+ kk = context.wrap_socket(k, server_hostname=address)
+ kk.sendall("GET / HTTP/1.2\r\nHost:{}\r\nUser-Agent:ServerStatus/cppla\r\nConnection:close\r\n\r\n".format(address).encode('utf-8'))
+ response = b""
+ while True:
+ data = kk.recv(4096)
+ if not data:
+ break
+ response += data
+ http_code = response.decode('utf-8').split('\r\n')[0].split()[1]
+ monitorServer[name]["download_time"] = int((timeit.default_timer() - m) * 1000)
+ kk.close()
+ k.close()
+ if http_code not in ['200', '204', '301', '302', '401']:
+ raise Exception("http code not in 200, 204, 301, 302, 401")
+ elif type == "tcp":
+ m = timeit.default_timer()
+ if PROBE_PROTOCOL_PREFER == 'ipv4':
+ IP = socket.getaddrinfo(host.split(":")[0], None, socket.AF_INET)[0][4][0]
+ else:
+ IP = socket.getaddrinfo(host.split(":")[0], None, socket.AF_INET6)[0][4][0]
+ monitorServer[name]["dns_time"] = int((timeit.default_timer() - m) * 1000)
+ m = timeit.default_timer()
+ k = socket.create_connection((IP, int(host.split(":")[1])), timeout=6)
+ monitorServer[name]["connect_time"] = int((timeit.default_timer() - m) * 1000)
+ m = timeit.default_timer()
+ k.send(b"GET / HTTP/1.2\r\n\r\n")
+ k.recv(1024)
+ monitorServer[name]["download_time"] = int((timeit.default_timer() - m) * 1000)
+ k.close()
+ packet_queue.put(1)
+ except Exception as e:
+ lostPacket += 1
+ packet_queue.put(0)
+ if packet_queue.qsize() > 5:
+ monitorServer[name]["online_rate"] = 1 - float(lostPacket) / packet_queue.qsize()
+ time.sleep(interval)
def byte_str(object):
'''
@@ -268,7 +429,7 @@ def byte_str(object):
elif 'INTERVAL' in argc:
INTERVAL = int(argc.split('INTERVAL=')[-1])
socket.setdefaulttimeout(30)
- get_realtime_date()
+ get_realtime_data()
while True:
try:
print("Connecting...")
@@ -288,6 +449,28 @@ def byte_str(object):
if data.find("You are connecting via") < 0:
data = byte_str(s.recv(1024))
print(data)
+ monitorServer.clear()
+ for i in data.split('\n'):
+ if "monitor" in i and "type" in i and "{" in i and "}" in i:
+ jdata = json.loads(i[i.find("{"):i.find("}")+1])
+ monitorServer[jdata.get("name")] = {
+ "type": jdata.get("type"),
+ "dns_time": 0,
+ "connect_time": 0,
+ "download_time": 0,
+ "online_rate": 1
+ }
+ t = threading.Thread(
+ target=_monitor_thread,
+ kwargs={
+ 'name': jdata.get("name"),
+ 'host': jdata.get("host"),
+ 'interval': jdata.get("interval"),
+ 'type': jdata.get("type")
+ }
+ )
+ t.daemon = True
+ t.start()
timer = 0
check_ip = 0
@@ -306,8 +489,6 @@ def byte_str(object):
Load_1, Load_5, Load_15 = os.getloadavg()
MemoryTotal, MemoryUsed, SwapTotal, SwapFree = get_memory()
HDDTotal, HDDUsed = get_hdd()
- IP_STATUS = ip_status()
-
array = {}
if not timer:
array['online' + str(check_ip)] = get_network(check_ip)
@@ -330,7 +511,6 @@ def byte_str(object):
array['network_tx'] = netSpeed.get("nettx")
array['network_in'] = NET_IN
array['network_out'] = NET_OUT
- array['ip_status'] = IP_STATUS
array['ping_10010'] = lostRate.get('10010') * 100
array['ping_189'] = lostRate.get('189') * 100
array['ping_10086'] = lostRate.get('10086') * 100
@@ -338,16 +518,20 @@ def byte_str(object):
array['time_189'] = pingTime.get('189')
array['time_10086'] = pingTime.get('10086')
array['tcp'], array['udp'], array['process'], array['thread'] = tupd()
-
+ array['io_read'] = diskIO.get("read")
+ array['io_write'] = diskIO.get("write")
+ array['custom'] = "
".join(f"{k}\\t解析: {v['dns_time']}\\t连接: {v['connect_time']}\\t下载: {v['download_time']}\\t在线率: {v['online_rate']*100:.1f}%
" for k, v in monitorServer.items())
s.send(byte_str("update " + json.dumps(array) + "\n"))
except KeyboardInterrupt:
raise
except socket.error:
+ monitorServer.clear()
print("Disconnected...")
if 's' in locals().keys():
del s
time.sleep(3)
except Exception as e:
+ monitorServer.clear()
print("Caught Exception:", e)
if 's' in locals().keys():
del s
diff --git a/clients/client-psutil.py b/clients/client-psutil.py
index 799ce56f..d3038095 100755
--- a/clients/client-psutil.py
+++ b/clients/client-psutil.py
@@ -1,33 +1,41 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# coding: utf-8
-# Update by : https://github.com/cppla/ServerStatus
+# Update by : https://github.com/cppla/ServerStatus, Update date: 20220530
# 依赖于psutil跨平台库
-# 支持Python版本:2.7 to 3.7
+# 版本:1.0.3, 支持Python版本:2.7 to 3.10
# 支持操作系统: Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
-# 时间: 20200407
+# ONLINE_PACKET_HISTORY_LEN, 探测间隔120s,记录24小时在线率(720);探测时间300s,记录24小时(288);探测间隔60s,记录7天(10080)
# 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义,例如:CU = "www.facebook.com"。
SERVER = "127.0.0.1"
USER = "s01"
-
-PORT = 35601
PASSWORD = "USER_DEFAULT_PASSWORD"
-INTERVAL = 1
-PORBEPORT = 80
+PORT = 35601
CU = "cu.tz.cloudcpp.com"
CT = "ct.tz.cloudcpp.com"
CM = "cm.tz.cloudcpp.com"
+PROBEPORT = 80
+PROBE_PROTOCOL_PREFER = "ipv4" # ipv4, ipv6
+PING_PACKET_HISTORY_LEN = 100
+ONLINE_PACKET_HISTORY_LEN = 288
+INTERVAL = 1
import socket
+import ssl
import time
import timeit
import os
+import sys
import json
+import errno
import psutil
-import sys
import threading
+if sys.version_info.major == 3:
+ from queue import Queue
+elif sys.version_info.major == 2:
+ from Queue import Queue
def get_uptime():
return int(time.time() - psutil.boot_time())
@@ -41,18 +49,22 @@ def get_swap():
return int(Mem.total/1024.0), int(Mem.used/1024.0)
def get_hdd():
- valid_fs = [ "ext4", "ext3", "ext2", "reiserfs", "jfs", "btrfs", "fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs" ]
- disks = dict()
- size = 0
- used = 0
- for disk in psutil.disk_partitions():
- if not disk.device in disks and disk.fstype.lower() in valid_fs:
- disks[disk.device] = disk.mountpoint
- for disk in disks.values():
- usage = psutil.disk_usage(disk)
- size += usage.total
- used += usage.used
- return int(size/1024.0/1024.0), int(used/1024.0/1024.0)
+ if "darwin" in sys.platform:
+ return int(psutil.disk_usage("/").total/1024.0/1024.0), int((psutil.disk_usage("/").total-psutil.disk_usage("/").free)/1024.0/1024.0)
+ else:
+ valid_fs = ["ext4", "ext3", "ext2", "reiserfs", "jfs", "btrfs", "fuseblk", "zfs", "simfs", "ntfs", "fat32",
+ "exfat", "xfs"]
+ disks = dict()
+ size = 0
+ used = 0
+ for disk in psutil.disk_partitions():
+ if not disk.device in disks and disk.fstype.lower() in valid_fs:
+ disks[disk.device] = disk.mountpoint
+ for disk in disks.values():
+ usage = psutil.disk_usage(disk)
+ size += usage.total
+ used += usage.used
+ return int(size/1024.0/1024.0), int(used/1024.0/1024.0)
def get_cpu():
return psutil.cpu_percent(interval=INTERVAL)
@@ -83,31 +95,29 @@ def tupd():
u = int(os.popen('ss -u|wc -l').read()[:-1])-1
p = int(os.popen('ps -ef|wc -l').read()[:-1])-2
d = int(os.popen('ps -eLf|wc -l').read()[:-1])-2
+ elif sys.platform.startswith("darwin") is True:
+ t = int(os.popen('lsof -nP -iTCP | wc -l').read()[:-1]) - 1
+ u = int(os.popen('lsof -nP -iUDP | wc -l').read()[:-1]) - 1
+ p = len(psutil.pids())
+ d = 0
+ for k in psutil.pids():
+ try:
+ d += psutil.Process(k).num_threads()
+ except:
+ pass
+
elif sys.platform.startswith("win") is True:
t = int(os.popen('netstat -an|find "TCP" /c').read()[:-1])-1
u = int(os.popen('netstat -an|find "UDP" /c').read()[:-1])-1
p = len(psutil.pids())
- d = 0
- # cpu is high, default: 0
- # d = sum([psutil.Process(k).num_threads() for k in [x for x in psutil.pids()]])
+ # if you find cpu is high, please set d=0
+ d = sum([psutil.Process(k).num_threads() for k in psutil.pids()])
else:
t,u,p,d = 0,0,0,0
return t,u,p,d
except:
return 0,0,0,0
-def ip_status():
- ip_check = 0
- for i in [CU, CT, CM]:
- try:
- socket.create_connection((i, PORBEPORT), timeout=1).close()
- except:
- ip_check += 1
- if ip_check >= 2:
- return False
- else:
- return True
-
def get_network(ip_version):
if(ip_version == 4):
HOST = "ipv4.google.com"
@@ -137,30 +147,47 @@ def get_network(ip_version):
'avgrx': 0,
'avgtx': 0
}
+diskIO = {
+ 'read': 0,
+ 'write': 0
+}
+monitorServer = {}
def _ping_thread(host, mark, port):
lostPacket = 0
- allPacket = 0
- startTime = time.time()
+ packet_queue = Queue(maxsize=PING_PACKET_HISTORY_LEN)
while True:
+ # flush dns, every time.
+ IP = host
+ if host.count(':') < 1: # if not plain ipv6 address, means ipv4 address or hostname
+ try:
+ if PROBE_PROTOCOL_PREFER == 'ipv4':
+ IP = socket.getaddrinfo(host, None, socket.AF_INET)[0][4][0]
+ else:
+ IP = socket.getaddrinfo(host, None, socket.AF_INET6)[0][4][0]
+ except Exception:
+ pass
+
+ if packet_queue.full():
+ if packet_queue.get() == 0:
+ lostPacket -= 1
try:
b = timeit.default_timer()
- socket.create_connection((host, port), timeout=1).close()
+ socket.create_connection((IP, port), timeout=1).close()
pingTime[mark] = int((timeit.default_timer() - b) * 1000)
- except:
- lostPacket += 1
- finally:
- allPacket += 1
-
- if allPacket > 100:
- lostRate[mark] = float(lostPacket) / allPacket
+ packet_queue.put(1)
+ except socket.error as error:
+ if error.errno == errno.ECONNREFUSED:
+ pingTime[mark] = int((timeit.default_timer() - b) * 1000)
+ packet_queue.put(1)
+ #elif error.errno == errno.ETIMEDOUT:
+ else:
+ lostPacket += 1
+ packet_queue.put(0)
- endTime = time.time()
- if endTime - startTime > 3600:
- lostPacket = 0
- allPacket = 0
- startTime = endTime
+ if packet_queue.qsize() > 30:
+ lostRate[mark] = float(lostPacket) / packet_queue.qsize()
time.sleep(INTERVAL)
@@ -185,13 +212,73 @@ def _net_speed():
netSpeed["avgtx"] = avgtx
time.sleep(INTERVAL)
-def get_realtime_date():
+def _disk_io():
+ """
+ the code is by: https://github.com/giampaolo/psutil/blob/master/scripts/iotop.py
+ good luck for opensource! modify: cpp.la
+ Calculate IO usage by comparing IO statics before and
+ after the interval.
+ Return a tuple including all currently running processes
+ sorted by IO activity and total disks I/O activity.
+ 磁盘IO:因为IOPS原因,SSD和HDD、包括RAID卡,ZFS等。IO对性能的影响还需要结合自身服务器情况来判断。
+ 比如我这里是机械硬盘,大量做随机小文件读写,那么很低的读写也就能造成硬盘长时间的等待。
+ 如果这里做连续性IO,那么普通机械硬盘写入到100Mb/s,那么也能造成硬盘长时间的等待。
+ 磁盘读写有误差:4k,8k ,https://stackoverflow.com/questions/34413926/psutil-vs-dd-monitoring-disk-i-o
+ macos/win,暂不处理。
+ """
+ if "darwin" in sys.platform or "win" in sys.platform:
+ diskIO["read"] = 0
+ diskIO["write"] = 0
+ else:
+ while True:
+ # first get a list of all processes and disk io counters
+ procs = [p for p in psutil.process_iter()]
+ for p in procs[:]:
+ try:
+ p._before = p.io_counters()
+ except psutil.Error:
+ procs.remove(p)
+ continue
+ disks_before = psutil.disk_io_counters()
+
+ # sleep some time, only when INTERVAL==1 , io read/write per_sec.
+ # when INTERVAL > 1, io read/write per_INTERVAL
+ time.sleep(INTERVAL)
+
+ # then retrieve the same info again
+ for p in procs[:]:
+ with p.oneshot():
+ try:
+ p._after = p.io_counters()
+ p._cmdline = ' '.join(p.cmdline())
+ if not p._cmdline:
+ p._cmdline = p.name()
+ p._username = p.username()
+ except (psutil.NoSuchProcess, psutil.ZombieProcess):
+ procs.remove(p)
+ disks_after = psutil.disk_io_counters()
+
+ # finally calculate results by comparing data before and
+ # after the interval
+ for p in procs:
+ p._read_per_sec = p._after.read_bytes - p._before.read_bytes
+ p._write_per_sec = p._after.write_bytes - p._before.write_bytes
+ p._total = p._read_per_sec + p._write_per_sec
+
+ diskIO["read"] = disks_after.read_bytes - disks_before.read_bytes
+ diskIO["write"] = disks_after.write_bytes - disks_before.write_bytes
+
+def get_realtime_data():
+ '''
+ real time get system data
+ :return:
+ '''
t1 = threading.Thread(
target=_ping_thread,
kwargs={
'host': CU,
'mark': '10010',
- 'port': PORBEPORT
+ 'port': PROBEPORT
}
)
t2 = threading.Thread(
@@ -199,7 +286,7 @@ def get_realtime_date():
kwargs={
'host': CT,
'mark': '189',
- 'port': PORBEPORT
+ 'port': PROBEPORT
}
)
t3 = threading.Thread(
@@ -207,20 +294,103 @@ def get_realtime_date():
kwargs={
'host': CM,
'mark': '10086',
- 'port': PORBEPORT
+ 'port': PROBEPORT
}
)
t4 = threading.Thread(
target=_net_speed,
)
- t1.setDaemon(True)
- t2.setDaemon(True)
- t3.setDaemon(True)
- t4.setDaemon(True)
- t1.start()
- t2.start()
- t3.start()
- t4.start()
+ t5 = threading.Thread(
+ target=_disk_io,
+ )
+ for ti in [t1, t2, t3, t4, t5]:
+ ti.daemon = True
+ ti.start()
+
+def _monitor_thread(name, host, interval, type):
+ lostPacket = 0
+ packet_queue = Queue(maxsize=ONLINE_PACKET_HISTORY_LEN)
+ while True:
+ if name not in monitorServer.keys():
+ break
+ if packet_queue.full():
+ if packet_queue.get() == 0:
+ lostPacket -= 1
+ try:
+ if type == "http":
+ address = host.replace("http://", "")
+ m = timeit.default_timer()
+ if PROBE_PROTOCOL_PREFER == 'ipv4':
+ IP = socket.getaddrinfo(address, None, socket.AF_INET)[0][4][0]
+ else:
+ IP = socket.getaddrinfo(address, None, socket.AF_INET6)[0][4][0]
+ monitorServer[name]["dns_time"] = int((timeit.default_timer() - m) * 1000)
+ m = timeit.default_timer()
+ k = socket.create_connection((IP, 80), timeout=6)
+ monitorServer[name]["connect_time"] = int((timeit.default_timer() - m) * 1000)
+ m = timeit.default_timer()
+ k.sendall("GET / HTTP/1.2\r\nHost:{}\r\nUser-Agent:ServerStatus/cppla\r\nConnection:close\r\n\r\n".format(address).encode('utf-8'))
+ response = b""
+ while True:
+ data = k.recv(4096)
+ if not data:
+ break
+ response += data
+ http_code = response.decode('utf-8').split('\r\n')[0].split()[1]
+ monitorServer[name]["download_time"] = int((timeit.default_timer() - m) * 1000)
+ k.close()
+ if http_code not in ['200', '204', '301', '302', '401']:
+ raise Exception("http code not in 200, 204, 301, 302, 401")
+ elif type == "https":
+ context = ssl._create_unverified_context()
+ address = host.replace("https://", "")
+ m = timeit.default_timer()
+ if PROBE_PROTOCOL_PREFER == 'ipv4':
+ IP = socket.getaddrinfo(address, None, socket.AF_INET)[0][4][0]
+ else:
+ IP = socket.getaddrinfo(address, None, socket.AF_INET6)[0][4][0]
+ monitorServer[name]["dns_time"] = int((timeit.default_timer() - m) * 1000)
+ m = timeit.default_timer()
+ k = socket.create_connection((IP, 443), timeout=6)
+ monitorServer[name]["connect_time"] = int((timeit.default_timer() - m) * 1000)
+ m = timeit.default_timer()
+ kk = context.wrap_socket(k, server_hostname=address)
+ kk.sendall("GET / HTTP/1.2\r\nHost:{}\r\nUser-Agent:ServerStatus/cppla\r\nConnection:close\r\n\r\n".format(address).encode('utf-8'))
+ response = b""
+ while True:
+ data = kk.recv(4096)
+ if not data:
+ break
+ response += data
+ http_code = response.decode('utf-8').split('\r\n')[0].split()[1]
+ monitorServer[name]["download_time"] = int((timeit.default_timer() - m) * 1000)
+ kk.close()
+ k.close()
+ if http_code not in ['200', '204', '301', '302', '401']:
+ raise Exception("http code not in 200, 204, 301, 302, 401")
+ elif type == "tcp":
+ m = timeit.default_timer()
+ if PROBE_PROTOCOL_PREFER == 'ipv4':
+ IP = socket.getaddrinfo(host.split(":")[0], None, socket.AF_INET)[0][4][0]
+ else:
+ IP = socket.getaddrinfo(host.split(":")[0], None, socket.AF_INET6)[0][4][0]
+ monitorServer[name]["dns_time"] = int((timeit.default_timer() - m) * 1000)
+ m = timeit.default_timer()
+ k = socket.create_connection((IP, int(host.split(":")[1])), timeout=6)
+ monitorServer[name]["connect_time"] = int((timeit.default_timer() - m) * 1000)
+ m = timeit.default_timer()
+ k.send(b"GET / HTTP/1.2\r\n\r\n")
+ k.recv(1024)
+ monitorServer[name]["download_time"] = int((timeit.default_timer() - m) * 1000)
+ k.close()
+ packet_queue.put(1)
+ except Exception as e:
+ lostPacket += 1
+ packet_queue.put(0)
+ if packet_queue.qsize() > 5:
+ monitorServer[name]["online_rate"] = 1 - float(lostPacket) / packet_queue.qsize()
+ time.sleep(interval)
+
def byte_str(object):
'''
@@ -248,7 +418,7 @@ def byte_str(object):
elif 'INTERVAL' in argc:
INTERVAL = int(argc.split('INTERVAL=')[-1])
socket.setdefaulttimeout(30)
- get_realtime_date()
+ get_realtime_data()
while 1:
try:
print("Connecting...")
@@ -268,6 +438,27 @@ def byte_str(object):
if data.find("You are connecting via") < 0:
data = byte_str(s.recv(1024))
print(data)
+ for i in data.split('\n'):
+ if "monitor" in i and "type" in i and "{" in i and "}" in i:
+ jdata = json.loads(i[i.find("{"):i.find("}")+1])
+ monitorServer[jdata.get("name")] = {
+ "type": jdata.get("type"),
+ "dns_time": 0,
+ "connect_time": 0,
+ "download_time": 0,
+ "online_rate": 1
+ }
+ t = threading.Thread(
+ target=_monitor_thread,
+ kwargs={
+ 'name': jdata.get("name"),
+ 'host': jdata.get("host"),
+ 'interval': jdata.get("interval"),
+ 'type': jdata.get("type")
+ }
+ )
+ t.daemon = True
+ t.start()
timer = 0
check_ip = 0
@@ -283,12 +474,10 @@ def byte_str(object):
CPU = get_cpu()
NET_IN, NET_OUT = liuliang()
Uptime = get_uptime()
- Load_1, Load_5, Load_15 = os.getloadavg() if 'linux' in sys.platform else (0.0, 0.0, 0.0)
+ Load_1, Load_5, Load_15 = os.getloadavg() if 'linux' in sys.platform or 'darwin' in sys.platform else (0.0, 0.0, 0.0)
MemoryTotal, MemoryUsed = get_memory()
SwapTotal, SwapUsed = get_swap()
HDDTotal, HDDUsed = get_hdd()
- IP_STATUS = ip_status()
-
array = {}
if not timer:
array['online' + str(check_ip)] = get_network(check_ip)
@@ -311,7 +500,6 @@ def byte_str(object):
array['network_tx'] = netSpeed.get("nettx")
array['network_in'] = NET_IN
array['network_out'] = NET_OUT
- array['ip_status'] = IP_STATUS
array['ping_10010'] = lostRate.get('10010') * 100
array['ping_189'] = lostRate.get('189') * 100
array['ping_10086'] = lostRate.get('10086') * 100
@@ -319,16 +507,20 @@ def byte_str(object):
array['time_189'] = pingTime.get('189')
array['time_10086'] = pingTime.get('10086')
array['tcp'], array['udp'], array['process'], array['thread'] = tupd()
-
+ array['io_read'] = diskIO.get("read")
+ array['io_write'] = diskIO.get("write")
+ array['custom'] = "
".join(f"{k}\\t解析: {v['dns_time']}\\t连接: {v['connect_time']}\\t下载: {v['download_time']}\\t在线率: {v['online_rate']*100:.2f}%
" for k, v in monitorServer.items())
s.send(byte_str("update " + json.dumps(array) + "\n"))
except KeyboardInterrupt:
raise
except socket.error:
+ monitorServer.clear()
print("Disconnected...")
if 's' in locals().keys():
del s
time.sleep(3)
except Exception as e:
+ monitorServer.clear()
print("Caught Exception:", e)
if 's' in locals().keys():
del s
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..42f8e12b
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,29 @@
+version: "3"
+services:
+ serverstatus:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ image: serverstatus_server
+ healthcheck:
+ test: curl --fail http://localhost:80 || bash -c 'kill -s 15 -1 && (sleep 10; kill -s 9 -1)'
+ interval: 30s
+ timeout: 10s
+ retries: 5
+ container_name: serverstatus
+ restart: unless-stopped
+ networks:
+ serverstatus-network:
+ ipv4_address: 172.23.0.2
+ volumes:
+ - ./server/config.json:/ServerStatus/server/config.json
+ - ./web/json:/usr/share/nginx/html/json
+ ports:
+ - 35601:35601
+ - 8080:80
+
+networks:
+ serverstatus-network:
+ ipam:
+ config:
+ - subnet: 172.23.0.0/24
diff --git a/plugin/Dockerfile-telegram b/plugin/Dockerfile-telegram
new file mode 100644
index 00000000..f9084efa
--- /dev/null
+++ b/plugin/Dockerfile-telegram
@@ -0,0 +1,10 @@
+FROM python:alpine
+
+LABEL maintainer="lidalao"
+LABEL version="0.0.1"
+LABEL description="Telegram Bot for ServerStatus"
+
+WORKDIR /app
+RUN pip install requests
+COPY ./bot-telegram.py .
+CMD [ "python", "./bot-telegram.py" ]
diff --git a/plugin/bot-telegram.py b/plugin/bot-telegram.py
new file mode 100644
index 00000000..67ef36e4
--- /dev/null
+++ b/plugin/bot-telegram.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+# coding: utf-8
+# Create by : https://github.com/lidalao/ServerStatus
+# 版本:0.0.1, 支持Python版本:2.7 to 3.9
+# 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
+
+import os
+import sys
+import requests
+import time
+import traceback
+
+NODE_STATUS_URL = 'http://serverstatus/json/stats.json'
+
+offs = []
+counterOff = {}
+counterOn = {}
+
+def _send(text):
+ chat_id = os.getenv('TG_CHAT_ID')
+ bot_token = os.environ.get('TG_BOT_TOKEN')
+ url = f"https://api.telegram.org/bot{bot_token}/sendMessage?parse_mode=HTML&disable_web_page_preview=true&chat_id=" + chat_id + "&text=" + text
+ try:
+ requests.get(url)
+ except Exception as e:
+ print("catch exception: ", traceback.format_exc())
+
+def send2tg(srv, flag):
+ if srv not in counterOff:
+ counterOff[srv] = 0
+ if srv not in counterOn:
+ counterOn[srv] = 0
+
+ if flag == 1 : # online
+ if srv in offs:
+ if counterOn[srv] < 10:
+ counterOn[srv] += 1
+ return
+ #1. Remove srv from offs; 2. Send to tg: I am online
+ offs.remove(srv)
+ counterOn[srv] = 0
+ text = 'Server Status' + '\n主机上线: ' + srv
+ _send(text)
+ else: #offline
+ if srv not in offs:
+ if counterOff[srv] < 10:
+ counterOff[srv] += 1
+ return
+ #1. Append srv to offs; 2. Send to tg: I am offline
+ offs.append(srv)
+ counterOff[srv] = 0
+ text = 'Server Status' + '\n主机下线: ' + srv
+ _send(text)
+
+def sscmd(address):
+ while True:
+ r = requests.get(url=address, headers={"User-Agent": "ServerStatus/20211116"})
+ try:
+ jsonR = r.json()
+ except Exception as e:
+ print('未发现任何节点')
+ continue
+ for i in jsonR["servers"]:
+ if i["online4"] is False and i["online6"] is False:
+ send2tg(i["name"], 0)
+ else:
+ send2tg(i["name"], 1)
+
+
+ time.sleep(3)
+
+if __name__ == '__main__':
+ sscmd(NODE_STATUS_URL)
diff --git a/plugin/docker-compose-telegram.yml b/plugin/docker-compose-telegram.yml
new file mode 100644
index 00000000..adbe5a72
--- /dev/null
+++ b/plugin/docker-compose-telegram.yml
@@ -0,0 +1,38 @@
+version: "3"
+services:
+ serverstatus:
+ build:
+ context: ..
+ dockerfile: Dockerfile
+ image: serverstatus_server
+ container_name: serverstatus
+ restart: unless-stopped
+ networks:
+ serverstatus-network:
+ ipv4_address: 172.23.0.2
+ volumes:
+ - ../server/config.json:/ServerStatus/server/config.json
+ - ../web/json:/usr/share/nginx/html/json
+ ports:
+ - 35601:35601
+ - 8080:80
+ bot:
+ build:
+ context: .
+ dockerfile: Dockerfile-telegram
+ image: serverstatus_bot
+ container_name: bot4sss
+ restart: unless-stopped
+ networks:
+ serverstatus-network:
+ ipv4_address: 172.23.0.3
+ environment:
+ - TG_CHAT_ID=${TG_CHAT_ID}
+ - TG_BOT_TOKEN=${TG_BOT_TOKEN}
+
+networks:
+ serverstatus-network:
+ name: serverstatus-network
+ ipam:
+ config:
+ - subnet: 172.23.0.0/24
diff --git a/server/Makefile b/server/Makefile
index 3ca07ff6..ccee6fbf 100644
--- a/server/Makefile
+++ b/server/Makefile
@@ -6,7 +6,7 @@ CFLAGS = -Wall -O2
#CXX = clang++
CXX = g++
-CXXFLAGS = -Wall -O2
+CXXFLAGS = -Wall -O2 -std=c++11
ODIR = obj
SDIR = src
@@ -26,7 +26,7 @@ $(ODIR)/%.o: $(SDIR)/%.cpp
$(CXX) -c $(INC) $(CXXFLAGS) $< -o $@
$(OUT): $(OBJS)
- $(CXX) $(LIBS) $^ -o $(OUT)
+ $(CXX) $(LIBS) $^ -o $(OUT) -lcurl
.PHONY: clean
diff --git a/server/config.json b/server/config.json
index d5e823bf..aa37dd50 100644
--- a/server/config.json
+++ b/server/config.json
@@ -1,37 +1,105 @@
-{"servers":
- [
+{
+ "servers": [
{
"username": "s01",
"name": "node1",
"type": "xen",
"host": "host1",
- "location": "cn",
- "password": "USER_DEFAULT_PASSWORD"
+ "location": "🇨🇳",
+ "password": "USER_DEFAULT_PASSWORD",
+ "monthstart": 1
},
{
"username": "s02",
"name": "node2",
"type": "vmware",
"host": "host2",
- "location": "jp",
- "password": "USER_DEFAULT_PASSWORD"
+ "location": "🇯🇵",
+ "password": "USER_DEFAULT_PASSWORD",
+ "monthstart": 1
},
{
"disabled": true,
"username": "s03",
"name": "node3",
- "type": "Nothing",
+ "type": "hyper",
"host": "host3",
- "location": "fr",
- "password": "USER_DEFAULT_PASSWORD"
+ "location": "🇫🇷",
+ "password": "USER_DEFAULT_PASSWORD",
+ "monthstart": 1
},
{
"username": "s04",
"name": "node4",
"type": "kvm",
"host": "host4",
- "location": "kr",
- "password": "USER_DEFAULT_PASSWORD"
+ "location": "🇰🇷",
+ "password": "USER_DEFAULT_PASSWORD",
+ "monthstart": 1
+ }
+ ],
+ "monitors": [
+ {
+ "name": "baidu",
+ "host": "https://www.baidu.com",
+ "interval": 300,
+ "type": "https"
+ },
+ {
+ "name": "114",
+ "host": "114.114.114.114:53",
+ "interval": 300,
+ "type": "tcp"
+ }
+ ],
+ "watchdog": [
+ {
+ "name": "cpu high warning,exclude username s01",
+ "rule": "cpu>90&load_1>5&username!='s01'",
+ "interval": 600,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "memory high warning, exclude less than 1GB vps",
+ "rule": "(memory_used/memory_total)*100>90&memory_total>1048576",
+ "interval": 300,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "offline warning",
+ "rule": "online4=0&online6=0",
+ "interval": 600,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "ddcc attack,limit type Oracle",
+ "rule": "tcp_count>600&type='Oracle'",
+ "interval": 300,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "month 999GB traffic warning",
+ "rule": "(network_out-last_network_out)/1024/1024/1024>999",
+ "interval": 3600,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "aliyun china free 18GB traffic warning",
+ "rule": "(network_out-last_network_out)/1024/1024/1024>18&(username='aliyun1'|username='aliyun2')",
+ "interval": 3600,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "packet loss rate warning",
+ "rule": "(ping_10010>10|ping_189>10|ping_10086>10)&(host='sgp'|host='qqhk'|host='hk-21-x'|host='hk-31-x')",
+ "interval": 3600,
+ "callback": "https://yourSMSurl"
+ },
+ {
+ "name": "you can parse an expression combining any known field",
+ "rule": "load_5>3",
+ "interval": 900,
+ "callback": "https://yourSMSurl"
}
]
-}
\ No newline at end of file
+}
diff --git a/server/src/exprtk.hpp b/server/src/exprtk.hpp
new file mode 100644
index 00000000..b4530ba0
--- /dev/null
+++ b/server/src/exprtk.hpp
@@ -0,0 +1,40983 @@
+/*
+ ******************************************************************
+ * C++ Mathematical Expression Toolkit Library *
+ * *
+ * Author: Arash Partow (1999-2023) *
+ * URL: https://www.partow.net/programming/exprtk/index.html *
+ * *
+ * Copyright notice: *
+ * Free use of the C++ Mathematical Expression Toolkit Library is *
+ * permitted under the guidelines and in accordance with the most *
+ * current version of the MIT License. *
+ * https://www.opensource.org/licenses/MIT *
+ * *
+ * Example expressions: *
+ * (00) (y + x / y) * (x - y / x) *
+ * (01) (x^2 / sin(2 * pi / y)) - x / 2 *
+ * (02) sqrt(1 - (x^2)) *
+ * (03) 1 - sin(2 * x) + cos(pi / y) *
+ * (04) a * exp(2 * t) + c *
+ * (05) if(((x + 2) == 3) and ((y + 5) <= 9),1 + w, 2 / z) *
+ * (06) (avg(x,y) <= x + y ? x - y : x * y) + 2 * pi / x *
+ * (07) z := x + sin(2 * pi / y) *
+ * (08) u := 2 * (pi * z) / (w := x + cos(y / pi)) *
+ * (09) clamp(-1,sin(2 * pi * x) + cos(y / 2 * pi),+1) *
+ * (10) inrange(-2,m,+2) == if(({-2 <= m} and [m <= +2]),1,0) *
+ * (11) (2sin(x)cos(2y)7 + 1) == (2 * sin(x) * cos(2*y) * 7 + 1) *
+ * (12) (x ilike 's*ri?g') and [y < (3 z^7 + w)] *
+ * *
+ ******************************************************************
+*/
+
+
+#ifndef INCLUDE_EXPRTK_HPP
+#define INCLUDE_EXPRTK_HPP
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include