diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..81229e8d2 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 259113f73..4beaadeee 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ +prometheus/data/ +grafana/data/ ### STS ### .apt_generated @@ -18,7 +20,7 @@ target/ *.iws *.iml *.ipr -./http-client.env.json +http-client.env.json ### NetBeans ### /nbproject/private/ diff --git a/grafana/provisioning/dashboards/ServicesStatistic.json b/grafana/provisioning/dashboards/ServicesStatistic.json index 684b97269..0c32b8927 100644 --- a/grafana/provisioning/dashboards/ServicesStatistic.json +++ b/grafana/provisioning/dashboards/ServicesStatistic.json @@ -24,614 +24,760 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, - "id": 4, "links": [], "liveNow": true, "panels": [ { - "collapsed": true, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, "gridPos": { - "h": 1, - "w": 24, + "h": 8, + "w": 12, "x": 0, "y": 0 }, - "id": 27, - "panels": [ + "id": 103, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "bars", - "fillOpacity": 23, - "gradientMode": "hue", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "normal" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 1 - }, - "id": 29, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(rate(payment_finished_total{service=~\"$service\"}[30s])) by (service, outcome, failReason)", - "hide": false, - "instant": false, - "legendFormat": "{{outcome}} - {{failReason}}", - "range": true, - "refId": "B" - } - ], - "title": "Payments outcome rate per second", - "type": "timeseries" + "editorMode": "code", + "expr": "increase(payment_requests_incoming_total[5m])", + "instant": false, + "legendFormat": "incoming", + "range": true, + "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 1 - }, - "id": 26, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": true, - "expr": "sum by (type) (rate(events_total{}[30s]))", - "interval": "", - "legendFormat": "{{type}}", - "range": true, - "refId": "A" - } - ], - "title": "Event during testing per second", - "type": "timeseries" + "editorMode": "code", + "expr": "increase(payment_requests_outgoing_total[5m])", + "hide": false, + "instant": false, + "legendFormat": "outgoing", + "range": true, + "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "bars", - "fillOpacity": 23, - "gradientMode": "hue", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "percent" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 265 - }, - "id": 28, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service))", - "hide": false, - "instant": false, - "legendFormat": "income - {{service}}", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service))", - "hide": false, - "instant": false, - "legendFormat": "expenses - {{service}} ", - "range": true, - "refId": "A" - } - ], - "title": "Income / Expenses percentage", - "type": "timeseries" + "editorMode": "code", + "expr": "increase(payment_requests_completed_total[5m])", + "hide": false, + "instant": false, + "legendFormat": "completed", + "range": true, + "refId": "C" }, { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-RdYlGr" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 265 - }, - "id": 36, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "editorMode": "code", + "expr": "increase(payment_requests_failed_total[5m])", + "hide": false, + "instant": false, + "legendFormat": "failed", + "range": true, + "refId": "D" + } + ], + "title": "Servise request increase", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 102, + "panels": [], + "title": "Custom", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 27, + "panels": [], + "title": "General", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 23, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } }, - "pluginVersion": "11.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service))", - "hide": false, - "instant": false, - "legendFormat": "income - {{service}}", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null }, - "editorMode": "code", - "expr": "sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service))", - "hide": false, - "instant": false, - "legendFormat": "expenses - {{service}} ", - "range": true, - "refId": "A" - } - ], - "title": "Income / Expenses percentage", - "type": "stat" + { + "color": "red", + "value": 80 + } + ] + } }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 29, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "bars", - "fillOpacity": 23, - "gradientMode": "hue", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": 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": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] + "editorMode": "code", + "expr": "sum(rate(payment_finished_total{service=~\"$service\"}[30s])) by (service, outcome, failReason)", + "hide": false, + "instant": false, + "legendFormat": "{{outcome}} - {{failReason}}", + "range": true, + "refId": "B" + } + ], + "title": "Payments outcome rate per second", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 273 + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 26, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" }, - "id": 37, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" + "editorMode": "code", + "exemplar": true, + "expr": "sum by (type) (rate(events_total{}[30s]))", + "interval": "", + "legendFormat": "{{type}}", + "range": true, + "refId": "A" + } + ], + "title": "Event during testing per second", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 23, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "percent" + }, + "thresholdsStyle": { + "mode": "off" } }, - "pluginVersion": "11.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null }, - "editorMode": "code", - "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service)", - "hide": false, - "instant": false, - "legendFormat": "income - {{service}}", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 28, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service))", + "hide": false, + "instant": false, + "legendFormat": "income - {{service}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service))", + "hide": false, + "instant": false, + "legendFormat": "expenses - {{service}} ", + "range": true, + "refId": "A" + } + ], + "title": "Income / Expenses percentage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-RdYlGr" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null }, - "editorMode": "code", - "expr": "sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service, accountName) ", - "hide": false, - "instant": false, - "legendFormat": "expenses - {{service}} - {{accountName}}", - "range": true, - "refId": "A" - } + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 18 + }, + "id": 36, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" ], - "title": "Income / Expenses amount", - "type": "timeseries" + "fields": "", + "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-RdYlGr" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] + "editorMode": "code", + "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service))", + "hide": false, + "instant": false, + "legendFormat": "income - {{service}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service))", + "hide": false, + "instant": false, + "legendFormat": "expenses - {{service}} ", + "range": true, + "refId": "A" + } + ], + "title": "Income / Expenses percentage", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 23, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": 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": [] + { + "color": "red", + "value": 80 + } + ] }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 273 + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 26 + }, + "id": 37, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" }, - "id": 31, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "editorMode": "code", + "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service)", + "hide": false, + "instant": false, + "legendFormat": "income - {{service}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" }, - "pluginVersion": "11.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service)", - "hide": false, - "instant": false, - "legendFormat": "income - {{service}}", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" + "editorMode": "code", + "expr": "sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service, accountName) ", + "hide": false, + "instant": false, + "legendFormat": "expenses - {{service}} - {{accountName}}", + "range": true, + "refId": "A" + } + ], + "title": "Income / Expenses amount", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-RdYlGr" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null }, - "editorMode": "code", - "expr": "sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service, accountName) ", - "hide": false, - "instant": false, - "legendFormat": "expenses - {{service}} - {{accountName}}", - "range": true, - "refId": "A" - } + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 26 + }, + "id": 31, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" ], - "title": "Income / Expenses amount", - "type": "stat" + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service)", + "hide": false, + "instant": false, + "legendFormat": "income - {{service}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service, accountName) ", + "hide": false, + "instant": false, + "legendFormat": "expenses - {{service}} - {{accountName}}", + "range": true, + "refId": "A" } ], - "title": "General", - "type": "row" + "title": "Income / Expenses amount", + "type": "stat" }, { "collapsed": true, @@ -639,7 +785,7 @@ "h": 1, "w": 24, "x": 0, - "y": 1 + "y": 34 }, "id": 10, "panels": [ @@ -658,8 +804,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -701,7 +846,7 @@ "h": 8, "w": 12, "x": 0, - "y": 2 + "y": 18 }, "id": 2, "options": { @@ -792,8 +937,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -840,7 +984,7 @@ "h": 8, "w": 12, "x": 12, - "y": 2 + "y": 18 }, "id": 13, "options": { @@ -924,8 +1068,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -941,7 +1084,7 @@ "h": 8, "w": 12, "x": 0, - "y": 226 + "y": 242 }, "id": 25, "options": { @@ -985,7 +1128,7 @@ "h": 1, "w": 24, "x": 0, - "y": 2 + "y": 35 }, "id": 12, "panels": [ @@ -1040,8 +1183,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1087,7 +1229,7 @@ "h": 8, "w": 12, "x": 0, - "y": 3 + "y": 19 }, "id": 7, "options": { @@ -1174,8 +1316,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1222,7 +1363,7 @@ "h": 8, "w": 12, "x": 12, - "y": 3 + "y": 19 }, "id": 8, "options": { @@ -1307,8 +1448,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -1320,7 +1460,7 @@ "h": 8, "w": 12, "x": 0, - "y": 123 + "y": 139 }, "id": 30, "options": { @@ -1378,7 +1518,7 @@ "h": 1, "w": 24, "x": 0, - "y": 3 + "y": 36 }, "id": 17, "panels": [ @@ -1433,8 +1573,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1481,7 +1620,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4 + "y": 20 }, "id": 3, "options": { @@ -1569,8 +1708,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1617,7 +1755,7 @@ "h": 8, "w": 12, "x": 12, - "y": 4 + "y": 20 }, "id": 14, "options": { @@ -1707,8 +1845,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1755,7 +1892,7 @@ "h": 8, "w": 12, "x": 0, - "y": 100 + "y": 116 }, "id": 32, "options": { @@ -1845,8 +1982,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1893,7 +2029,7 @@ "h": 8, "w": 12, "x": 12, - "y": 100 + "y": 116 }, "id": 34, "options": { @@ -1983,8 +2119,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2055,7 +2190,7 @@ "h": 8, "w": 12, "x": 12, - "y": 108 + "y": 124 }, "id": 43, "options": { @@ -2103,7 +2238,7 @@ "h": 1, "w": 24, "x": 0, - "y": 4 + "y": 37 }, "id": 21, "panels": [ @@ -2155,8 +2290,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2171,7 +2305,7 @@ "h": 8, "w": 12, "x": 0, - "y": 5 + "y": 21 }, "id": 19, "options": { @@ -2253,8 +2387,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2269,7 +2402,7 @@ "h": 8, "w": 12, "x": 12, - "y": 5 + "y": 21 }, "id": 22, "options": { @@ -2351,8 +2484,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2367,7 +2499,7 @@ "h": 8, "w": 12, "x": 12, - "y": 85 + "y": 101 }, "id": 100, "options": { @@ -2411,7 +2543,7 @@ "h": 1, "w": 24, "x": 0, - "y": 5 + "y": 38 }, "id": 38, "panels": [ @@ -2463,8 +2595,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -2476,7 +2607,7 @@ "h": 8, "w": 12, "x": 0, - "y": 6 + "y": 22 }, "id": 40, "options": { @@ -2557,8 +2688,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -2570,7 +2700,7 @@ "h": 8, "w": 12, "x": 12, - "y": 6 + "y": 22 }, "id": 39, "options": { @@ -2652,8 +2782,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2669,7 +2798,7 @@ "h": 8, "w": 12, "x": 0, - "y": 38 + "y": 54 }, "id": 95, "options": { @@ -2766,8 +2895,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -2779,7 +2907,7 @@ "h": 8, "w": 12, "x": 12, - "y": 38 + "y": 54 }, "id": 42, "options": { @@ -2860,8 +2988,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -2873,7 +3000,7 @@ "h": 8, "w": 12, "x": 0, - "y": 46 + "y": 62 }, "id": 41, "options": { @@ -2916,7 +3043,7 @@ "h": 1, "w": 24, "x": 0, - "y": 6 + "y": 39 }, "id": 97, "panels": [ @@ -2937,7 +3064,6 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, - "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", @@ -2984,7 +3110,7 @@ "h": 8, "w": 12, "x": 0, - "y": 7 + "y": 47 }, "id": 98, "maxDataPoints": 100, @@ -2998,6 +3124,7 @@ "showLegend": true }, "tooltip": { + "maxHeight": 600, "mode": "single", "sort": "none" } @@ -3039,7 +3166,6 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, - "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", @@ -3086,7 +3212,7 @@ "h": 8, "w": 12, "x": 12, - "y": 7 + "y": 47 }, "id": 99, "maxDataPoints": 100, @@ -3100,6 +3226,7 @@ "showLegend": true }, "tooltip": { + "maxHeight": 600, "mode": "single", "sort": "none" } @@ -3173,8 +3300,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3190,7 +3316,7 @@ "h": 8, "w": 24, "x": 0, - "y": 15 + "y": 55 }, "id": 24, "options": { @@ -3312,8 +3438,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3329,7 +3454,7 @@ "h": 8, "w": 12, "x": 0, - "y": 23 + "y": 63 }, "id": 4, "options": { @@ -3412,8 +3537,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3429,7 +3553,7 @@ "h": 8, "w": 12, "x": 12, - "y": 23 + "y": 63 }, "id": 96, "options": { @@ -3470,15 +3594,15 @@ "type": "row" } ], - "preload": false, "refresh": "5s", - "schemaVersion": 40, + "schemaVersion": 39, "tags": [], "templating": { "list": [ { "allValue": ".*", "current": { + "selected": false, "text": "All", "value": "$__all" }, @@ -3487,7 +3611,9 @@ "uid": "PBFA97CFB590B2093" }, "definition": "label_values(service)", + "hide": 0, "includeAll": true, + "multi": false, "name": "service", "options": [], "query": { @@ -3496,6 +3622,8 @@ }, "refresh": 1, "regex": "", + "skipUrlSync": false, + "sort": 0, "type": "query" } ] @@ -3504,6 +3632,7 @@ "from": "now-15m", "to": "now" }, + "timeRangeUpdatedDuringEditOrView": false, "timepicker": { "refresh_intervals": [ "5s", @@ -3514,6 +3643,6 @@ "timezone": "", "title": "Services statistic", "uid": "KVr-Vmpnz", - "version": 4, + "version": 1, "weekStart": "" } \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5724b2568..7c83195d1 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ Application for resilience and highly-loaded applications course - 2.2.0 + 2.1.0 1.9.0 4.12.0 2.7.5 diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 000000000..254fc3aa6 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/main/.DS_Store b/src/main/.DS_Store new file mode 100644 index 000000000..a06cf1a73 Binary files /dev/null and b/src/main/.DS_Store differ diff --git a/src/main/kotlin/.DS_Store b/src/main/kotlin/.DS_Store new file mode 100644 index 000000000..f7103b774 Binary files /dev/null and b/src/main/kotlin/.DS_Store differ diff --git a/src/main/kotlin/ru/.DS_Store b/src/main/kotlin/ru/.DS_Store new file mode 100644 index 000000000..67775a855 Binary files /dev/null and b/src/main/kotlin/ru/.DS_Store differ diff --git a/src/main/kotlin/ru/quipy/.DS_Store b/src/main/kotlin/ru/quipy/.DS_Store new file mode 100644 index 000000000..4a9b381be Binary files /dev/null and b/src/main/kotlin/ru/quipy/.DS_Store differ diff --git a/src/main/kotlin/ru/quipy/OnlineShopApplication.kt b/src/main/kotlin/ru/quipy/OnlineShopApplication.kt index 9ac22e094..41681afe8 100644 --- a/src/main/kotlin/ru/quipy/OnlineShopApplication.kt +++ b/src/main/kotlin/ru/quipy/OnlineShopApplication.kt @@ -3,12 +3,14 @@ package ru.quipy import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.scheduling.annotation.EnableScheduling import org.springframework.boot.runApplication import ru.quipy.common.utils.NamedThreadFactory import java.util.concurrent.Executors @SpringBootApplication +@EnableScheduling class OnlineShopApplication { val log: Logger = LoggerFactory.getLogger(OnlineShopApplication::class.java) diff --git a/src/main/kotlin/ru/quipy/apigateway/APIController.kt b/src/main/kotlin/ru/quipy/apigateway/APIController.kt index 6f23fa18d..c2d8184ab 100644 --- a/src/main/kotlin/ru/quipy/apigateway/APIController.kt +++ b/src/main/kotlin/ru/quipy/apigateway/APIController.kt @@ -3,13 +3,26 @@ package ru.quipy.apigateway import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import ru.quipy.common.utils.SlidingWindowRateLimiter import ru.quipy.orders.repository.OrderRepository import ru.quipy.payments.logic.OrderPayer +import ru.quipy.payments.logic.MetricsReporter import java.util.* +import java.time.Duration @RestController -class APIController { +class APIController( + private val apiRateLimit: Long +) { + + private val rateLimiter by lazy { + SlidingWindowRateLimiter(apiRateLimit, Duration.ofSeconds(1)) + } + val logger: Logger = LoggerFactory.getLogger(APIController::class.java) @@ -19,6 +32,9 @@ class APIController { @Autowired private lateinit var orderPayer: OrderPayer + @Autowired + private lateinit var metricsReporter: MetricsReporter + @PostMapping("/users") fun createUser(@RequestBody req: CreateUserRequest): User { return User(UUID.randomUUID(), req.name) @@ -55,16 +71,18 @@ class APIController { } @PostMapping("/orders/{orderId}/payment") - fun payOrder(@PathVariable orderId: UUID, @RequestParam deadline: Long): PaymentSubmissionDto { + fun payOrder(@PathVariable orderId: UUID, @RequestParam deadline: Long): ResponseEntity { val paymentId = UUID.randomUUID() val order = orderRepository.findById(orderId)?.let { orderRepository.save(it.copy(status = OrderStatus.PAYMENT_IN_PROGRESS)) it } ?: throw IllegalArgumentException("No such order $orderId") - + if (!rateLimiter.tick()){ + return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build(); + } val createdAt = orderPayer.processPayment(orderId, order.price, paymentId, deadline) - return PaymentSubmissionDto(createdAt, paymentId) + return ResponseEntity.ok(PaymentSubmissionDto(createdAt, paymentId)) } class PaymentSubmissionDto( diff --git a/src/main/kotlin/ru/quipy/config/RateConfiguration.kt b/src/main/kotlin/ru/quipy/config/RateConfiguration.kt new file mode 100644 index 000000000..432d46c51 --- /dev/null +++ b/src/main/kotlin/ru/quipy/config/RateConfiguration.kt @@ -0,0 +1,18 @@ +package ru.quipy.config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import ru.quipy.payments.logic.PaymentExternalSystemAdapter +import ru.quipy.payments.logic.PaymentExternalSystemAdapterImpl + +@Configuration +class RateConfiguration { + + @Bean + fun apiRateLimit(accountAdapters: List): Long { + return accountAdapters + .filterIsInstance() + .sumOf { it.getRateLimitPerSec().toLong() } + } +} + diff --git a/src/main/kotlin/ru/quipy/payments/.DS_Store b/src/main/kotlin/ru/quipy/payments/.DS_Store new file mode 100644 index 000000000..79ead5ce5 Binary files /dev/null and b/src/main/kotlin/ru/quipy/payments/.DS_Store differ diff --git a/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt b/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt index eceb90cff..0bb510f43 100644 --- a/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt +++ b/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt @@ -6,6 +6,7 @@ import com.fasterxml.jackson.module.kotlin.registerKotlinModule import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import ru.quipy.payments.logic.PaymentRateLimiterFactory import ru.quipy.core.EventSourcingService import ru.quipy.payments.api.PaymentAggregate import ru.quipy.payments.logic.* @@ -36,7 +37,10 @@ class PaymentAccountsConfig { lateinit var allowedAccounts: List @Bean - fun accountAdapters(paymentService: EventSourcingService): List { + fun accountAdapters( + paymentService: ru.quipy.core.EventSourcingService, + metricsReporter: MetricsReporter + ): List { val request = HttpRequest.newBuilder() .uri(URI("http://${paymentProviderHostPort}/external/accounts?serviceName=$serviceName&token=$token")) .GET() @@ -57,7 +61,9 @@ class PaymentAccountsConfig { it, paymentService, paymentProviderHostPort, - token + token, + PaymentRateLimiterFactory(), + metricsReporter ) } } diff --git a/src/main/kotlin/ru/quipy/payments/logic/MetricsReporter.kt b/src/main/kotlin/ru/quipy/payments/logic/MetricsReporter.kt new file mode 100644 index 000000000..01b62490e --- /dev/null +++ b/src/main/kotlin/ru/quipy/payments/logic/MetricsReporter.kt @@ -0,0 +1,80 @@ +package ru.quipy.payments.logic + +import io.micrometer.core.instrument.Counter +import io.micrometer.core.instrument.MeterRegistry +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Component +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicLong + +@Component +class MetricsReporter { + + private val logger = LoggerFactory.getLogger(MetricsReporter::class.java) + + private val incomingRequests = AtomicLong(0) + private val outgoingRequests = AtomicLong(0) + private val completedRequests = AtomicLong(0) + private val failedRequests = AtomicLong(0) + + @Autowired + private lateinit var meterRegistry: MeterRegistry + + private lateinit var incomingCounter: Counter + private lateinit var outgoingCounter: Counter + private lateinit var completedCounter: Counter + private lateinit var failedCounter: Counter + + @Autowired + fun initMetrics() { + incomingCounter = Counter.builder("payment.requests.incoming") + .description("Total incoming payment requests") + .register(meterRegistry) + + outgoingCounter = Counter.builder("payment.requests.outgoing") + .description("Total outgoing payment requests to external systems") + .register(meterRegistry) + + completedCounter = Counter.builder("payment.requests.completed") + .description("Total completed payment requests") + .register(meterRegistry) + + failedCounter = Counter.builder("payment.requests.failed") + .description("Total failed payment requests") + .register(meterRegistry) + } + + fun incrementIncoming() { + incomingRequests.incrementAndGet() + incomingCounter.increment() + } + + fun incrementOutgoing() { + outgoingRequests.incrementAndGet() + outgoingCounter.increment() + } + + fun incrementCompleted() { + completedRequests.incrementAndGet() + completedCounter.increment() + } + + fun incrementFailed() { + failedRequests.incrementAndGet() + failedCounter.increment() + } + + @Scheduled(fixedRate = 10, timeUnit = TimeUnit.SECONDS) + fun reportMetrics() { + val incoming = incomingRequests.get() + val outgoing = outgoingRequests.get() + val completed = completedRequests.get() + val failed = failedRequests.get() + + logger.info( + "METRICS | Incoming: $incoming | Outgoing: $outgoing | Completed: $completed | Failed: $failed" + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt index a5909b85b..095db057d 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt @@ -26,6 +26,9 @@ class OrderPayer { @Autowired private lateinit var paymentService: PaymentService + @Autowired + private lateinit var metricsReporter: MetricsReporter + private val paymentExecutor = ThreadPoolExecutor( 16, 16, diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt index 5cb12106a..90bd751e8 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt @@ -6,11 +6,15 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import ru.quipy.payments.logic.PaymentRateLimiterFactory import ru.quipy.core.EventSourcingService import ru.quipy.payments.api.PaymentAggregate import java.net.SocketTimeoutException import java.time.Duration import java.util.* +import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicLong // Advice: always treat time as a Duration @@ -19,11 +23,12 @@ class PaymentExternalSystemAdapterImpl( private val paymentESService: EventSourcingService, private val paymentProviderHostPort: String, private val token: String, + private val rateLimiterFactory: PaymentRateLimiterFactory, + private val metricsReporter: MetricsReporter ) : PaymentExternalSystemAdapter { companion object { val logger = LoggerFactory.getLogger(PaymentExternalSystemAdapter::class.java) - val emptyBody = RequestBody.create(null, ByteArray(0)) val mapper = ObjectMapper().registerKotlinModule() } @@ -33,10 +38,31 @@ class PaymentExternalSystemAdapterImpl( private val requestAverageProcessingTime = properties.averageProcessingTime private val rateLimitPerSec = properties.rateLimitPerSec private val parallelRequests = properties.parallelRequests + private val rateLimiter by lazy { + rateLimiterFactory.getRateLimiterForAccount(accountName, (rateLimitPerSec * 0.9).toInt()) + } + private val paymentExecutor = Executors.newFixedThreadPool(parallelRequests) private val client = OkHttpClient.Builder().build() + fun getRateLimitPerSec(): Int { + return this.rateLimitPerSec; + } + override fun performPaymentAsync(paymentId: UUID, amount: Int, paymentStartedAt: Long, deadline: Long) { + paymentExecutor.submit { + try { + metricsReporter.incrementOutgoing() + rateLimiter.tickBlocking() + + executePayment(paymentId, amount, paymentStartedAt, deadline) + } catch (e: Exception) { + logger.error("[$accountName] Failed to process payment $paymentId", e) + } + } + } + + private fun executePayment(paymentId: UUID, amount: Int, paymentStartedAt: Long, deadline: Long) { logger.warn("[$accountName] Submitting payment request for payment $paymentId") val transactionId = UUID.randomUUID() @@ -70,6 +96,12 @@ class PaymentExternalSystemAdapterImpl( paymentESService.update(paymentId) { it.logProcessing(body.result, now(), transactionId, reason = body.message) } + + if (body.result) { + metricsReporter.incrementCompleted() + } else { + metricsReporter.incrementFailed() + } } } catch (e: Exception) { when (e) { @@ -88,6 +120,7 @@ class PaymentExternalSystemAdapterImpl( } } } + metricsReporter.incrementFailed() } } @@ -96,7 +129,6 @@ class PaymentExternalSystemAdapterImpl( override fun isEnabled() = properties.enabled override fun name() = properties.accountName - } -public fun now() = System.currentTimeMillis() \ No newline at end of file +fun now() = System.currentTimeMillis() \ No newline at end of file diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentRateLimiterFactory.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentRateLimiterFactory.kt new file mode 100644 index 000000000..5a6bded01 --- /dev/null +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentRateLimiterFactory.kt @@ -0,0 +1,21 @@ +package ru.quipy.payments.logic + +import org.springframework.stereotype.Component +import ru.quipy.common.utils.SlidingWindowRateLimiter +import java.time.Duration +import java.util.concurrent.ConcurrentHashMap + +@Component +class PaymentRateLimiterFactory { + + private val rateLimiters = ConcurrentHashMap() + + fun getRateLimiterForAccount(accountName: String, rateLimitPerSec: Int): SlidingWindowRateLimiter { + return rateLimiters.computeIfAbsent(accountName) { + SlidingWindowRateLimiter( + rate = rateLimitPerSec.toLong(), + window = Duration.ofSeconds(1) + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentServiceImpl.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentServiceImpl.kt index 1c24e5a72..80d184523 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentServiceImpl.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentServiceImpl.kt @@ -21,7 +21,11 @@ class PaymentSystemImpl( val logger = LoggerFactory.getLogger(PaymentSystemImpl::class.java) } + @Autowired + private lateinit var metricsReporter: MetricsReporter + override fun submitPaymentRequest(paymentId: UUID, amount: Int, paymentStartedAt: Long, deadline: Long) { + metricsReporter.incrementIncoming() for (account in paymentAccounts) { account.performPaymentAsync(paymentId, amount, paymentStartedAt, deadline) } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 33d51a58b..16a480e5d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,5 +26,5 @@ management.endpoints.web.exposure.include=info,health,prometheus,metrics payment.service-name=${PAYMENT_SERVICE_NAME} payment.token=${PAYMENT_TOKEN} -payment.accounts=${PAYMENT_ACCOUNTS:acc-12,acc-20} +payment.accounts=${PAYMENT_ACCOUNTS:acc-23} payment.hostPort=${PAYMENT_HOST:localhost}:${PAYMENT_PORT:1234} \ No newline at end of file