funboost 49.0__py3-none-any.whl → 49.2__py3-none-any.whl
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.
Potentially problematic release.
This version of funboost might be problematic. Click here for more details.
- funboost/__init__.py +1 -1
- funboost/concurrent_pool/async_helper.py +20 -1
- funboost/concurrent_pool/async_pool_executor.py +40 -56
- funboost/concurrent_pool/backup/async_pool_executor_back.py +4 -2
- funboost/constant.py +27 -3
- funboost/consumers/base_consumer.py +11 -6
- funboost/core/active_cousumer_info_getter.py +51 -4
- funboost/function_result_web/__pycache__/app.cpython-313.pyc +0 -0
- funboost/function_result_web/__pycache__/app.cpython-37.pyc +0 -0
- funboost/function_result_web/__pycache__/functions.cpython-313.pyc +0 -0
- funboost/function_result_web/app.py +35 -7
- funboost/function_result_web/app_debug_start.py +4 -0
- funboost/function_result_web/templates/queue_op.html +149 -192
- funboost/function_result_web/templates/rpc_call.html +1 -1
- funboost/publishers/faststream_publisher.py +3 -2
- funboost/set_frame_config.py +2 -1
- funboost/timing_job/apscheduler_use_redis_store.py +8 -1
- funboost/timing_job/timing_job_base.py +21 -8
- funboost/timing_job/timing_push.py +26 -8
- funboost/utils/dependency_packages_in_pythonpath/aioredis/readme.md +0 -6
- funboost/utils/dependency_packages_in_pythonpath/readme.md +0 -6
- {funboost-49.0.dist-info → funboost-49.2.dist-info}/METADATA +7 -6
- {funboost-49.0.dist-info → funboost-49.2.dist-info}/RECORD +27 -25
- {funboost-49.0.dist-info → funboost-49.2.dist-info}/LICENSE +0 -0
- {funboost-49.0.dist-info → funboost-49.2.dist-info}/WHEEL +0 -0
- {funboost-49.0.dist-info → funboost-49.2.dist-info}/entry_points.txt +0 -0
- {funboost-49.0.dist-info → funboost-49.2.dist-info}/top_level.txt +0 -0
|
@@ -76,7 +76,15 @@
|
|
|
76
76
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
77
77
|
</div>
|
|
78
78
|
<div class="modal-body">
|
|
79
|
-
|
|
79
|
+
<!-- 新增:时间范围筛选控件 -->
|
|
80
|
+
<div style="margin-bottom: 10px; display: flex; align-items: center;">
|
|
81
|
+
<label style="margin-right:5px;">起始时间:</label>
|
|
82
|
+
<input type="datetime-local" id="chartStartTime" style="margin-right: 10px;">
|
|
83
|
+
<label style="margin-right:5px;">结束时间:</label>
|
|
84
|
+
<input type="datetime-local" id="chartEndTime" style="margin-right: 10px;">
|
|
85
|
+
<button class="btn btn-primary btn-sm" onclick="reloadQueueChartWithTimeRange()">查询</button>
|
|
86
|
+
</div>
|
|
87
|
+
<canvas id="queueDataChart" style="height:600px;max-height:600px;"></canvas>
|
|
80
88
|
</div>
|
|
81
89
|
<div class="modal-footer">
|
|
82
90
|
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
|
|
@@ -159,22 +167,36 @@
|
|
|
159
167
|
frozen: true,
|
|
160
168
|
formatter: function(cell, formatterParams, onRendered) {
|
|
161
169
|
const queueName = cell.getValue();
|
|
162
|
-
//
|
|
163
|
-
const buttonDisplay = isAutoRefreshing ? 'inline-block' : 'none';
|
|
170
|
+
// 让按钮始终显示,不再依赖 isAutoRefreshing
|
|
164
171
|
return `<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
165
172
|
<span>${queueName}</span>
|
|
166
173
|
<button class="btn btn-xs btn-info view-chart-btn"
|
|
167
174
|
data-queue-name="${queueName}"
|
|
168
|
-
style="margin-left: 10px; display:
|
|
175
|
+
style="margin-left: 10px; display: inline-block;"
|
|
169
176
|
onclick="showQueueChart('${queueName}')">
|
|
170
177
|
<i class="glyphicon glyphicon-stats"></i> 查看曲线图
|
|
171
178
|
</button>
|
|
172
179
|
</div>`;
|
|
173
180
|
}
|
|
174
181
|
},
|
|
182
|
+
{ title: "<br><br>consumer数量", field: "consumer_count", sorter: "number", width: 200,
|
|
183
|
+
formatter: function(cell) {
|
|
184
|
+
const row = cell.getRow().getData();
|
|
185
|
+
var consumers = row.active_consumers;
|
|
186
|
+
return `
|
|
187
|
+
<div style="display: flex; align-items: center; justify-content: space-between; width: 100%; padding-right: 10px;">
|
|
188
|
+
<span style="min-width: 50px; text-align: right; padding-right: 15px;">${cell.getValue() || ''}</span>
|
|
189
|
+
<button class="btn btn-primary btn-sm" onclick='showConsumerDetails(${JSON.stringify(consumers)}, "${row.queue_name}")'>
|
|
190
|
+
查看消费者详情
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
},
|
|
175
196
|
|
|
176
197
|
{ title: "<br>broker<br>类型", field: "broker_kind", sorter: "string" },
|
|
177
198
|
{ title: "<br>消费<br>函数", field: "consuming_function_name", sorter: "string" },
|
|
199
|
+
|
|
178
200
|
{ title: "<br>历史运<br>行次数", field: "history_run_count", sorter: "number", width: 150 },
|
|
179
201
|
{ title: "<br>历史运<br>行失败<br>次数", field: "history_run_fail_count", sorter: "number", width: 150 },
|
|
180
202
|
{ title: "<br>近10秒<br>完成", field: "all_consumers_last_x_s_execute_count", sorter: "number", width: 100 },
|
|
@@ -196,20 +218,7 @@
|
|
|
196
218
|
`;
|
|
197
219
|
}
|
|
198
220
|
},
|
|
199
|
-
|
|
200
|
-
formatter: function(cell) {
|
|
201
|
-
const row = cell.getRow().getData();
|
|
202
|
-
var consumers = row.active_consumers;
|
|
203
|
-
return `
|
|
204
|
-
<div style="display: flex; align-items: center; justify-content: space-between; width: 100%; padding-right: 10px;">
|
|
205
|
-
<span style="min-width: 50px; text-align: right; padding-right: 15px;">${cell.getValue() || ''}</span>
|
|
206
|
-
<button class="btn btn-primary btn-sm" onclick='showConsumerDetails(${JSON.stringify(consumers)}, "${row.queue_name}")'>
|
|
207
|
-
查看消费者详情
|
|
208
|
-
</button>
|
|
209
|
-
</div>
|
|
210
|
-
`;
|
|
211
|
-
}
|
|
212
|
-
},
|
|
221
|
+
|
|
213
222
|
{
|
|
214
223
|
title: "暂停<br>消费<br>状态",
|
|
215
224
|
field: "pause_flag",
|
|
@@ -256,16 +265,6 @@
|
|
|
256
265
|
all_consumers_avarage_function_spend_time_from_start:data.all_consumers_avarage_function_spend_time_from_start
|
|
257
266
|
}));
|
|
258
267
|
|
|
259
|
-
const processedTableDataForHistory = tableData.map(item => {
|
|
260
|
-
// 确保 msg_count 用于历史记录时是数字
|
|
261
|
-
const numericMsgCount = parseFloat(item.msg_count);
|
|
262
|
-
return {
|
|
263
|
-
...item,
|
|
264
|
-
msg_count_numeric: isNaN(numericMsgCount) ? 0 : numericMsgCount
|
|
265
|
-
};
|
|
266
|
-
});
|
|
267
|
-
updateHistoricalData(processedTableDataForHistory);
|
|
268
|
-
|
|
269
268
|
return tableData;
|
|
270
269
|
},
|
|
271
270
|
});
|
|
@@ -502,9 +501,8 @@
|
|
|
502
501
|
let isAutoRefreshing = false;
|
|
503
502
|
let autoRefreshIntervalId = null;
|
|
504
503
|
const AUTO_REFRESH_INTERVAL = 10000; // 10 秒
|
|
505
|
-
let
|
|
506
|
-
|
|
507
|
-
|
|
504
|
+
let chartInstance = null; // 用于存储Chart.js的实例
|
|
505
|
+
|
|
508
506
|
// 定义需要记录并展示在图表中的列字段及其显示名称
|
|
509
507
|
const CHARTABLE_FIELDS_MAP = {
|
|
510
508
|
"history_run_count": "历史运行次数",
|
|
@@ -513,186 +511,165 @@
|
|
|
513
511
|
"all_consumers_last_x_s_execute_count_fail": "近10秒失败",
|
|
514
512
|
"all_consumers_last_x_s_avarage_function_spend_time": "近10秒函数运行平均耗时",
|
|
515
513
|
"all_consumers_avarage_function_spend_time_from_start": "累计函数运行平均耗时",
|
|
516
|
-
"
|
|
514
|
+
"msg_num_in_broker": "消息数量"
|
|
517
515
|
};
|
|
518
|
-
|
|
519
|
-
let activeChartQueueName = null; // 新增:跟踪当前活动图表的队列名称
|
|
520
|
-
|
|
521
|
-
// 新增:预定义的颜色数组,用于图表线条 (更新后的高区分度颜色)
|
|
516
|
+
// 预定义颜色
|
|
522
517
|
const PREDEFINED_COLORS = [
|
|
523
|
-
'#E60012',
|
|
524
|
-
'#005AC8', // 鲜蓝 (Blue)
|
|
525
|
-
'#00A600', // 鲜绿 (Green)
|
|
526
|
-
'#FF9900', // 亮橙 (Orange)
|
|
527
|
-
'#8B28B7', // 紫色 (Purple)
|
|
528
|
-
'#9A6324', // 棕色 (Brown)
|
|
529
|
-
'#5E8C78', // 暗青色 (Teal)
|
|
530
|
-
'#F58231', // 亮橙红 (Vermilion)
|
|
531
|
-
'#42D4F4', // 天蓝色 (Cyan)
|
|
532
|
-
'#BF6131', // 锈红色 (Rust)
|
|
533
|
-
'#3CB44B', // 另一种绿色 (Lime Green)
|
|
534
|
-
'#4363D8', // 另一种蓝色 (Indigo)
|
|
535
|
-
'#F032E6', // 品红色 (Magenta)
|
|
536
|
-
'#BCF60C', // 黄绿色 (Chartreuse)
|
|
537
|
-
'#FABEBE', // 浅粉色 (Light Pink)
|
|
538
|
-
'#AAFFC3', // 浅绿色 (Mint Green)
|
|
539
|
-
'#E6BEFF', // 浅紫色 (Lavender)
|
|
540
|
-
'#FFFAC8' // 浅黄色 (Cream)
|
|
518
|
+
'#E60012', '#005AC8', '#00A600', '#FF9900', '#8B28B7', '#9A6324', '#5E8C78', '#F58231', '#42D4F4', '#BF6131', '#3CB44B', '#4363D8', '#F032E6', '#BCF60C', '#FABEBE', '#AAFFC3', '#E6BEFF', '#FFFAC8'
|
|
541
519
|
];
|
|
542
520
|
|
|
543
|
-
function updateHistoricalData(newDataFromTable) {
|
|
544
|
-
const now = Date.now();
|
|
545
|
-
newDataFromTable.forEach(item => {
|
|
546
|
-
const queueName = item.queue_name;
|
|
547
|
-
if (!historicalData[queueName]) {
|
|
548
|
-
historicalData[queueName] = [];
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
let dataPoint = { timestamp: now };
|
|
552
|
-
Object.keys(CHARTABLE_FIELDS_MAP).forEach(fieldKey => {
|
|
553
|
-
dataPoint[fieldKey] = item[fieldKey]; // item已经包含了转换后的msg_count_numeric
|
|
554
|
-
});
|
|
555
|
-
historicalData[queueName].push(dataPoint);
|
|
556
|
-
|
|
557
|
-
// 清理超过1小时的旧数据
|
|
558
|
-
historicalData[queueName] = historicalData[queueName].filter(dp => (now - dp.timestamp) <= MAX_DATA_AGE_MS);
|
|
559
|
-
});
|
|
560
|
-
updateActiveChartIfVisible(); // 在历史数据更新后,尝试更新活动图表
|
|
561
|
-
}
|
|
562
|
-
|
|
563
521
|
function refreshTableData() {
|
|
564
|
-
|
|
565
|
-
table.replaceData() // replaceData会重新调用ajaxURL,进而触发ajaxResponse中的updateHistoricalData
|
|
522
|
+
table.replaceData()
|
|
566
523
|
.then(() => {
|
|
567
524
|
console.log("Auto-refresh: table data refreshed successfully.");
|
|
568
525
|
})
|
|
569
526
|
.catch(error => {
|
|
570
527
|
console.error("Auto-refresh: error refreshing table data:", error);
|
|
571
|
-
// 可以考虑在这里停止自动刷新或通知用户
|
|
572
528
|
});
|
|
573
529
|
}
|
|
574
530
|
|
|
575
531
|
function toggleAutoRefresh() {
|
|
576
532
|
const button = document.getElementById("toggle-auto-refresh");
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (isAutoRefreshing) { // 如果正在刷新,则停止
|
|
533
|
+
if (isAutoRefreshing) {
|
|
580
534
|
clearInterval(autoRefreshIntervalId);
|
|
581
535
|
isAutoRefreshing = false;
|
|
582
536
|
button.textContent = "启动自动刷新";
|
|
583
537
|
button.classList.remove("btn-danger");
|
|
584
538
|
button.classList.add("btn-success");
|
|
585
|
-
// chartButtons.forEach(btn => btn.style.display = 'none'); // Formatter将处理
|
|
586
|
-
console.log("Auto-refresh stopped.");
|
|
587
539
|
if (table) {
|
|
588
|
-
table.redraw(true);
|
|
540
|
+
table.redraw(true);
|
|
589
541
|
}
|
|
590
|
-
} else {
|
|
542
|
+
} else {
|
|
591
543
|
isAutoRefreshing = true;
|
|
592
544
|
button.textContent = "暂停自动刷新";
|
|
593
545
|
button.classList.remove("btn-success");
|
|
594
546
|
button.classList.add("btn-danger");
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
refreshTableData(); // 立即执行一次刷新, 这会调用replaceData, 触发重绘, formatter会显示按钮
|
|
547
|
+
refreshTableData();
|
|
598
548
|
autoRefreshIntervalId = setInterval(refreshTableData, AUTO_REFRESH_INTERVAL);
|
|
599
|
-
console.log("Auto-refresh started. Interval ID:", autoRefreshIntervalId);
|
|
600
549
|
}
|
|
601
550
|
}
|
|
602
551
|
|
|
603
552
|
document.getElementById("toggle-auto-refresh").addEventListener("click", toggleAutoRefresh);
|
|
604
553
|
|
|
605
|
-
|
|
554
|
+
let currentChartQueueName = null;
|
|
555
|
+
let endTimeUserChanged = false;
|
|
556
|
+
document.getElementById("chartEndTime").addEventListener("input", function() {
|
|
557
|
+
endTimeUserChanged = true;
|
|
558
|
+
});
|
|
559
|
+
// 工具函数:将Date对象转为input[type=datetime-local]需要的本地时间字符串
|
|
560
|
+
function toDatetimeLocalString(date) {
|
|
561
|
+
const pad = n => n < 10 ? '0' + n : n;
|
|
562
|
+
return date.getFullYear() + '-' +
|
|
563
|
+
pad(date.getMonth() + 1) + '-' +
|
|
564
|
+
pad(date.getDate()) + 'T' +
|
|
565
|
+
pad(date.getHours()) + ':' +
|
|
566
|
+
pad(date.getMinutes());
|
|
567
|
+
}
|
|
606
568
|
function showQueueChart(queueName) {
|
|
607
|
-
|
|
608
|
-
document.getElementById("chartQueueName").textContent = queueName;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
569
|
+
currentChartQueueName = queueName;
|
|
570
|
+
document.getElementById("chartQueueName").textContent = queueName;
|
|
571
|
+
// 设置默认时间范围:最近1小时(本地时区字符串)
|
|
572
|
+
const now = new Date();
|
|
573
|
+
const start = new Date(now.getTime() - 60 * 60 * 1000); // 1小时
|
|
574
|
+
const startStr = toDatetimeLocalString(start);
|
|
575
|
+
const endStr = toDatetimeLocalString(now);
|
|
576
|
+
const minStart = toDatetimeLocalString(new Date(now.getTime() - 24 * 60 * 60 * 1000)); // 24小时
|
|
577
|
+
document.getElementById("chartStartTime").value = startStr;
|
|
578
|
+
document.getElementById("chartEndTime").value = endStr;
|
|
579
|
+
document.getElementById("chartStartTime").setAttribute('max', endStr);
|
|
580
|
+
document.getElementById("chartStartTime").setAttribute('min', minStart);
|
|
581
|
+
document.getElementById("chartEndTime").removeAttribute('max');
|
|
582
|
+
document.getElementById("chartEndTime").setAttribute('min', minStart);
|
|
583
|
+
endTimeUserChanged = false; // 重置
|
|
584
|
+
loadQueueChartData(queueName, Math.floor(start.getTime() / 1000), Math.floor(now.getTime() / 1000));
|
|
585
|
+
}
|
|
586
|
+
function reloadQueueChartWithTimeRange() {
|
|
587
|
+
const start = document.getElementById("chartStartTime").value;
|
|
588
|
+
const end = document.getElementById("chartEndTime").value;
|
|
589
|
+
let start_ts = start ? (new Date(start).getTime() / 1000) : null;
|
|
590
|
+
let end_ts = end ? (new Date(end).getTime() / 1000) : null;
|
|
591
|
+
if (endTimeUserChanged) {
|
|
592
|
+
console.log('用户手动填写了结束时间:', end);
|
|
593
|
+
} else {
|
|
594
|
+
console.log('结束时间为默认值:', end);
|
|
613
595
|
}
|
|
614
|
-
|
|
596
|
+
loadQueueChartData(currentChartQueueName, start_ts, end_ts);
|
|
597
|
+
}
|
|
598
|
+
function loadQueueChartData(queueName, start_ts, end_ts) {
|
|
599
|
+
if (chartInstance) chartInstance.destroy();
|
|
615
600
|
const chartCanvas = document.getElementById('queueDataChart');
|
|
616
601
|
const ctx = chartCanvas.getContext('2d');
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
},
|
|
675
|
-
plugins: {
|
|
676
|
-
legend: {
|
|
677
|
-
position: 'top',
|
|
602
|
+
ctx.clearRect(0, 0, chartCanvas.width, chartCanvas.height);
|
|
603
|
+
ctx.font = "16px Arial";
|
|
604
|
+
ctx.textAlign = "center";
|
|
605
|
+
ctx.fillText("正在加载数据...", chartCanvas.width / 2, chartCanvas.height / 2);
|
|
606
|
+
let url = `/queue/get_time_series_data/${queueName}`;
|
|
607
|
+
let params = [];
|
|
608
|
+
if (start_ts) params.push(`start_ts=${start_ts}`);
|
|
609
|
+
if (end_ts) params.push(`end_ts=${end_ts}`);
|
|
610
|
+
if (params.length > 0) url += '?' + params.join('&');
|
|
611
|
+
$.get(url, function(response) {
|
|
612
|
+
if (!response || response.length === 0) {
|
|
613
|
+
ctx.clearRect(0, 0, chartCanvas.width, chartCanvas.height);
|
|
614
|
+
ctx.fillText("暂无历史数据", chartCanvas.width / 2, chartCanvas.height / 2);
|
|
615
|
+
$('#chartModal').modal('show');
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
// 横坐标用本地时间字符串显示
|
|
619
|
+
const labels = response.map(dp => {
|
|
620
|
+
const d = new Date(dp.report_ts * 1000);
|
|
621
|
+
return d.toLocaleString('zh-CN', { hour12: false });
|
|
622
|
+
});
|
|
623
|
+
const datasets = Object.keys(CHARTABLE_FIELDS_MAP).map((fieldKey, index) => {
|
|
624
|
+
const displayName = CHARTABLE_FIELDS_MAP[fieldKey];
|
|
625
|
+
const isDefaultVisible = fieldKey === 'all_consumers_last_x_s_execute_count' || fieldKey === 'all_consumers_last_x_s_execute_count_fail';
|
|
626
|
+
return {
|
|
627
|
+
label: displayName,
|
|
628
|
+
data: response.map(dp => {
|
|
629
|
+
let v = dp.report_data[fieldKey];
|
|
630
|
+
if (typeof v === 'string') v = parseFloat(v);
|
|
631
|
+
return v === undefined || v === null ? NaN : v;
|
|
632
|
+
}),
|
|
633
|
+
fill: false,
|
|
634
|
+
borderColor: PREDEFINED_COLORS[index % PREDEFINED_COLORS.length],
|
|
635
|
+
backgroundColor: PREDEFINED_COLORS[index % PREDEFINED_COLORS.length] + '33',
|
|
636
|
+
tension: 0.4,
|
|
637
|
+
pointRadius: 1,
|
|
638
|
+
pointHoverRadius: 3,
|
|
639
|
+
borderWidth: 2,
|
|
640
|
+
hidden: !isDefaultVisible
|
|
641
|
+
};
|
|
642
|
+
});
|
|
643
|
+
chartInstance = new Chart(ctx, {
|
|
644
|
+
type: 'line',
|
|
645
|
+
data: { labels, datasets },
|
|
646
|
+
options: {
|
|
647
|
+
responsive: true,
|
|
648
|
+
maintainAspectRatio: false,
|
|
649
|
+
scales: {
|
|
650
|
+
x: {
|
|
651
|
+
title: { display: true, text: '时间' },
|
|
652
|
+
ticks: {
|
|
653
|
+
autoSkip: true,
|
|
654
|
+
maxTicksLimit: 20
|
|
655
|
+
}
|
|
656
|
+
},
|
|
657
|
+
y: { beginAtZero: false, title: { display: true, text: '数值' } }
|
|
678
658
|
},
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
text: `队列 [${queueName}] 各项指标变化趋势`
|
|
659
|
+
plugins: {
|
|
660
|
+
legend: { position: 'top' },
|
|
661
|
+
title: { display: true, text: `队列 [${queueName}] 各项指标变化趋势` },
|
|
662
|
+
tooltip: { mode: 'index', intersect: false }
|
|
682
663
|
},
|
|
683
|
-
|
|
684
|
-
mode: 'index',
|
|
685
|
-
intersect: false,
|
|
686
|
-
}
|
|
687
|
-
},
|
|
688
|
-
interaction: { // 增强交互性
|
|
689
|
-
mode: 'nearest',
|
|
690
|
-
axis: 'x',
|
|
691
|
-
intersect: false
|
|
664
|
+
interaction: { mode: 'nearest', axis: 'x', intersect: false }
|
|
692
665
|
}
|
|
693
|
-
}
|
|
666
|
+
});
|
|
667
|
+
$('#chartModal').modal('show');
|
|
668
|
+
}).fail(function() {
|
|
669
|
+
ctx.clearRect(0, 0, chartCanvas.width, chartCanvas.height);
|
|
670
|
+
ctx.fillText("获取数据失败", chartCanvas.width / 2, chartCanvas.height / 2);
|
|
671
|
+
$('#chartModal').modal('show');
|
|
694
672
|
});
|
|
695
|
-
$('#chartModal').modal('show'); // 显示模态框
|
|
696
673
|
}
|
|
697
674
|
|
|
698
675
|
// 新增:模态框关闭时的处理
|
|
@@ -701,35 +678,15 @@
|
|
|
701
678
|
chartInstance.destroy();
|
|
702
679
|
chartInstance = null;
|
|
703
680
|
}
|
|
704
|
-
activeChartQueueName = null; // 清除活动图表队列名
|
|
705
681
|
console.log("Chart modal closed and instance destroyed.");
|
|
706
682
|
});
|
|
707
683
|
|
|
708
|
-
// 新增:如果图表可见,则用最新数据更新它
|
|
709
|
-
function updateActiveChartIfVisible() {
|
|
710
|
-
if (activeChartQueueName && chartInstance && historicalData[activeChartQueueName]) {
|
|
711
|
-
const dataForChart = historicalData[activeChartQueueName];
|
|
712
|
-
if (dataForChart.length > 0) {
|
|
713
|
-
const newLabels = dataForChart.map(dp => new Date(dp.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }));
|
|
714
|
-
|
|
715
|
-
chartInstance.data.labels = newLabels;
|
|
716
|
-
Object.keys(CHARTABLE_FIELDS_MAP).forEach((fieldKey, index) => {
|
|
717
|
-
if (chartInstance.data.datasets[index]) {
|
|
718
|
-
chartInstance.data.datasets[index].data = dataForChart.map(dp => dp[fieldKey] === undefined || dp[fieldKey] === null ? NaN : dp[fieldKey]);
|
|
719
|
-
}
|
|
720
|
-
});
|
|
721
|
-
chartInstance.update(); // 更新图表
|
|
722
|
-
console.log(`Chart for ${activeChartQueueName} updated dynamically.`);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
684
|
// 新增:显示说明模态框的函数
|
|
728
685
|
function showExplanationModal() {
|
|
729
686
|
const explanationTextHtml = `
|
|
730
687
|
<li>消息队列的各项指标数据是 funboost 消费者每隔10秒周期上报到 redis 的,所以不是毫秒级实时,而是10秒级实时。</li>
|
|
731
688
|
<li>"更新所有队列消息数量"按钮和表格"消息数量"列的获取按钮,是实时查询 broker 的消息数量,不是基于消费者上报到 redis 的数据。(因为有的队列可能没有启动相应的消费者,也就没有上报方)</li>
|
|
732
|
-
|
|
689
|
+
|
|
733
690
|
`;
|
|
734
691
|
document.getElementById('explanation-text').innerHTML = explanationTextHtml;
|
|
735
692
|
$('#explanationModal').modal('show');
|
|
@@ -9,6 +9,7 @@ import time
|
|
|
9
9
|
import typing
|
|
10
10
|
|
|
11
11
|
from funboost import PriorityConsumingControlConfig
|
|
12
|
+
from funboost.concurrent_pool.async_helper import get_or_create_event_loop
|
|
12
13
|
from funboost.core.serialization import Serialization
|
|
13
14
|
from funboost.publishers.base_publisher import AbstractPublisher
|
|
14
15
|
from funboost.assist.faststream_helper import app,get_broker
|
|
@@ -23,13 +24,13 @@ class FastStreamPublisher(AbstractPublisher, metaclass=abc.ABCMeta):
|
|
|
23
24
|
pass
|
|
24
25
|
# asyncio.get_event_loop().run_until_complete(broker.start())
|
|
25
26
|
self.broker = get_broker()
|
|
26
|
-
|
|
27
|
+
get_or_create_event_loop().run_until_complete(self.broker.connect())
|
|
27
28
|
|
|
28
29
|
def publish(self, msg: typing.Union[str, dict], task_id=None,
|
|
29
30
|
priority_control_config: PriorityConsumingControlConfig = None) :
|
|
30
31
|
msg, msg_function_kw, extra_params, task_id = self._convert_msg(msg, task_id, priority_control_config)
|
|
31
32
|
t_start = time.time()
|
|
32
|
-
faststream_result =
|
|
33
|
+
faststream_result = get_or_create_event_loop().run_until_complete(self.broker.publish(Serialization.to_json_str(msg), self.queue_name))
|
|
33
34
|
self.logger.debug(f'向{self._queue_name} 队列,推送消息 耗时{round(time.time() - t_start, 4)}秒 {msg_function_kw}') # 显示msg太长了。
|
|
34
35
|
with self._lock_for_count:
|
|
35
36
|
self.count_per_minute += 1
|
funboost/set_frame_config.py
CHANGED
|
@@ -165,7 +165,8 @@ def _auto_creat_config_file_to_project_root_path():
|
|
|
165
165
|
# return
|
|
166
166
|
if '/lib/python' in sys.path[1] or r'\lib\python' in sys.path[1] or '.zip' in sys.path[1]:
|
|
167
167
|
raise EnvironmentError(f'''如果是cmd 或者shell启动而不是pycharm 这种ide启动脚本,请先在会话窗口设置临时PYTHONPATH为你的项目路径,
|
|
168
|
-
windwos 使用 set PYTHONNPATH=你的当前python项目根目录,
|
|
168
|
+
windwos cmd 使用 set PYTHONNPATH=你的当前python项目根目录,
|
|
169
|
+
windows powershell 使用 $env:PYTHONPATH=你的当前python项目根目录,
|
|
169
170
|
linux 使用 export PYTHONPATH=你的当前你python项目根目录,
|
|
170
171
|
PYTHONPATH 作用是python的基本常识,请百度一下。
|
|
171
172
|
需要在会话窗口命令行设置临时的环境变量,而不是修改linux配置文件的方式设置永久环境变量,每个python项目的PYTHONPATH都要不一样,不要在配置文件写死
|
|
@@ -20,10 +20,11 @@ class FunboostBackgroundSchedulerProcessJobsWithinRedisLock(FunboostBackgroundSc
|
|
|
20
20
|
继承 Custom schedulers https://apscheduler.readthedocs.io/en/3.x/extending.html 可以重写 _create_lock
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
process_jobs_redis_lock_key =
|
|
23
|
+
process_jobs_redis_lock_key = None
|
|
24
24
|
|
|
25
25
|
def set_process_jobs_redis_lock_key(self, lock_key):
|
|
26
26
|
self.process_jobs_redis_lock_key = lock_key
|
|
27
|
+
return self
|
|
27
28
|
|
|
28
29
|
# def _create_lock(self):
|
|
29
30
|
# return RedisDistributedBlockLockContextManager(RedisMixin().redis_db_frame,self.process_jobs_redis_lock_key,) 这个类的写法不适合固定的单例,
|
|
@@ -40,6 +41,8 @@ class FunboostBackgroundSchedulerProcessJobsWithinRedisLock(FunboostBackgroundSc
|
|
|
40
41
|
# return 0.1
|
|
41
42
|
|
|
42
43
|
def _process_jobs(self):
|
|
44
|
+
if self.process_jobs_redis_lock_key is None:
|
|
45
|
+
raise ValueError('process_jobs_redis_lock_key is not set')
|
|
43
46
|
with RedisDistributedBlockLockContextManager(RedisMixin().redis_db_frame, self.process_jobs_redis_lock_key, ):
|
|
44
47
|
return super()._process_jobs()
|
|
45
48
|
|
|
@@ -50,6 +53,10 @@ jobstores = {
|
|
|
50
53
|
username=BrokerConnConfig.REDIS_USERNAME, jobs_key='funboost.apscheduler.jobs',run_times_key="funboost.apscheduler.run_times")
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
"""
|
|
57
|
+
建议不要亲自使用这个 funboost_background_scheduler_redis_store 对象,而是 ApsJobAdder来添加定时任务,自动多个apscheduler对象实例,
|
|
58
|
+
尤其是redis作为jobstores时候,使用不同的jobstores,每个消费函数使用各自单独的jobs_key和 run_times_key
|
|
59
|
+
"""
|
|
53
60
|
funboost_background_scheduler_redis_store = FunboostBackgroundSchedulerProcessJobsWithinRedisLock(timezone=FunboostCommonConfig.TIMEZONE, daemon=False, jobstores=jobstores)
|
|
54
61
|
|
|
55
62
|
|
|
@@ -59,7 +59,7 @@ class ThreadPoolExecutorForAps(BasePoolExecutor):
|
|
|
59
59
|
ThreadPoolExecutor constructor
|
|
60
60
|
"""
|
|
61
61
|
|
|
62
|
-
def __init__(self, max_workers=
|
|
62
|
+
def __init__(self, max_workers=100, pool_kwargs=None):
|
|
63
63
|
pool = ThreadPoolExecutorShrinkAble(int(max_workers), )
|
|
64
64
|
super().__init__(pool)
|
|
65
65
|
|
|
@@ -131,13 +131,14 @@ class FunboostBackgroundScheduler(BackgroundScheduler):
|
|
|
131
131
|
#
|
|
132
132
|
# threading.Thread(target=_block_exit,).start() # 既不希望用BlockingScheduler阻塞主进程也不希望定时退出。
|
|
133
133
|
# self._daemon = False
|
|
134
|
-
def _when_exit():
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if block_exit:
|
|
140
|
-
|
|
134
|
+
# def _when_exit():
|
|
135
|
+
# while 1:
|
|
136
|
+
# # print('阻止退出')
|
|
137
|
+
# time.sleep(100)
|
|
138
|
+
|
|
139
|
+
# if block_exit:
|
|
140
|
+
# atexit.register(_when_exit)
|
|
141
|
+
self._daemon = False # 这里强制默认改成非守护线程。默认是守护线程,主线程退出会报错。
|
|
141
142
|
super().start(paused=paused, )
|
|
142
143
|
# _block_exit() # python3.9 判断守护线程结束必须主线程在运行。你自己在你的运行代碼的最末尾加上 while 1: time.sleep(100) ,来阻止主线程退出。
|
|
143
144
|
|
|
@@ -179,10 +180,22 @@ class FunboostBackgroundScheduler(BackgroundScheduler):
|
|
|
179
180
|
FsdfBackgroundScheduler = FunboostBackgroundScheduler # 兼容一下名字,fsdf是 function-scheduling-distributed-framework 老框架名字的缩写
|
|
180
181
|
# funboost_aps_scheduler定时配置基于内存的,不可以跨机器远程动态添加/修改/删除定时任务配置。如果需要动态增删改查定时任务,可以使用funboost_background_scheduler_redis_store
|
|
181
182
|
|
|
183
|
+
"""
|
|
184
|
+
建议不要亲自使用这个 funboost_aps_scheduler 对象,而是 ApsJobAdder来添加定时任务,自动多个apscheduler对象实例,
|
|
185
|
+
尤其是redis作为jobstores时候,使用不同的jobstores,每个消费函数使用各自单独的jobs_key和 run_times_key
|
|
186
|
+
"""
|
|
182
187
|
funboost_aps_scheduler = FunboostBackgroundScheduler(timezone=FunboostCommonConfig.TIMEZONE, daemon=False, )
|
|
183
188
|
fsdf_background_scheduler = funboost_aps_scheduler # 兼容一下老名字。
|
|
184
189
|
|
|
190
|
+
|
|
191
|
+
|
|
185
192
|
if __name__ == '__main__':
|
|
193
|
+
|
|
194
|
+
"""
|
|
195
|
+
下面的例子过时了,可以用但不建议,建议统一使用 ApsJobAdder 来添加定时任务。
|
|
196
|
+
|
|
197
|
+
"""
|
|
198
|
+
|
|
186
199
|
# 定时运行消费演示
|
|
187
200
|
import datetime
|
|
188
201
|
from funboost import boost, BrokerEnum, fsdf_background_scheduler, timing_publish_deco, run_forever
|