-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Calculate network/device uptime statistics over any specified period #3586
base: staging
Are you sure you want to change the base?
Changes from all commits
bc55da1
e2c9e44
93f91e3
037d8d3
faa5eab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -714,6 +714,249 @@ | |||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
/*** | ||||||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||||||
* To use the function (getUptimeStatistics) below | ||||||||||||||||||||||||||||||||
* const uptimeStats = await DeviceModel(tenant).getUptimeStatistics({ | ||||||||||||||||||||||||||||||||
devices: ['device1Id', 'device2Id'], | ||||||||||||||||||||||||||||||||
startDate: '2023-01-01', | ||||||||||||||||||||||||||||||||
endDate: '2023-12-31', | ||||||||||||||||||||||||||||||||
timeFrame: 'monthly', | ||||||||||||||||||||||||||||||||
networkFilter: 'airqo' | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
deviceSchema.statics.getUptimeStatistics = async function({ | ||||||||||||||||||||||||||||||||
devices = [], | ||||||||||||||||||||||||||||||||
startDate = new Date(new Date().setDate(new Date().getDate() - 14)), | ||||||||||||||||||||||||||||||||
endDate = new Date(), | ||||||||||||||||||||||||||||||||
timeFrame, | ||||||||||||||||||||||||||||||||
networkFilter, | ||||||||||||||||||||||||||||||||
} = {}) { | ||||||||||||||||||||||||||||||||
const pipeline = []; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Match stage | ||||||||||||||||||||||||||||||||
const matchStage = {}; | ||||||||||||||||||||||||||||||||
if (devices.length > 0) { | ||||||||||||||||||||||||||||||||
matchStage._id = { $in: devices }; | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
if (networkFilter) { | ||||||||||||||||||||||||||||||||
matchStage.network = networkFilter; | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Add date range to match stage if provided | ||||||||||||||||||||||||||||||||
if (startDate && endDate) { | ||||||||||||||||||||||||||||||||
matchStage.createdAt = { | ||||||||||||||||||||||||||||||||
$gte: new Date(startDate), | ||||||||||||||||||||||||||||||||
$lte: new Date(endDate), | ||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
Comment on lines
+749
to
+753
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Handle cases where date ranges are partially provided Currently, the code checks if both
Updating the conditions will make the method more flexible. Apply this diff to adjust the date range handling: // Add date range to match stage if provided
-if (startDate && endDate) {
+if (startDate || endDate) {
matchStage.createdAt = {
- $gte: new Date(startDate),
- $lte: new Date(endDate),
+ ...(startDate && { $gte: new Date(startDate) }),
+ ...(endDate && { $lte: new Date(endDate) }),
};
} Similarly, adjust the status history date range filtering: // Match status history within the date range
-if (startDate && endDate) {
+if (startDate || endDate) {
pipeline.push({
$match: {
"statusHistory.timestamp": {
- $gte: new Date(startDate),
- $lte: new Date(endDate),
+ ...(startDate && { $gte: new Date(startDate) }),
+ ...(endDate && { $lte: new Date(endDate) }),
},
},
});
} Also applies to: 760-770 |
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
logObject("matchStage", matchStage); | ||||||||||||||||||||||||||||||||
pipeline.push({ $match: matchStage }); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Group by device and calculate uptime | ||||||||||||||||||||||||||||||||
pipeline.push({ | ||||||||||||||||||||||||||||||||
$group: { | ||||||||||||||||||||||||||||||||
_id: "$_id", | ||||||||||||||||||||||||||||||||
name: { $first: "$name" }, | ||||||||||||||||||||||||||||||||
network: { $first: "$network" }, | ||||||||||||||||||||||||||||||||
isOnline: { $first: "$isOnline" }, | ||||||||||||||||||||||||||||||||
createdAt: { $first: "$createdAt" }, | ||||||||||||||||||||||||||||||||
totalTime: { | ||||||||||||||||||||||||||||||||
$sum: { | ||||||||||||||||||||||||||||||||
$cond: [{ $eq: [true, "$isOnline"] }, 1, 0], | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
uptime: { | ||||||||||||||||||||||||||||||||
$sum: { | ||||||||||||||||||||||||||||||||
$cond: [ | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
$and: [ | ||||||||||||||||||||||||||||||||
{ $eq: [true, "$isOnline"] }, | ||||||||||||||||||||||||||||||||
{ $gt: ["$createdAt", startDate] }, | ||||||||||||||||||||||||||||||||
], | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
1, | ||||||||||||||||||||||||||||||||
0, | ||||||||||||||||||||||||||||||||
], | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Calculate uptime percentage | ||||||||||||||||||||||||||||||||
pipeline.push({ | ||||||||||||||||||||||||||||||||
$project: { | ||||||||||||||||||||||||||||||||
name: 1, | ||||||||||||||||||||||||||||||||
network: 1, | ||||||||||||||||||||||||||||||||
isOnline: 1, | ||||||||||||||||||||||||||||||||
createdAt: 1, | ||||||||||||||||||||||||||||||||
uptimePercentage: { | ||||||||||||||||||||||||||||||||
$multiply: [{ $divide: ["$uptime", "$totalTime"] }, 100], | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
Comment on lines
+796
to
+797
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent division by zero in uptime percentage calculation The calculation of Apply this diff to handle cases where uptimePercentage: {
- $multiply: [{ $divide: ["$uptime", "$totalTime"] }, 100],
+ $cond: {
+ if: { $eq: ["$totalTime", 0] },
+ then: 0,
+ else: {
+ $multiply: [{ $divide: ["$uptime", "$totalTime"] }, 100],
+ },
+ },
}, 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Group by network | ||||||||||||||||||||||||||||||||
pipeline.push({ | ||||||||||||||||||||||||||||||||
$group: { | ||||||||||||||||||||||||||||||||
_id: "$network", | ||||||||||||||||||||||||||||||||
devices: { $push: "$$ROOT" }, | ||||||||||||||||||||||||||||||||
totalDevices: { $sum: 1 }, | ||||||||||||||||||||||||||||||||
avgUptimePercentage: { $avg: "$uptimePercentage" }, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Calculate online and offline devices based on current status | ||||||||||||||||||||||||||||||||
pipeline.push({ | ||||||||||||||||||||||||||||||||
$project: { | ||||||||||||||||||||||||||||||||
network: "$_id", | ||||||||||||||||||||||||||||||||
totalDevices: 1, | ||||||||||||||||||||||||||||||||
avgUptimePercentage: 1, | ||||||||||||||||||||||||||||||||
devices: 1, | ||||||||||||||||||||||||||||||||
onlineDevices: { | ||||||||||||||||||||||||||||||||
$size: { | ||||||||||||||||||||||||||||||||
$filter: { | ||||||||||||||||||||||||||||||||
input: "$devices", | ||||||||||||||||||||||||||||||||
as: "device", | ||||||||||||||||||||||||||||||||
cond: { $eq: ["$$device.isOnline", true] }, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
offlineDevices: { | ||||||||||||||||||||||||||||||||
$size: { | ||||||||||||||||||||||||||||||||
$filter: { | ||||||||||||||||||||||||||||||||
input: "$devices", | ||||||||||||||||||||||||||||||||
as: "device", | ||||||||||||||||||||||||||||||||
cond: { $ne: ["$$device.isOnline", true] }, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Calculate percentages and prepare device names | ||||||||||||||||||||||||||||||||
pipeline.push({ | ||||||||||||||||||||||||||||||||
$project: { | ||||||||||||||||||||||||||||||||
network: 1, | ||||||||||||||||||||||||||||||||
totalDevices: 1, | ||||||||||||||||||||||||||||||||
avgUptimePercentage: 1, | ||||||||||||||||||||||||||||||||
onlineDevices: 1, | ||||||||||||||||||||||||||||||||
offlineDevices: 1, | ||||||||||||||||||||||||||||||||
onlinePercentage: { | ||||||||||||||||||||||||||||||||
$multiply: [{ $divide: ["$onlineDevices", "$totalDevices"] }, 100], | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
offlinePercentage: { | ||||||||||||||||||||||||||||||||
$multiply: [{ $divide: ["$offlineDevices", "$totalDevices"] }, 100], | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
onlineDeviceNames: { | ||||||||||||||||||||||||||||||||
$map: { | ||||||||||||||||||||||||||||||||
input: { | ||||||||||||||||||||||||||||||||
$filter: { | ||||||||||||||||||||||||||||||||
input: "$devices", | ||||||||||||||||||||||||||||||||
as: "device", | ||||||||||||||||||||||||||||||||
cond: { $eq: ["$$device.isOnline", true] }, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
as: "device", | ||||||||||||||||||||||||||||||||
in: "$$device.name", | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
offlineDeviceNames: { | ||||||||||||||||||||||||||||||||
$map: { | ||||||||||||||||||||||||||||||||
input: { | ||||||||||||||||||||||||||||||||
$filter: { | ||||||||||||||||||||||||||||||||
input: "$devices", | ||||||||||||||||||||||||||||||||
as: "device", | ||||||||||||||||||||||||||||||||
cond: { $ne: ["$$device.isOnline", true] }, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
as: "device", | ||||||||||||||||||||||||||||||||
in: "$$device.name", | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Apply time frame aggregation if specified | ||||||||||||||||||||||||||||||||
if (timeFrame) { | ||||||||||||||||||||||||||||||||
pipeline.push({ | ||||||||||||||||||||||||||||||||
$group: { | ||||||||||||||||||||||||||||||||
_id: { | ||||||||||||||||||||||||||||||||
network: "$network", | ||||||||||||||||||||||||||||||||
timeFrame: { | ||||||||||||||||||||||||||||||||
$switch: { | ||||||||||||||||||||||||||||||||
branches: [ | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
case: { $eq: [timeFrame, "daily"] }, | ||||||||||||||||||||||||||||||||
then: { | ||||||||||||||||||||||||||||||||
$dateToString: { format: "%Y-%m-%d", date: "$$NOW" }, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
case: { $eq: [timeFrame, "weekly"] }, | ||||||||||||||||||||||||||||||||
then: { $dateToString: { format: "%Y-W%V", date: "$$NOW" } }, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
case: { $eq: [timeFrame, "monthly"] }, | ||||||||||||||||||||||||||||||||
then: { $dateToString: { format: "%Y-%m", date: "$$NOW" } }, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
], | ||||||||||||||||||||||||||||||||
default: "all", | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
network: { $first: "$network" }, | ||||||||||||||||||||||||||||||||
totalDevices: { $sum: "$totalDevices" }, | ||||||||||||||||||||||||||||||||
avgUptimePercentage: { $avg: "$avgUptimePercentage" }, | ||||||||||||||||||||||||||||||||
onlineDevices: { $sum: "$onlineDevices" }, | ||||||||||||||||||||||||||||||||
offlineDevices: { $sum: "$offlineDevices" }, | ||||||||||||||||||||||||||||||||
onlinePercentage: { $avg: "$onlinePercentage" }, | ||||||||||||||||||||||||||||||||
offlinePercentage: { $avg: "$offlinePercentage" }, | ||||||||||||||||||||||||||||||||
onlineDeviceNames: { $addToSet: "$onlineDeviceNames" }, | ||||||||||||||||||||||||||||||||
offlineDeviceNames: { $addToSet: "$offlineDeviceNames" }, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
pipeline.push({ | ||||||||||||||||||||||||||||||||
$project: { | ||||||||||||||||||||||||||||||||
network: 1, | ||||||||||||||||||||||||||||||||
timeFrame: "$_id.timeFrame", | ||||||||||||||||||||||||||||||||
totalDevices: 1, | ||||||||||||||||||||||||||||||||
avgUptimePercentage: 1, | ||||||||||||||||||||||||||||||||
onlineDevices: 1, | ||||||||||||||||||||||||||||||||
offlineDevices: 1, | ||||||||||||||||||||||||||||||||
onlinePercentage: 1, | ||||||||||||||||||||||||||||||||
offlinePercentage: 1, | ||||||||||||||||||||||||||||||||
onlineDeviceNames: { | ||||||||||||||||||||||||||||||||
$reduce: { | ||||||||||||||||||||||||||||||||
input: "$onlineDeviceNames", | ||||||||||||||||||||||||||||||||
initialValue: [], | ||||||||||||||||||||||||||||||||
in: { $setUnion: ["$$value", "$$this"] }, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
offlineDeviceNames: { | ||||||||||||||||||||||||||||||||
$reduce: { | ||||||||||||||||||||||||||||||||
input: "$offlineDeviceNames", | ||||||||||||||||||||||||||||||||
initialValue: [], | ||||||||||||||||||||||||||||||||
in: { $setUnion: ["$$value", "$$this"] }, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Sort by network and timeFrame if applicable | ||||||||||||||||||||||||||||||||
pipeline.push({ | ||||||||||||||||||||||||||||||||
$sort: timeFrame ? { network: 1, timeFrame: 1 } : { network: 1 }, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Execute the aggregation pipeline | ||||||||||||||||||||||||||||||||
const result = await this.aggregate(pipeline); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
return result; | ||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||
Comment on lines
+955
to
+958
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for the aggregation pipeline execution Currently, the aggregation execution does not handle potential errors that may occur during database operations. Wrapping the aggregation call in a try-catch block will allow the method to handle exceptions gracefully and provide meaningful error messages. Apply this diff to add error handling: // Execute the aggregation pipeline
-const result = await this.aggregate(pipeline);
+let result;
+try {
+ result = await this.aggregate(pipeline);
+} catch (error) {
+ logger.error(`Aggregation error: ${error.message}`);
+ throw new Error('Error fetching uptime statistics');
+}
return result; 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
const DeviceModel = (tenant) => { | ||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||
let devices = mongoose.model("devices"); | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize the match stage in the aggregation pipeline
To improve performance, consider moving the initial
$match
stage after the$unwind
ofstatusHistory
if the filters are primarily onstatusHistory
fields. Additionally, ensure indexes are in place for the fields used in the$match
stage to speed up query execution.