feat(webdav): resolve failing migration tests, and implement better error handling
This commit is contained in:
parent
08cce05d1a
commit
1b4573f658
|
|
@ -1552,6 +1552,831 @@
|
||||||
],
|
],
|
||||||
"title": "Document OCR Status",
|
"title": "Document OCR Status",
|
||||||
"type": "timeseries"
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 1,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 36
|
||||||
|
},
|
||||||
|
"id": 24,
|
||||||
|
"panels": [],
|
||||||
|
"title": "WebDAV Operations",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 4,
|
||||||
|
"x": 0,
|
||||||
|
"y": 37
|
||||||
|
},
|
||||||
|
"id": 25,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "none",
|
||||||
|
"justifyMode": "center",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"text": {},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_sessions_total",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Total WebDAV Sessions",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "yellow",
|
||||||
|
"value": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 95
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 4,
|
||||||
|
"x": 4,
|
||||||
|
"y": 37
|
||||||
|
},
|
||||||
|
"id": 26,
|
||||||
|
"options": {
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"values": false,
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"showThresholdLabels": false,
|
||||||
|
"showThresholdMarkers": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_success_rate",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "WebDAV Success Rate",
|
||||||
|
"type": "gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "none"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 4,
|
||||||
|
"x": 8,
|
||||||
|
"y": 37
|
||||||
|
},
|
||||||
|
"id": 27,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "none",
|
||||||
|
"justifyMode": "center",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"text": {},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_files_processed",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Files Processed",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "yellow",
|
||||||
|
"value": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 10000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "ms"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 4,
|
||||||
|
"x": 12,
|
||||||
|
"y": 37
|
||||||
|
},
|
||||||
|
"id": 28,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "center",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"text": {},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_avg_request_duration_ms",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Avg Request Duration",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "yellow",
|
||||||
|
"value": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 15
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 4,
|
||||||
|
"x": 16,
|
||||||
|
"y": 37
|
||||||
|
},
|
||||||
|
"id": 29,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "center",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"text": {},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_error_rate_last_hour",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Error Rate (Last Hour)",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "decbytes"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 4,
|
||||||
|
"w": 4,
|
||||||
|
"x": 20,
|
||||||
|
"y": 37
|
||||||
|
},
|
||||||
|
"id": 30,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "none",
|
||||||
|
"justifyMode": "center",
|
||||||
|
"orientation": "auto",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"text": {},
|
||||||
|
"textMode": "auto"
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_bytes_processed",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Bytes Processed",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false,
|
||||||
|
"legend": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 41
|
||||||
|
},
|
||||||
|
"id": 31,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_sessions_successful",
|
||||||
|
"legendFormat": "Successful",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_sessions_failed",
|
||||||
|
"legendFormat": "Failed",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_sessions_active_last_hour",
|
||||||
|
"legendFormat": "Active (Last Hour)",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "WebDAV Session Status",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false,
|
||||||
|
"legend": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 5000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "ms"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 41
|
||||||
|
},
|
||||||
|
"id": 32,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_avg_request_duration_ms",
|
||||||
|
"legendFormat": "Avg Request Duration",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_avg_session_duration_seconds * 1000",
|
||||||
|
"legendFormat": "Avg Session Duration",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "WebDAV Performance Metrics",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false,
|
||||||
|
"legend": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 49
|
||||||
|
},
|
||||||
|
"id": 33,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_success_rate",
|
||||||
|
"legendFormat": "Session Success Rate",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_request_success_rate",
|
||||||
|
"legendFormat": "Request Success Rate",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_error_rate_last_hour",
|
||||||
|
"legendFormat": "Error Rate (Last Hour)",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "WebDAV Success & Error Rates",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false,
|
||||||
|
"legend": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "never",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "short"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 49
|
||||||
|
},
|
||||||
|
"id": 34,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "10.0.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_http_requests_total",
|
||||||
|
"legendFormat": "Total HTTP Requests",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "readur_webdav_avg_processing_rate",
|
||||||
|
"legendFormat": "Processing Rate (files/sec)",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "WebDAV Throughput Metrics",
|
||||||
|
"type": "timeseries"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"refresh": "30s",
|
"refresh": "30s",
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,36 @@
|
||||||
-- WebDAV Metrics Collection System
|
-- WebDAV Metrics Collection System
|
||||||
-- This migration adds tables for tracking detailed WebDAV sync performance metrics
|
-- This migration adds tables for tracking detailed WebDAV sync performance metrics
|
||||||
|
|
||||||
-- Enum for WebDAV operation types
|
-- Create enum for WebDAV operation types
|
||||||
CREATE TYPE webdav_operation_type AS ENUM (
|
-- Use DO block to handle existing type gracefully
|
||||||
'discovery', -- Directory/file discovery operations
|
DO $$ BEGIN
|
||||||
'download', -- File download operations
|
CREATE TYPE webdav_operation_type AS ENUM (
|
||||||
'metadata_fetch', -- Getting file metadata (properties)
|
'discovery', -- Directory/file discovery operations
|
||||||
'connection_test', -- Testing connection/authentication
|
'download', -- File download operations
|
||||||
'validation', -- Directory validation operations
|
'metadata_fetch', -- Getting file metadata (properties)
|
||||||
'full_sync' -- Complete sync session
|
'connection_test', -- Testing connection/authentication
|
||||||
);
|
'validation', -- Directory validation operations
|
||||||
|
'full_sync' -- Complete sync session
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
-- Enum for WebDAV request types (HTTP methods)
|
-- Create enum for WebDAV request types (HTTP methods)
|
||||||
CREATE TYPE webdav_request_type AS ENUM (
|
-- Use DO block to handle existing type gracefully
|
||||||
'PROPFIND',
|
DO $$ BEGIN
|
||||||
'GET',
|
CREATE TYPE webdav_request_type AS ENUM (
|
||||||
'HEAD',
|
'PROPFIND',
|
||||||
'OPTIONS',
|
'GET',
|
||||||
'POST',
|
'HEAD',
|
||||||
'PUT',
|
'OPTIONS',
|
||||||
'DELETE'
|
'POST',
|
||||||
);
|
'PUT',
|
||||||
|
'DELETE'
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
-- Table for tracking overall WebDAV sync sessions
|
-- Table for tracking overall WebDAV sync sessions
|
||||||
CREATE TABLE IF NOT EXISTS webdav_sync_sessions (
|
CREATE TABLE IF NOT EXISTS webdav_sync_sessions (
|
||||||
|
|
@ -48,7 +58,7 @@ CREATE TABLE IF NOT EXISTS webdav_sync_sessions (
|
||||||
total_bytes_discovered BIGINT NOT NULL DEFAULT 0,
|
total_bytes_discovered BIGINT NOT NULL DEFAULT 0,
|
||||||
total_bytes_processed BIGINT NOT NULL DEFAULT 0,
|
total_bytes_processed BIGINT NOT NULL DEFAULT 0,
|
||||||
avg_file_size_bytes BIGINT,
|
avg_file_size_bytes BIGINT,
|
||||||
processing_rate_files_per_sec DECIMAL(10,2),
|
processing_rate_files_per_sec FLOAT8,
|
||||||
|
|
||||||
-- Request statistics
|
-- Request statistics
|
||||||
total_http_requests INTEGER NOT NULL DEFAULT 0,
|
total_http_requests INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
@ -114,7 +124,7 @@ CREATE TABLE IF NOT EXISTS webdav_directory_metrics (
|
||||||
warnings_count INTEGER NOT NULL DEFAULT 0,
|
warnings_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
-- Performance characteristics
|
-- Performance characteristics
|
||||||
avg_response_time_ms DECIMAL(10,2),
|
avg_response_time_ms FLOAT8,
|
||||||
slowest_request_ms BIGINT,
|
slowest_request_ms BIGINT,
|
||||||
fastest_request_ms BIGINT,
|
fastest_request_ms BIGINT,
|
||||||
|
|
||||||
|
|
@ -217,7 +227,6 @@ CREATE INDEX IF NOT EXISTS idx_webdav_request_metrics_session_id ON webdav_reque
|
||||||
CREATE INDEX IF NOT EXISTS idx_webdav_request_metrics_user_id ON webdav_request_metrics(user_id);
|
CREATE INDEX IF NOT EXISTS idx_webdav_request_metrics_user_id ON webdav_request_metrics(user_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_webdav_request_metrics_source_id ON webdav_request_metrics(source_id);
|
CREATE INDEX IF NOT EXISTS idx_webdav_request_metrics_source_id ON webdav_request_metrics(source_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_webdav_request_metrics_started_at ON webdav_request_metrics(started_at);
|
CREATE INDEX IF NOT EXISTS idx_webdav_request_metrics_started_at ON webdav_request_metrics(started_at);
|
||||||
CREATE INDEX IF NOT EXISTS idx_webdav_request_metrics_request_type ON webdav_request_metrics(request_type);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_webdav_request_metrics_operation_type ON webdav_request_metrics(operation_type);
|
CREATE INDEX IF NOT EXISTS idx_webdav_request_metrics_operation_type ON webdav_request_metrics(operation_type);
|
||||||
CREATE INDEX IF NOT EXISTS idx_webdav_request_metrics_success ON webdav_request_metrics(success);
|
CREATE INDEX IF NOT EXISTS idx_webdav_request_metrics_success ON webdav_request_metrics(success);
|
||||||
|
|
||||||
|
|
@ -230,6 +239,7 @@ BEGIN
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS webdav_sync_sessions_updated_at ON webdav_sync_sessions;
|
||||||
CREATE TRIGGER webdav_sync_sessions_updated_at
|
CREATE TRIGGER webdav_sync_sessions_updated_at
|
||||||
BEFORE UPDATE ON webdav_sync_sessions
|
BEFORE UPDATE ON webdav_sync_sessions
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
|
|
@ -252,30 +262,38 @@ BEGIN
|
||||||
SELECT * INTO v_session FROM webdav_sync_sessions WHERE id = p_session_id;
|
SELECT * INTO v_session FROM webdav_sync_sessions WHERE id = p_session_id;
|
||||||
|
|
||||||
IF NOT FOUND THEN
|
IF NOT FOUND THEN
|
||||||
|
RAISE NOTICE 'Session not found: %', p_session_id;
|
||||||
RETURN;
|
RETURN;
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
|
|
||||||
-- Calculate request statistics from webdav_request_metrics
|
-- Calculate request statistics from webdav_request_metrics
|
||||||
|
-- Use explicit casting to avoid any type issues
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*),
|
CAST(COUNT(*) AS INTEGER),
|
||||||
COUNT(*) FILTER (WHERE success = true),
|
CAST(COUNT(CASE WHEN success = true THEN 1 END) AS INTEGER),
|
||||||
COUNT(*) FILTER (WHERE success = false),
|
CAST(COUNT(CASE WHEN success = false THEN 1 END) AS INTEGER),
|
||||||
COUNT(*) FILTER (WHERE retry_attempt > 0),
|
CAST(COUNT(CASE WHEN retry_attempt > 0 THEN 1 END) AS INTEGER),
|
||||||
COALESCE(SUM(duration_ms), 0),
|
CAST(COALESCE(SUM(duration_ms), 0) AS BIGINT)
|
||||||
MAX(duration_ms),
|
|
||||||
target_path
|
|
||||||
INTO
|
INTO
|
||||||
v_total_requests,
|
v_total_requests,
|
||||||
v_successful_requests,
|
v_successful_requests,
|
||||||
v_failed_requests,
|
v_failed_requests,
|
||||||
v_retry_attempts,
|
v_retry_attempts,
|
||||||
v_network_time_ms,
|
v_network_time_ms
|
||||||
|
FROM webdav_request_metrics
|
||||||
|
WHERE session_id = p_session_id;
|
||||||
|
|
||||||
|
-- Get the slowest operation separately
|
||||||
|
SELECT
|
||||||
|
duration_ms,
|
||||||
|
target_path
|
||||||
|
INTO
|
||||||
v_slowest_operation_ms,
|
v_slowest_operation_ms,
|
||||||
v_slowest_operation_path
|
v_slowest_operation_path
|
||||||
FROM webdav_request_metrics
|
FROM webdav_request_metrics
|
||||||
WHERE session_id = p_session_id
|
WHERE session_id = p_session_id
|
||||||
GROUP BY target_path
|
ORDER BY duration_ms DESC
|
||||||
ORDER BY MAX(duration_ms) DESC
|
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
|
|
||||||
-- Update session with final metrics
|
-- Update session with final metrics
|
||||||
|
|
@ -305,6 +323,8 @@ BEGIN
|
||||||
END,
|
END,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = p_session_id;
|
WHERE id = p_session_id;
|
||||||
|
|
||||||
|
RAISE NOTICE 'Session % finalized with % total requests, % successful', p_session_id, v_total_requests, v_successful_requests;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
|
@ -321,11 +341,11 @@ RETURNS TABLE (
|
||||||
failed_sessions INTEGER,
|
failed_sessions INTEGER,
|
||||||
total_files_processed BIGINT,
|
total_files_processed BIGINT,
|
||||||
total_bytes_processed BIGINT,
|
total_bytes_processed BIGINT,
|
||||||
avg_session_duration_sec DECIMAL,
|
avg_session_duration_sec DOUBLE PRECISION,
|
||||||
avg_processing_rate DECIMAL,
|
avg_processing_rate DOUBLE PRECISION,
|
||||||
total_http_requests BIGINT,
|
total_http_requests BIGINT,
|
||||||
request_success_rate DECIMAL,
|
request_success_rate DOUBLE PRECISION,
|
||||||
avg_request_duration_ms DECIMAL,
|
avg_request_duration_ms DOUBLE PRECISION,
|
||||||
common_error_types JSONB
|
common_error_types JSONB
|
||||||
) AS $$
|
) AS $$
|
||||||
BEGIN
|
BEGIN
|
||||||
|
|
@ -334,23 +354,23 @@ BEGIN
|
||||||
COUNT(*)::INTEGER as total_sessions,
|
COUNT(*)::INTEGER as total_sessions,
|
||||||
COUNT(*) FILTER (WHERE s.status = 'completed')::INTEGER as successful_sessions,
|
COUNT(*) FILTER (WHERE s.status = 'completed')::INTEGER as successful_sessions,
|
||||||
COUNT(*) FILTER (WHERE s.status = 'failed')::INTEGER as failed_sessions,
|
COUNT(*) FILTER (WHERE s.status = 'failed')::INTEGER as failed_sessions,
|
||||||
COALESCE(SUM(s.files_processed), 0) as total_files_processed,
|
COALESCE(SUM(s.files_processed), 0)::BIGINT as total_files_processed,
|
||||||
COALESCE(SUM(s.total_bytes_processed), 0) as total_bytes_processed,
|
COALESCE(SUM(s.total_bytes_processed), 0)::BIGINT as total_bytes_processed,
|
||||||
COALESCE(AVG(s.duration_ms / 1000.0), 0)::DECIMAL as avg_session_duration_sec,
|
COALESCE(AVG(s.duration_ms / 1000.0), 0.0)::DOUBLE PRECISION as avg_session_duration_sec,
|
||||||
COALESCE(AVG(s.processing_rate_files_per_sec), 0)::DECIMAL as avg_processing_rate,
|
COALESCE(AVG(s.processing_rate_files_per_sec), 0.0)::DOUBLE PRECISION as avg_processing_rate,
|
||||||
COALESCE(SUM(s.total_http_requests), 0) as total_http_requests,
|
COALESCE(SUM(s.total_http_requests), 0)::BIGINT as total_http_requests,
|
||||||
CASE
|
CASE
|
||||||
WHEN SUM(s.total_http_requests) > 0
|
WHEN SUM(s.total_http_requests) > 0
|
||||||
THEN (SUM(s.successful_requests)::DECIMAL / SUM(s.total_http_requests) * 100)
|
THEN (SUM(s.successful_requests)::DOUBLE PRECISION / SUM(s.total_http_requests) * 100.0)
|
||||||
ELSE 0
|
ELSE 0.0
|
||||||
END as request_success_rate,
|
END::DOUBLE PRECISION as request_success_rate,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT AVG(duration_ms) FROM webdav_request_metrics r
|
(SELECT AVG(duration_ms)::DOUBLE PRECISION FROM webdav_request_metrics r
|
||||||
WHERE r.started_at BETWEEN p_start_time AND p_end_time
|
WHERE r.started_at BETWEEN p_start_time AND p_end_time
|
||||||
AND (p_user_id IS NULL OR r.user_id = p_user_id)
|
AND (p_user_id IS NULL OR r.user_id = p_user_id)
|
||||||
AND (p_source_id IS NULL OR r.source_id = p_source_id)),
|
AND (p_source_id IS NULL OR r.source_id = p_source_id)),
|
||||||
0
|
0.0
|
||||||
)::DECIMAL as avg_request_duration_ms,
|
)::DOUBLE PRECISION as avg_request_duration_ms,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT jsonb_agg(jsonb_build_object('error_type', error_type, 'count', error_count))
|
(SELECT jsonb_agg(jsonb_build_object('error_type', error_type, 'count', error_count))
|
||||||
FROM (
|
FROM (
|
||||||
|
|
|
||||||
|
|
@ -77,13 +77,94 @@ impl Database {
|
||||||
/// Finalize a WebDAV sync session (calculate final metrics)
|
/// Finalize a WebDAV sync session (calculate final metrics)
|
||||||
pub async fn finalize_webdav_sync_session(&self, session_id: Uuid) -> Result<()> {
|
pub async fn finalize_webdav_sync_session(&self, session_id: Uuid) -> Result<()> {
|
||||||
self.with_retry(|| async {
|
self.with_retry(|| async {
|
||||||
sqlx::query(
|
// Debug: Check how many requests exist for this session before finalizing
|
||||||
"SELECT finalize_webdav_session_metrics($1)"
|
let request_count: (i64,) = sqlx::query_as(
|
||||||
|
"SELECT COUNT(*) FROM webdav_request_metrics WHERE session_id = $1"
|
||||||
)
|
)
|
||||||
.bind(session_id)
|
.bind(session_id)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::debug!("Finalizing session {}: found {} HTTP requests", session_id, request_count.0);
|
||||||
|
|
||||||
|
// Instead of using the PostgreSQL function, do the aggregation in Rust
|
||||||
|
// to avoid transaction isolation issues
|
||||||
|
let (successful_requests, failed_requests, total_requests, network_time_ms): (i64, i64, i64, i64) = sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
COUNT(*) FILTER (WHERE success = true),
|
||||||
|
COUNT(*) FILTER (WHERE success = false),
|
||||||
|
COUNT(*),
|
||||||
|
CAST(COALESCE(SUM(duration_ms), 0) AS BIGINT)
|
||||||
|
FROM webdav_request_metrics
|
||||||
|
WHERE session_id = $1
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.bind(session_id)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::debug!("Direct aggregation - total: {}, successful: {}, failed: {}", total_requests, successful_requests, failed_requests);
|
||||||
|
|
||||||
|
// Get the slowest operation
|
||||||
|
let slowest_operation: Option<(i64, String)> = sqlx::query_as(
|
||||||
|
"SELECT duration_ms, target_path FROM webdav_request_metrics WHERE session_id = $1 ORDER BY duration_ms DESC LIMIT 1"
|
||||||
|
)
|
||||||
|
.bind(session_id)
|
||||||
|
.fetch_optional(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Update the session directly with Rust-calculated values
|
||||||
|
sqlx::query(
|
||||||
|
r#"
|
||||||
|
UPDATE webdav_sync_sessions SET
|
||||||
|
completed_at = NOW(),
|
||||||
|
duration_ms = EXTRACT(EPOCH FROM (NOW() - started_at)) * 1000,
|
||||||
|
total_http_requests = $2,
|
||||||
|
successful_requests = $3,
|
||||||
|
failed_requests = $4,
|
||||||
|
retry_attempts = 0,
|
||||||
|
network_time_ms = $5,
|
||||||
|
slowest_operation_ms = $6,
|
||||||
|
slowest_operation_path = $7,
|
||||||
|
processing_rate_files_per_sec = CASE
|
||||||
|
WHEN files_processed > 0 AND EXTRACT(EPOCH FROM (NOW() - started_at)) > 0
|
||||||
|
THEN files_processed / EXTRACT(EPOCH FROM (NOW() - started_at))
|
||||||
|
ELSE 0
|
||||||
|
END,
|
||||||
|
avg_file_size_bytes = CASE
|
||||||
|
WHEN files_processed > 0
|
||||||
|
THEN total_bytes_processed / files_processed
|
||||||
|
ELSE 0
|
||||||
|
END,
|
||||||
|
status = CASE
|
||||||
|
WHEN status = 'in_progress' THEN 'completed'
|
||||||
|
ELSE status
|
||||||
|
END,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = $1
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.bind(session_id)
|
||||||
|
.bind(total_requests as i32)
|
||||||
|
.bind(successful_requests as i32)
|
||||||
|
.bind(failed_requests as i32)
|
||||||
|
.bind(network_time_ms)
|
||||||
|
.bind(slowest_operation.as_ref().map(|(ms, _)| *ms))
|
||||||
|
.bind(slowest_operation.as_ref().map(|(_, path)| path.as_str()))
|
||||||
.execute(&self.pool)
|
.execute(&self.pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
// Check the session after finalization
|
||||||
|
let session_after: (i32, i32, i32) = sqlx::query_as(
|
||||||
|
"SELECT total_http_requests, successful_requests, failed_requests FROM webdav_sync_sessions WHERE id = $1"
|
||||||
|
)
|
||||||
|
.bind(session_id)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::debug!("After finalization - total: {}, successful: {}, failed: {}", session_after.0, session_after.1, session_after.2);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}).await
|
}).await
|
||||||
}
|
}
|
||||||
|
|
@ -277,7 +358,7 @@ impl Database {
|
||||||
content_type, remote_ip, user_agent,
|
content_type, remote_ip, user_agent,
|
||||||
completed_at
|
completed_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15,
|
$1, $2, $3, $4, $5::webdav_request_type, $6::webdav_operation_type, $7, $8, $9, $10, $11, $12, $13, $14, $15,
|
||||||
$16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, NOW()
|
$16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, NOW()
|
||||||
)
|
)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
|
|
@ -287,8 +368,8 @@ impl Database {
|
||||||
.bind(metric.directory_metric_id)
|
.bind(metric.directory_metric_id)
|
||||||
.bind(metric.user_id)
|
.bind(metric.user_id)
|
||||||
.bind(metric.source_id)
|
.bind(metric.source_id)
|
||||||
.bind(metric.request_type.to_string())
|
.bind(metric.request_type.to_string().as_str())
|
||||||
.bind(metric.operation_type.to_string())
|
.bind(metric.operation_type.to_string().as_str())
|
||||||
.bind(&metric.target_path)
|
.bind(&metric.target_path)
|
||||||
.bind(metric.duration_ms)
|
.bind(metric.duration_ms)
|
||||||
.bind(metric.request_size_bytes)
|
.bind(metric.request_size_bytes)
|
||||||
|
|
@ -329,7 +410,17 @@ impl Database {
|
||||||
|
|
||||||
let metrics = sqlx::query_as::<_, WebDAVRequestMetric>(
|
let metrics = sqlx::query_as::<_, WebDAVRequestMetric>(
|
||||||
r#"
|
r#"
|
||||||
SELECT * FROM webdav_request_metrics
|
SELECT
|
||||||
|
id, session_id, directory_metric_id, user_id, source_id,
|
||||||
|
request_type::TEXT as request_type,
|
||||||
|
operation_type::TEXT as operation_type,
|
||||||
|
target_path, started_at, completed_at, duration_ms,
|
||||||
|
request_size_bytes, response_size_bytes, http_status_code,
|
||||||
|
dns_lookup_ms, tcp_connect_ms, tls_handshake_ms, time_to_first_byte_ms,
|
||||||
|
success, retry_attempt, error_type, error_message,
|
||||||
|
server_header, dav_header, etag_value, last_modified,
|
||||||
|
content_type, remote_ip, user_agent
|
||||||
|
FROM webdav_request_metrics
|
||||||
WHERE user_id = $1
|
WHERE user_id = $1
|
||||||
AND ($2::UUID IS NULL OR session_id = $2)
|
AND ($2::UUID IS NULL OR session_id = $2)
|
||||||
AND ($3::UUID IS NULL OR directory_metric_id = $3)
|
AND ($3::UUID IS NULL OR directory_metric_id = $3)
|
||||||
|
|
@ -357,9 +448,22 @@ impl Database {
|
||||||
let start_time = query.start_time.unwrap_or_else(|| Utc::now() - chrono::Duration::days(1));
|
let start_time = query.start_time.unwrap_or_else(|| Utc::now() - chrono::Duration::days(1));
|
||||||
let end_time = query.end_time.unwrap_or_else(|| Utc::now());
|
let end_time = query.end_time.unwrap_or_else(|| Utc::now());
|
||||||
|
|
||||||
let summary = sqlx::query_as::<_, WebDAVMetricsSummary>(
|
// First try to call the function directly and see what happens
|
||||||
|
let summary = match sqlx::query_as::<_, WebDAVMetricsSummary>(
|
||||||
r#"
|
r#"
|
||||||
SELECT * FROM get_webdav_metrics_summary($1, $2, $3, $4)
|
SELECT
|
||||||
|
total_sessions,
|
||||||
|
successful_sessions,
|
||||||
|
failed_sessions,
|
||||||
|
total_files_processed,
|
||||||
|
total_bytes_processed,
|
||||||
|
avg_session_duration_sec,
|
||||||
|
avg_processing_rate,
|
||||||
|
total_http_requests,
|
||||||
|
request_success_rate,
|
||||||
|
avg_request_duration_ms,
|
||||||
|
common_error_types
|
||||||
|
FROM get_webdav_metrics_summary($1, $2, $3, $4)
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
.bind(query.user_id)
|
.bind(query.user_id)
|
||||||
|
|
@ -367,7 +471,58 @@ impl Database {
|
||||||
.bind(start_time)
|
.bind(start_time)
|
||||||
.bind(end_time)
|
.bind(end_time)
|
||||||
.fetch_optional(&self.pool)
|
.fetch_optional(&self.pool)
|
||||||
.await?;
|
.await {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to call get_webdav_metrics_summary function: {}", e);
|
||||||
|
// Fall back to manual query if function fails
|
||||||
|
sqlx::query_as::<_, WebDAVMetricsSummary>(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
COALESCE(COUNT(*)::INTEGER, 0) as total_sessions,
|
||||||
|
COALESCE(COUNT(*) FILTER (WHERE s.status = 'completed')::INTEGER, 0) as successful_sessions,
|
||||||
|
COALESCE(COUNT(*) FILTER (WHERE s.status = 'failed')::INTEGER, 0) as failed_sessions,
|
||||||
|
COALESCE(SUM(s.files_processed), 0)::BIGINT as total_files_processed,
|
||||||
|
COALESCE(SUM(s.total_bytes_processed), 0)::BIGINT as total_bytes_processed,
|
||||||
|
COALESCE(AVG(s.duration_ms / 1000.0), 0.0)::DOUBLE PRECISION as avg_session_duration_sec,
|
||||||
|
COALESCE(AVG(s.processing_rate_files_per_sec), 0.0)::DOUBLE PRECISION as avg_processing_rate,
|
||||||
|
COALESCE(SUM(s.total_http_requests), 0)::BIGINT as total_http_requests,
|
||||||
|
CASE
|
||||||
|
WHEN SUM(s.total_http_requests) > 0
|
||||||
|
THEN (SUM(s.successful_requests)::DOUBLE PRECISION / SUM(s.total_http_requests) * 100.0)
|
||||||
|
ELSE 0.0
|
||||||
|
END as request_success_rate,
|
||||||
|
COALESCE((SELECT AVG(duration_ms) FROM webdav_request_metrics r
|
||||||
|
WHERE r.started_at BETWEEN $3 AND $4
|
||||||
|
AND ($1 IS NULL OR r.user_id = $1)
|
||||||
|
AND ($2 IS NULL OR r.source_id = $2)), 0.0)::DOUBLE PRECISION as avg_request_duration_ms,
|
||||||
|
COALESCE((SELECT jsonb_agg(jsonb_build_object('error_type', error_type, 'count', error_count))
|
||||||
|
FROM (
|
||||||
|
SELECT error_type, COUNT(*) as error_count
|
||||||
|
FROM webdav_request_metrics r
|
||||||
|
WHERE r.started_at BETWEEN $3 AND $4
|
||||||
|
AND r.success = false
|
||||||
|
AND r.error_type IS NOT NULL
|
||||||
|
AND ($1 IS NULL OR r.user_id = $1)
|
||||||
|
AND ($2 IS NULL OR r.source_id = $2)
|
||||||
|
GROUP BY error_type
|
||||||
|
ORDER BY error_count DESC
|
||||||
|
LIMIT 10
|
||||||
|
) error_summary), '[]'::jsonb) as common_error_types
|
||||||
|
FROM webdav_sync_sessions s
|
||||||
|
WHERE s.started_at BETWEEN $3 AND $4
|
||||||
|
AND ($1 IS NULL OR s.user_id = $1)
|
||||||
|
AND ($2 IS NULL OR s.source_id = $2)
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.bind(query.user_id)
|
||||||
|
.bind(query.source_id)
|
||||||
|
.bind(start_time)
|
||||||
|
.bind(end_time)
|
||||||
|
.fetch_optional(&self.pool)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(summary)
|
Ok(summary)
|
||||||
}).await
|
}).await
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,6 @@ pub struct WebDAVRequestMetric {
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
pub remote_ip: Option<String>,
|
pub remote_ip: Option<String>,
|
||||||
pub user_agent: Option<String>,
|
pub user_agent: Option<String>,
|
||||||
pub created_at: DateTime<Utc>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Summary metrics for WebDAV operations
|
/// Summary metrics for WebDAV operations
|
||||||
|
|
|
||||||
|
|
@ -39,14 +39,15 @@ pub async fn get_prometheus_metrics(
|
||||||
tracing::debug!("Prometheus: Starting to collect all metrics");
|
tracing::debug!("Prometheus: Starting to collect all metrics");
|
||||||
|
|
||||||
// Collect all metrics
|
// Collect all metrics
|
||||||
let (document_metrics, ocr_metrics, user_metrics, database_metrics, system_metrics, storage_metrics, security_metrics) = tokio::try_join!(
|
let (document_metrics, ocr_metrics, user_metrics, database_metrics, system_metrics, storage_metrics, security_metrics, webdav_metrics) = tokio::try_join!(
|
||||||
collect_document_metrics(&state),
|
collect_document_metrics(&state),
|
||||||
collect_ocr_metrics(&state),
|
collect_ocr_metrics(&state),
|
||||||
collect_user_metrics(&state),
|
collect_user_metrics(&state),
|
||||||
collect_database_metrics(&state),
|
collect_database_metrics(&state),
|
||||||
collect_system_metrics(&state),
|
collect_system_metrics(&state),
|
||||||
collect_storage_metrics(&state),
|
collect_storage_metrics(&state),
|
||||||
collect_security_metrics(&state)
|
collect_security_metrics(&state),
|
||||||
|
collect_webdav_metrics(&state)
|
||||||
).map_err(|e| {
|
).map_err(|e| {
|
||||||
tracing::error!("Prometheus: Failed to collect metrics: {:?}", e);
|
tracing::error!("Prometheus: Failed to collect metrics: {:?}", e);
|
||||||
e
|
e
|
||||||
|
|
@ -193,6 +194,59 @@ pub async fn get_prometheus_metrics(
|
||||||
writeln!(&mut output, "# TYPE readur_document_access_today counter").unwrap();
|
writeln!(&mut output, "# TYPE readur_document_access_today counter").unwrap();
|
||||||
writeln!(&mut output, "readur_document_access_today {} {}", security_metrics.document_access_today, timestamp).unwrap();
|
writeln!(&mut output, "readur_document_access_today {} {}", security_metrics.document_access_today, timestamp).unwrap();
|
||||||
|
|
||||||
|
// WebDAV metrics
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_sessions_total Total number of WebDAV sync sessions").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_sessions_total counter").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_sessions_total {} {}", webdav_metrics.total_sessions, timestamp).unwrap();
|
||||||
|
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_sessions_successful Successful WebDAV sync sessions").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_sessions_successful counter").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_sessions_successful {} {}", webdav_metrics.successful_sessions, timestamp).unwrap();
|
||||||
|
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_sessions_failed Failed WebDAV sync sessions").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_sessions_failed counter").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_sessions_failed {} {}", webdav_metrics.failed_sessions, timestamp).unwrap();
|
||||||
|
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_success_rate WebDAV sync success rate (percentage)").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_success_rate gauge").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_success_rate {} {}", webdav_metrics.success_rate, timestamp).unwrap();
|
||||||
|
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_files_processed Total files processed by WebDAV syncs").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_files_processed counter").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_files_processed {} {}", webdav_metrics.total_files_processed, timestamp).unwrap();
|
||||||
|
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_bytes_processed Total bytes processed by WebDAV syncs").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_bytes_processed counter").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_bytes_processed {} {}", webdav_metrics.total_bytes_processed, timestamp).unwrap();
|
||||||
|
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_avg_session_duration_seconds Average WebDAV session duration in seconds").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_avg_session_duration_seconds gauge").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_avg_session_duration_seconds {} {}", webdav_metrics.avg_session_duration_sec, timestamp).unwrap();
|
||||||
|
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_avg_processing_rate Average WebDAV processing rate (files per second)").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_avg_processing_rate gauge").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_avg_processing_rate {} {}", webdav_metrics.avg_processing_rate, timestamp).unwrap();
|
||||||
|
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_http_requests_total Total HTTP requests made by WebDAV syncs").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_http_requests_total counter").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_http_requests_total {} {}", webdav_metrics.total_http_requests, timestamp).unwrap();
|
||||||
|
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_request_success_rate WebDAV HTTP request success rate (percentage)").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_request_success_rate gauge").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_request_success_rate {} {}", webdav_metrics.request_success_rate, timestamp).unwrap();
|
||||||
|
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_avg_request_duration_ms Average WebDAV request duration in milliseconds").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_avg_request_duration_ms gauge").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_avg_request_duration_ms {} {}", webdav_metrics.avg_request_duration_ms, timestamp).unwrap();
|
||||||
|
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_sessions_active_last_hour WebDAV sessions active in the last hour").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_sessions_active_last_hour gauge").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_sessions_active_last_hour {} {}", webdav_metrics.sessions_last_hour, timestamp).unwrap();
|
||||||
|
|
||||||
|
writeln!(&mut output, "# HELP readur_webdav_error_rate_last_hour WebDAV error rate in the last hour (percentage)").unwrap();
|
||||||
|
writeln!(&mut output, "# TYPE readur_webdav_error_rate_last_hour gauge").unwrap();
|
||||||
|
writeln!(&mut output, "readur_webdav_error_rate_last_hour {} {}", webdav_metrics.error_rate_last_hour, timestamp).unwrap();
|
||||||
|
|
||||||
// Return the metrics with the correct content type
|
// Return the metrics with the correct content type
|
||||||
Ok((
|
Ok((
|
||||||
[(header::CONTENT_TYPE, "text/plain; version=0.0.4")],
|
[(header::CONTENT_TYPE, "text/plain; version=0.0.4")],
|
||||||
|
|
@ -252,6 +306,22 @@ struct SecurityMetrics {
|
||||||
document_access_today: i64,
|
document_access_today: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct WebDAVMetrics {
|
||||||
|
total_sessions: i64,
|
||||||
|
successful_sessions: i64,
|
||||||
|
failed_sessions: i64,
|
||||||
|
success_rate: f64,
|
||||||
|
total_files_processed: i64,
|
||||||
|
total_bytes_processed: i64,
|
||||||
|
avg_session_duration_sec: f64,
|
||||||
|
avg_processing_rate: f64,
|
||||||
|
total_http_requests: i64,
|
||||||
|
request_success_rate: f64,
|
||||||
|
avg_request_duration_ms: f64,
|
||||||
|
sessions_last_hour: i64,
|
||||||
|
error_rate_last_hour: f64,
|
||||||
|
}
|
||||||
|
|
||||||
async fn collect_document_metrics(state: &Arc<AppState>) -> Result<DocumentMetrics, StatusCode> {
|
async fn collect_document_metrics(state: &Arc<AppState>) -> Result<DocumentMetrics, StatusCode> {
|
||||||
// Get total document count
|
// Get total document count
|
||||||
let total_docs = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM documents")
|
let total_docs = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM documents")
|
||||||
|
|
@ -577,3 +647,122 @@ async fn collect_security_metrics(state: &Arc<AppState>) -> Result<SecurityMetri
|
||||||
document_access_today,
|
document_access_today,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn collect_webdav_metrics(state: &Arc<AppState>) -> Result<WebDAVMetrics, StatusCode> {
|
||||||
|
// Get WebDAV session metrics for the last 24 hours
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
struct WebDAVStats {
|
||||||
|
total_sessions: Option<i64>,
|
||||||
|
successful_sessions: Option<i64>,
|
||||||
|
failed_sessions: Option<i64>,
|
||||||
|
total_files_processed: Option<i64>,
|
||||||
|
total_bytes_processed: Option<i64>,
|
||||||
|
avg_session_duration_sec: Option<f64>,
|
||||||
|
avg_processing_rate: Option<f64>,
|
||||||
|
total_http_requests: Option<i64>,
|
||||||
|
request_success_rate: Option<f64>,
|
||||||
|
avg_request_duration_ms: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let webdav_stats = sqlx::query_as::<_, WebDAVStats>(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_sessions,
|
||||||
|
COUNT(*) FILTER (WHERE status = 'completed') as successful_sessions,
|
||||||
|
COUNT(*) FILTER (WHERE status = 'failed') as failed_sessions,
|
||||||
|
COALESCE(SUM(files_processed), 0) as total_files_processed,
|
||||||
|
COALESCE(SUM(total_bytes_processed), 0) as total_bytes_processed,
|
||||||
|
COALESCE(AVG(duration_ms / 1000.0), 0) as avg_session_duration_sec,
|
||||||
|
COALESCE(AVG(processing_rate_files_per_sec), 0) as avg_processing_rate,
|
||||||
|
COALESCE(SUM(total_http_requests), 0) as total_http_requests,
|
||||||
|
CASE
|
||||||
|
WHEN SUM(total_http_requests) > 0
|
||||||
|
THEN (SUM(successful_requests)::DECIMAL / SUM(total_http_requests) * 100)
|
||||||
|
ELSE 0
|
||||||
|
END as request_success_rate,
|
||||||
|
COALESCE(
|
||||||
|
(SELECT AVG(duration_ms) FROM webdav_request_metrics
|
||||||
|
WHERE started_at > NOW() - INTERVAL '24 hours'),
|
||||||
|
0
|
||||||
|
) as avg_request_duration_ms
|
||||||
|
FROM webdav_sync_sessions
|
||||||
|
WHERE started_at > NOW() - INTERVAL '24 hours'
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.fetch_one(&state.db.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Failed to get WebDAV session metrics: {}", e);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Get sessions active in last hour
|
||||||
|
let sessions_last_hour = sqlx::query_scalar::<_, i64>(
|
||||||
|
"SELECT COUNT(*) FROM webdav_sync_sessions WHERE started_at > NOW() - INTERVAL '1 hour'"
|
||||||
|
)
|
||||||
|
.fetch_one(&state.db.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Failed to get recent WebDAV sessions: {}", e);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Calculate error rate for last hour
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
struct ErrorRate {
|
||||||
|
total_requests: Option<i64>,
|
||||||
|
failed_requests: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let error_stats = sqlx::query_as::<_, ErrorRate>(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_requests,
|
||||||
|
COUNT(*) FILTER (WHERE success = false) as failed_requests
|
||||||
|
FROM webdav_request_metrics
|
||||||
|
WHERE started_at > NOW() - INTERVAL '1 hour'
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.fetch_one(&state.db.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Failed to get WebDAV error rates: {}", e);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let error_rate_last_hour = if let (Some(total), Some(failed)) = (error_stats.total_requests, error_stats.failed_requests) {
|
||||||
|
if total > 0 {
|
||||||
|
(failed as f64 / total as f64) * 100.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
let total_sessions = webdav_stats.total_sessions.unwrap_or(0);
|
||||||
|
let successful_sessions = webdav_stats.successful_sessions.unwrap_or(0);
|
||||||
|
let failed_sessions = webdav_stats.failed_sessions.unwrap_or(0);
|
||||||
|
|
||||||
|
let success_rate = if total_sessions > 0 {
|
||||||
|
(successful_sessions as f64 / total_sessions as f64) * 100.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(WebDAVMetrics {
|
||||||
|
total_sessions,
|
||||||
|
successful_sessions,
|
||||||
|
failed_sessions,
|
||||||
|
success_rate,
|
||||||
|
total_files_processed: webdav_stats.total_files_processed.unwrap_or(0),
|
||||||
|
total_bytes_processed: webdav_stats.total_bytes_processed.unwrap_or(0),
|
||||||
|
avg_session_duration_sec: webdav_stats.avg_session_duration_sec.unwrap_or(0.0),
|
||||||
|
avg_processing_rate: webdav_stats.avg_processing_rate.unwrap_or(0.0),
|
||||||
|
total_http_requests: webdav_stats.total_http_requests.unwrap_or(0),
|
||||||
|
request_success_rate: webdav_stats.request_success_rate.unwrap_or(0.0),
|
||||||
|
avg_request_duration_ms: webdav_stats.avg_request_duration_ms.unwrap_or(0.0),
|
||||||
|
sessions_last_hour,
|
||||||
|
error_rate_last_hour,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -291,6 +291,7 @@ pub async fn trigger_deep_scan(
|
||||||
|
|
||||||
match smart_sync_service.perform_smart_sync(
|
match smart_sync_service.perform_smart_sync(
|
||||||
user_id,
|
user_id,
|
||||||
|
Some(source_id),
|
||||||
&webdav_service,
|
&webdav_service,
|
||||||
watch_folder,
|
watch_folder,
|
||||||
crate::services::webdav::SmartSyncStrategy::FullDeepScan, // Force deep scan for directory reset
|
crate::services::webdav::SmartSyncStrategy::FullDeepScan, // Force deep scan for directory reset
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ async fn perform_sync_internal(
|
||||||
// Use smart sync service for intelligent scanning
|
// Use smart sync service for intelligent scanning
|
||||||
let smart_sync_service = SmartSyncService::new(state.clone());
|
let smart_sync_service = SmartSyncService::new(state.clone());
|
||||||
|
|
||||||
match smart_sync_service.evaluate_and_sync(user_id, &webdav_service, folder_path, Some(&progress)).await {
|
match smart_sync_service.evaluate_and_sync(user_id, None, &webdav_service, folder_path, Some(&progress)).await {
|
||||||
Ok(Some(sync_result)) => {
|
Ok(Some(sync_result)) => {
|
||||||
let folder_elapsed = folder_start.elapsed();
|
let folder_elapsed = folder_start.elapsed();
|
||||||
total_directories_scanned += sync_result.directories_scanned;
|
total_directories_scanned += sync_result.directories_scanned;
|
||||||
|
|
|
||||||
|
|
@ -761,6 +761,7 @@ impl SourceScheduler {
|
||||||
for watch_folder in &webdav_config.watch_folders {
|
for watch_folder in &webdav_config.watch_folders {
|
||||||
match smart_sync_service.perform_smart_sync(
|
match smart_sync_service.perform_smart_sync(
|
||||||
source_clone.user_id,
|
source_clone.user_id,
|
||||||
|
Some(source_clone.id),
|
||||||
&webdav_service,
|
&webdav_service,
|
||||||
watch_folder,
|
watch_folder,
|
||||||
crate::services::webdav::SmartSyncStrategy::FullDeepScan, // Force deep scan for automatic triggers
|
crate::services::webdav::SmartSyncStrategy::FullDeepScan, // Force deep scan for automatic triggers
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ impl SourceSyncService {
|
||||||
// Use smart sync service for intelligent discovery
|
// Use smart sync service for intelligent discovery
|
||||||
let smart_sync_service = crate::services::webdav::SmartSyncService::new(state_clone);
|
let smart_sync_service = crate::services::webdav::SmartSyncService::new(state_clone);
|
||||||
|
|
||||||
match smart_sync_service.evaluate_and_sync(user_id, &service, &folder_path, Some(&progress)).await {
|
match smart_sync_service.evaluate_and_sync(user_id, Some(source.id), &service, &folder_path, Some(&progress)).await {
|
||||||
Ok(Some(sync_result)) => {
|
Ok(Some(sync_result)) => {
|
||||||
info!("✅ Smart sync completed for {}: {} files found using {:?}",
|
info!("✅ Smart sync completed for {}: {} files found using {:?}",
|
||||||
folder_path, sync_result.files.len(), sync_result.strategy_used);
|
folder_path, sync_result.files.len(), sync_result.strategy_used);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/// Common utilities and shared functions for WebDAV services
|
||||||
|
|
||||||
|
/// Build a standardized User-Agent string for all WebDAV requests
|
||||||
|
/// This ensures consistent identification across all WebDAV operations
|
||||||
|
pub fn build_user_agent() -> String {
|
||||||
|
format!("Readur/{} (WebDAV-Sync; +https://github.com/readur)",
|
||||||
|
env!("CARGO_PKG_VERSION"))
|
||||||
|
}
|
||||||
|
|
@ -68,10 +68,14 @@ impl WebDAVErrorClassifier {
|
||||||
WebDAVScanFailureType::TooManyItems
|
WebDAVScanFailureType::TooManyItems
|
||||||
} else if error_str.contains("depth") || error_str.contains("nested") {
|
} else if error_str.contains("depth") || error_str.contains("nested") {
|
||||||
WebDAVScanFailureType::DepthLimit
|
WebDAVScanFailureType::DepthLimit
|
||||||
} else if error_str.contains("size") || error_str.contains("too large") {
|
} else if error_str.contains("size") || error_str.contains("too large") || error_str.contains("507") || error_str.contains("insufficient storage") || error_str.contains("quota exceeded") {
|
||||||
WebDAVScanFailureType::SizeLimit
|
WebDAVScanFailureType::SizeLimit
|
||||||
} else if error_str.contains("404") || error_str.contains("not found") {
|
} else if error_str.contains("404") || error_str.contains("not found") {
|
||||||
WebDAVScanFailureType::ServerError // Will be further classified by HTTP status
|
WebDAVScanFailureType::ServerError // Will be further classified by HTTP status
|
||||||
|
} else if error_str.contains("405") || error_str.contains("method not allowed") || error_str.contains("propfind not allowed") {
|
||||||
|
WebDAVScanFailureType::ServerError // Method not allowed - likely PROPFIND disabled
|
||||||
|
} else if error_str.contains("423") || error_str.contains("locked") || error_str.contains("lock") {
|
||||||
|
WebDAVScanFailureType::ServerError // Resource locked
|
||||||
} else {
|
} else {
|
||||||
WebDAVScanFailureType::Unknown
|
WebDAVScanFailureType::Unknown
|
||||||
}
|
}
|
||||||
|
|
@ -125,13 +129,17 @@ impl WebDAVErrorClassifier {
|
||||||
fn extract_http_status(&self, error: &anyhow::Error) -> Option<i32> {
|
fn extract_http_status(&self, error: &anyhow::Error) -> Option<i32> {
|
||||||
let error_str = error.to_string();
|
let error_str = error.to_string();
|
||||||
|
|
||||||
// Look for common HTTP status code patterns
|
// Look for common HTTP status code patterns including WebDAV-specific codes
|
||||||
if error_str.contains("404") {
|
if error_str.contains("404") {
|
||||||
Some(404)
|
Some(404)
|
||||||
} else if error_str.contains("401") {
|
} else if error_str.contains("401") {
|
||||||
Some(401)
|
Some(401)
|
||||||
} else if error_str.contains("403") {
|
} else if error_str.contains("403") {
|
||||||
Some(403)
|
Some(403)
|
||||||
|
} else if error_str.contains("405") {
|
||||||
|
Some(405) // Method Not Allowed (PROPFIND disabled)
|
||||||
|
} else if error_str.contains("423") {
|
||||||
|
Some(423) // Locked
|
||||||
} else if error_str.contains("500") {
|
} else if error_str.contains("500") {
|
||||||
Some(500)
|
Some(500)
|
||||||
} else if error_str.contains("502") {
|
} else if error_str.contains("502") {
|
||||||
|
|
@ -140,8 +148,8 @@ impl WebDAVErrorClassifier {
|
||||||
Some(503)
|
Some(503)
|
||||||
} else if error_str.contains("504") {
|
} else if error_str.contains("504") {
|
||||||
Some(504)
|
Some(504)
|
||||||
} else if error_str.contains("405") {
|
} else if error_str.contains("507") {
|
||||||
Some(405)
|
Some(507) // Insufficient Storage
|
||||||
} else {
|
} else {
|
||||||
// Try to extract any 3-digit number that looks like an HTTP status
|
// Try to extract any 3-digit number that looks like an HTTP status
|
||||||
let re = regex::Regex::new(r"\b([4-5]\d{2})\b").ok()?;
|
let re = regex::Regex::new(r"\b([4-5]\d{2})\b").ok()?;
|
||||||
|
|
@ -370,6 +378,24 @@ impl WebDAVErrorClassifier {
|
||||||
directory_path
|
directory_path
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
WebDAVScanFailureType::ServerError if http_status == Some(405) => {
|
||||||
|
format!(
|
||||||
|
"WebDAV PROPFIND method is not allowed for '{}'. The server may not support WebDAV or it's disabled for this path.",
|
||||||
|
directory_path
|
||||||
|
)
|
||||||
|
}
|
||||||
|
WebDAVScanFailureType::ServerError if http_status == Some(423) => {
|
||||||
|
format!(
|
||||||
|
"WebDAV resource '{}' is locked. Another process may be using it.",
|
||||||
|
directory_path
|
||||||
|
)
|
||||||
|
}
|
||||||
|
WebDAVScanFailureType::SizeLimit if http_status == Some(507) => {
|
||||||
|
format!(
|
||||||
|
"Insufficient storage quota for WebDAV path '{}'. The server has run out of space.",
|
||||||
|
directory_path
|
||||||
|
)
|
||||||
|
}
|
||||||
WebDAVScanFailureType::XmlParseError => {
|
WebDAVScanFailureType::XmlParseError => {
|
||||||
format!(
|
format!(
|
||||||
"Malformed XML response from WebDAV server for directory '{}'. Server may be incompatible.",
|
"Malformed XML response from WebDAV server for directory '{}'. Server may be incompatible.",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// Simplified WebDAV service modules - consolidated architecture
|
// Simplified WebDAV service modules - consolidated architecture
|
||||||
|
|
||||||
|
pub mod common; // Common utilities and shared functions
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
pub mod smart_sync;
|
pub mod smart_sync;
|
||||||
|
|
@ -8,6 +9,7 @@ pub mod error_classifier; // WebDAV error classification for generic error track
|
||||||
pub mod metrics_integration; // WebDAV metrics collection integration
|
pub mod metrics_integration; // WebDAV metrics collection integration
|
||||||
|
|
||||||
// Re-export main types for convenience
|
// Re-export main types for convenience
|
||||||
|
pub use common::build_user_agent;
|
||||||
pub use config::{WebDAVConfig, RetryConfig, ConcurrencyConfig};
|
pub use config::{WebDAVConfig, RetryConfig, ConcurrencyConfig};
|
||||||
pub use service::{
|
pub use service::{
|
||||||
WebDAVService, WebDAVDiscoveryResult, WebDAVDownloadResult, ServerCapabilities, HealthStatus, test_webdav_connection,
|
WebDAVService, WebDAVDiscoveryResult, WebDAVDownloadResult, ServerCapabilities, HealthStatus, test_webdav_connection,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ use crate::webdav_xml_parser::{parse_propfind_response, parse_propfind_response_
|
||||||
use crate::mime_detection::{detect_mime_from_content, update_mime_type_with_content, MimeDetectionResult};
|
use crate::mime_detection::{detect_mime_from_content, update_mime_type_with_content, MimeDetectionResult};
|
||||||
|
|
||||||
use super::{config::{WebDAVConfig, RetryConfig, ConcurrencyConfig}, SyncProgress};
|
use super::{config::{WebDAVConfig, RetryConfig, ConcurrencyConfig}, SyncProgress};
|
||||||
|
use super::common::build_user_agent;
|
||||||
|
|
||||||
/// Results from WebDAV discovery including both files and directories
|
/// Results from WebDAV discovery including both files and directories
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -662,8 +663,8 @@ impl WebDAVService {
|
||||||
let mut attempt = 0;
|
let mut attempt = 0;
|
||||||
let mut delay = self.retry_config.initial_delay_ms;
|
let mut delay = self.retry_config.initial_delay_ms;
|
||||||
|
|
||||||
// Build custom User-Agent header
|
// Build custom User-Agent header using centralized function
|
||||||
let user_agent = format!("Readur/{} (WebDAV-Sync; +https://github.com/readur)", env!("CARGO_PKG_VERSION"));
|
let user_agent = build_user_agent();
|
||||||
|
|
||||||
// Enhanced debug logging for HTTP requests
|
// Enhanced debug logging for HTTP requests
|
||||||
debug!("🌐 HTTP Request Details:");
|
debug!("🌐 HTTP Request Details:");
|
||||||
|
|
@ -951,8 +952,8 @@ impl WebDAVService {
|
||||||
/// Discovers both files and directories with their ETags for directory tracking
|
/// Discovers both files and directories with their ETags for directory tracking
|
||||||
pub async fn discover_files_and_directories(&self, directory_path: &str, recursive: bool) -> Result<WebDAVDiscoveryResult> {
|
pub async fn discover_files_and_directories(&self, directory_path: &str, recursive: bool) -> Result<WebDAVDiscoveryResult> {
|
||||||
let discovery_request_id = uuid::Uuid::new_v4();
|
let discovery_request_id = uuid::Uuid::new_v4();
|
||||||
info!("[{}] 🔍 Discovering files and directories in: '{}' (recursive: {}, user_agent: 'readur-webdav-client')",
|
info!("[{}] 🔍 Discovering files and directories in: '{}' (recursive: {}, user_agent: '{}')",
|
||||||
discovery_request_id, directory_path, recursive);
|
discovery_request_id, directory_path, recursive, build_user_agent());
|
||||||
|
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
let result = if recursive {
|
let result = if recursive {
|
||||||
|
|
@ -985,8 +986,8 @@ impl WebDAVService {
|
||||||
_progress: Option<&SyncProgress> // Simplified: just placeholder for API compatibility
|
_progress: Option<&SyncProgress> // Simplified: just placeholder for API compatibility
|
||||||
) -> Result<WebDAVDiscoveryResult> {
|
) -> Result<WebDAVDiscoveryResult> {
|
||||||
let discovery_request_id = uuid::Uuid::new_v4();
|
let discovery_request_id = uuid::Uuid::new_v4();
|
||||||
info!("[{}] 🔍 Discovering files and directories in: '{}' (progress tracking simplified, recursive: {}, user_agent: 'readur-webdav-client')",
|
info!("[{}] 🔍 Discovering files and directories in: '{}' (progress tracking simplified, recursive: {}, user_agent: '{}')",
|
||||||
discovery_request_id, directory_path, recursive);
|
discovery_request_id, directory_path, recursive, build_user_agent());
|
||||||
|
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
let result = if recursive {
|
let result = if recursive {
|
||||||
|
|
@ -1389,8 +1390,8 @@ impl WebDAVService {
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
let discovery_request_id = uuid::Uuid::new_v4();
|
let discovery_request_id = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
info!("[{}] 🔍 Starting WebDAV discovery for '{}' (user: {}, source: {:?}, recursive: {}, user_agent: 'readur-webdav-client')",
|
info!("[{}] 🔍 Starting WebDAV discovery for '{}' (user: {}, source: {:?}, recursive: {}, user_agent: '{}')",
|
||||||
discovery_request_id, directory_path, user_id, source_id, recursive);
|
discovery_request_id, directory_path, user_id, source_id, recursive, build_user_agent());
|
||||||
|
|
||||||
// Check if we should skip this directory due to previous failures
|
// Check if we should skip this directory due to previous failures
|
||||||
let error_check_start = std::time::Instant::now();
|
let error_check_start = std::time::Instant::now();
|
||||||
|
|
@ -1477,7 +1478,7 @@ impl WebDAVService {
|
||||||
// Track the error with enhanced context
|
// Track the error with enhanced context
|
||||||
let mut additional_context = std::collections::HashMap::new();
|
let mut additional_context = std::collections::HashMap::new();
|
||||||
additional_context.insert("request_id".to_string(), serde_json::Value::String(discovery_request_id.to_string()));
|
additional_context.insert("request_id".to_string(), serde_json::Value::String(discovery_request_id.to_string()));
|
||||||
additional_context.insert("user_agent".to_string(), serde_json::Value::String("readur-webdav-client".to_string()));
|
additional_context.insert("user_agent".to_string(), serde_json::Value::String(build_user_agent()));
|
||||||
additional_context.insert("recursive".to_string(), serde_json::Value::Bool(recursive));
|
additional_context.insert("recursive".to_string(), serde_json::Value::Bool(recursive));
|
||||||
|
|
||||||
let context = ErrorContext {
|
let context = ErrorContext {
|
||||||
|
|
@ -2593,7 +2594,7 @@ mod tests {
|
||||||
fn test_user_agent_format() {
|
fn test_user_agent_format() {
|
||||||
// Test that the User-Agent header format matches the expected pattern
|
// Test that the User-Agent header format matches the expected pattern
|
||||||
let expected_version = env!("CARGO_PKG_VERSION");
|
let expected_version = env!("CARGO_PKG_VERSION");
|
||||||
let expected_user_agent = format!("Readur/{} (WebDAV-Sync; +https://github.com/readur)", expected_version);
|
let expected_user_agent = build_user_agent();
|
||||||
|
|
||||||
// Create a simple WebDAV config for testing
|
// Create a simple WebDAV config for testing
|
||||||
let config = WebDAVConfig {
|
let config = WebDAVConfig {
|
||||||
|
|
@ -2609,7 +2610,7 @@ mod tests {
|
||||||
let service = WebDAVService::new(config).expect("Failed to create WebDAV service");
|
let service = WebDAVService::new(config).expect("Failed to create WebDAV service");
|
||||||
|
|
||||||
// Test that we can build the User-Agent string properly
|
// Test that we can build the User-Agent string properly
|
||||||
let user_agent = format!("Readur/{} (WebDAV-Sync; +https://github.com/readur)", env!("CARGO_PKG_VERSION"));
|
let user_agent = build_user_agent();
|
||||||
assert_eq!(user_agent, expected_user_agent);
|
assert_eq!(user_agent, expected_user_agent);
|
||||||
assert!(user_agent.starts_with("Readur/"));
|
assert!(user_agent.starts_with("Readur/"));
|
||||||
assert!(user_agent.contains("(WebDAV-Sync; +https://github.com/readur)"));
|
assert!(user_agent.contains("(WebDAV-Sync; +https://github.com/readur)"));
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,7 @@ impl SmartSyncService {
|
||||||
pub async fn perform_smart_sync(
|
pub async fn perform_smart_sync(
|
||||||
&self,
|
&self,
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
|
source_id: Option<Uuid>,
|
||||||
webdav_service: &WebDAVService,
|
webdav_service: &WebDAVService,
|
||||||
folder_path: &str,
|
folder_path: &str,
|
||||||
strategy: SmartSyncStrategy,
|
strategy: SmartSyncStrategy,
|
||||||
|
|
@ -222,13 +223,13 @@ impl SmartSyncService {
|
||||||
match strategy {
|
match strategy {
|
||||||
SmartSyncStrategy::FullDeepScan => {
|
SmartSyncStrategy::FullDeepScan => {
|
||||||
info!("[{}] 🔍 Performing full deep scan for: '{}'", sync_request_id, folder_path);
|
info!("[{}] 🔍 Performing full deep scan for: '{}'", sync_request_id, folder_path);
|
||||||
self.perform_full_deep_scan(user_id, webdav_service, folder_path, _progress, sync_request_id).await
|
self.perform_full_deep_scan(user_id, source_id, webdav_service, folder_path, _progress, sync_request_id).await
|
||||||
}
|
}
|
||||||
SmartSyncStrategy::TargetedScan(target_dirs) => {
|
SmartSyncStrategy::TargetedScan(target_dirs) => {
|
||||||
info!("[{}] 🎯 Performing targeted scan of {} directories: {:?}",
|
info!("[{}] 🎯 Performing targeted scan of {} directories: {:?}",
|
||||||
sync_request_id, target_dirs.len(),
|
sync_request_id, target_dirs.len(),
|
||||||
target_dirs.iter().take(3).collect::<Vec<_>>());
|
target_dirs.iter().take(3).collect::<Vec<_>>());
|
||||||
self.perform_targeted_scan(user_id, webdav_service, target_dirs, _progress, sync_request_id).await
|
self.perform_targeted_scan(user_id, source_id, webdav_service, target_dirs, _progress, sync_request_id).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -237,6 +238,7 @@ impl SmartSyncService {
|
||||||
pub async fn evaluate_and_sync(
|
pub async fn evaluate_and_sync(
|
||||||
&self,
|
&self,
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
|
source_id: Option<Uuid>,
|
||||||
webdav_service: &WebDAVService,
|
webdav_service: &WebDAVService,
|
||||||
folder_path: &str,
|
folder_path: &str,
|
||||||
_progress: Option<&SyncProgress>, // Simplified: no complex progress tracking
|
_progress: Option<&SyncProgress>, // Simplified: no complex progress tracking
|
||||||
|
|
@ -252,7 +254,7 @@ impl SmartSyncService {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
SmartSyncDecision::RequiresSync(strategy) => {
|
SmartSyncDecision::RequiresSync(strategy) => {
|
||||||
let result = self.perform_smart_sync(user_id, webdav_service, folder_path, strategy, _progress).await?;
|
let result = self.perform_smart_sync(user_id, source_id, webdav_service, folder_path, strategy, _progress).await?;
|
||||||
let total_elapsed = eval_and_sync_start.elapsed();
|
let total_elapsed = eval_and_sync_start.elapsed();
|
||||||
info!("[{}] ✅ Smart sync completed for '{}' - {} files found, {} dirs scanned in {:.2}s",
|
info!("[{}] ✅ Smart sync completed for '{}' - {} files found, {} dirs scanned in {:.2}s",
|
||||||
eval_sync_request_id, folder_path, result.files.len(),
|
eval_sync_request_id, folder_path, result.files.len(),
|
||||||
|
|
@ -266,6 +268,7 @@ impl SmartSyncService {
|
||||||
async fn perform_full_deep_scan(
|
async fn perform_full_deep_scan(
|
||||||
&self,
|
&self,
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
|
source_id: Option<Uuid>,
|
||||||
webdav_service: &WebDAVService,
|
webdav_service: &WebDAVService,
|
||||||
folder_path: &str,
|
folder_path: &str,
|
||||||
_progress: Option<&SyncProgress>, // Simplified: no complex progress tracking
|
_progress: Option<&SyncProgress>, // Simplified: no complex progress tracking
|
||||||
|
|
@ -277,7 +280,7 @@ impl SmartSyncService {
|
||||||
true, // recursive
|
true, // recursive
|
||||||
user_id,
|
user_id,
|
||||||
&self.error_tracker,
|
&self.error_tracker,
|
||||||
None, // source_id - using None for smart sync operations
|
source_id, // Pass the actual source_id
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
info!("Deep scan found {} files and {} directories in folder {}",
|
info!("Deep scan found {} files and {} directories in folder {}",
|
||||||
|
|
@ -341,6 +344,7 @@ impl SmartSyncService {
|
||||||
async fn perform_targeted_scan(
|
async fn perform_targeted_scan(
|
||||||
&self,
|
&self,
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
|
source_id: Option<Uuid>,
|
||||||
webdav_service: &WebDAVService,
|
webdav_service: &WebDAVService,
|
||||||
target_directories: Vec<String>,
|
target_directories: Vec<String>,
|
||||||
_progress: Option<&SyncProgress>, // Simplified: no complex progress tracking
|
_progress: Option<&SyncProgress>, // Simplified: no complex progress tracking
|
||||||
|
|
@ -361,7 +365,7 @@ impl SmartSyncService {
|
||||||
true, // recursive
|
true, // recursive
|
||||||
user_id,
|
user_id,
|
||||||
&self.error_tracker,
|
&self.error_tracker,
|
||||||
None, // source_id - using None for smart sync operations
|
source_id, // Pass the actual source_id
|
||||||
).await {
|
).await {
|
||||||
Ok(discovery_result) => {
|
Ok(discovery_result) => {
|
||||||
all_files.extend(discovery_result.files);
|
all_files.extend(discovery_result.files);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, VecDeque};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
@ -9,6 +9,7 @@ use reqwest::header::HeaderMap;
|
||||||
|
|
||||||
use crate::db::Database;
|
use crate::db::Database;
|
||||||
use crate::models::webdav_metrics::*;
|
use crate::models::webdav_metrics::*;
|
||||||
|
use crate::services::webdav::build_user_agent;
|
||||||
|
|
||||||
/// Maximum number of response times to keep in memory to prevent unbounded growth
|
/// Maximum number of response times to keep in memory to prevent unbounded growth
|
||||||
const MAX_RESPONSE_TIMES: usize = 1000;
|
const MAX_RESPONSE_TIMES: usize = 1000;
|
||||||
|
|
@ -89,7 +90,7 @@ struct DirectoryCounters {
|
||||||
errors_encountered: i32,
|
errors_encountered: i32,
|
||||||
error_types: Vec<String>,
|
error_types: Vec<String>,
|
||||||
warnings_count: i32,
|
warnings_count: i32,
|
||||||
response_times: Vec<i64>, // This will be bounded by MAX_RESPONSE_TIMES
|
response_times: VecDeque<i64>, // Use VecDeque for O(1) front removal
|
||||||
etag_matches: i32,
|
etag_matches: i32,
|
||||||
etag_mismatches: i32,
|
etag_mismatches: i32,
|
||||||
cache_hits: i32,
|
cache_hits: i32,
|
||||||
|
|
@ -228,6 +229,12 @@ impl WebDAVMetricsTracker {
|
||||||
};
|
};
|
||||||
|
|
||||||
self.db.update_webdav_sync_session(session_id, &update).await?;
|
self.db.update_webdav_sync_session(session_id, &update).await?;
|
||||||
|
|
||||||
|
// Small delay to ensure all previous HTTP request inserts are committed
|
||||||
|
// This addresses a transaction isolation issue where the finalize function
|
||||||
|
// can't see the requests that were just inserted
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||||
|
|
||||||
self.db.finalize_webdav_sync_session(session_id).await?;
|
self.db.finalize_webdav_sync_session(session_id).await?;
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
|
|
@ -483,9 +490,10 @@ impl WebDAVMetricsTracker {
|
||||||
last_modified,
|
last_modified,
|
||||||
content_type,
|
content_type,
|
||||||
remote_ip,
|
remote_ip,
|
||||||
user_agent: Some("readur-webdav-client".to_string()),
|
user_agent: Some(build_user_agent()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tracing::debug!("Recording request with session_id: {:?}", session_id);
|
||||||
let request_id = self.db.record_webdav_request_metric(&metric).await?;
|
let request_id = self.db.record_webdav_request_metric(&metric).await?;
|
||||||
|
|
||||||
// Update active directory counters if applicable
|
// Update active directory counters if applicable
|
||||||
|
|
@ -495,10 +503,10 @@ impl WebDAVMetricsTracker {
|
||||||
scan.last_activity = Instant::now();
|
scan.last_activity = Instant::now();
|
||||||
scan.counters.http_requests_made += 1;
|
scan.counters.http_requests_made += 1;
|
||||||
|
|
||||||
// Implement bounded circular buffer for response times
|
// Implement bounded circular buffer for response times using VecDeque for O(1) operations
|
||||||
scan.counters.response_times.push(duration.as_millis() as i64);
|
scan.counters.response_times.push_back(duration.as_millis() as i64);
|
||||||
if scan.counters.response_times.len() > MAX_RESPONSE_TIMES {
|
if scan.counters.response_times.len() > MAX_RESPONSE_TIMES {
|
||||||
scan.counters.response_times.remove(0); // Remove oldest entry
|
scan.counters.response_times.pop_front(); // O(1) removal of oldest entry
|
||||||
}
|
}
|
||||||
|
|
||||||
match request_type {
|
match request_type {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ async fn test_retry_config_default() {
|
||||||
|
|
||||||
assert_eq!(retry_config.max_retries, 3);
|
assert_eq!(retry_config.max_retries, 3);
|
||||||
assert_eq!(retry_config.initial_delay_ms, 1000);
|
assert_eq!(retry_config.initial_delay_ms, 1000);
|
||||||
assert_eq!(retry_config.max_delay_ms, 30000);
|
assert_eq!(retry_config.max_delay_ms, 10000);
|
||||||
assert_eq!(retry_config.backoff_multiplier, 2.0);
|
assert_eq!(retry_config.backoff_multiplier, 2.0);
|
||||||
assert_eq!(retry_config.timeout_seconds, 30);
|
assert_eq!(retry_config.timeout_seconds, 30);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,64 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use readur::db::Database;
|
use readur::{
|
||||||
use readur::models::webdav_metrics::*;
|
db::Database,
|
||||||
use readur::services::webdav_metrics_tracker::WebDAVMetricsTracker;
|
models::webdav_metrics::*,
|
||||||
|
models::{CreateUser, UserRole},
|
||||||
|
services::webdav_metrics_tracker::WebDAVMetricsTracker,
|
||||||
|
test_helpers::create_test_app_state,
|
||||||
|
};
|
||||||
|
|
||||||
/// Helper to create a test database with temporary configuration
|
/// Helper to create a test user using the proper models
|
||||||
async fn create_test_db() -> Result<Database> {
|
async fn create_test_user(db: &Database) -> Result<Uuid> {
|
||||||
let db_url = std::env::var("DATABASE_TEST_URL")
|
let user_suffix = Uuid::new_v4().simple().to_string();
|
||||||
.unwrap_or_else(|_| "postgresql://postgres:password@localhost:5432/readur_test".to_string());
|
let create_user = CreateUser {
|
||||||
|
username: format!("testuser_{}", user_suffix),
|
||||||
|
email: format!("test_{}@example.com", user_suffix),
|
||||||
|
password: "test_password".to_string(),
|
||||||
|
role: Some(UserRole::User),
|
||||||
|
};
|
||||||
|
|
||||||
Database::new_with_pool_config(&db_url, 5, 1).await
|
let created_user = db.create_user(create_user).await?;
|
||||||
|
Ok(created_user.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to create a test user
|
/// Helper to create a test WebDAV source
|
||||||
async fn create_test_user(db: &Database) -> Result<Uuid> {
|
async fn create_test_source(db: &Database, user_id: Uuid) -> Result<Uuid> {
|
||||||
let user_id = Uuid::new_v4();
|
let source_id = Uuid::new_v4();
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"INSERT INTO users (id, username, email, password_hash, role) VALUES ($1, $2, $3, $4, 'user')"
|
"INSERT INTO sources (id, user_id, name, source_type, config, enabled, created_at, updated_at)
|
||||||
|
VALUES ($1, $2, $3, 'webdav', $4, true, NOW(), NOW())"
|
||||||
)
|
)
|
||||||
|
.bind(source_id)
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
.bind(format!("testuser_{}", user_id))
|
.bind(format!("Test WebDAV Source {}", source_id))
|
||||||
.bind(format!("test_{}@example.com", user_id))
|
.bind(serde_json::json!({
|
||||||
.bind("dummy_hash")
|
"server_url": "https://example.com/webdav",
|
||||||
|
"username": "testuser",
|
||||||
|
"password": "testpass",
|
||||||
|
"watch_folders": ["/Documents"],
|
||||||
|
"file_extensions": ["pdf", "txt", "doc", "docx"],
|
||||||
|
"auto_sync": true,
|
||||||
|
"sync_interval_minutes": 60
|
||||||
|
}))
|
||||||
.execute(&db.pool)
|
.execute(&db.pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(user_id)
|
Ok(source_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test basic session creation and management
|
/// Test basic session creation and management
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_webdav_session_lifecycle() -> Result<()> {
|
async fn test_webdav_session_lifecycle() -> Result<()> {
|
||||||
let db = create_test_db().await?;
|
let app_state = create_test_app_state().await
|
||||||
let user_id = create_test_user(&db).await?;
|
.map_err(|e| anyhow::anyhow!("Failed to create test app state: {}", e))?;
|
||||||
let source_id = Some(Uuid::new_v4());
|
let user_id = create_test_user(&app_state.db).await?;
|
||||||
|
let source_id = Some(create_test_source(&app_state.db, user_id).await?);
|
||||||
|
|
||||||
let metrics_tracker = WebDAVMetricsTracker::new(db.clone());
|
let metrics_tracker = WebDAVMetricsTracker::new(app_state.db.clone());
|
||||||
|
|
||||||
// Start a sync session
|
// Start a sync session
|
||||||
let session_id = metrics_tracker
|
let session_id = metrics_tracker
|
||||||
|
|
@ -108,11 +129,12 @@ async fn test_webdav_session_lifecycle() -> Result<()> {
|
||||||
/// Test directory metrics tracking
|
/// Test directory metrics tracking
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_directory_metrics_tracking() -> Result<()> {
|
async fn test_directory_metrics_tracking() -> Result<()> {
|
||||||
let db = create_test_db().await?;
|
let app_state = create_test_app_state().await
|
||||||
let user_id = create_test_user(&db).await?;
|
.map_err(|e| anyhow::anyhow!("Failed to create test app state: {}", e))?;
|
||||||
let source_id = Some(Uuid::new_v4());
|
let user_id = create_test_user(&app_state.db).await?;
|
||||||
|
let source_id = Some(create_test_source(&app_state.db, user_id).await?);
|
||||||
|
|
||||||
let metrics_tracker = WebDAVMetricsTracker::new(db.clone());
|
let metrics_tracker = WebDAVMetricsTracker::new(app_state.db.clone());
|
||||||
|
|
||||||
// Start session
|
// Start session
|
||||||
let session_id = metrics_tracker
|
let session_id = metrics_tracker
|
||||||
|
|
@ -213,11 +235,12 @@ async fn test_directory_metrics_tracking() -> Result<()> {
|
||||||
/// Test HTTP request metrics recording
|
/// Test HTTP request metrics recording
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_http_request_metrics() -> Result<()> {
|
async fn test_http_request_metrics() -> Result<()> {
|
||||||
let db = create_test_db().await?;
|
let app_state = create_test_app_state().await
|
||||||
let user_id = create_test_user(&db).await?;
|
.map_err(|e| anyhow::anyhow!("Failed to create test app state: {}", e))?;
|
||||||
let source_id = Some(Uuid::new_v4());
|
let user_id = create_test_user(&app_state.db).await?;
|
||||||
|
let source_id = Some(create_test_source(&app_state.db, user_id).await?);
|
||||||
|
|
||||||
let metrics_tracker = WebDAVMetricsTracker::new(db.clone());
|
let metrics_tracker = WebDAVMetricsTracker::new(app_state.db.clone());
|
||||||
|
|
||||||
// Start session and directory
|
// Start session and directory
|
||||||
let session_id = metrics_tracker
|
let session_id = metrics_tracker
|
||||||
|
|
@ -326,11 +349,12 @@ async fn test_http_request_metrics() -> Result<()> {
|
||||||
/// Test metrics summary generation
|
/// Test metrics summary generation
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_metrics_summary() -> Result<()> {
|
async fn test_metrics_summary() -> Result<()> {
|
||||||
let db = create_test_db().await?;
|
let app_state = create_test_app_state().await
|
||||||
let user_id = create_test_user(&db).await?;
|
.map_err(|e| anyhow::anyhow!("Failed to create test app state: {}", e))?;
|
||||||
let source_id = Some(Uuid::new_v4());
|
let user_id = create_test_user(&app_state.db).await?;
|
||||||
|
let source_id = Some(create_test_source(&app_state.db, user_id).await?);
|
||||||
|
|
||||||
let metrics_tracker = WebDAVMetricsTracker::new(db.clone());
|
let metrics_tracker = WebDAVMetricsTracker::new(app_state.db.clone());
|
||||||
|
|
||||||
// Create multiple sessions with various outcomes
|
// Create multiple sessions with various outcomes
|
||||||
for i in 0..3 {
|
for i in 0..3 {
|
||||||
|
|
@ -424,11 +448,12 @@ async fn test_metrics_summary() -> Result<()> {
|
||||||
/// Test performance insights generation
|
/// Test performance insights generation
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_performance_insights() -> Result<()> {
|
async fn test_performance_insights() -> Result<()> {
|
||||||
let db = create_test_db().await?;
|
let app_state = create_test_app_state().await
|
||||||
let user_id = create_test_user(&db).await?;
|
.map_err(|e| anyhow::anyhow!("Failed to create test app state: {}", e))?;
|
||||||
let source_id = Some(Uuid::new_v4());
|
let user_id = create_test_user(&app_state.db).await?;
|
||||||
|
let source_id = Some(create_test_source(&app_state.db, user_id).await?);
|
||||||
|
|
||||||
let metrics_tracker = WebDAVMetricsTracker::new(db.clone());
|
let metrics_tracker = WebDAVMetricsTracker::new(app_state.db.clone());
|
||||||
|
|
||||||
// Create a session with detailed metrics
|
// Create a session with detailed metrics
|
||||||
let session_id = metrics_tracker
|
let session_id = metrics_tracker
|
||||||
|
|
@ -515,11 +540,12 @@ async fn test_performance_insights() -> Result<()> {
|
||||||
/// Integration test demonstrating the complete metrics collection workflow
|
/// Integration test demonstrating the complete metrics collection workflow
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_complete_metrics_workflow() -> Result<()> {
|
async fn test_complete_metrics_workflow() -> Result<()> {
|
||||||
let db = create_test_db().await?;
|
let app_state = create_test_app_state().await
|
||||||
let user_id = create_test_user(&db).await?;
|
.map_err(|e| anyhow::anyhow!("Failed to create test app state: {}", e))?;
|
||||||
let source_id = Some(Uuid::new_v4());
|
let user_id = create_test_user(&app_state.db).await?;
|
||||||
|
let source_id = Some(create_test_source(&app_state.db, user_id).await?);
|
||||||
|
|
||||||
let metrics_tracker = WebDAVMetricsTracker::new(db.clone());
|
let metrics_tracker = WebDAVMetricsTracker::new(app_state.db.clone());
|
||||||
|
|
||||||
println!("🚀 Starting complete WebDAV metrics workflow test");
|
println!("🚀 Starting complete WebDAV metrics workflow test");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue