Browse Source

Server: Endpoint GET /server/{:serverId}/stats - for quick average stats over a time period

Christian Kahlau 3 years ago
parent
commit
6e347a32ab

+ 39 - 1
server/docs/Monitoring.postman_collection.json

@@ -72,6 +72,44 @@
 					},
 					"response": []
 				},
+				{
+					"name": "/server/{:id}/stats? - Query Server Stats",
+					"request": {
+						"method": "GET",
+						"header": [],
+						"url": {
+							"raw": "http://10.8.0.1:8880/server/3/stats?type=ram&start=2022-12-28T16:00:00.000Z&end=2022-12-29T15:59:59.999Z",
+							"protocol": "http",
+							"host": [
+								"10",
+								"8",
+								"0",
+								"1"
+							],
+							"port": "8880",
+							"path": [
+								"server",
+								"3",
+								"stats"
+							],
+							"query": [
+								{
+									"key": "type",
+									"value": "ram"
+								},
+								{
+									"key": "start",
+									"value": "2022-12-28T16:00:00.000Z"
+								},
+								{
+									"key": "end",
+									"value": "2022-12-29T15:59:59.999Z"
+								}
+							]
+						}
+					},
+					"response": []
+				},
 				{
 					"name": "/server/{:id}/data/types - Get available Server Data Types",
 					"request": {
@@ -212,7 +250,7 @@
 					"response": []
 				},
 				{
-					"name": "/fcm/topics/{:topic}",
+					"name": "/fcm/topics/{:topic} - Send a (Test) Notification to a Topic",
 					"request": {
 						"method": "POST",
 						"header": [],

+ 41 - 0
server/src/ctrl/database.class.ts

@@ -247,6 +247,47 @@ export class Database extends SQLiteController {
     return result.rows.map(r => ({ time: new Date(r.Timegroup), avg: r.avg, peak: r.peak, max: r.max }));
   }
 
+  public async queryServerStats(serverID: number, type: ServerDataType, from: Date, to: Date): Promise<ReducedValuesPerc> {
+    const select_max = type !== 'cpu';
+    const select_types = select_max ? [type, type, type] : [type, type];
+    const result = await this.stmt(
+      `
+      SELECT 
+        AVG(VALUE_AVG.Value) as 'avg',
+        AVG(VALUE_PEAK.Value) as 'peak'${
+          select_max
+            ? `,
+        MAX(VALUE_MAX.Value) as 'max'`
+            : ''
+        }
+      FROM ServerDataEntry
+      JOIN ServerDataValue AS VALUE_AVG ON ServerDataEntry.ID = VALUE_AVG.EntryID AND VALUE_AVG.Type = ? AND VALUE_AVG.Key = 'avg'
+      JOIN ServerDataValue AS VALUE_PEAK ON ServerDataEntry.ID = VALUE_PEAK.EntryID AND VALUE_PEAK.Type = ? AND VALUE_PEAK.Key = 'peak'
+      ${
+        select_max
+          ? "JOIN ServerDataValue AS VALUE_MAX ON ServerDataEntry.ID = VALUE_MAX.EntryID AND VALUE_MAX.Type = ? AND VALUE_MAX.Key = 'max'"
+          : ''
+      }
+      WHERE ServerDataEntry.ServerID = ?
+      AND ServerDataEntry.Timestamp BETWEEN ? AND ?;
+    `,
+      [...select_types, serverID, from.getTime(), to.getTime()]
+    );
+
+    const row = result.rows[0];
+    if (Object.keys(row).includes('max')) {
+      return {
+        avg: ((row['avg'] as number) / (row['max'] as number)) * 100,
+        peak: ((row['peak'] as number) / (row['peak'] as number)) * 100
+      };
+    } else {
+      return {
+        avg: row['avg'] as number,
+        peak: row['peak'] as number
+      };
+    }
+  }
+
   private async getHealthCheckConfigs(serverID?: number, type = 'http') {
     const res = await this.stmt(
       `SELECT 

+ 31 - 0
server/src/webhdl/server-api-handler.class.ts

@@ -65,5 +65,36 @@ export class ServerAPIHandler extends WebHandler {
         next(err);
       }
     });
+
+    this.router.get('/:serverID/stats', async (req, res, next) => {
+      try {
+        const serverID = Number(req.params.serverID);
+
+        if (Number.isNaN(serverID)) {
+          throw new HttpStatusException(`Not a valid server id: ${req.params.serverID}`, 400);
+        }
+
+        const qStart = (req.query.start || '').toString();
+        const qEnd = (req.query.end || '').toString();
+        const qType = (req.query.type || '').toString();
+
+        if (!qStart || !qEnd || !qType) throw new HttpStatusException("QueryParams 'type', 'start' and 'end' are mandatory.", 400);
+
+        const start = new Date(qStart);
+        const end = new Date(qEnd);
+        if ([start.toString(), end.toString()].includes('Invalid Date')) {
+          throw new HttpStatusException("QueryParams 'start' and 'end' must be parseable dates or unix epoch timestamps (ms).", 400);
+        }
+
+        const data = await this.ctrlPool.db.queryServerStats(serverID, qType, start, end);
+        res.send({
+          start,
+          end,
+          data
+        } as QueryResponse<ReducedValuesPerc>);
+      } catch (err) {
+        next(err);
+      }
+    });
   }
 }