modbus-connect
Version:
Modbus RTU over Web Serial and Node.js SerialPort
219 lines (174 loc) • 7.18 kB
JavaScript
// utils/diagnostics.js
class Diagnostics {
constructor() {
this.reset();
}
reset() {
this.startTime = Date.now();
this.totalRequests = 0;
this.successfulResponses = 0;
this.errorResponses = 0;
this.timeouts = 0;
this.crcErrors = 0;
this.modbusExceptions = 0;
this.totalRetries = 0;
this.totalRetrySuccesses = 0;
this.lastResponseTime = null;
this._totalResponseTime = 0;
this._totalResponseTimeAll = 0;
this.lastErrorMessage = null;
this.lastErrors = [];
this.functionCallCounts = {};
this.errorMessageCounts = {};
this.lastSuccessDetails = null;
this._lastFuncCode = null;
this.lastExceptionCode = null;
this.totalDataSent = 0;
this.totalDataReceived = 0;
this.lastRequestTimestamp = null;
this.lastSuccessTimestamp = null;
this.lastErrorTimestamp = null;
this.totalSessions ??= 0;
this.totalSessions++;
}
recordRequest() {
this.totalRequests++;
this.lastRequestTimestamp = new Date().toISOString();
}
recordRetry(attempts) {
this.totalRetries += attempts;
}
recordRetrySuccess() {
this.totalRetrySuccesses++;
}
recordFunctionCall(funcCode) {
if (funcCode == null) return;
this._lastFuncCode = funcCode;
this.functionCallCounts[funcCode] ??= 0;
this.functionCallCounts[funcCode]++;
}
recordSuccess(responseTimeMs) {
this.successfulResponses++;
this.lastResponseTime = responseTimeMs;
this._totalResponseTime += responseTimeMs;
this._totalResponseTimeAll += responseTimeMs;
this.lastSuccessTimestamp = new Date().toISOString();
this.lastSuccessDetails = {
responseTime: responseTimeMs,
timestamp: this.lastSuccessTimestamp,
funcCode: this._lastFuncCode ?? null
};
}
recordError(error, { code = null, responseTimeMs = 0, exceptionCode = null } = {}) {
this.errorResponses++;
this.lastErrorMessage = error.message || String(error);
this._totalResponseTimeAll += responseTimeMs;
this.lastErrorTimestamp = new Date().toISOString();
this.lastErrors.push(this.lastErrorMessage);
if (this.lastErrors.length > 10) {
this.lastErrors.shift();
}
const msg = (error.message || '').toLowerCase();
if (code === 'timeout' || msg.includes('timeout')) {
this.timeouts++;
} else if (code === 'crc' || msg.includes('crc')) {
this.crcErrors++;
} else if (code === 'modbus-exception' || msg.includes('modbus exception')) {
this.modbusExceptions++;
if (typeof exceptionCode === 'number') {
this.lastExceptionCode = exceptionCode;
}
}
this.errorMessageCounts[this.lastErrorMessage] ??= 0;
this.errorMessageCounts[this.lastErrorMessage]++;
}
recordDataSent(byteLength) {
this.totalDataSent += byteLength;
}
recordDataReceived(byteLength) {
this.totalDataReceived += byteLength;
}
get averageResponseTime() {
return this.successfulResponses === 0
? null
: this._totalResponseTime / this.successfulResponses;
}
get averageResponseTimeAll() {
const total = this.successfulResponses + this.errorResponses;
return total === 0 ? null : this._totalResponseTimeAll / total;
}
get errorRate() {
return this.totalRequests === 0 ? null : (this.errorResponses / this.totalRequests) * 100;
}
get uptimeSeconds() {
return Math.floor((Date.now() - this.startTime) / 1000);
}
getStats() {
return {
uptimeSeconds: this.uptimeSeconds,
totalSessions: this.totalSessions,
totalRequests: this.totalRequests,
successfulResponses: this.successfulResponses,
errorResponses: this.errorResponses,
timeouts: this.timeouts,
crcErrors: this.crcErrors,
modbusExceptions: this.modbusExceptions,
lastExceptionCode: this.lastExceptionCode,
totalRetries: this.totalRetries,
totalRetrySuccesses: this.totalRetrySuccesses,
lastResponseTime: this.lastResponseTime,
averageResponseTime: this.averageResponseTime,
averageResponseTimeAll: this.averageResponseTimeAll,
errorRate: this.errorRate,
lastErrorMessage: this.lastErrorMessage,
lastErrors: [...this.lastErrors],
lastSuccessDetails: this.lastSuccessDetails,
functionCallCounts: { ...this.functionCallCounts },
commonErrors: Object.entries(this.errorMessageCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 3)
.map(([message, count]) => ({ message, count })),
dataSent: this.totalDataSent,
dataReceived: this.totalDataReceived,
lastRequestTimestamp: this.lastRequestTimestamp,
lastSuccessTimestamp: this.lastSuccessTimestamp,
lastErrorTimestamp: this.lastErrorTimestamp
};
}
serialize() {
return JSON.stringify(this.getStats(), null, 2);
}
toTable() {
const stats = this.getStats();
return Object.entries(stats).map(([metric, value]) => ({ metric, value }));
}
mergeWith(other) {
if (!(other instanceof Diagnostics)) return;
this.totalRequests += other.totalRequests;
this.successfulResponses += other.successfulResponses;
this.errorResponses += other.errorResponses;
this.timeouts += other.timeouts;
this.crcErrors += other.crcErrors;
this.modbusExceptions += other.modbusExceptions;
this.totalRetries += other.totalRetries;
this.totalRetrySuccesses += other.totalRetrySuccesses;
this._totalResponseTime += other._totalResponseTime;
this._totalResponseTimeAll += other._totalResponseTimeAll;
this.totalDataSent += other.totalDataSent;
this.totalDataReceived += other.totalDataReceived;
other.lastErrors.forEach(err => this.lastErrors.push(err));
if (this.lastErrors.length > 10) {
this.lastErrors = this.lastErrors.slice(-10);
}
for (const [code, count] of Object.entries(other.functionCallCounts)) {
this.functionCallCounts[code] ??= 0;
this.functionCallCounts[code] += count;
}
for (const [msg, count] of Object.entries(other.errorMessageCounts)) {
this.errorMessageCounts[msg] ??= 0;
this.errorMessageCounts[msg] += count;
}
this.totalSessions += other.totalSessions ?? 0;
}
}
module.exports = { Diagnostics }