ryry-cli 6.17__tar.gz → 6.18__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ryry_cli-6.17/ryry_cli.egg-info → ryry_cli-6.18}/PKG-INFO +1 -3
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/constant.py +2 -2
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/ryry_webapi.py +0 -10
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/shared_memory.py +18 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/store.py +1 -1
- ryry_cli-6.18/ryry/taskUtils.py +496 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/upload.py +0 -28
- {ryry_cli-6.17 → ryry_cli-6.18/ryry_cli.egg-info}/PKG-INFO +1 -3
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry_cli.egg-info/requires.txt +0 -3
- {ryry_cli-6.17 → ryry_cli-6.18}/setup.py +1 -2
- ryry_cli-6.17/ryry/taskUtils.py +0 -304
- {ryry_cli-6.17 → ryry_cli-6.18}/LICENSE +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/README.md +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/__init__.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/daemon_base.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/daemon_manager.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/main.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/proxy_manager.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/ryry_server_socket.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/ryry_service.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/ryry_widget.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/script_template/__init__.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/script_template/daemon.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/script_template/main.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/script_template/run.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/server_func.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/task.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry/utils.py +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry_cli.egg-info/SOURCES.txt +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry_cli.egg-info/dependency_links.txt +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry_cli.egg-info/entry_points.txt +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/ryry_cli.egg-info/top_level.txt +0 -0
- {ryry_cli-6.17 → ryry_cli-6.18}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ryry-cli
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.18
|
|
4
4
|
Summary: ryry tools
|
|
5
5
|
Home-page: https://github.com/dalipenMedia
|
|
6
6
|
Author: dalipen
|
|
@@ -24,8 +24,6 @@ Requires-Dist: urlparser
|
|
|
24
24
|
Requires-Dist: urllib3
|
|
25
25
|
Requires-Dist: portalocker
|
|
26
26
|
Requires-Dist: PyYAML
|
|
27
|
-
Provides-Extra: with-mecord
|
|
28
|
-
Requires-Dist: mecord-cli>=0.7.407; extra == "with-mecord"
|
|
29
27
|
|
|
30
28
|
ryry Python Tool
|
|
31
29
|
===============================================
|
|
@@ -311,16 +311,6 @@ def upload(localFilePath, ext, keepItAlways=False, needTranscode=False, addition
|
|
|
311
311
|
return _upload(localFilePath, ext, keepItAlways=keepItAlways)
|
|
312
312
|
except:
|
|
313
313
|
pass
|
|
314
|
-
from mecord import upload as mecord_upload
|
|
315
|
-
try:
|
|
316
|
-
url = mecord_upload.upload(localFilePath,
|
|
317
|
-
None,
|
|
318
|
-
"sg",
|
|
319
|
-
needTranscode=needTranscode,
|
|
320
|
-
needAddtionUrl=True) #mecord会使用签名信息,强制needAddtionUrl
|
|
321
|
-
return _changeDomain(url, "m.mecordai.com")
|
|
322
|
-
except:
|
|
323
|
-
pass
|
|
324
314
|
def upload_oss(localFilePath, ext, bucket, endpoint):
|
|
325
315
|
import hashlib
|
|
326
316
|
import hmac
|
|
@@ -78,6 +78,23 @@ class SharedMemoryService:
|
|
|
78
78
|
return 0o666
|
|
79
79
|
return 0o600
|
|
80
80
|
|
|
81
|
+
def _path_permission_info(self, path: str) -> str:
|
|
82
|
+
try:
|
|
83
|
+
st = os.stat(path)
|
|
84
|
+
return f"mode={oct(st.st_mode & 0o7777)} uid={st.st_uid} gid={st.st_gid}"
|
|
85
|
+
except OSError as e:
|
|
86
|
+
return f"stat failed: {e}"
|
|
87
|
+
|
|
88
|
+
def _assert_shared_dir_writable(self):
|
|
89
|
+
if os.access(self.data_dir, os.W_OK | os.X_OK):
|
|
90
|
+
return
|
|
91
|
+
raise PermissionError(
|
|
92
|
+
f"shared memory dir is not writable: {self.data_dir} "
|
|
93
|
+
f"({self._path_permission_info(self.data_dir)}). "
|
|
94
|
+
f"fix: sudo chown -R $(id -u):$(id -g) {self.data_dir} "
|
|
95
|
+
f"or sudo chmod 1777 {self.data_dir}"
|
|
96
|
+
)
|
|
97
|
+
|
|
81
98
|
def _prepare_existing_shared_file(self, path: str):
|
|
82
99
|
if os.path.isdir(path):
|
|
83
100
|
raise PermissionError(f"shared memory file path is a directory: {path}")
|
|
@@ -98,6 +115,7 @@ class SharedMemoryService:
|
|
|
98
115
|
"""确保固定共享目录和锁文件可写"""
|
|
99
116
|
os.makedirs(self.data_dir, mode=self._shared_dir_mode(), exist_ok=True)
|
|
100
117
|
self._chmod_if_possible(self.data_dir, self._shared_dir_mode())
|
|
118
|
+
self._assert_shared_dir_writable()
|
|
101
119
|
self._touch_shared_file(self.lock_file)
|
|
102
120
|
self._touch_shared_file(self.process_count_lock_file)
|
|
103
121
|
self._prepare_existing_shared_file(self.data_file)
|
|
@@ -177,7 +177,7 @@ def extend():
|
|
|
177
177
|
_genExtend()
|
|
178
178
|
elif read_data["extend"].get("device_id", "") != read_data.get("deviceInfo", {}).get("device_id", ""):
|
|
179
179
|
_genExtend()
|
|
180
|
-
elif read_data["extend"].get("app", "") == "ryry-cli 6.
|
|
180
|
+
elif read_data["extend"].get("app", "") == "ryry-cli 6.18":
|
|
181
181
|
_genExtend()
|
|
182
182
|
elif any(k not in read_data["extend"] for k in ["device_id", "host_name", "cpu", "memory", "disk", "gpu"]):
|
|
183
183
|
_genExtend()
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
import datetime
|
|
4
|
+
import json
|
|
5
|
+
import socket
|
|
6
|
+
from io import BytesIO
|
|
7
|
+
from requests_toolbelt import MultipartEncoder
|
|
8
|
+
from urllib import parse
|
|
9
|
+
import base64
|
|
10
|
+
import calendar, time
|
|
11
|
+
import hashlib
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
from threading import RLock
|
|
14
|
+
from ryry import utils, constant
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import fcntl
|
|
18
|
+
except ImportError:
|
|
19
|
+
fcntl = None
|
|
20
|
+
|
|
21
|
+
# 获取版本信息的兼容函数
|
|
22
|
+
def get_version():
|
|
23
|
+
try:
|
|
24
|
+
from importlib.metadata import version
|
|
25
|
+
return version("ryry-cli")
|
|
26
|
+
except ImportError:
|
|
27
|
+
from importlib_metadata import version
|
|
28
|
+
return version("ryry-cli")
|
|
29
|
+
|
|
30
|
+
task_config_file = os.path.join(constant.base_path, f"task_config.txt")
|
|
31
|
+
def taskInfoWithFirstTask():
|
|
32
|
+
if os.path.exists(task_config_file):
|
|
33
|
+
with open(task_config_file, 'r', encoding='utf-8') as f:
|
|
34
|
+
data = json.load(f)
|
|
35
|
+
for it in data:
|
|
36
|
+
if it not in ["last_task_pts"]:
|
|
37
|
+
return it, data[it]
|
|
38
|
+
return None, None
|
|
39
|
+
def taskInfoWithUUID(taskUUID):
|
|
40
|
+
if os.path.exists(task_config_file):
|
|
41
|
+
with open(task_config_file, 'r', encoding='utf-8') as f:
|
|
42
|
+
task_config = json.load(f)
|
|
43
|
+
if taskUUID in task_config:
|
|
44
|
+
return task_config[taskUUID]
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
WECHAT_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=1b006560-7012-4bf0-8c5e-a23830bc0a0b"
|
|
48
|
+
def uploadFile2Wechat(filepath):
|
|
49
|
+
real_robot_url = WECHAT_URL
|
|
50
|
+
params = parse.parse_qs( parse.urlparse( real_robot_url ).query )
|
|
51
|
+
webHookKey=params['key'][0]
|
|
52
|
+
upload_url = f'https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key={webHookKey}&type=file'
|
|
53
|
+
headers = {"Accept": "application/json, text/plain, */*", "Accept-Encoding": "gzip, deflate",
|
|
54
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36"}
|
|
55
|
+
filename = os.path.basename(filepath)
|
|
56
|
+
try:
|
|
57
|
+
multipart = MultipartEncoder(
|
|
58
|
+
fields={'filename': filename, 'filelength': '', 'name': 'media', 'media': (filename, open(filepath, 'rb'), 'application/octet-stream')},
|
|
59
|
+
boundary='-------------------------acebdf13572468')
|
|
60
|
+
headers['Content-Type'] = multipart.content_type
|
|
61
|
+
resp = requests.post(upload_url, headers=headers, data=multipart, timeout=300)
|
|
62
|
+
json_res = resp.json()
|
|
63
|
+
if json_res.get('media_id'):
|
|
64
|
+
return json_res.get('media_id')
|
|
65
|
+
except Exception as e:
|
|
66
|
+
return ""
|
|
67
|
+
def notifyWechatRobot(param):
|
|
68
|
+
real_robot_url = WECHAT_URL
|
|
69
|
+
try:
|
|
70
|
+
s = requests.session()
|
|
71
|
+
s.headers.update({'Connection':'close'})
|
|
72
|
+
headers = dict()
|
|
73
|
+
headers['Content-Type'] = "application/json"
|
|
74
|
+
res = s.post(real_robot_url, json.dumps(param), headers=headers, verify=False, timeout=30)
|
|
75
|
+
s.close()
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(f"===== qyapi.weixin.qq.com fail ", True)
|
|
78
|
+
|
|
79
|
+
logs = {}
|
|
80
|
+
def taskPrint(taskUUID, msg):
|
|
81
|
+
global logs
|
|
82
|
+
if (taskUUID == None or len(taskUUID) == 0) and msg == None:
|
|
83
|
+
return
|
|
84
|
+
if taskUUID and msg == None:
|
|
85
|
+
del logs[taskUUID]
|
|
86
|
+
return
|
|
87
|
+
if taskUUID and msg:
|
|
88
|
+
if taskUUID not in logs:
|
|
89
|
+
logs[taskUUID] = []
|
|
90
|
+
logs[taskUUID].append(msg)
|
|
91
|
+
if taskUUID == None and msg:
|
|
92
|
+
for uuid in logs:
|
|
93
|
+
logs[uuid].append(msg)
|
|
94
|
+
print(msg)
|
|
95
|
+
def getTaskLog(taskUUID):
|
|
96
|
+
if taskUUID in logs:
|
|
97
|
+
return logs[taskUUID]
|
|
98
|
+
return []
|
|
99
|
+
|
|
100
|
+
def _uploadLog(taskUUID):
|
|
101
|
+
log_path = f"{constant.base_path}/log_{taskUUID}.log"
|
|
102
|
+
try:
|
|
103
|
+
with open(log_path, 'w', encoding='utf-8') as f:
|
|
104
|
+
f.write("\n".join(getTaskLog(taskUUID)))
|
|
105
|
+
notifyWechatRobot({
|
|
106
|
+
"msgtype": "file",
|
|
107
|
+
"file": {
|
|
108
|
+
"media_id": uploadFile2Wechat(log_path)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
except:
|
|
112
|
+
pass
|
|
113
|
+
finally:
|
|
114
|
+
if os.path.exists(log_path):
|
|
115
|
+
os.remove(log_path)
|
|
116
|
+
|
|
117
|
+
def notifyTaskFail(taskUUID, reason):
|
|
118
|
+
try:
|
|
119
|
+
real_reason = ""
|
|
120
|
+
if len(reason) > 2010:
|
|
121
|
+
real_reason = f"{reason[0:1000]}\n...\n{reason[len(reason)-1000:]}"
|
|
122
|
+
else:
|
|
123
|
+
real_reason = reason
|
|
124
|
+
notifyWechatRobot({
|
|
125
|
+
"msgtype": "markdown",
|
|
126
|
+
"markdown": {
|
|
127
|
+
"content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> 执行任务<{taskUUID}>失败\n<{real_reason}>"
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
_uploadLog(taskUUID)
|
|
131
|
+
except:
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
def notifyServerError(taskUUID):
|
|
135
|
+
try:
|
|
136
|
+
notifyWechatRobot({
|
|
137
|
+
"msgtype": "markdown",
|
|
138
|
+
"markdown": {
|
|
139
|
+
"content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> 执行任务<{taskUUID}>上报失败, retry..."
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
except:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
def notifyScriptError(taskUUID):
|
|
146
|
+
try:
|
|
147
|
+
notifyWechatRobot({
|
|
148
|
+
"msgtype": "markdown",
|
|
149
|
+
"markdown": {
|
|
150
|
+
"content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> 执行任务<{taskUUID}>异常"
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
_uploadLog(taskUUID)
|
|
154
|
+
except:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
def idlingNotify(cnt):
|
|
158
|
+
device_id = utils.generate_unique_id()
|
|
159
|
+
machine_name = socket.gethostname()
|
|
160
|
+
hour = int(float(cnt)/(60.0*60.0))
|
|
161
|
+
if hour<72:
|
|
162
|
+
if hour not in [1, 2, 3, 10, 30, 50, 70]:
|
|
163
|
+
return
|
|
164
|
+
notifyWechatRobot({
|
|
165
|
+
"msgtype": "text",
|
|
166
|
+
"text": {
|
|
167
|
+
"content": f"机器<{machine_name}[{device_id}]> 空转{hour}小时"
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
def onlineNotify():
|
|
172
|
+
device_id = utils.generate_unique_id()
|
|
173
|
+
machine_name = socket.gethostname()
|
|
174
|
+
ver = get_version()
|
|
175
|
+
notifyWechatRobot({
|
|
176
|
+
"msgtype": "text",
|
|
177
|
+
"text": {
|
|
178
|
+
"content": f"机器<{machine_name}[{device_id}]>[{ver}] 上线"
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
def restartNotify(msg):
|
|
183
|
+
device_id = utils.generate_unique_id()
|
|
184
|
+
machine_name = socket.gethostname()
|
|
185
|
+
notifyWechatRobot({
|
|
186
|
+
"msgtype": "text",
|
|
187
|
+
"text": {
|
|
188
|
+
"content": f"机器<{machine_name}[{device_id}]> 即将下线,原因:{msg}"
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
def offlineNotify():
|
|
193
|
+
device_id = utils.generate_unique_id()
|
|
194
|
+
machine_name = socket.gethostname()
|
|
195
|
+
ver = get_version()
|
|
196
|
+
notifyWechatRobot({
|
|
197
|
+
"msgtype": "text",
|
|
198
|
+
"text": {
|
|
199
|
+
"content": f"机器<{machine_name}[{device_id}]> 下线"
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
#===================================================== counter ===============================================#
|
|
204
|
+
task_counter_file = os.path.join(constant.base_path, "task_counter.txt")
|
|
205
|
+
task_counter_lock_file = os.path.join(constant.base_path, "task_counter.lock")
|
|
206
|
+
counter_lock = RLock()
|
|
207
|
+
|
|
208
|
+
@contextmanager
|
|
209
|
+
def counterFileLock():
|
|
210
|
+
with counter_lock:
|
|
211
|
+
lock_fp = open(task_counter_lock_file, 'a+', encoding='utf-8')
|
|
212
|
+
try:
|
|
213
|
+
if fcntl:
|
|
214
|
+
fcntl.flock(lock_fp.fileno(), fcntl.LOCK_EX)
|
|
215
|
+
yield
|
|
216
|
+
finally:
|
|
217
|
+
if fcntl:
|
|
218
|
+
fcntl.flock(lock_fp.fileno(), fcntl.LOCK_UN)
|
|
219
|
+
lock_fp.close()
|
|
220
|
+
|
|
221
|
+
def _readCounterData():
|
|
222
|
+
if os.path.exists(task_counter_file) == False:
|
|
223
|
+
return {}
|
|
224
|
+
try:
|
|
225
|
+
with open(task_counter_file, 'r', encoding='utf-8') as f:
|
|
226
|
+
data = json.load(f)
|
|
227
|
+
if isinstance(data, dict):
|
|
228
|
+
return data
|
|
229
|
+
except:
|
|
230
|
+
pass
|
|
231
|
+
return {}
|
|
232
|
+
|
|
233
|
+
def _writeCounterData(data):
|
|
234
|
+
tmp_file = f"{task_counter_file}.tmp"
|
|
235
|
+
with open(tmp_file, 'w', encoding='utf-8') as f:
|
|
236
|
+
json.dump(data, f)
|
|
237
|
+
os.replace(tmp_file, task_counter_file)
|
|
238
|
+
|
|
239
|
+
def _hourKeys(data):
|
|
240
|
+
return [str(i) for i in range(0, 24) if str(i) in data and isinstance(data[str(i)], dict)]
|
|
241
|
+
|
|
242
|
+
def _resetCounterData(counter_date, last_report_date=None):
|
|
243
|
+
data = {
|
|
244
|
+
"_counter_date": counter_date
|
|
245
|
+
}
|
|
246
|
+
if last_report_date:
|
|
247
|
+
data["_last_report_date"] = last_report_date
|
|
248
|
+
return data
|
|
249
|
+
|
|
250
|
+
def _counterReportStats(data):
|
|
251
|
+
s_cnt = 0
|
|
252
|
+
f_cnt = 0
|
|
253
|
+
all_day_usage = 0
|
|
254
|
+
s_l = []
|
|
255
|
+
f_l = []
|
|
256
|
+
usage_l = []
|
|
257
|
+
for i in range(0,24):
|
|
258
|
+
usage = 0
|
|
259
|
+
if str(i) in data:
|
|
260
|
+
s_cnt += data[str(i)]["success"]
|
|
261
|
+
f_cnt += data[str(i)]["fail"]
|
|
262
|
+
s_l.append(data[str(i)]["success"])
|
|
263
|
+
f_l.append(data[str(i)]["fail"])
|
|
264
|
+
if "usage" in data[str(i)]:
|
|
265
|
+
usage = data[str(i)]["usage"]
|
|
266
|
+
all_day_usage += usage
|
|
267
|
+
else:
|
|
268
|
+
s_cnt += 0
|
|
269
|
+
f_cnt += 0
|
|
270
|
+
s_l.append(0)
|
|
271
|
+
f_l.append(0)
|
|
272
|
+
usage_l.append(int((float(usage)/float(60*60))*100) if usage > 0 else 0)
|
|
273
|
+
usage_percentage = int((float(all_day_usage)/float(24*60*60))*100)
|
|
274
|
+
return {
|
|
275
|
+
"success": s_cnt,
|
|
276
|
+
"fail": f_cnt,
|
|
277
|
+
"total": s_cnt + f_cnt,
|
|
278
|
+
"usage": usage_percentage,
|
|
279
|
+
"hour_success": s_l,
|
|
280
|
+
"hour_fail": f_l,
|
|
281
|
+
"hour_usage": usage_l
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
def _sendCounterMarkdownReport(report_date, stats):
|
|
285
|
+
notifyWechatRobot({
|
|
286
|
+
"msgtype": "markdown",
|
|
287
|
+
"markdown": {
|
|
288
|
+
"content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> {report_date} 日报 \n\n\
|
|
289
|
+
>过去24小时执行任务<<font color=\"warning\">{stats['total']}</font>>个, 负载<<font color=\"warning\">{stats['usage']}%</font>> \n\
|
|
290
|
+
>成功<font color=\"warning\">{stats['success']}</font>个 \n\
|
|
291
|
+
>失败<font color=\"warning\">{stats['fail']}</font>个"
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
def _reportFont(size, bold=False):
|
|
296
|
+
try:
|
|
297
|
+
from PIL import ImageFont
|
|
298
|
+
font_paths = [
|
|
299
|
+
"/System/Library/Fonts/STHeiti Medium.ttc" if bold else "/System/Library/Fonts/STHeiti Light.ttc",
|
|
300
|
+
"/System/Library/Fonts/PingFang.ttc",
|
|
301
|
+
"/Library/Fonts/Arial Unicode.ttf",
|
|
302
|
+
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
|
303
|
+
"/usr/share/fonts/opentype/noto/NotoSansCJKSC-Regular.otf",
|
|
304
|
+
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
|
|
305
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
|
306
|
+
"C:/Windows/Fonts/msyhbd.ttc" if bold else "C:/Windows/Fonts/msyh.ttc",
|
|
307
|
+
]
|
|
308
|
+
for font_path in font_paths:
|
|
309
|
+
if font_path and os.path.exists(font_path):
|
|
310
|
+
return ImageFont.truetype(font_path, size)
|
|
311
|
+
return ImageFont.load_default()
|
|
312
|
+
except:
|
|
313
|
+
return None
|
|
314
|
+
|
|
315
|
+
def _text(draw, xy, text, fill, font=None, **kwargs):
|
|
316
|
+
try:
|
|
317
|
+
draw.text(xy, text, fill=fill, font=font, **kwargs)
|
|
318
|
+
except TypeError:
|
|
319
|
+
kwargs.pop("anchor", None)
|
|
320
|
+
draw.text(xy, text, fill=fill, font=font, **kwargs)
|
|
321
|
+
except UnicodeEncodeError:
|
|
322
|
+
safe_text = text.encode("ascii", "ignore").decode("ascii")
|
|
323
|
+
try:
|
|
324
|
+
draw.text(xy, safe_text, fill=fill, font=font, **kwargs)
|
|
325
|
+
except TypeError:
|
|
326
|
+
kwargs.pop("anchor", None)
|
|
327
|
+
draw.text(xy, safe_text, fill=fill, font=font, **kwargs)
|
|
328
|
+
|
|
329
|
+
def _roundedRectangle(draw, box, radius, fill, outline=None):
|
|
330
|
+
if hasattr(draw, "rounded_rectangle"):
|
|
331
|
+
draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline)
|
|
332
|
+
else:
|
|
333
|
+
draw.rectangle(box, fill=fill, outline=outline)
|
|
334
|
+
|
|
335
|
+
def _counterReportImageBytes(report_date, stats):
|
|
336
|
+
from PIL import Image, ImageDraw
|
|
337
|
+
|
|
338
|
+
width = 1120
|
|
339
|
+
height = 700
|
|
340
|
+
margin = 54
|
|
341
|
+
bg = "#f6f8fb"
|
|
342
|
+
ink = "#172033"
|
|
343
|
+
muted = "#667085"
|
|
344
|
+
grid = "#d8dee8"
|
|
345
|
+
green = "#34a853"
|
|
346
|
+
red = "#ea4335"
|
|
347
|
+
blue = "#1a73e8"
|
|
348
|
+
|
|
349
|
+
img = Image.new("RGB", (width, height), bg)
|
|
350
|
+
draw = ImageDraw.Draw(img)
|
|
351
|
+
title_font = _reportFont(36, True)
|
|
352
|
+
card_font = _reportFont(22, True)
|
|
353
|
+
text_font = _reportFont(18)
|
|
354
|
+
small_font = _reportFont(14)
|
|
355
|
+
|
|
356
|
+
host = socket.gethostname()
|
|
357
|
+
_text(draw, (margin, 38), f"机器<{host}> {report_date} 日报", ink, title_font)
|
|
358
|
+
_text(draw, (margin, 88), "每日任务执行概览", muted, text_font)
|
|
359
|
+
|
|
360
|
+
cards = [
|
|
361
|
+
("执行任务", stats["total"], blue),
|
|
362
|
+
("成功", stats["success"], green),
|
|
363
|
+
("失败", stats["fail"], red),
|
|
364
|
+
("负载", f"{stats['usage']}%", "#f29900"),
|
|
365
|
+
]
|
|
366
|
+
card_y = 126
|
|
367
|
+
card_w = 238
|
|
368
|
+
card_h = 92
|
|
369
|
+
gap = 18
|
|
370
|
+
for idx, (label, value, color) in enumerate(cards):
|
|
371
|
+
x = margin + idx * (card_w + gap)
|
|
372
|
+
_roundedRectangle(draw, (x, card_y, x + card_w, card_y + card_h), 16, "#ffffff", "#e4e8f0")
|
|
373
|
+
_text(draw, (x + 22, card_y + 18), label, muted, text_font)
|
|
374
|
+
_text(draw, (x + 22, card_y + 46), str(value), color, card_font)
|
|
375
|
+
|
|
376
|
+
chart_left = margin
|
|
377
|
+
chart_top = 272
|
|
378
|
+
chart_right = width - margin
|
|
379
|
+
chart_bottom = height - 86
|
|
380
|
+
chart_w = chart_right - chart_left
|
|
381
|
+
_roundedRectangle(draw, (chart_left - 16, chart_top - 26, chart_right + 16, chart_bottom + 48), 18, "#ffffff", "#e4e8f0")
|
|
382
|
+
_text(draw, (chart_left, chart_top - 6), "24小时任务分布", ink, card_font)
|
|
383
|
+
_text(draw, (chart_right - 220, chart_top - 2), "绿色=成功 红色=失败", muted, small_font)
|
|
384
|
+
|
|
385
|
+
plot_top = chart_top + 48
|
|
386
|
+
plot_bottom = chart_bottom - 36
|
|
387
|
+
plot_h = plot_bottom - plot_top
|
|
388
|
+
max_count = max([s + f for s, f in zip(stats["hour_success"], stats["hour_fail"])] + [1])
|
|
389
|
+
for i in range(0, 5):
|
|
390
|
+
y = plot_bottom - int(plot_h * i / 4)
|
|
391
|
+
draw.line((chart_left, y, chart_right, y), fill=grid, width=1)
|
|
392
|
+
tick = int(max_count * i / 4)
|
|
393
|
+
_text(draw, (chart_left - 36, y - 8), str(tick), muted, small_font)
|
|
394
|
+
|
|
395
|
+
slot = chart_w / 24.0
|
|
396
|
+
bar_w = max(12, int(slot * 0.54))
|
|
397
|
+
for hour in range(0, 24):
|
|
398
|
+
success = stats["hour_success"][hour]
|
|
399
|
+
fail = stats["hour_fail"][hour]
|
|
400
|
+
total = success + fail
|
|
401
|
+
x_center = chart_left + slot * hour + slot / 2
|
|
402
|
+
x0 = int(x_center - bar_w / 2)
|
|
403
|
+
x1 = int(x_center + bar_w / 2)
|
|
404
|
+
total_h = int((float(total) / max_count) * plot_h) if total > 0 else 0
|
|
405
|
+
y_total = plot_bottom - total_h
|
|
406
|
+
fail_h = int((float(fail) / max(total, 1)) * total_h) if total > 0 else 0
|
|
407
|
+
y_fail = y_total + fail_h
|
|
408
|
+
if success > 0:
|
|
409
|
+
draw.rectangle((x0, y_fail, x1, plot_bottom), fill=green)
|
|
410
|
+
if fail > 0:
|
|
411
|
+
draw.rectangle((x0, y_total, x1, y_fail), fill=red)
|
|
412
|
+
if total > 0:
|
|
413
|
+
_text(draw, (x_center, y_total - 18), str(total), ink, small_font, anchor="mm")
|
|
414
|
+
if hour % 2 == 0:
|
|
415
|
+
_text(draw, (x_center, plot_bottom + 18), str(hour), muted, small_font, anchor="mm")
|
|
416
|
+
if stats["hour_usage"][hour] > 0 and hour % 3 == 0:
|
|
417
|
+
_text(draw, (x_center, plot_bottom + 38), f"{stats['hour_usage'][hour]}%", muted, small_font, anchor="mm")
|
|
418
|
+
|
|
419
|
+
_text(draw, (chart_left, height - 50), "柱顶数字为任务数;底部百分比为该小时任务累计忙时。", muted, small_font)
|
|
420
|
+
output = BytesIO()
|
|
421
|
+
img.save(output, format="PNG", optimize=True)
|
|
422
|
+
return output.getvalue()
|
|
423
|
+
|
|
424
|
+
def _sendCounterImageReport(report_date, stats):
|
|
425
|
+
image_bytes = _counterReportImageBytes(report_date, stats)
|
|
426
|
+
md5 = hashlib.md5()
|
|
427
|
+
md5.update(image_bytes)
|
|
428
|
+
notifyWechatRobot({
|
|
429
|
+
"msgtype": "image",
|
|
430
|
+
"image": {
|
|
431
|
+
"base64": str(base64.b64encode(image_bytes), encoding='utf-8'),
|
|
432
|
+
"md5": md5.hexdigest()
|
|
433
|
+
}
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
def _notifyCounterWithData(data, report_date):
|
|
437
|
+
stats = _counterReportStats(data)
|
|
438
|
+
try:
|
|
439
|
+
_sendCounterImageReport(report_date, stats)
|
|
440
|
+
except:
|
|
441
|
+
_sendCounterMarkdownReport(report_date, stats)
|
|
442
|
+
|
|
443
|
+
def _rolloverCounterIfNeed(data, now):
|
|
444
|
+
today = now.strftime('%Y-%m-%d')
|
|
445
|
+
hour_keys = _hourKeys(data)
|
|
446
|
+
if "_counter_date" not in data:
|
|
447
|
+
# Legacy counters have no date and may already contain stale totals.
|
|
448
|
+
# Start a fresh dated counter so an old file cannot pollute tomorrow's report.
|
|
449
|
+
return _resetCounterData(today), True
|
|
450
|
+
counter_date = data.get("_counter_date")
|
|
451
|
+
if counter_date == today:
|
|
452
|
+
return data, False
|
|
453
|
+
if len(hour_keys) == 0:
|
|
454
|
+
return _resetCounterData(today, data.get("_last_report_date")), True
|
|
455
|
+
if data.get("_last_report_date") != counter_date:
|
|
456
|
+
_notifyCounterWithData(data, counter_date)
|
|
457
|
+
return _resetCounterData(today, counter_date), True
|
|
458
|
+
|
|
459
|
+
def notifyCounterIfNeed():
|
|
460
|
+
try:
|
|
461
|
+
with counterFileLock():
|
|
462
|
+
data = _readCounterData()
|
|
463
|
+
data, changed = _rolloverCounterIfNeed(data, datetime.datetime.now())
|
|
464
|
+
if changed:
|
|
465
|
+
_writeCounterData(data)
|
|
466
|
+
except:
|
|
467
|
+
pass
|
|
468
|
+
|
|
469
|
+
def saveCounter(taskUUID, duration, isSuccess):
|
|
470
|
+
try:
|
|
471
|
+
with counterFileLock():
|
|
472
|
+
now = datetime.datetime.now()
|
|
473
|
+
data = _readCounterData()
|
|
474
|
+
data, _ = _rolloverCounterIfNeed(data, now)
|
|
475
|
+
if "_counter_date" not in data:
|
|
476
|
+
data["_counter_date"] = now.strftime('%Y-%m-%d')
|
|
477
|
+
#update
|
|
478
|
+
now_hour = str(now.hour)
|
|
479
|
+
if now_hour in data:
|
|
480
|
+
if isSuccess:
|
|
481
|
+
data[now_hour]["success"] += 1
|
|
482
|
+
else:
|
|
483
|
+
data[now_hour]["fail"] += 1
|
|
484
|
+
if "usage" not in data[now_hour]:
|
|
485
|
+
data[now_hour]["usage"] = 0
|
|
486
|
+
data[now_hour]["usage"] += duration
|
|
487
|
+
else:
|
|
488
|
+
data[now_hour] = {
|
|
489
|
+
"success" : 1 if isSuccess else 0,
|
|
490
|
+
"fail" : 0 if isSuccess else 1,
|
|
491
|
+
"usage" : duration
|
|
492
|
+
}
|
|
493
|
+
#save
|
|
494
|
+
_writeCounterData(data)
|
|
495
|
+
except:
|
|
496
|
+
pass
|
|
@@ -59,12 +59,6 @@ def upload(src, taskUUID=None, needTranscode=False, keepItAlways=False, addition
|
|
|
59
59
|
taskInfo = ryry_taskUtils.taskInfoWithUUID(taskUUID)
|
|
60
60
|
if taskInfo:
|
|
61
61
|
targetDomain = taskInfo.get("domain", None)
|
|
62
|
-
if targetDomain == None or len(targetDomain) <= 0:
|
|
63
|
-
from mecord import taskUtils as mecord_taskUtils
|
|
64
|
-
uuid_has_country_info = mecord_taskUtils.taskCountryWithUUID(taskUUID)
|
|
65
|
-
if uuid_has_country_info:
|
|
66
|
-
#这里不区分meco还是mecord,请求meco/mecord后端接口时,根据本地状态来向不同服务器请求
|
|
67
|
-
targetDomain = "https://m.mecoai.cn/"
|
|
68
62
|
|
|
69
63
|
needDeleteSrc = False
|
|
70
64
|
if needTranscode:
|
|
@@ -96,12 +90,6 @@ def uploadByDomain(src, targetDomain,
|
|
|
96
90
|
ext = os.path.splitext(file_name)[-1][1:]
|
|
97
91
|
|
|
98
92
|
parsed_domain, upload_path = parseDomainAndPath(targetDomain)
|
|
99
|
-
if isMecordConfig(parsed_domain):
|
|
100
|
-
from mecord import upload as mecord_upload
|
|
101
|
-
return mecord_upload.upload(src, taskUUID,
|
|
102
|
-
needTranscode=needTranscode,
|
|
103
|
-
needAddtionUrl=True) #mecord会使用签名信息,强制needAddtionUrl
|
|
104
|
-
|
|
105
93
|
if isMekoConfig(parsed_domain):
|
|
106
94
|
return uploadToMeko(src, ext=ext)
|
|
107
95
|
|
|
@@ -338,8 +326,6 @@ DOMAIN_CONFIG = {
|
|
|
338
326
|
"res.zjtemplate.com": {"type": "oss", "config": oss_res_config},
|
|
339
327
|
"upload.zjtemplate.com": {"type": "oss", "config": oss_upload_config},
|
|
340
328
|
"oss.zjtemplate.com": {"type": "oss", "config": oss_template_config},
|
|
341
|
-
# "*.mecoai.cn": {"type": "mecord-cli", "config": {}},
|
|
342
|
-
# "*.mecordai.com": {"type": "mecord-cli", "config": {}},
|
|
343
329
|
"*.mekoapp.com": {"type": "", "config": {}},
|
|
344
330
|
}
|
|
345
331
|
|
|
@@ -370,12 +356,6 @@ def getOssConfig(targetDomain):
|
|
|
370
356
|
return config["config"]
|
|
371
357
|
return None
|
|
372
358
|
|
|
373
|
-
def isMecordConfig(targetDomain):
|
|
374
|
-
config = get_domain_config(targetDomain)
|
|
375
|
-
if config and config["type"] == "mecord-cli":
|
|
376
|
-
return True
|
|
377
|
-
return False
|
|
378
|
-
|
|
379
359
|
def isMekoConfig(targetDomain):
|
|
380
360
|
config = get_domain_config(targetDomain)
|
|
381
361
|
if config:
|
|
@@ -418,14 +398,6 @@ def getFirstSupportSubdomain():
|
|
|
418
398
|
for domain, config in DOMAIN_CONFIG.items():
|
|
419
399
|
if config["type"] == "oss":
|
|
420
400
|
return {"type": "oss", "config": config["config"], "domain": config["config"]["domain"]}
|
|
421
|
-
if config["type"] == "mecord-cli":
|
|
422
|
-
# 对于通配符域名,返回一个示例域名
|
|
423
|
-
if domain.startswith("*."):
|
|
424
|
-
# 将 *.mecoai.cn 转换为 example.mecoai.cn
|
|
425
|
-
example_domain = "example" + domain[1:]
|
|
426
|
-
return {"type": "mecord-cli", "config": {}, "domain": example_domain}
|
|
427
|
-
else:
|
|
428
|
-
return {"type": "mecord-cli", "config": {}, "domain": domain}
|
|
429
401
|
elif config["type"] == "ftp":
|
|
430
402
|
ftp_config = config["config"]
|
|
431
403
|
if _can_connect_ftp(ftp_config["host"], ftp_config["port"], ftp_config["username"], ftp_config["password"], ftp_config["writepath"]):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ryry-cli
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.18
|
|
4
4
|
Summary: ryry tools
|
|
5
5
|
Home-page: https://github.com/dalipenMedia
|
|
6
6
|
Author: dalipen
|
|
@@ -24,8 +24,6 @@ Requires-Dist: urlparser
|
|
|
24
24
|
Requires-Dist: urllib3
|
|
25
25
|
Requires-Dist: portalocker
|
|
26
26
|
Requires-Dist: PyYAML
|
|
27
|
-
Provides-Extra: with-mecord
|
|
28
|
-
Requires-Dist: mecord-cli>=0.7.407; extra == "with-mecord"
|
|
29
27
|
|
|
30
28
|
ryry Python Tool
|
|
31
29
|
===============================================
|
|
@@ -3,7 +3,7 @@ import os
|
|
|
3
3
|
import subprocess
|
|
4
4
|
import datetime
|
|
5
5
|
|
|
6
|
-
ryry_version = "6.
|
|
6
|
+
ryry_version = "6.18"
|
|
7
7
|
cur_dir = os.path.dirname(os.path.abspath(__file__))
|
|
8
8
|
constanspy = os.path.join(cur_dir, "ryry", "constant.py")
|
|
9
9
|
try:
|
|
@@ -73,7 +73,6 @@ setuptools.setup(
|
|
|
73
73
|
'PyYAML'
|
|
74
74
|
],
|
|
75
75
|
extras_require={
|
|
76
|
-
'with_mecord': ['mecord-cli>=0.7.407'],
|
|
77
76
|
},
|
|
78
77
|
dependency_links=[],
|
|
79
78
|
entry_points={
|
ryry_cli-6.17/ryry/taskUtils.py
DELETED
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import requests
|
|
3
|
-
import datetime
|
|
4
|
-
import json
|
|
5
|
-
import socket
|
|
6
|
-
from requests_toolbelt import MultipartEncoder
|
|
7
|
-
from urllib import parse
|
|
8
|
-
import base64
|
|
9
|
-
import calendar, time
|
|
10
|
-
import hashlib
|
|
11
|
-
from ryry import utils, constant
|
|
12
|
-
|
|
13
|
-
# 获取版本信息的兼容函数
|
|
14
|
-
def get_version():
|
|
15
|
-
try:
|
|
16
|
-
from importlib.metadata import version
|
|
17
|
-
return version("ryry-cli")
|
|
18
|
-
except ImportError:
|
|
19
|
-
from importlib_metadata import version
|
|
20
|
-
return version("ryry-cli")
|
|
21
|
-
|
|
22
|
-
task_config_file = os.path.join(constant.base_path, f"task_config.txt")
|
|
23
|
-
def taskInfoWithFirstTask():
|
|
24
|
-
if os.path.exists(task_config_file):
|
|
25
|
-
with open(task_config_file, 'r', encoding='utf-8') as f:
|
|
26
|
-
data = json.load(f)
|
|
27
|
-
for it in data:
|
|
28
|
-
if it not in ["last_task_pts"]:
|
|
29
|
-
return it, data[it]
|
|
30
|
-
return None, None
|
|
31
|
-
def taskInfoWithUUID(taskUUID):
|
|
32
|
-
if os.path.exists(task_config_file):
|
|
33
|
-
with open(task_config_file, 'r', encoding='utf-8') as f:
|
|
34
|
-
task_config = json.load(f)
|
|
35
|
-
if taskUUID in task_config:
|
|
36
|
-
return task_config[taskUUID]
|
|
37
|
-
return None
|
|
38
|
-
|
|
39
|
-
WECHAT_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=1b006560-7012-4bf0-8c5e-a23830bc0a0b"
|
|
40
|
-
def uploadFile2Wechat(filepath):
|
|
41
|
-
real_robot_url = WECHAT_URL
|
|
42
|
-
params = parse.parse_qs( parse.urlparse( real_robot_url ).query )
|
|
43
|
-
webHookKey=params['key'][0]
|
|
44
|
-
upload_url = f'https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key={webHookKey}&type=file'
|
|
45
|
-
headers = {"Accept": "application/json, text/plain, */*", "Accept-Encoding": "gzip, deflate",
|
|
46
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36"}
|
|
47
|
-
filename = os.path.basename(filepath)
|
|
48
|
-
try:
|
|
49
|
-
multipart = MultipartEncoder(
|
|
50
|
-
fields={'filename': filename, 'filelength': '', 'name': 'media', 'media': (filename, open(filepath, 'rb'), 'application/octet-stream')},
|
|
51
|
-
boundary='-------------------------acebdf13572468')
|
|
52
|
-
headers['Content-Type'] = multipart.content_type
|
|
53
|
-
resp = requests.post(upload_url, headers=headers, data=multipart, timeout=300)
|
|
54
|
-
json_res = resp.json()
|
|
55
|
-
if json_res.get('media_id'):
|
|
56
|
-
return json_res.get('media_id')
|
|
57
|
-
except Exception as e:
|
|
58
|
-
return ""
|
|
59
|
-
def notifyWechatRobot(param):
|
|
60
|
-
real_robot_url = WECHAT_URL
|
|
61
|
-
try:
|
|
62
|
-
s = requests.session()
|
|
63
|
-
s.headers.update({'Connection':'close'})
|
|
64
|
-
headers = dict()
|
|
65
|
-
headers['Content-Type'] = "application/json"
|
|
66
|
-
res = s.post(real_robot_url, json.dumps(param), headers=headers, verify=False, timeout=30)
|
|
67
|
-
s.close()
|
|
68
|
-
except Exception as e:
|
|
69
|
-
print(f"===== qyapi.weixin.qq.com fail ", True)
|
|
70
|
-
|
|
71
|
-
logs = {}
|
|
72
|
-
def taskPrint(taskUUID, msg):
|
|
73
|
-
global logs
|
|
74
|
-
if (taskUUID == None or len(taskUUID) == 0) and msg == None:
|
|
75
|
-
return
|
|
76
|
-
if taskUUID and msg == None:
|
|
77
|
-
del logs[taskUUID]
|
|
78
|
-
return
|
|
79
|
-
if taskUUID and msg:
|
|
80
|
-
if taskUUID not in logs:
|
|
81
|
-
logs[taskUUID] = []
|
|
82
|
-
logs[taskUUID].append(msg)
|
|
83
|
-
if taskUUID == None and msg:
|
|
84
|
-
for uuid in logs:
|
|
85
|
-
logs[uuid].append(msg)
|
|
86
|
-
print(msg)
|
|
87
|
-
def getTaskLog(taskUUID):
|
|
88
|
-
if taskUUID in logs:
|
|
89
|
-
return logs[taskUUID]
|
|
90
|
-
return []
|
|
91
|
-
|
|
92
|
-
def _uploadLog(taskUUID):
|
|
93
|
-
log_path = f"{constant.base_path}/log_{taskUUID}.log"
|
|
94
|
-
try:
|
|
95
|
-
with open(log_path, 'w', encoding='utf-8') as f:
|
|
96
|
-
f.write("\n".join(getTaskLog(taskUUID)))
|
|
97
|
-
notifyWechatRobot({
|
|
98
|
-
"msgtype": "file",
|
|
99
|
-
"file": {
|
|
100
|
-
"media_id": uploadFile2Wechat(log_path)
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
except:
|
|
104
|
-
pass
|
|
105
|
-
finally:
|
|
106
|
-
if os.path.exists(log_path):
|
|
107
|
-
os.remove(log_path)
|
|
108
|
-
|
|
109
|
-
def notifyTaskFail(taskUUID, reason):
|
|
110
|
-
try:
|
|
111
|
-
real_reason = ""
|
|
112
|
-
if len(reason) > 2010:
|
|
113
|
-
real_reason = f"{reason[0:1000]}\n...\n{reason[len(reason)-1000:]}"
|
|
114
|
-
else:
|
|
115
|
-
real_reason = reason
|
|
116
|
-
notifyWechatRobot({
|
|
117
|
-
"msgtype": "markdown",
|
|
118
|
-
"markdown": {
|
|
119
|
-
"content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> 执行任务<{taskUUID}>失败\n<{real_reason}>"
|
|
120
|
-
}
|
|
121
|
-
})
|
|
122
|
-
_uploadLog(taskUUID)
|
|
123
|
-
except:
|
|
124
|
-
pass
|
|
125
|
-
|
|
126
|
-
def notifyServerError(taskUUID):
|
|
127
|
-
try:
|
|
128
|
-
notifyWechatRobot({
|
|
129
|
-
"msgtype": "markdown",
|
|
130
|
-
"markdown": {
|
|
131
|
-
"content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> 执行任务<{taskUUID}>上报失败, retry..."
|
|
132
|
-
}
|
|
133
|
-
})
|
|
134
|
-
except:
|
|
135
|
-
pass
|
|
136
|
-
|
|
137
|
-
def notifyScriptError(taskUUID):
|
|
138
|
-
try:
|
|
139
|
-
notifyWechatRobot({
|
|
140
|
-
"msgtype": "markdown",
|
|
141
|
-
"markdown": {
|
|
142
|
-
"content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> 执行任务<{taskUUID}>异常"
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
_uploadLog(taskUUID)
|
|
146
|
-
except:
|
|
147
|
-
pass
|
|
148
|
-
|
|
149
|
-
def idlingNotify(cnt):
|
|
150
|
-
device_id = utils.generate_unique_id()
|
|
151
|
-
machine_name = socket.gethostname()
|
|
152
|
-
hour = int(float(cnt)/(60.0*60.0))
|
|
153
|
-
if hour<72:
|
|
154
|
-
if hour not in [1, 2, 3, 10, 30, 50, 70]:
|
|
155
|
-
return
|
|
156
|
-
notifyWechatRobot({
|
|
157
|
-
"msgtype": "text",
|
|
158
|
-
"text": {
|
|
159
|
-
"content": f"机器<{machine_name}[{device_id}]> 空转{hour}小时"
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
def onlineNotify():
|
|
164
|
-
device_id = utils.generate_unique_id()
|
|
165
|
-
machine_name = socket.gethostname()
|
|
166
|
-
ver = get_version()
|
|
167
|
-
notifyWechatRobot({
|
|
168
|
-
"msgtype": "text",
|
|
169
|
-
"text": {
|
|
170
|
-
"content": f"机器<{machine_name}[{device_id}]>[{ver}] 上线"
|
|
171
|
-
}
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
def restartNotify(msg):
|
|
175
|
-
device_id = utils.generate_unique_id()
|
|
176
|
-
machine_name = socket.gethostname()
|
|
177
|
-
notifyWechatRobot({
|
|
178
|
-
"msgtype": "text",
|
|
179
|
-
"text": {
|
|
180
|
-
"content": f"机器<{machine_name}[{device_id}]> 即将下线,原因:{msg}"
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
def offlineNotify():
|
|
185
|
-
device_id = utils.generate_unique_id()
|
|
186
|
-
machine_name = socket.gethostname()
|
|
187
|
-
ver = get_version()
|
|
188
|
-
notifyWechatRobot({
|
|
189
|
-
"msgtype": "text",
|
|
190
|
-
"text": {
|
|
191
|
-
"content": f"机器<{machine_name}[{device_id}]> 下线"
|
|
192
|
-
}
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
#===================================================== counter ===============================================#
|
|
196
|
-
task_counter_file = os.path.join(constant.base_path, "task_counter.txt")
|
|
197
|
-
def notifyCounterIfNeed():
|
|
198
|
-
if os.path.exists(task_counter_file) == False:
|
|
199
|
-
return
|
|
200
|
-
with open(task_counter_file, 'r', encoding='utf-8') as f:
|
|
201
|
-
data = json.load(f)
|
|
202
|
-
now_hour = datetime.datetime.now().hour
|
|
203
|
-
if now_hour == 0 and len(data) > 2:
|
|
204
|
-
yesterday = (datetime.datetime.now() + datetime.timedelta(days=-1)).strftime('%Y-%m-%d')
|
|
205
|
-
s_cnt = 0
|
|
206
|
-
f_cnt = 0
|
|
207
|
-
all_day_usage = 0
|
|
208
|
-
t_l = []
|
|
209
|
-
s_l = []
|
|
210
|
-
f_l = []
|
|
211
|
-
for i in range(0,24):
|
|
212
|
-
tips = ""
|
|
213
|
-
if str(i) in data:
|
|
214
|
-
s_cnt += data[str(i)]["success"]
|
|
215
|
-
f_cnt += data[str(i)]["fail"]
|
|
216
|
-
s_l.append(data[str(i)]["success"])
|
|
217
|
-
f_l.append(data[str(i)]["fail"])
|
|
218
|
-
if "usage" in data[str(i)]:
|
|
219
|
-
all_day_usage += data[str(i)]["usage"]
|
|
220
|
-
usage_percentage = int((float(data[str(i)]["usage"])/float(60*60))*100)
|
|
221
|
-
tips = f"({usage_percentage}%)"
|
|
222
|
-
else:
|
|
223
|
-
s_cnt += 0
|
|
224
|
-
f_cnt += 0
|
|
225
|
-
s_l.append(0)
|
|
226
|
-
f_l.append(0)
|
|
227
|
-
t_l.append(f"{i}{tips}")
|
|
228
|
-
usage_percentage = int((float(all_day_usage)/float(24*60*60))*100)
|
|
229
|
-
notifyWechatRobot({
|
|
230
|
-
"msgtype": "markdown",
|
|
231
|
-
"markdown": {
|
|
232
|
-
"content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> {yesterday} 日报 \n\n\
|
|
233
|
-
>过去24小时执行任务<<font color=\"warning\">{s_cnt+f_cnt}</font>>个, 负载<<font color=\"warning\">{usage_percentage}%</font>> \n\
|
|
234
|
-
>成功<font color=\"warning\">{s_cnt}</font>个 \n\
|
|
235
|
-
>失败<font color=\"warning\">{f_cnt}</font>个"
|
|
236
|
-
}
|
|
237
|
-
})
|
|
238
|
-
os.remove(task_counter_file)
|
|
239
|
-
|
|
240
|
-
# import subprocess, platform
|
|
241
|
-
# #darwin Command Line cannot create pyplot gui because not application with gui
|
|
242
|
-
# if platform.system() != 'Darwin':
|
|
243
|
-
# try:
|
|
244
|
-
# import matplotlib.pyplot as plt
|
|
245
|
-
# plt.figure(figsize=(8,3))
|
|
246
|
-
# plt.rcParams.update({
|
|
247
|
-
# 'font.size': 7
|
|
248
|
-
# })
|
|
249
|
-
# plt.bar(t_l, s_l, color='g', label='success')
|
|
250
|
-
# plt.bar(t_l, f_l, bottom=s_l, color='r', label='fail')
|
|
251
|
-
# plt.title(f'[{socket.gethostname()}] [{yesterday}] success/fail={s_cnt}/{f_cnt}')
|
|
252
|
-
# plt.xlabel('time')
|
|
253
|
-
# plt.xticks(ticks=t_l,rotation=45)
|
|
254
|
-
# plt.ylabel('count')
|
|
255
|
-
# plt.subplots_adjust(bottom=0.25)
|
|
256
|
-
# plt.legend()
|
|
257
|
-
# fff = os.path.join(constant.base_path, "plt.png")
|
|
258
|
-
# plt.savefig(fff)
|
|
259
|
-
# with open(fff, "rb") as f:
|
|
260
|
-
# encode_string = str(base64.b64encode(f.read()), encoding='utf-8')
|
|
261
|
-
# md5 = hashlib.md5()
|
|
262
|
-
# md5.update(base64.b64decode(encode_string))
|
|
263
|
-
# hash = md5.hexdigest()
|
|
264
|
-
# notifyWechatRobot({
|
|
265
|
-
# "msgtype": "image",
|
|
266
|
-
# "image": {
|
|
267
|
-
# "base64": encode_string,
|
|
268
|
-
# "md5": hash
|
|
269
|
-
# }
|
|
270
|
-
# })
|
|
271
|
-
# os.remove(fff)
|
|
272
|
-
# os.remove(task_counter_file)
|
|
273
|
-
# except:
|
|
274
|
-
# pass
|
|
275
|
-
|
|
276
|
-
def saveCounter(taskUUID, duration, isSuccess):
|
|
277
|
-
try:
|
|
278
|
-
notifyCounterIfNeed()
|
|
279
|
-
if os.path.exists(task_counter_file) == False:
|
|
280
|
-
with open(task_counter_file, 'w', encoding='utf-8') as f:
|
|
281
|
-
json.dump({}, f)
|
|
282
|
-
with open(task_counter_file, 'r', encoding='utf-8') as f:
|
|
283
|
-
data = json.load(f)
|
|
284
|
-
#update
|
|
285
|
-
now_hour = str(datetime.datetime.now().hour)
|
|
286
|
-
if now_hour in data:
|
|
287
|
-
if isSuccess:
|
|
288
|
-
data[now_hour]["success"] += 1
|
|
289
|
-
else:
|
|
290
|
-
data[now_hour]["fail"] += 1
|
|
291
|
-
if "usage" not in data[now_hour]:
|
|
292
|
-
data[now_hour]["usage"] = 0
|
|
293
|
-
data[now_hour]["usage"] += duration
|
|
294
|
-
else:
|
|
295
|
-
data[now_hour] = {
|
|
296
|
-
"success" : 1 if isSuccess else 0,
|
|
297
|
-
"fail" : 0 if isSuccess else 1,
|
|
298
|
-
"usage" : 0
|
|
299
|
-
}
|
|
300
|
-
#save
|
|
301
|
-
with open(task_counter_file, 'w', encoding='utf-8') as f:
|
|
302
|
-
json.dump(data, f)
|
|
303
|
-
except:
|
|
304
|
-
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|