Refine error handling for "Interrupted" events, optimize screenshot throttling, and reduce redundant logs and heavy operations in ADB workflows.
This commit is contained in:
parent
0168cf7610
commit
fb9fa279a6
@ -223,9 +223,15 @@ void TaskDumpRecorder::finishInternal(const QString &errorMessage) {
|
|||||||
m_finished = true;
|
m_finished = true;
|
||||||
if (m_folder.isEmpty()) return;
|
if (m_folder.isEmpty()) return;
|
||||||
|
|
||||||
if (errorMessage.isEmpty()) {
|
// "Interrupted" трактуем как НЕ-ошибку для дампа: это следствие внешнего
|
||||||
|
// requestInterruption() (watchdog-таймаут или clearAll при остановке/выходе),
|
||||||
|
// а не самостоятельный сбой. Причину уже репортит инициатор прерывания
|
||||||
|
// (watchdog шлёт "Script timeout (N min)"), поэтому тяжёлый ZIP-дамп по
|
||||||
|
// Interrupted не шлём — иначе на один таймаут копится дубль log+dump.
|
||||||
|
if (errorMessage.isEmpty() || errorMessage == "Interrupted") {
|
||||||
QDir(m_folder).removeRecursively();
|
QDir(m_folder).removeRecursively();
|
||||||
qDebug() << "[TaskDumpRecorder] success — removed" << m_folder;
|
qDebug() << "[TaskDumpRecorder]" << (errorMessage.isEmpty() ? "success" : "interrupted")
|
||||||
|
<< "— removed" << m_folder;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -355,10 +355,19 @@ void DeviceScreener::start() {
|
|||||||
appsDue.insert(serial);
|
appsDue.insert(serial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// С каких устройств пора снять превью-кадр (троттл screencap, см.
|
||||||
|
// kScreencapIntervalSecs) — чтобы не грузить adb каждый цикл.
|
||||||
|
QSet<QString> shotDue;
|
||||||
|
for (const QString &serial : deviceSerials) {
|
||||||
|
if (nowSec - m_lastScreencap.value(serial, 0) >= kScreencapIntervalSecs) {
|
||||||
|
shotDue.insert(serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
const QList<ScanResult> scans = QtConcurrent::blockingMapped<QList<ScanResult> >(
|
const QList<ScanResult> scans = QtConcurrent::blockingMapped<QList<ScanResult> >(
|
||||||
&scanPool, deviceSerials,
|
&scanPool, deviceSerials,
|
||||||
[&cacheSnapshot, &appsDue](const QString &serial) {
|
[&cacheSnapshot, &appsDue, &shotDue](const QString &serial) {
|
||||||
return scanDeviceIo(serial, cacheSnapshot.value(serial), appsDue.contains(serial));
|
return scanDeviceIo(serial, cacheSnapshot.value(serial),
|
||||||
|
appsDue.contains(serial), shotDue.contains(serial));
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Фаза B: всё, что трогает БД/члены класса — последовательно в этом потоке.
|
// --- Фаза B: всё, что трогает БД/члены класса — последовательно в этом потоке.
|
||||||
@ -398,6 +407,12 @@ void DeviceScreener::start() {
|
|||||||
|
|
||||||
liveSet.insert(device.id);
|
liveSet.insert(device.id);
|
||||||
|
|
||||||
|
// Отмечаем попытку захвата превью (даже если кадр пустой) — чтобы не
|
||||||
|
// дёргать screencap каждый цикл на устройстве, где он подвисает.
|
||||||
|
if (shotDue.contains(scan.adbSerial)) {
|
||||||
|
m_lastScreencap[scan.adbSerial] = nowSec;
|
||||||
|
}
|
||||||
|
|
||||||
device.image = scan.image;
|
device.image = scan.image;
|
||||||
if (!device.image.isEmpty()) {
|
if (!device.image.isEmpty()) {
|
||||||
if (!DeviceDAO::updateImage(device.id, device.image)) {
|
if (!DeviceDAO::updateImage(device.id, device.image)) {
|
||||||
@ -624,7 +639,7 @@ static const QRegularExpression batteryRegex(R"(level:\s*(\d+))");
|
|||||||
|
|
||||||
DeviceScreener::ScanResult DeviceScreener::scanDeviceIo(const QString &adbSerial,
|
DeviceScreener::ScanResult DeviceScreener::scanDeviceIo(const QString &adbSerial,
|
||||||
const DeviceInfo &cachedStatic,
|
const DeviceInfo &cachedStatic,
|
||||||
bool readApps) {
|
bool readApps, bool takeShot) {
|
||||||
ScanResult result;
|
ScanResult result;
|
||||||
result.adbSerial = adbSerial;
|
result.adbSerial = adbSerial;
|
||||||
|
|
||||||
@ -692,8 +707,12 @@ DeviceScreener::ScanResult DeviceScreener::scanDeviceIo(const QString &adbSerial
|
|||||||
info.updateTime = QDateTime::currentDateTime();
|
info.updateTime = QDateTime::currentDateTime();
|
||||||
result.info = info;
|
result.info = info;
|
||||||
|
|
||||||
// Тяжёлый чистый adb-I/O — скриншот всегда, список приложений по троттлу.
|
// Тяжёлый чистый adb-I/O — превью-кадр и список приложений по троттлу.
|
||||||
result.image = takeScreenshot(adbSerial);
|
// takeShot=false → кадр не снимаем (result.image пустой), фаза B просто не
|
||||||
|
// обновит превью в этом цикле; следующий захват — через kScreencapIntervalSecs.
|
||||||
|
if (takeShot) {
|
||||||
|
result.image = takeScreenshot(adbSerial);
|
||||||
|
}
|
||||||
if (readApps) {
|
if (readApps) {
|
||||||
result.apps = AdbUtils::getListApps(adbSerial);
|
result.apps = AdbUtils::getListApps(adbSerial);
|
||||||
result.appsRead = true;
|
result.appsRead = true;
|
||||||
|
|||||||
@ -24,6 +24,13 @@ public:
|
|||||||
// Сколько устройств опрашивать по adb параллельно (фаза A). Ограничено, чтобы
|
// Сколько устройств опрашивать по adb параллельно (фаза A). Ограничено, чтобы
|
||||||
// не насыщать USB-шину одновременными screencap'ами.
|
// не насыщать USB-шину одновременными screencap'ами.
|
||||||
static constexpr int kMaxParallelScans = 6;
|
static constexpr int kMaxParallelScans = 6;
|
||||||
|
// Как часто снимать превью-кадр устройства (screencap). Раньше кадр снимался
|
||||||
|
// КАЖДЫЙ цикл (~1-2с на устройство) и при ~10 девайсах насыщал общий adb-сервер,
|
||||||
|
// конкурируя с uiautomator-дампами рабочих скриптов (FETCH_TRANSACTIONS) и
|
||||||
|
// приближая их к 10-минутному watchdog'у. Превью в UI и истории экрана так часто
|
||||||
|
// не нужно (отправка в мониторинг и так троттлится kMonitoringScreenIntervalSecs),
|
||||||
|
// поэтому захват ограничиваем. Online/offline-детект от кадра не зависит.
|
||||||
|
static constexpr qint64 kScreencapIntervalSecs = 4;
|
||||||
// Как часто перечитывать список установленных приложений (getListApps). Список
|
// Как часто перечитывать список установленных приложений (getListApps). Список
|
||||||
// меняется редко, поэтому не дёргаем adb каждый цикл.
|
// меняется редко, поэтому не дёргаем adb каждый цикл.
|
||||||
static constexpr qint64 kAppsCheckIntervalSecs = 60;
|
static constexpr qint64 kAppsCheckIntervalSecs = 60;
|
||||||
@ -57,6 +64,7 @@ private:
|
|||||||
// устройства в OFFLINE (на случай переподключения другого аппарата по тому же serial).
|
// устройства в OFFLINE (на случай переподключения другого аппарата по тому же serial).
|
||||||
QMap<QString, DeviceInfo> m_staticInfoCache;
|
QMap<QString, DeviceInfo> m_staticInfoCache;
|
||||||
QMap<QString, qint64> m_lastAppsCheck; // adbSerial → unix ts последнего getListApps
|
QMap<QString, qint64> m_lastAppsCheck; // adbSerial → unix ts последнего getListApps
|
||||||
|
QMap<QString, qint64> m_lastScreencap; // adbSerial → unix ts последнего screencap (троттл превью)
|
||||||
|
|
||||||
// Результат чистого adb-опроса одного устройства (фаза A, выполняется в пуле
|
// Результат чистого adb-опроса одного устройства (фаза A, выполняется в пуле
|
||||||
// потоков). Не содержит ничего, что требует БД/членов класса.
|
// потоков). Не содержит ничего, что требует БД/членов класса.
|
||||||
@ -76,7 +84,9 @@ private:
|
|||||||
// Чистый adb-I/O одного устройства: потокобезопасно (не трогает БД и члены).
|
// Чистый adb-I/O одного устройства: потокобезопасно (не трогает БД и члены).
|
||||||
// cachedStatic — снимок ранее прочитанной статики (пустой id → читаем с нуля).
|
// cachedStatic — снимок ранее прочитанной статики (пустой id → читаем с нуля).
|
||||||
// readApps — перечитывать ли список установленных приложений в этом цикле.
|
// readApps — перечитывать ли список установленных приложений в этом цикле.
|
||||||
static ScanResult scanDeviceIo(const QString &adbSerial, const DeviceInfo &cachedStatic, bool readApps);
|
// takeShot — снимать ли превью-кадр (screencap) в этом цикле (троттл, см.
|
||||||
|
// kScreencapIntervalSecs). false → result.image пустой, превью не обновляется.
|
||||||
|
static ScanResult scanDeviceIo(const QString &adbSerial, const DeviceInfo &cachedStatic, bool readApps, bool takeShot);
|
||||||
|
|
||||||
void postDevicesData(const QList<DeviceInfo> &devices);
|
void postDevicesData(const QList<DeviceInfo> &devices);
|
||||||
|
|
||||||
|
|||||||
@ -363,22 +363,35 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
|
|||||||
if (newStatus == EventStatus::Error) {
|
if (newStatus == EventStatus::Error) {
|
||||||
// Используем скриншот из скрипта если есть, иначе снимаем новый
|
// Используем скриншот из скрипта если есть, иначе снимаем новый
|
||||||
QByteArray screenshot = m_screenshots.take(key);
|
QByteArray screenshot = m_screenshots.take(key);
|
||||||
if (screenshot.isEmpty() && !event.deviceId.isEmpty()) {
|
|
||||||
const DeviceInfo device = DeviceDAO::getDeviceById(event.deviceId);
|
|
||||||
const QString adbSerial = device.adbSerial.isEmpty() ? event.deviceId : device.adbSerial;
|
|
||||||
screenshot = AdbUtils::captureScreenshotBytes(adbSerial);
|
|
||||||
}
|
|
||||||
if (!screenshot.isEmpty()) {
|
|
||||||
EventDAO::updateScreenshot(event.id, screenshot);
|
|
||||||
}
|
|
||||||
// Дублируем ошибку в general_logs со скриншотом
|
|
||||||
const DeviceInfo logDevice = DeviceDAO::getDeviceById(event.deviceId);
|
|
||||||
const QString deviceLabel = logDevice.name.isEmpty() ? event.deviceId : logDevice.name;
|
|
||||||
const QString logMsg = QString("[%1] device=%2 bank=%3: %4")
|
|
||||||
.arg(eventTypeToString(event.type), deviceLabel, event.bankName, result);
|
|
||||||
GeneralLogDAO::insert("CRITICAL", "EventHandler", logMsg, screenshot);
|
|
||||||
|
|
||||||
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, result, screenshot, event.id, event.transactionId, event.accountId);
|
// "Interrupted" — не самостоятельная ошибка, а следствие внешнего
|
||||||
|
// requestInterruption(): его выставляет либо watchdog (он уже шлёт
|
||||||
|
// "Script timeout (N min)" в startThreadForTask), либо clearAll() при
|
||||||
|
// остановке/выходе (намеренная остановка). Реальную причину репортит
|
||||||
|
// инициатор прерывания, поэтому здесь Interrupted на сервер НЕ
|
||||||
|
// дублируем — иначе на один таймаут выходит три записи в мониторинге
|
||||||
|
// (timeout-log + interrupted-log + interrupted-dump). Логику declined
|
||||||
|
// для CREATE_TRANSACTION ниже это не затрагивает.
|
||||||
|
if (result != "Interrupted") {
|
||||||
|
if (screenshot.isEmpty() && !event.deviceId.isEmpty()) {
|
||||||
|
const DeviceInfo device = DeviceDAO::getDeviceById(event.deviceId);
|
||||||
|
const QString adbSerial = device.adbSerial.isEmpty() ? event.deviceId : device.adbSerial;
|
||||||
|
screenshot = AdbUtils::captureScreenshotBytes(adbSerial);
|
||||||
|
}
|
||||||
|
if (!screenshot.isEmpty()) {
|
||||||
|
EventDAO::updateScreenshot(event.id, screenshot);
|
||||||
|
}
|
||||||
|
// Дублируем ошибку в general_logs со скриншотом
|
||||||
|
const DeviceInfo logDevice = DeviceDAO::getDeviceById(event.deviceId);
|
||||||
|
const QString deviceLabel = logDevice.name.isEmpty() ? event.deviceId : logDevice.name;
|
||||||
|
const QString logMsg = QString("[%1] device=%2 bank=%3: %4")
|
||||||
|
.arg(eventTypeToString(event.type), deviceLabel, event.bankName, result);
|
||||||
|
GeneralLogDAO::insert("CRITICAL", "EventHandler", logMsg, screenshot);
|
||||||
|
|
||||||
|
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, result, screenshot, event.id, event.transactionId, event.accountId);
|
||||||
|
} else {
|
||||||
|
qDebug() << "[EventHandler] Interrupted — server error report skipped (cause already reported by interrupter), event id:" << event.id;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.type == EventType::CreateTransaction) {
|
if (event.type == EventType::CreateTransaction) {
|
||||||
const MaterialInfo declAcc = MaterialDAO::getAccountById(event.accountId);
|
const MaterialInfo declAcc = MaterialDAO::getAccountById(event.accountId);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user